diff --git a/key-wallet-ffi/FFI_API.md b/key-wallet-ffi/FFI_API.md index aa8ad7327..be336b79e 100644 --- a/key-wallet-ffi/FFI_API.md +++ b/key-wallet-ffi/FFI_API.md @@ -1284,14 +1284,14 @@ This function dereferences a raw pointer to FFIWallet. The caller must ensure th #### `wallet_build_and_sign_transaction` ```c -wallet_build_and_sign_transaction(managed_wallet: *mut FFIManagedWalletInfo, wallet: *const FFIWallet, account_index: c_uint, outputs: *const FFITxOutput, outputs_count: usize, fee_per_kb: u64, current_height: u32, tx_bytes_out: *mut *mut u8, tx_len_out: *mut usize, error: *mut FFIError,) -> bool +wallet_build_and_sign_transaction(manager: *const FFIWalletManager, wallet: *const FFIWallet, account_index: u32, outputs: *const FFITxOutput, outputs_count: usize, fee_per_kb: u64, fee_out: *mut u64, tx_bytes_out: *mut *mut u8, tx_len_out: *mut usize, error: *mut FFIError,) -> bool ``` **Description:** -Build and sign a transaction using the wallet's managed info This is the recommended way to build transactions. It handles: - UTXO selection using coin selection algorithms - Fee calculation - Change address generation - Transaction signing # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `wallet` must be a valid pointer to an FFIWallet - `outputs` must be a valid pointer to an array of FFITxOutput with at least `outputs_count` elements - `tx_bytes_out` must be a valid pointer to store the transaction bytes pointer - `tx_len_out` must be a valid pointer to store the transaction length - `error` must be a valid pointer to an FFIError - The returned transaction bytes must be freed with `transaction_bytes_free` +Build and sign a transaction using the wallet's managed info This is the recommended way to build transactions. It handles: - UTXO selection using coin selection algorithms - Fee calculation - Change address generation - Transaction signing # Safety - `manager` must be a valid pointer to an FFIWalletManager - `wallet` must be a valid pointer to an FFIWallet - `account_index` must be a valid BIP44 account index present in the wallet - `outputs` must be a valid pointer to an array of FFITxOutput with at least `outputs_count` elements - `fee_rate` must be a valid variant of FFIFeeRate - `fee_out` must be a valid, non-null pointer to a `u64`; on success it receives the calculated transaction fee in duffs - `tx_bytes_out` must be a valid pointer to store the transaction bytes pointer - `tx_len_out` must be a valid pointer to store the transaction length - `error` must be a valid pointer to an FFIError - The returned transaction bytes must be freed with `transaction_bytes_free` **Safety:** -- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `wallet` must be a valid pointer to an FFIWallet - `outputs` must be a valid pointer to an array of FFITxOutput with at least `outputs_count` elements - `tx_bytes_out` must be a valid pointer to store the transaction bytes pointer - `tx_len_out` must be a valid pointer to store the transaction length - `error` must be a valid pointer to an FFIError - The returned transaction bytes must be freed with `transaction_bytes_free` +- `manager` must be a valid pointer to an FFIWalletManager - `wallet` must be a valid pointer to an FFIWallet - `account_index` must be a valid BIP44 account index present in the wallet - `outputs` must be a valid pointer to an array of FFITxOutput with at least `outputs_count` elements - `fee_rate` must be a valid variant of FFIFeeRate - `fee_out` must be a valid, non-null pointer to a `u64`; on success it receives the calculated transaction fee in duffs - `tx_bytes_out` must be a valid pointer to store the transaction bytes pointer - `tx_len_out` must be a valid pointer to store the transaction length - `error` must be a valid pointer to an FFIError - The returned transaction bytes must be freed with `transaction_bytes_free` **Module:** `transaction` diff --git a/key-wallet-ffi/include/key_wallet_ffi.h b/key-wallet-ffi/include/key_wallet_ffi.h index cb0794175..2b70891dd 100644 --- a/key-wallet-ffi/include/key_wallet_ffi.h +++ b/key-wallet-ffi/include/key_wallet_ffi.h @@ -3511,22 +3511,26 @@ bool mnemonic_to_seed(const char *mnemonic, # Safety - - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo + - `manager` must be a valid pointer to an FFIWalletManager - `wallet` must be a valid pointer to an FFIWallet + - `account_index` must be a valid BIP44 account index present in the wallet - `outputs` must be a valid pointer to an array of FFITxOutput with at least `outputs_count` elements + - `fee_rate` must be a valid variant of FFIFeeRate + - `fee_out` must be a valid, non-null pointer to a `u64`; on success it receives the + calculated transaction fee in duffs - `tx_bytes_out` must be a valid pointer to store the transaction bytes pointer - `tx_len_out` must be a valid pointer to store the transaction length - `error` must be a valid pointer to an FFIError - The returned transaction bytes must be freed with `transaction_bytes_free` */ -bool wallet_build_and_sign_transaction(FFIManagedWalletInfo *managed_wallet, +bool wallet_build_and_sign_transaction(const FFIWalletManager *manager, const FFIWallet *wallet, - unsigned int account_index, + uint32_t account_index, const FFITxOutput *outputs, size_t outputs_count, uint64_t fee_per_kb, - uint32_t current_height, + uint64_t *fee_out, uint8_t **tx_bytes_out, size_t *tx_len_out, FFIError *error) diff --git a/key-wallet-ffi/src/transaction.rs b/key-wallet-ffi/src/transaction.rs index 9f157b55a..4297ea28a 100644 --- a/key-wallet-ffi/src/transaction.rs +++ b/key-wallet-ffi/src/transaction.rs @@ -1,7 +1,7 @@ //! Transaction building and management use std::ffi::{CStr, CString}; -use std::os::raw::{c_char, c_uint}; +use std::os::raw::c_char; use std::ptr; use std::slice; @@ -9,11 +9,13 @@ use dashcore::{ consensus, hashes::Hash, sighash::SighashCache, EcdsaSighashType, Network, OutPoint, Script, ScriptBuf, Transaction, TxIn, TxOut, Txid, }; +use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; +use key_wallet_manager::FeeRate; use secp256k1::{Message, Secp256k1, SecretKey}; use crate::error::{FFIError, FFIErrorCode}; -use crate::managed_wallet::FFIManagedWalletInfo; use crate::types::{FFINetwork, FFITransactionContext, FFIWallet}; +use crate::FFIWalletManager; // MARK: - Transaction Types @@ -65,32 +67,37 @@ pub struct FFITxOutput { /// /// # Safety /// -/// - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo +/// - `manager` must be a valid pointer to an FFIWalletManager /// - `wallet` must be a valid pointer to an FFIWallet +/// - `account_index` must be a valid BIP44 account index present in the wallet /// - `outputs` must be a valid pointer to an array of FFITxOutput with at least `outputs_count` elements +/// - `fee_rate` must be a valid variant of FFIFeeRate +/// - `fee_out` must be a valid, non-null pointer to a `u64`; on success it receives the +/// calculated transaction fee in duffs /// - `tx_bytes_out` must be a valid pointer to store the transaction bytes pointer /// - `tx_len_out` must be a valid pointer to store the transaction length /// - `error` must be a valid pointer to an FFIError /// - The returned transaction bytes must be freed with `transaction_bytes_free` #[no_mangle] pub unsafe extern "C" fn wallet_build_and_sign_transaction( - managed_wallet: *mut FFIManagedWalletInfo, + manager: *const FFIWalletManager, wallet: *const FFIWallet, - account_index: c_uint, + account_index: u32, outputs: *const FFITxOutput, outputs_count: usize, fee_per_kb: u64, - current_height: u32, + fee_out: *mut u64, tx_bytes_out: *mut *mut u8, tx_len_out: *mut usize, error: *mut FFIError, ) -> bool { // Validate inputs - if managed_wallet.is_null() + if manager.is_null() || wallet.is_null() || outputs.is_null() || tx_bytes_out.is_null() || tx_len_out.is_null() + || fee_out.is_null() { FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); return false; @@ -107,225 +114,245 @@ pub unsafe extern "C" fn wallet_build_and_sign_transaction( unsafe { use key_wallet::wallet::managed_wallet_info::coin_selection::SelectionStrategy; - use key_wallet::wallet::managed_wallet_info::fee::{FeeLevel, FeeRate}; + use key_wallet::wallet::managed_wallet_info::fee::FeeLevel; use key_wallet::wallet::managed_wallet_info::transaction_builder::TransactionBuilder; - let managed_wallet_ref = &mut *managed_wallet; + let manager_ref = &*manager; let wallet_ref = &*wallet; - let network_rust = managed_wallet_ref.inner().network; + let network_rust = wallet_ref.inner().network; let outputs_slice = slice::from_raw_parts(outputs, outputs_count); - // Get the managed account - let managed_account = match managed_wallet_ref - .inner_mut() - .accounts - .standard_bip44_accounts - .get_mut(&account_index) - { - Some(account) => account, - None => { + manager_ref.runtime.block_on(async { + let mut manager = manager_ref.manager.write().await; + + let managed_wallet = manager.get_wallet_info_mut(&wallet_ref.inner().wallet_id); + + let Some(managed_wallet) = managed_wallet else { FFIError::set_error( error, - FFIErrorCode::WalletError, - format!("Account {} not found", account_index), + FFIErrorCode::InvalidInput, + "Could not obtain ManagedWalletInfo for the provided wallet".to_string(), ); return false; - } - }; + }; - // Verify wallet and managed wallet have matching networks - if wallet_ref.inner().network != network_rust { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "Wallet and managed wallet have different networks".to_string(), - ); - return false; - } + // Get the managed account + let managed_account = + match managed_wallet.accounts.standard_bip44_accounts.get_mut(&account_index) { + Some(account) => account, + None => { + FFIError::set_error( + error, + FFIErrorCode::WalletError, + format!("Account {} not found", account_index), + ); + return false; + } + }; - let wallet_account = - match wallet_ref.inner().accounts.standard_bip44_accounts.get(&account_index) { - Some(account) => account, - None => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Wallet account {} not found", account_index), - ); - return false; - } - }; + let wallet_account = + match wallet_ref.inner().accounts.standard_bip44_accounts.get(&account_index) { + Some(account) => account, + None => { + FFIError::set_error( + error, + FFIErrorCode::WalletError, + format!("Wallet account {} not found", account_index), + ); + return false; + } + }; - // Convert FFI outputs to Rust outputs - let mut tx_builder = TransactionBuilder::new(); + // Convert FFI outputs to Rust outputs + let mut tx_builder = TransactionBuilder::new(); - for output in outputs_slice { - // Convert address from C string - let address_str = match CStr::from_ptr(output.address).to_str() { - Ok(s) => s, - Err(_) => { + for output in outputs_slice { + if output.address.is_null() { FFIError::set_error( error, FFIErrorCode::InvalidInput, - "Invalid UTF-8 in output address".to_string(), + "Output address pointer is null".to_string(), ); return false; } - }; - // Parse address using dashcore - use std::str::FromStr; - let address = match dashcore::Address::from_str(address_str) { - Ok(addr) => { - // Verify network matches - let addr_network = addr.require_network(network_rust).map_err(|e| { + // Convert address from C string + let address_str = match CStr::from_ptr(output.address).to_str() { + Ok(s) => s, + Err(_) => { + FFIError::set_error( + error, + FFIErrorCode::InvalidInput, + "Invalid UTF-8 in output address".to_string(), + ); + return false; + } + }; + + // Parse address using dashcore + use std::str::FromStr; + let address = match dashcore::Address::from_str(address_str) { + Ok(addr) => { + // Verify network matches + let addr_network = addr.require_network(network_rust).map_err(|e| { + FFIError::set_error( + error, + FFIErrorCode::InvalidAddress, + format!("Address network mismatch: {}", e), + ); + }); + if addr_network.is_err() { + return false; + } + addr_network.unwrap() + } + Err(e) => { FFIError::set_error( error, FFIErrorCode::InvalidAddress, - format!("Address network mismatch: {}", e), + format!("Invalid address: {}", e), ); - }); - if addr_network.is_err() { return false; } - addr_network.unwrap() - } + }; + + // Add output + tx_builder = match tx_builder.add_output(&address, output.amount) { + Ok(builder) => builder, + Err(e) => { + FFIError::set_error( + error, + FFIErrorCode::WalletError, + format!("Failed to add output: {}", e), + ); + return false; + } + }; + } + + // Get change address (next internal address) + let xpub = wallet_account.extended_public_key(); + let change_address = match managed_account.next_change_address(Some(&xpub), true) { + Ok(addr) => addr, Err(e) => { FFIError::set_error( error, - FFIErrorCode::InvalidAddress, - format!("Invalid address: {}", e), + FFIErrorCode::WalletError, + format!("Failed to get change address: {}", e), ); return false; } }; - // Add output - tx_builder = match tx_builder.add_output(&address, output.amount) { - Ok(builder) => builder, - Err(e) => { + tx_builder = tx_builder + .set_change_address(change_address) + .set_fee_level(FeeLevel::Custom(FeeRate::new(fee_per_kb))); + + // Get available UTXOs (collect owned UTXOs, not references) + let utxos: Vec = managed_account.utxos.values().cloned().collect(); + + // Get the wallet's root extended private key for signing + use key_wallet::wallet::WalletType; + + let root_xpriv = match &wallet_ref.inner().wallet_type { + WalletType::Mnemonic { + root_extended_private_key, + .. + } => root_extended_private_key, + WalletType::Seed { + root_extended_private_key, + .. + } => root_extended_private_key, + WalletType::ExtendedPrivKey(root_extended_private_key) => root_extended_private_key, + _ => { FFIError::set_error( error, FFIErrorCode::WalletError, - format!("Failed to add output: {}", e), + "Cannot sign with watch-only wallet".to_string(), ); return false; } }; - } - // Get change address (next internal address) - let xpub = wallet_account.extended_public_key(); - let change_address = match managed_account.next_change_address(Some(&xpub), true) { - Ok(addr) => addr, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to get change address: {}", e), - ); - return false; - } - }; + // Build a map of address -> derivation path for all addresses in the account + use std::collections::HashMap; + let mut address_to_path: HashMap = + HashMap::new(); - // Set change address and fee level - // Convert fee_per_kb to fee_per_byte (1 KB = 1000 bytes) - let fee_per_byte = fee_per_kb / 1000; - let fee_rate = FeeRate::from_duffs_per_byte(fee_per_byte); - tx_builder = - tx_builder.set_change_address(change_address).set_fee_level(FeeLevel::Custom(fee_rate)); - - // Get available UTXOs (collect owned UTXOs, not references) - let utxos: Vec = managed_account.utxos.values().cloned().collect(); - - // Get the wallet's root extended private key for signing - use key_wallet::wallet::WalletType; - - let root_xpriv = match &wallet_ref.inner().wallet_type { - WalletType::Mnemonic { - root_extended_private_key, - .. - } => root_extended_private_key, - WalletType::Seed { - root_extended_private_key, - .. - } => root_extended_private_key, - WalletType::ExtendedPrivKey(root_extended_private_key) => root_extended_private_key, - _ => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "Cannot sign with watch-only wallet".to_string(), - ); - return false; + // Collect from all address pools (receive, change, etc.) + for pool in managed_account.account_type.address_pools() { + for addr_info in pool.addresses.values() { + address_to_path.insert(addr_info.address.clone(), addr_info.path.clone()); + } } - }; - // Build a map of address -> derivation path for all addresses in the account - use std::collections::HashMap; - let mut address_to_path: HashMap = - HashMap::new(); - - // Collect from all address pools (receive, change, etc.) - for pool in managed_account.account_type.address_pools() { - for addr_info in pool.addresses.values() { - address_to_path.insert(addr_info.address.clone(), addr_info.path.clone()); - } - } + // Select inputs and build transaction + let mut tx_builder_with_inputs = match tx_builder.select_inputs( + &utxos, + SelectionStrategy::BranchAndBound, + managed_wallet.synced_height(), + |utxo| { + // Look up the derivation path for this UTXO's address + let path = address_to_path.get(&utxo.address)?; + + // Convert root key to ExtendedPrivKey and derive the child key + let root_ext_priv = root_xpriv.to_extended_priv_key(network_rust); + let secp = secp256k1::Secp256k1::new(); + let derived_xpriv = root_ext_priv.derive_priv(&secp, path).ok()?; + + Some(derived_xpriv.private_key) + }, + ) { + Ok(builder) => builder, + Err(e) => { + FFIError::set_error( + error, + FFIErrorCode::WalletError, + format!("Coin selection failed: {}", e), + ); + return false; + } + }; - // Select inputs and build transaction - let tx_builder_with_inputs = match tx_builder.select_inputs( - &utxos, - SelectionStrategy::BranchAndBound, - current_height, - |utxo| { - // Look up the derivation path for this UTXO's address - let path = address_to_path.get(&utxo.address)?; - - // Convert root key to ExtendedPrivKey and derive the child key - let root_ext_priv = root_xpriv.to_extended_priv_key(network_rust); - let secp = secp256k1::Secp256k1::new(); - let derived_xpriv = root_ext_priv.derive_priv(&secp, path).ok()?; - - Some(derived_xpriv.private_key) - }, - ) { - Ok(builder) => builder, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Coin selection failed: {}", e), - ); - return false; - } - }; + // Build and sign the transaction + let transaction = match tx_builder_with_inputs.build() { + Ok(tx) => tx, + Err(e) => { + FFIError::set_error( + error, + FFIErrorCode::WalletError, + format!("Failed to build transaction: {}", e), + ); + return false; + } + }; - // Build and sign the transaction - let transaction = match tx_builder_with_inputs.build() { - Ok(tx) => tx, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to build transaction: {}", e), - ); - return false; - } - }; + // This is tricky, the transaction creation + fee calculation need a little + // bit of love to avoid this kind of logic. + // + // First, we need to know that TransactionBuilder may add an extra output for change + // to the final transaction but not to itself, with that knowledge, we can compare the + // number of outputs in the transaction with the number of outputs in the TransactionBuilder + // to then call the appropriate fee calculation method + *fee_out = if transaction.output.len() > tx_builder_with_inputs.outputs().len() { + tx_builder_with_inputs.calculate_fee_with_extra_output() + } else { + tx_builder_with_inputs.calculate_fee() + }; - // Serialize the transaction - let serialized = consensus::serialize(&transaction); - let size = serialized.len(); + // Serialize the transaction + let serialized = consensus::serialize(&transaction); + let size = serialized.len(); - let boxed = serialized.into_boxed_slice(); - let tx_bytes = Box::into_raw(boxed) as *mut u8; + let boxed = serialized.into_boxed_slice(); + let tx_bytes = Box::into_raw(boxed) as *mut u8; - *tx_bytes_out = tx_bytes; - *tx_len_out = size; + *tx_bytes_out = tx_bytes; + *tx_len_out = size; - FFIError::set_success(error); - true + FFIError::set_success(error); + true + }) } } diff --git a/key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs b/key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs index 608b75813..47d995906 100644 --- a/key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs +++ b/key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs @@ -82,6 +82,10 @@ impl TransactionBuilder { } } + pub fn outputs(&self) -> &Vec { + &self.outputs + } + /// Add a UTXO input with optional private key for signing pub fn add_input(mut self, utxo: Utxo, key: Option) -> Self { self.inputs.push((utxo, key)); @@ -330,32 +334,6 @@ impl TransactionBuilder { size } - /// Build the transaction - /// - /// Uses the special payload if one was set via `set_special_payload` - pub fn build(self) -> Result { - self.build_internal() - } - - /// Build the transaction with an explicit special transaction payload - /// - /// This overrides any payload set via `set_special_payload`. - /// Supports Dash-specific transaction types like: - /// - ProRegTx (Provider Registration) - /// - ProUpServTx (Provider Update Service) - /// - ProUpRegTx (Provider Update Registrar) - /// - ProUpRevTx (Provider Update Revocation) - /// - CoinJoin transactions - /// - InstantSend transactions - /// - And other special transaction types - pub fn build_with_payload( - mut self, - payload: Option, - ) -> Result { - self.special_payload = payload; - self.build_internal() - } - /// Calculates the transaction fee for the current number of outputs and inputs pub fn calculate_fee(&self) -> u64 { let fee_rate = self.fee_level.fee_rate(); @@ -376,8 +354,8 @@ impl TransactionBuilder { fee_rate.calculate_fee(estimated_size) } - /// Internal build method that uses the stored special_payload - fn build_internal(mut self) -> Result { + /// Build the transaction + pub fn build(&mut self) -> Result { if self.inputs.is_empty() { return Err(BuilderError::NoInputs); } @@ -468,7 +446,7 @@ impl TransactionBuilder { lock_time: self.lock_time, input: tx_inputs, output: tx_outputs, - special_transaction_payload: self.special_payload.take(), + special_transaction_payload: self.special_payload.clone(), }; // Sign inputs if keys are provided @@ -521,7 +499,9 @@ impl TransactionBuilder { platform_p2p_port, platform_http_port, }; - self.build_with_payload(Some(TransactionPayload::ProviderRegistrationPayloadType(payload))) + + self.set_special_payload(TransactionPayload::ProviderRegistrationPayloadType(payload)) + .build() } /// Build a Provider Update Service Transaction (ProUpServTx) @@ -558,7 +538,8 @@ impl TransactionBuilder { platform_http_port, payload_sig, }; - self.build_with_payload(Some(TransactionPayload::ProviderUpdateServicePayloadType(payload))) + self.set_special_payload(TransactionPayload::ProviderUpdateServicePayloadType(payload)) + .build() } /// Build a Provider Update Registrar Transaction (ProUpRegTx) @@ -589,9 +570,8 @@ impl TransactionBuilder { inputs_hash, payload_sig, }; - self.build_with_payload(Some(TransactionPayload::ProviderUpdateRegistrarPayloadType( - payload, - ))) + self.set_special_payload(TransactionPayload::ProviderUpdateRegistrarPayloadType(payload)) + .build() } /// Build a Provider Update Revocation Transaction (ProUpRevTx) @@ -611,9 +591,8 @@ impl TransactionBuilder { inputs_hash, payload_sig, }; - self.build_with_payload(Some(TransactionPayload::ProviderUpdateRevocationPayloadType( - payload, - ))) + self.set_special_payload(TransactionPayload::ProviderUpdateRevocationPayloadType(payload)) + .build() } /// Build a Coinbase Transaction @@ -637,7 +616,7 @@ impl TransactionBuilder { best_cl_signature, asset_locked_amount, }; - self.build_with_payload(Some(TransactionPayload::CoinbasePayloadType(payload))) + self.set_special_payload(TransactionPayload::CoinbasePayloadType(payload)).build() } /// Build an Asset Lock Transaction @@ -648,7 +627,7 @@ impl TransactionBuilder { version: 0, credit_outputs, }; - self.build_with_payload(Some(TransactionPayload::AssetLockPayloadType(payload))) + self.set_special_payload(TransactionPayload::AssetLockPayloadType(payload)).build() } /// Estimate transaction size in bytes @@ -1092,52 +1071,6 @@ mod tests { assert!(base_size2 > 180, "Base size with Coinbase payload should be larger"); } - #[test] - fn test_build_with_payload_override() { - // Test that build_with_payload overrides set_special_payload - let utxo = Utxo::dummy(0, 100000, 100, false, true); - let destination = Address::dummy(Network::Testnet, 0); - let change = Address::dummy(Network::Testnet, 0); - - let credit_outputs = vec![TxOut { - value: 50000, - script_pubkey: ScriptBuf::new(), - }]; - - let original_payload = AssetLockPayload { - version: 1, - credit_outputs: credit_outputs.clone(), - }; - - let override_payload = AssetLockPayload { - version: 2, - credit_outputs: vec![TxOut { - value: 75000, - script_pubkey: ScriptBuf::new(), - }], - }; - - let tx = TransactionBuilder::new() - .add_input(utxo, None) - .add_output(&destination, 30000) - .unwrap() - .set_change_address(change) - .set_special_payload(TransactionPayload::AssetLockPayloadType(original_payload)) - .build_with_payload(Some(TransactionPayload::AssetLockPayloadType(override_payload))) - .unwrap(); - - // Should use the override payload - if let Some(TransactionPayload::AssetLockPayloadType(payload)) = - &tx.special_transaction_payload - { - assert_eq!(payload.version, 2); - assert_eq!(payload.credit_outputs.len(), 1); - assert_eq!(payload.credit_outputs[0].value, 75000); - } else { - panic!("Expected AssetLockPayload"); - } - } - #[test] fn test_bip69_output_ordering() { // Test that outputs are sorted according to BIP-69 @@ -1299,7 +1232,7 @@ mod tests { .select_inputs(&utxos, SelectionStrategy::SmallestFirst, 200, |_| None); assert!(result.is_ok()); - let builder = result.unwrap(); + let mut builder = result.unwrap(); let tx = builder.build().unwrap(); // Should have selected enough inputs to cover output + fees for larger transaction diff --git a/key-wallet/src/wallet/managed_wallet_info/transaction_building.rs b/key-wallet/src/wallet/managed_wallet_info/transaction_building.rs index 1344c4afd..8798292a0 100644 --- a/key-wallet/src/wallet/managed_wallet_info/transaction_building.rs +++ b/key-wallet/src/wallet/managed_wallet_info/transaction_building.rs @@ -306,7 +306,7 @@ mod tests { .require_network(Network::Testnet) .unwrap(); - let builder = TransactionBuilder::new() + let mut builder = TransactionBuilder::new() .set_fee_level(FeeLevel::Normal) .set_change_address(change_address.clone()) .add_output(&recipient_address, 150000) @@ -341,7 +341,7 @@ mod tests { .require_network(Network::Testnet) .unwrap(); - let builder = TransactionBuilder::new() + let mut builder = TransactionBuilder::new() .set_fee_level(FeeLevel::Normal) // 1 duff per byte .set_change_address(change_address.clone()) .add_output(&recipient_address, 500000) @@ -399,7 +399,7 @@ mod tests { .require_network(Network::Testnet) .unwrap(); - let builder = TransactionBuilder::new() + let mut builder = TransactionBuilder::new() .set_fee_level(FeeLevel::Normal) .set_change_address(change_address.clone()) .add_output(&recipient_address, 150000)