diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index ab853e35..cddb9247 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -264,6 +264,7 @@ interface TransactionError { ParseFailed(); UnsupportedSegwitFlag(u8 flag); OtherTransactionErr(); + InvalidHexString(); }; // ------------------------------------------------------------------------ diff --git a/bdk-ffi/src/bitcoin.rs b/bdk-ffi/src/bitcoin.rs index 7172f868..01688638 100644 --- a/bdk-ffi/src/bitcoin.rs +++ b/bdk-ffi/src/bitcoin.rs @@ -14,6 +14,7 @@ use bdk_wallet::bitcoin::consensus::encode::serialize; use bdk_wallet::bitcoin::consensus::Decodable; use bdk_wallet::bitcoin::hashes::sha256::Hash as BitcoinSha256Hash; use bdk_wallet::bitcoin::hashes::sha256d::Hash as BitcoinDoubleSha256Hash; +use bdk_wallet::bitcoin::hex::FromHex; use bdk_wallet::bitcoin::io::Cursor; use bdk_wallet::bitcoin::secp256k1::Secp256k1; use bdk_wallet::bitcoin::Amount as BdkAmount; @@ -333,6 +334,13 @@ impl Transaction { Ok(Transaction(tx)) } + /// Creates a new `Transaction` instance from a hexadecimal string representation. + #[uniffi::constructor] + pub fn from_string(tx_hex: String) -> Result { + let tx_bytes = Vec::from_hex(&tx_hex)?; + Self::new(tx_bytes) + } + /// Computes the Txid. /// Hashes the transaction excluding the segwit data (i.e. the marker, flag bytes, and the witness fields themselves). pub fn compute_txid(&self) -> Arc { @@ -677,6 +685,8 @@ impl_hash_like!(TxMerkleNode, BitcoinDoubleSha256Hash); mod tests { use crate::bitcoin::Address; use crate::bitcoin::Network; + use crate::bitcoin::Transaction; + use crate::error::TransactionError; #[test] fn test_is_valid_for_network() { @@ -1032,4 +1042,41 @@ mod tests { let segwit_data = segwit.to_address_data(); println!("Segwit data: {:#?}", segwit_data); } + + #[test] + fn test_transaction_from_string() { + // A simple transaction hex (mainnet coinbase transaction) + let tx_hex = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000"; + + // Test successful parsing + let tx = Transaction::from_string(tx_hex.to_string()).unwrap(); + + // Verify the transaction was parsed correctly + assert_eq!(tx.version(), 1); + assert_eq!(tx.input().len(), 1); + assert_eq!(tx.output().len(), 1); + assert!(tx.is_coinbase()); + + // Test that serializing and re-parsing gives the same result + let serialized = tx.serialize(); + let tx2 = Transaction::new(serialized).unwrap(); + assert_eq!( + tx.compute_txid().to_string(), + tx2.compute_txid().to_string() + ); + + // Test invalid hex string + let invalid_hex = "invalid_hex_string"; + let result = Transaction::from_string(invalid_hex.to_string()); + assert!(result.is_err()); + match result.unwrap_err() { + TransactionError::InvalidHexString => {} + _ => panic!("Expected InvalidHexString error"), + } + + // Test hex string with invalid transaction data + let invalid_tx_hex = "deadbeef"; + let result = Transaction::from_string(invalid_tx_hex.to_string()); + assert!(result.is_err()); + } } diff --git a/bdk-ffi/src/error.rs b/bdk-ffi/src/error.rs index 8e123ecb..ff56a7f2 100644 --- a/bdk-ffi/src/error.rs +++ b/bdk-ffi/src/error.rs @@ -8,7 +8,7 @@ use bdk_wallet::bitcoin::amount::ParseAmountError as BdkParseAmountError; use bdk_wallet::bitcoin::bip32::Error as BdkBip32Error; use bdk_wallet::bitcoin::consensus::encode::Error as BdkEncodeError; use bdk_wallet::bitcoin::hashes::hex::HexToArrayError as BdkHexToArrayError; -use bdk_wallet::bitcoin::hex::DisplayHex; +use bdk_wallet::bitcoin::hex::{DisplayHex, HexToBytesError}; use bdk_wallet::bitcoin::psbt::Error as BdkPsbtError; use bdk_wallet::bitcoin::psbt::ExtractTxError as BdkExtractTxError; use bdk_wallet::bitcoin::psbt::PsbtParseError as BdkPsbtParseError; @@ -768,6 +768,9 @@ pub enum TransactionError { // This is required because the bdk::bitcoin::consensus::encode::Error is non-exhaustive #[error("other transaction error")] OtherTransactionErr, + + #[error("invalid hex string")] + InvalidHexString, } #[derive(Debug, thiserror::Error, uniffi::Error)] @@ -1512,6 +1515,12 @@ impl From for TransactionError { } } +impl From for TransactionError { + fn from(_: HexToBytesError) -> Self { + TransactionError::InvalidHexString + } +} + #[derive(Debug, thiserror::Error, uniffi::Error)] pub enum HashParseError { #[error("invalid hash: expected length 32 bytes, got {len} bytes")]