From 11c1734a932a06ce26b6395314d3f7d301b79d72 Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Mon, 29 Jun 2026 10:32:44 -0700 Subject: [PATCH 1/2] refactor(platform-wallet-ffi): expose the new core TransactionBuilder API --- Cargo.lock | 60 +++ Cargo.toml | 16 +- .../src/core_wallet/broadcast.rs | 106 +----- .../src/core_wallet/mod.rs | 2 + .../src/core_wallet/transaction_builder.rs | 341 ++++++++++++++++++ .../src/wallet/core/broadcast.rs | 132 +------ .../src/wallet/core/wallet.rs | 4 + .../src/mnemonic_resolver_core_signer.rs | 65 ++-- .../CoreWallet/CoreTransactionBuilder.swift | 147 ++++++++ .../CoreWallet/ManagedCoreWallet.swift | 74 ---- .../Core/ViewModels/SendViewModel.swift | 35 +- 11 files changed, 632 insertions(+), 350 deletions(-) create mode 100644 packages/rs-platform-wallet-ffi/src/core_wallet/transaction_builder.rs create mode 100644 packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/CoreTransactionBuilder.swift diff --git a/Cargo.lock b/Cargo.lock index 5148cee60c1..dfc157014b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1636,8 +1636,13 @@ dependencies = [ [[package]] name = "dash-network" +<<<<<<< HEAD version = "0.44.0" source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" +======= +version = "0.43.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" +>>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) dependencies = [ "bincode", "bincode_derive", @@ -1647,8 +1652,13 @@ dependencies = [ [[package]] name = "dash-network-seeds" +<<<<<<< HEAD version = "0.44.0" source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" +======= +version = "0.43.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" +>>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) dependencies = [ "dash-network", ] @@ -1724,8 +1734,13 @@ dependencies = [ [[package]] name = "dash-spv" +<<<<<<< HEAD version = "0.44.0" source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" +======= +version = "0.43.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" +>>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) dependencies = [ "async-trait", "chrono", @@ -1753,8 +1768,13 @@ dependencies = [ [[package]] name = "dashcore" +<<<<<<< HEAD version = "0.44.0" source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" +======= +version = "0.43.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" +>>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) dependencies = [ "anyhow", "base64-compat", @@ -1779,6 +1799,7 @@ dependencies = [ [[package]] name = "dashcore-private" +<<<<<<< HEAD version = "0.44.0" source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" @@ -1786,6 +1807,15 @@ source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d90 name = "dashcore-rpc" version = "0.44.0" source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" +======= +version = "0.43.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" + +[[package]] +name = "dashcore-rpc" +version = "0.43.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" +>>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) dependencies = [ "dashcore-rpc-json", "hex", @@ -1797,8 +1827,13 @@ dependencies = [ [[package]] name = "dashcore-rpc-json" +<<<<<<< HEAD version = "0.44.0" source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" +======= +version = "0.43.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" +>>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) dependencies = [ "bincode", "dashcore", @@ -1812,8 +1847,13 @@ dependencies = [ [[package]] name = "dashcore_hashes" +<<<<<<< HEAD version = "0.44.0" source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" +======= +version = "0.43.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" +>>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) dependencies = [ "bincode", "dashcore-private", @@ -2866,8 +2906,13 @@ dependencies = [ [[package]] name = "git-state" +<<<<<<< HEAD version = "0.44.0" source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" +======= +version = "0.43.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" +>>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) [[package]] name = "glob" @@ -4033,8 +4078,13 @@ dependencies = [ [[package]] name = "key-wallet" +<<<<<<< HEAD version = "0.44.0" source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" +======= +version = "0.43.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" +>>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) dependencies = [ "aes", "async-trait", @@ -4062,8 +4112,13 @@ dependencies = [ [[package]] name = "key-wallet-ffi" +<<<<<<< HEAD version = "0.44.0" source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" +======= +version = "0.43.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" +>>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) dependencies = [ "cbindgen 0.29.4", "dash-network", @@ -4078,8 +4133,13 @@ dependencies = [ [[package]] name = "key-wallet-manager" +<<<<<<< HEAD version = "0.44.0" source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" +======= +version = "0.43.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" +>>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) dependencies = [ "async-trait", "bincode", diff --git a/Cargo.toml b/Cargo.toml index c7703f6b683..84d8a4f2b94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,14 +50,14 @@ members = [ ] [workspace.dependencies] -dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "991c6ebe24d7ea8ba7d900a052b25be8c5498409" } -dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "991c6ebe24d7ea8ba7d900a052b25be8c5498409" } -dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "991c6ebe24d7ea8ba7d900a052b25be8c5498409" } -key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "991c6ebe24d7ea8ba7d900a052b25be8c5498409" } -key-wallet-ffi = { git = "https://github.com/dashpay/rust-dashcore", rev = "991c6ebe24d7ea8ba7d900a052b25be8c5498409" } -key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "991c6ebe24d7ea8ba7d900a052b25be8c5498409" } -dash-network = { git = "https://github.com/dashpay/rust-dashcore", rev = "991c6ebe24d7ea8ba7d900a052b25be8c5498409" } -dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "991c6ebe24d7ea8ba7d900a052b25be8c5498409" } +dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "a8a096838b829cf5bec3c2374a23511640a0c35c" } +dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "a8a096838b829cf5bec3c2374a23511640a0c35c" } +dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "a8a096838b829cf5bec3c2374a23511640a0c35c" } +key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "a8a096838b829cf5bec3c2374a23511640a0c35c" } +key-wallet-ffi = { git = "https://github.com/dashpay/rust-dashcore", rev = "a8a096838b829cf5bec3c2374a23511640a0c35c" } +key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "a8a096838b829cf5bec3c2374a23511640a0c35c" } +dash-network = { git = "https://github.com/dashpay/rust-dashcore", rev = "a8a096838b829cf5bec3c2374a23511640a0c35c" } +dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "a8a096838b829cf5bec3c2374a23511640a0c35c" } tokio-metrics = "0.5" diff --git a/packages/rs-platform-wallet-ffi/src/core_wallet/broadcast.rs b/packages/rs-platform-wallet-ffi/src/core_wallet/broadcast.rs index 24d54bb0776..76c8f674ca2 100644 --- a/packages/rs-platform-wallet-ffi/src/core_wallet/broadcast.rs +++ b/packages/rs-platform-wallet-ffi/src/core_wallet/broadcast.rs @@ -1,13 +1,10 @@ -//! FFI bindings for CoreWallet transaction building and broadcasting. +//! FFI bindings for CoreWallet transaction broadcasting. use crate::error::*; use crate::handle::*; use crate::runtime::runtime; use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; -use rs_sdk_ffi::MnemonicResolverCoreSigner; -use rs_sdk_ffi::MnemonicResolverHandle; use std::os::raw::c_char; -use std::str::FromStr; /// Broadcast a signed transaction to the network. #[no_mangle] @@ -33,104 +30,3 @@ pub unsafe extern "C" fn core_wallet_broadcast_transaction( *out_txid = c_str.into_raw(); PlatformWalletFFIResult::ok() } - -/// Build, sign, and broadcast a payment to the given addresses via -/// an external mnemonic-resolver-backed signer. -/// -/// The Swift caller supplies a [`MnemonicResolverHandle`] — the same -/// vtable shape used by -/// [`crate::dash_sdk_sign_with_mnemonic_resolver_and_path`] — which -/// the FFI wraps in a [`MnemonicResolverCoreSigner`] for the -/// lifetime of this call. Private keys never cross the FFI as raw -/// bytes; every signature is produced inside the signer's atomic -/// derive-and-sign step. -/// -/// # Safety -/// - `core_signer_handle` must be a valid, non-destroyed -/// `*mut MnemonicResolverHandle`. Ownership is retained by the -/// caller — this function does NOT destroy it. -#[no_mangle] -#[allow(clippy::too_many_arguments)] -pub unsafe extern "C" fn core_wallet_send_to_addresses( - handle: Handle, - account_type: u32, - account_index: u32, - addresses: *const *const c_char, - amounts: *const u64, - count: usize, - core_signer_handle: *mut MnemonicResolverHandle, - out_tx_bytes: *mut *mut u8, - out_tx_len: *mut usize, -) -> PlatformWalletFFIResult { - check_ptr!(core_signer_handle); - if count > 0 { - check_ptr!(addresses); - check_ptr!(amounts); - } - check_ptr!(out_tx_bytes); - check_ptr!(out_tx_len); - - let mut outputs = Vec::with_capacity(count); - let addr_ptrs = std::slice::from_raw_parts(addresses, count); - let amount_slice = std::slice::from_raw_parts(amounts, count); - - for i in 0..count { - let c_str = unwrap_result_or_return!(std::ffi::CStr::from_ptr(addr_ptrs[i]).to_str()); - - let addr = unwrap_result_or_return!(dashcore::Address::from_str(c_str)).assume_checked(); - - outputs.push((addr, amount_slice[i])); - } - - use key_wallet::account::account_type::StandardAccountType; - let std_account_type = match account_type { - 0 => StandardAccountType::BIP44Account, - 1 => StandardAccountType::BIP32Account, - _ => { - return PlatformWalletFFIResult::err( - PlatformWalletFFIResultCode::ErrorInvalidParameter, - format!("Unknown account type: {account_type}"), - ); - } - }; - - let signer_addr = core_signer_handle as usize; - - let option = CORE_WALLET_STORAGE.with_item(handle, |wallet| { - let wallet_id = wallet.wallet_id(); - let network = wallet.network(); - // SAFETY: the resolver handle is pinned alive for the duration - // of this FFI call (see fn-level safety doc). The - // `MnemonicResolverCoreSigner` lives on this stack frame and - // is dropped before the function returns. - let signer = unsafe { - MnemonicResolverCoreSigner::new( - signer_addr as *mut MnemonicResolverHandle, - wallet_id, - network, - ) - }; - runtime().block_on(wallet.send_to_addresses( - std_account_type, - account_index, - outputs, - &signer, - )) - }); - let result = unwrap_option_or_return!(option); - let tx = unwrap_result_or_return!(result); - let serialized = dashcore::consensus::serialize(&tx); - let len = serialized.len(); - let boxed = serialized.into_boxed_slice(); - *out_tx_bytes = Box::into_raw(boxed) as *mut u8; - *out_tx_len = len; - PlatformWalletFFIResult::ok() -} - -/// Free transaction bytes returned by `core_wallet_send_to_addresses`. -#[no_mangle] -pub unsafe extern "C" fn core_wallet_free_tx_bytes(bytes: *mut u8, len: usize) { - if !bytes.is_null() && len > 0 { - let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(bytes, len)); - } -} diff --git a/packages/rs-platform-wallet-ffi/src/core_wallet/mod.rs b/packages/rs-platform-wallet-ffi/src/core_wallet/mod.rs index c48f9f772a4..8e12ebc1783 100644 --- a/packages/rs-platform-wallet-ffi/src/core_wallet/mod.rs +++ b/packages/rs-platform-wallet-ffi/src/core_wallet/mod.rs @@ -4,8 +4,10 @@ mod addresses; mod broadcast; +mod transaction_builder; mod wallet; pub use addresses::*; pub use broadcast::*; +pub use transaction_builder::*; pub use wallet::*; diff --git a/packages/rs-platform-wallet-ffi/src/core_wallet/transaction_builder.rs b/packages/rs-platform-wallet-ffi/src/core_wallet/transaction_builder.rs new file mode 100644 index 00000000000..4d1cfe1b14c --- /dev/null +++ b/packages/rs-platform-wallet-ffi/src/core_wallet/transaction_builder.rs @@ -0,0 +1,341 @@ +use crate::error::*; +use crate::handle::{Handle, CORE_WALLET_STORAGE}; +use crate::runtime::runtime; +use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; +use dashcore::blockdata::transaction::special_transaction::TransactionPayload; +use dashcore::{Address as DashAddress, Transaction}; +use key_wallet::account::ManagedAccountCollection; +use key_wallet::managed_account::managed_account_trait::ManagedAccountTrait; +use key_wallet::managed_account::ManagedCoreFundsAccount; +use key_wallet::wallet::managed_wallet_info::coin_selection::SelectionStrategy; +use key_wallet::wallet::managed_wallet_info::fee::FeeRate; +use key_wallet::wallet::managed_wallet_info::transaction_builder::TransactionBuilder; +use key_wallet::wallet::managed_wallet_info::transaction_building::AccountTypePreference; +use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; +use rs_sdk_ffi::{MnemonicResolverCoreSigner, MnemonicResolverHandle}; +use std::os::raw::{c_char, c_void}; +use std::str::FromStr; + +#[repr(C)] +pub struct FFITransactionBuilder { + inner: *mut c_void, +} + +#[repr(C)] +pub enum CoreAccountTypeFFI { + BIP44, + BIP32, + CoinJoin, +} + +impl From for AccountTypePreference { + fn from(value: CoreAccountTypeFFI) -> Self { + match value { + CoreAccountTypeFFI::BIP44 => AccountTypePreference::BIP44, + CoreAccountTypeFFI::BIP32 => AccountTypePreference::BIP32, + CoreAccountTypeFFI::CoinJoin => AccountTypePreference::CoinJoin, + } + } +} + +#[repr(C)] +pub enum CoreSelectionStrategyFFI { + SmallestFirst, + LargestFirst, + BranchAndBound, + OptimalConsolidation, + Random, + All, +} + +impl From for SelectionStrategy { + fn from(value: CoreSelectionStrategyFFI) -> Self { + match value { + CoreSelectionStrategyFFI::SmallestFirst => SelectionStrategy::SmallestFirst, + CoreSelectionStrategyFFI::LargestFirst => SelectionStrategy::LargestFirst, + CoreSelectionStrategyFFI::BranchAndBound => SelectionStrategy::BranchAndBound, + CoreSelectionStrategyFFI::OptimalConsolidation => { + SelectionStrategy::OptimalConsolidation + } + CoreSelectionStrategyFFI::Random => SelectionStrategy::Random, + CoreSelectionStrategyFFI::All => SelectionStrategy::All, + } + } +} + +fn managed_account( + accounts: &ManagedAccountCollection, + source: AccountTypePreference, + account_index: u32, +) -> Option<&ManagedCoreFundsAccount> { + match source { + AccountTypePreference::BIP44 => accounts.standard_bip44_accounts.get(&account_index), + AccountTypePreference::BIP32 => accounts.standard_bip32_accounts.get(&account_index), + AccountTypePreference::CoinJoin => accounts.coinjoin_accounts.get(&account_index), + } +} + +fn managed_account_mut( + accounts: &mut ManagedAccountCollection, + source: AccountTypePreference, + account_index: u32, +) -> Option<&mut ManagedCoreFundsAccount> { + match source { + AccountTypePreference::BIP44 => accounts.standard_bip44_accounts.get_mut(&account_index), + AccountTypePreference::BIP32 => accounts.standard_bip32_accounts.get_mut(&account_index), + AccountTypePreference::CoinJoin => accounts.coinjoin_accounts.get_mut(&account_index), + } +} + +unsafe fn apply( + builder: *mut FFITransactionBuilder, + f: impl FnOnce(TransactionBuilder) -> TransactionBuilder, +) { + let tb = &mut *((*builder).inner as *mut TransactionBuilder); + let taken = std::mem::replace(tb, TransactionBuilder::new()); + *tb = f(taken); +} + +/// Free with `core_wallet_tx_builder_destroy`. +#[no_mangle] +pub unsafe extern "C" fn core_wallet_tx_builder_new() -> *mut FFITransactionBuilder { + let inner = Box::into_raw(Box::new(TransactionBuilder::new())) as *mut c_void; + Box::into_raw(Box::new(FFITransactionBuilder { inner })) +} + +#[no_mangle] +pub unsafe extern "C" fn core_wallet_tx_builder_add_output( + builder: *mut FFITransactionBuilder, + address: *const c_char, + amount: u64, +) -> PlatformWalletFFIResult { + check_ptr!(builder); + check_ptr!(address); + + let addr_str = unwrap_result_or_return!(std::ffi::CStr::from_ptr(address).to_str()); + let address = unwrap_result_or_return!(DashAddress::from_str(addr_str)).assume_checked(); + + apply(builder, |b| b.add_output(&address, amount)); + + PlatformWalletFFIResult::ok() +} + +#[no_mangle] +pub unsafe extern "C" fn core_wallet_tx_builder_set_change_address( + builder: *mut FFITransactionBuilder, + address: *const c_char, +) -> PlatformWalletFFIResult { + check_ptr!(builder); + check_ptr!(address); + + let addr_str = unwrap_result_or_return!(std::ffi::CStr::from_ptr(address).to_str()); + let address = unwrap_result_or_return!(DashAddress::from_str(addr_str)).assume_checked(); + + apply(builder, |b| b.set_change_address(address)); + + PlatformWalletFFIResult::ok() +} + +#[no_mangle] +pub unsafe extern "C" fn core_wallet_tx_builder_set_fee_rate( + builder: *mut FFITransactionBuilder, + sat_per_kb: u64, +) -> PlatformWalletFFIResult { + check_ptr!(builder); + + apply(builder, |b| b.set_fee_rate(FeeRate::new(sat_per_kb))); + + PlatformWalletFFIResult::ok() +} + +#[no_mangle] +pub unsafe extern "C" fn core_wallet_tx_builder_set_selection_strategy( + builder: *mut FFITransactionBuilder, + strategy: CoreSelectionStrategyFFI, +) -> PlatformWalletFFIResult { + check_ptr!(builder); + + apply(builder, |b| b.set_selection_strategy(strategy.into())); + + PlatformWalletFFIResult::ok() +} + +#[no_mangle] +pub unsafe extern "C" fn core_wallet_tx_builder_set_current_height( + builder: *mut FFITransactionBuilder, + height: u32, +) -> PlatformWalletFFIResult { + check_ptr!(builder); + + apply(builder, |b| b.set_current_height(height)); + + PlatformWalletFFIResult::ok() +} + +/// `payload_bytes` is a bincode-encoded `TransactionPayload`. +#[no_mangle] +pub unsafe extern "C" fn core_wallet_tx_builder_set_special_payload( + builder: *mut FFITransactionBuilder, + payload_bytes: *const u8, + payload_len: usize, +) -> PlatformWalletFFIResult { + check_ptr!(builder); + check_ptr!(payload_bytes); + + let bytes = std::slice::from_raw_parts(payload_bytes, payload_len); + let payload: TransactionPayload = + match bincode::decode_from_slice(bytes, bincode::config::standard()) { + Ok((p, _)) => p, + Err(e) => { + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorDeserialization, + format!("invalid special payload: {e}"), + ); + } + }; + + apply(builder, |b| b.set_special_payload(payload)); + + PlatformWalletFFIResult::ok() +} + +/// Fund the builder from the wallet account, setting inputs and change. +#[no_mangle] +pub unsafe extern "C" fn core_wallet_tx_builder_set_funding( + builder: *mut FFITransactionBuilder, + wallet: Handle, + account_type: CoreAccountTypeFFI, + account_index: u32, +) -> PlatformWalletFFIResult { + check_ptr!(builder); + + let wallet = unwrap_option_or_return!(CORE_WALLET_STORAGE.with_item(wallet, |w| w.clone())); + let wallet_id = wallet.wallet_id(); + + let source: AccountTypePreference = account_type.into(); + + let tb = &mut *((*builder).inner as *mut TransactionBuilder); + let taken = std::mem::replace(tb, TransactionBuilder::new()); + + let funded = runtime().block_on(async { + let mut wm = wallet.wallet_manager().write().await; + let (w, info) = wm + .get_wallet_and_info_mut(&wallet_id) + .ok_or_else(|| "wallet not found".to_string())?; + + let account = match source { + AccountTypePreference::BIP44 => w.get_bip44_account(account_index), + AccountTypePreference::BIP32 => w.get_bip32_account(account_index), + AccountTypePreference::CoinJoin => w.get_coinjoin_account(account_index), + } + .ok_or_else(|| format!("wallet account {source:?} #{account_index} not found"))?; + + let height = info.core_wallet.last_processed_height(); + + let managed = managed_account_mut(&mut info.core_wallet.accounts, source, account_index) + .ok_or_else(|| format!("managed account {source:?} #{account_index} not found"))?; + + Ok::<_, String>( + taken + .set_current_height(height) + .set_funding(managed, account), + ) + }); + + match funded { + Ok(b) => { + *tb = b; + PlatformWalletFFIResult::ok() + } + Err(e) => PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, + format!("set_funding failed: {e}"), + ), + } +} + +/// Build and sign, resolving signing paths from the wallet account. Returns +/// consensus-serialized signed bytes and the fee; does not broadcast. +/// +/// This function also frees the builder +#[no_mangle] +#[allow(clippy::too_many_arguments)] +pub unsafe extern "C" fn core_wallet_tx_builder_build_signed( + builder: *mut FFITransactionBuilder, + wallet: Handle, + account_type: CoreAccountTypeFFI, + account_index: u32, + core_signer_handle: *mut MnemonicResolverHandle, + out_tx_bytes: *mut *mut u8, + out_tx_len: *mut usize, + out_fee: *mut u64, +) -> PlatformWalletFFIResult { + check_ptr!(builder); + check_ptr!(core_signer_handle); + check_ptr!(out_tx_bytes); + check_ptr!(out_tx_len); + check_ptr!(out_fee); + + let wallet = unwrap_option_or_return!(CORE_WALLET_STORAGE.with_item(wallet, |w| w.clone())); + let wallet_id = wallet.wallet_id(); + let source: AccountTypePreference = account_type.into(); + let signer = MnemonicResolverCoreSigner::new(core_signer_handle, wallet_id, wallet.network()); + + let inner = std::mem::replace( + &mut *((*builder).inner as *mut TransactionBuilder), + TransactionBuilder::new(), + ); + + let build = runtime().block_on(async { + let mut wm = wallet.wallet_manager().write().await; + let info = wm + .get_wallet_info_mut(&wallet_id) + .ok_or_else(|| "wallet not found".to_string())?; + + let height = info.core_wallet.last_processed_height(); + + let managed = managed_account(&info.core_wallet.accounts, source, account_index) + .ok_or_else(|| format!("managed account {source:?} #{account_index} not found"))?; + + inner + .set_current_height(height) + .build_signed(&signer, |addr| managed.address_derivation_path(&addr)) + .await + .map_err(|e| e.to_string()) + }); + + let (tx, fee): (Transaction, u64) = match build { + Ok(v) => v, + Err(e) => { + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, + format!("transaction build failed: {e}"), + ); + } + }; + + let serialized = dashcore::consensus::serialize(&tx); + let len = serialized.len(); + + *out_tx_bytes = Box::into_raw(serialized.into_boxed_slice()) as *mut u8; + *out_tx_len = len; + *out_fee = fee; + + PlatformWalletFFIResult::ok() +} + +#[no_mangle] +pub unsafe extern "C" fn core_wallet_tx_builder_destroy(builder: *mut FFITransactionBuilder) { + if !builder.is_null() { + let b = Box::from_raw(builder); + drop(Box::from_raw(b.inner as *mut TransactionBuilder)); + } +} + +/// Free transaction bytes returned by `core_wallet_tx_builder_build_signed`. +#[no_mangle] +pub unsafe extern "C" fn core_wallet_free_tx_bytes(bytes: *mut u8, len: usize) { + if !bytes.is_null() && len > 0 { + let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(bytes, len)); + } +} diff --git a/packages/rs-platform-wallet/src/wallet/core/broadcast.rs b/packages/rs-platform-wallet/src/wallet/core/broadcast.rs index 4609d1fb6d2..02acb3768da 100644 --- a/packages/rs-platform-wallet/src/wallet/core/broadcast.rs +++ b/packages/rs-platform-wallet/src/wallet/core/broadcast.rs @@ -1,7 +1,4 @@ -use dashcore::{Address as DashAddress, Transaction}; -use key_wallet::account::account_type::StandardAccountType; -use key_wallet::managed_account::managed_account_trait::ManagedAccountTrait; -use key_wallet::signer::Signer; +use dashcore::Transaction; use crate::broadcaster::TransactionBroadcaster; use crate::{CoreWallet, PlatformWalletError}; @@ -9,9 +6,8 @@ use crate::{CoreWallet, PlatformWalletError}; impl CoreWallet { /// Broadcast a signed transaction to the network. /// - /// Build the transaction using key-wallet's - /// [`TransactionBuilder`](key_wallet::wallet::managed_wallet_info::transaction_builder::TransactionBuilder), - /// then pass the result here for broadcasting. + /// Build and sign the transaction with key-wallet's + /// [`TransactionBuilder`](key_wallet::wallet::managed_wallet_info::transaction_builder::TransactionBuilder) /// /// Delegates to the injected [`TransactionBroadcaster`] which may use /// SPV (P2P) or DAPI (gRPC) depending on how the wallet was constructed. @@ -23,126 +19,4 @@ impl CoreWallet { ) -> Result { self.broadcaster.broadcast(transaction).await } - - /// Build, sign, and broadcast a payment to the given addresses. - /// - /// Uses key-wallet's - /// [`TransactionBuilder`](key_wallet::wallet::managed_wallet_info::transaction_builder::TransactionBuilder) - /// for UTXO selection, fee estimation, and signing. Change is sent to - /// the next internal address of the specified account. - /// - /// Signing is delegated to the caller-supplied - /// [`Signer`](key_wallet::signer::Signer) via the - /// `impl TransactionSigner for S` blanket in - /// `key-wallet`'s `transaction_builder.rs`. For Swift wallets this - /// is typically a - /// [`MnemonicResolverCoreSigner`](crate::wallet::asset_lock::build) - /// from `platform-wallet-ffi`, backed by the Keychain-resolver - /// vtable so private keys never cross the FFI boundary. - /// - /// **Note (smell):** the body of this method is a near-duplicate of - /// `ManagedWalletInfo::build_and_sign_transaction` in `key-wallet` - /// (`wallet/managed_wallet_info/transaction_building.rs`). - /// It's reimplemented here because the upstream helper is BIP-44-only, - /// parametrizing upstream on `AccountTypePreference` so it picks - /// `standard_bip{32,44}_accounts` would be a trivial change - pub async fn send_to_addresses( - &self, - account_type: StandardAccountType, - account_index: u32, - outputs: Vec<(DashAddress, u64)>, - signer: &S, - ) -> Result { - use key_wallet::wallet::managed_wallet_info::coin_selection::SelectionStrategy; - use key_wallet::wallet::managed_wallet_info::transaction_builder::TransactionBuilder; - use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; - - if outputs.is_empty() { - return Err(PlatformWalletError::TransactionBuild( - "No outputs specified".to_string(), - )); - } - - let tx = { - let mut wm = self.wallet_manager.write().await; - let (wallet, info) = wm.get_wallet_and_info_mut(&self.wallet_id).ok_or_else(|| { - crate::error::PlatformWalletError::WalletNotFound( - "Wallet not found in wallet manager".to_string(), - ) - })?; - - let current_height = info.core_wallet.synced_height(); - - let (managed_account, account) = match account_type { - StandardAccountType::BIP44Account => ( - info.core_wallet - .accounts - .standard_bip44_accounts - .get_mut(&account_index) - .ok_or_else(|| { - PlatformWalletError::TransactionBuild(format!( - "{:?} managed account {} not found", - account_type, account_index - )) - })?, - wallet - .accounts - .standard_bip44_accounts - .get(&account_index) - .ok_or_else(|| { - PlatformWalletError::TransactionBuild(format!( - "{:?} account {} not found in wallet", - account_type, account_index - )) - })?, - ), - StandardAccountType::BIP32Account => ( - info.core_wallet - .accounts - .standard_bip32_accounts - .get_mut(&account_index) - .ok_or_else(|| { - PlatformWalletError::TransactionBuild(format!( - "{:?} managed account {} not found", - account_type, account_index - )) - })?, - wallet - .accounts - .standard_bip32_accounts - .get(&account_index) - .ok_or_else(|| { - PlatformWalletError::TransactionBuild(format!( - "{:?} account {} not found in wallet", - account_type, account_index - )) - })?, - ), - }; - - // The blanket `impl TransactionSigner for S` in - // `key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs:482` - // makes the signer drop-in for the previously `Wallet`-backed - // path; the funds-derived `address_derivation_path` lookup is - // unchanged. - let mut builder = TransactionBuilder::new() - .set_current_height(current_height) - .set_selection_strategy(SelectionStrategy::LargestFirst) - .set_funding(managed_account, account); - for (addr, amount) in &outputs { - builder = builder.add_output(addr, *amount); - } - - let (tx, _fee) = builder - .build_signed(signer, |addr| { - managed_account.address_derivation_path(&addr) - }) - .await - .map_err(|e| PlatformWalletError::TransactionBuild(e.to_string()))?; - tx - }; - - self.broadcast_transaction(&tx).await?; - Ok(tx) - } } diff --git a/packages/rs-platform-wallet/src/wallet/core/wallet.rs b/packages/rs-platform-wallet/src/wallet/core/wallet.rs index 8e83fd6b947..fc0e89e9074 100644 --- a/packages/rs-platform-wallet/src/wallet/core/wallet.rs +++ b/packages/rs-platform-wallet/src/wallet/core/wallet.rs @@ -63,6 +63,10 @@ impl CoreWallet { self.wallet_id } + pub fn wallet_manager(&self) -> &Arc>> { + &self.wallet_manager + } + /// Get the next unused BIP-44 external (receive) address for a specific account. pub async fn next_receive_address_for_account( &self, diff --git a/packages/rs-sdk-ffi/src/mnemonic_resolver_core_signer.rs b/packages/rs-sdk-ffi/src/mnemonic_resolver_core_signer.rs index ade11828594..d7dc7c7a8fc 100644 --- a/packages/rs-sdk-ffi/src/mnemonic_resolver_core_signer.rs +++ b/packages/rs-sdk-ffi/src/mnemonic_resolver_core_signer.rs @@ -64,7 +64,7 @@ use std::ffi::c_void; use std::os::raw::c_char; use async_trait::async_trait; -use key_wallet::bip32::{DerivationPath, ExtendedPrivKey}; +use key_wallet::bip32::{DerivationPath, ExtendedPrivKey, ExtendedPubKey}; use key_wallet::dashcore::secp256k1::{self, Secp256k1}; use key_wallet::signer::{Signer, SignerMethod}; use key_wallet::Network; @@ -222,18 +222,15 @@ impl MnemonicResolverCoreSigner { } } - /// Resolve the mnemonic from the Swift-side callback, then - /// derive the secp256k1 private key at `path`. Returns the raw - /// 32-byte scalar in a `Zeroizing` wrapper so the caller's last - /// drop point zeros it. - /// - /// All other intermediate buffers (mnemonic, seed) are dropped - /// (and zeroed) before this method returns — only the final - /// derived scalar leaks out, and even that is `Zeroizing`-wrapped. - fn derive_priv( + /// Resolve the mnemonic from the Swift-side callback, then derive the + /// BIP-32 extended private key at `path`. Intermediate buffers + /// (mnemonic, seed) and the master key are zeroed before returning; + /// the caller MUST wipe the returned key's scalar + /// (`key.private_key.non_secure_erase()`) once done. + fn derive_extended_priv( &self, path: &DerivationPath, - ) -> Result, MnemonicResolverSignerError> { + ) -> Result { if self.resolver_addr == 0 { return Err(MnemonicResolverSignerError::NullHandle); } @@ -293,27 +290,31 @@ impl MnemonicResolverCoreSigner { let secp = Secp256k1::new(); let mut master = ExtendedPrivKey::new_master(self.network, seed.as_ref()) .map_err(|e| MnemonicResolverSignerError::DerivationFailed(format!("master: {e}")))?; - let mut derived = master + let derived = master .derive_priv(&secp, path) .map_err(|e| MnemonicResolverSignerError::DerivationFailed(format!("path: {e}")))?; - // `secret_bytes()` returns a plain `[u8; 32]`; wrap in - // `Zeroizing` so the caller (and any panic-unwind path) - // wipes it on drop. - let bytes = Zeroizing::new(derived.private_key.secret_bytes()); - // TODO(upstream): `key_wallet::bip32::ExtendedPrivKey` has no // `Drop` / `Zeroize` impl — the inner `secp256k1::SecretKey` - // scalars on `master` and `derived` would otherwise drop - // un-wiped. Mirrors the SecretKey-copy hole CodeRabbit R7 - // flagged at the sign-site. Proper fix is a `Zeroize` / - // `ZeroizeOnDrop` impl in `dashpay/rust-dashcore`'s - // `key-wallet/src/bip32.rs`; until that lands, wipe the two - // SecretKey fields explicitly here. Mirrored in the sibling - // FFI at `rs-platform-wallet-ffi/src/sign_with_mnemonic_resolver.rs`. + // scalar on `master` would otherwise drop un-wiped. Wipe it + // explicitly here; the caller wipes the returned `derived`. + // Proper fix is a `Zeroize` impl upstream in + // `dashpay/rust-dashcore`'s `key-wallet/src/bip32.rs`. master.private_key.non_secure_erase(); - derived.private_key.non_secure_erase(); + Ok(derived) + } + + /// Resolve the mnemonic and derive the raw 32-byte secp256k1 private + /// key at `path`, in a `Zeroizing` wrapper so the caller's last drop + /// zeros it. + fn derive_priv( + &self, + path: &DerivationPath, + ) -> Result, MnemonicResolverSignerError> { + let mut derived = self.derive_extended_priv(path)?; + let bytes = Zeroizing::new(derived.private_key.secret_bytes()); + derived.private_key.non_secure_erase(); Ok(bytes) } } @@ -362,6 +363,20 @@ impl Signer for MnemonicResolverCoreSigner { secret.non_secure_erase(); Ok(pubkey) } + + async fn extended_public_key( + &self, + path: &DerivationPath, + ) -> Result { + let mut derived = self.derive_extended_priv(path)?; + + let secp = Secp256k1::new(); + let xpub = ExtendedPubKey::from_priv(&secp, &derived); + + derived.private_key.non_secure_erase(); + + Ok(xpub) + } } #[cfg(test)] diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/CoreTransactionBuilder.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/CoreTransactionBuilder.swift new file mode 100644 index 00000000000..cf37dd60512 --- /dev/null +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/CoreTransactionBuilder.swift @@ -0,0 +1,147 @@ +import Foundation +import DashSDKFFI + +/// key-wallet transaction builder over FFI. Build step by step, `buildSigned`, +/// then broadcast separately via `ManagedCoreWallet.broadcastTransaction`. +public final class CoreTransactionBuilder { + public enum AccountType { + case bip44 + case bip32 + case coinJoin + + var ffi: CoreAccountTypeFFI { + switch self { + case .bip44: return CORE_ACCOUNT_TYPE_FFI_BIP44 + case .bip32: return CORE_ACCOUNT_TYPE_FFI_BIP32 + case .coinJoin: return CORE_ACCOUNT_TYPE_FFI_COIN_JOIN + } + } + } + + /// Mirrors key-wallet's `SelectionStrategy`. `all` drains the account. + public enum SelectionStrategy { + case smallestFirst + case largestFirst + case branchAndBound + case optimalConsolidation + case random + case all + + var ffi: CoreSelectionStrategyFFI { + switch self { + case .smallestFirst: return CORE_SELECTION_STRATEGY_FFI_SMALLEST_FIRST + case .largestFirst: return CORE_SELECTION_STRATEGY_FFI_LARGEST_FIRST + case .branchAndBound: return CORE_SELECTION_STRATEGY_FFI_BRANCH_AND_BOUND + case .optimalConsolidation: return CORE_SELECTION_STRATEGY_FFI_OPTIMAL_CONSOLIDATION + case .random: return CORE_SELECTION_STRATEGY_FFI_RANDOM + case .all: return CORE_SELECTION_STRATEGY_FFI_ALL + } + } + } + + private let handle: UnsafeMutablePointer + + public init() throws { + guard let handle = core_wallet_tx_builder_new() else { + throw PlatformWalletError.nullPointer("core_wallet_tx_builder_new returned NULL") + } + self.handle = handle + } + + deinit { + core_wallet_tx_builder_destroy(handle) + } + + /// Fund from the account's UTXOs and set its change address. + @discardableResult + public func setFunding( + wallet: ManagedCoreWallet, + accountType: AccountType, + accountIndex: UInt32 + ) throws -> CoreTransactionBuilder { + try core_wallet_tx_builder_set_funding( + handle, wallet.handle, accountType.ffi, accountIndex + ).check() + return self + } + + @discardableResult + public func addOutput(address: String, amountDuffs: UInt64) throws -> CoreTransactionBuilder { + let c = strdup(address) + defer { free(c) } + try core_wallet_tx_builder_add_output(handle, c, amountDuffs).check() + return self + } + + @discardableResult + public func setChangeAddress(_ address: String) throws -> CoreTransactionBuilder { + let c = strdup(address) + defer { free(c) } + try core_wallet_tx_builder_set_change_address(handle, c).check() + return self + } + + @discardableResult + public func setFeeRate(satPerKb: UInt64) throws -> CoreTransactionBuilder { + try core_wallet_tx_builder_set_fee_rate(handle, satPerKb).check() + return self + } + + @discardableResult + public func setSelectionStrategy(_ strategy: SelectionStrategy) throws -> CoreTransactionBuilder { + try core_wallet_tx_builder_set_selection_strategy(handle, strategy.ffi).check() + return self + } + + @discardableResult + public func setCurrentHeight(_ height: UInt32) throws -> CoreTransactionBuilder { + try core_wallet_tx_builder_set_current_height(handle, height).check() + return self + } + + /// `bincodeBytes` is a bincode-encoded `TransactionPayload`. + @discardableResult + public func setSpecialPayload(_ bincodeBytes: Data) throws -> CoreTransactionBuilder { + try bincodeBytes.withUnsafeBytes { buf in + try core_wallet_tx_builder_set_special_payload( + handle, + buf.baseAddress?.assumingMemoryBound(to: UInt8.self), + UInt(bincodeBytes.count) + ).check() + } + return self + } + + /// Build and sign against the account; returns the signed transaction + /// and fee without broadcasting. Frees the builder + public func buildSigned( + wallet: ManagedCoreWallet, + accountType: AccountType, + accountIndex: UInt32 + ) throws -> (transaction: Data, fee: UInt64) { + var txBytesPtr: UnsafeMutablePointer? = nil + var txLen: UInt = 0 + var fee: UInt64 = 0 + + let resolver = MnemonicResolver() + try withExtendedLifetime(resolver) { + try core_wallet_tx_builder_build_signed( + handle, + wallet.handle, + accountType.ffi, + accountIndex, + resolver.handle, + &txBytesPtr, + &txLen, + &fee + ).check() + } + + guard let ptr = txBytesPtr, txLen > 0 else { + throw PlatformWalletError.unknown("FFI returned success but tx buffer was empty") + } + defer { core_wallet_free_tx_bytes(ptr, txLen) } + + return (Data(bytes: ptr, count: Int(txLen)), fee) + } +} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/ManagedCoreWallet.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/ManagedCoreWallet.swift index 4e45600e01b..1b3a97b940c 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/ManagedCoreWallet.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/ManagedCoreWallet.swift @@ -88,80 +88,6 @@ public class ManagedCoreWallet { // MARK: - Transactions - /// Account type for transaction building. - public enum AccountType: UInt32 { - case bip44 = 0 - case bip32 = 1 - } - - /// Build, sign, and broadcast a payment to the given addresses. - /// - /// Returns the serialized signed transaction. - public func sendToAddresses( - accountType: AccountType = .bip44, - accountIndex: UInt32 = 0, - recipients: [(address: String, amountDuffs: UInt64)] - ) throws -> Data { - var txBytesPtr: UnsafeMutablePointer? = nil - var txLen: UInt = 0 - - // Own the C-string storage explicitly. The naive shape - // `recipients.map { ($0.address as NSString).utf8String }` - // yields pointers into a bridged `NSString` temporary whose - // lifetime ends with the `.map` closure — Rust would then - // `CStr::from_ptr` against freed (or autorelease-pool- - // recycled) memory. `strdup` malloc's a fresh NUL-terminated - // copy we own through the FFI call; `defer` frees them after - // Rust returns. - let cStringStorage: [UnsafeMutablePointer?] = recipients.map { - strdup($0.address) - } - defer { - for ptr in cStringStorage { - if let p = ptr { free(p) } - } - } - // Promote the owned buffers to immutable `UnsafePointer?` - // for the FFI's `*const *const c_char` signature. No - // `assumingMemoryBound` re-interpretation needed — the bytes - // are already correctly typed. - let cStringPointers: [UnsafePointer?] = cStringStorage.map { ptr in - ptr.map { UnsafePointer($0) } - } - let amounts = recipients.map { $0.amountDuffs } - - // Resolver-backed signer owns mnemonic access for the lifetime - // of this call. Each Core ECDSA signature happens atomically - // inside the resolver vtable (mnemonic fetched, key derived, - // digest signed, buffers zeroed) — no priv key leaves Swift. - let resolver = MnemonicResolver() - - try cStringPointers.withUnsafeBufferPointer { addrBuf in - try amounts.withUnsafeBufferPointer { amountBuf in - try withExtendedLifetime(resolver) { - try core_wallet_send_to_addresses( - handle, - accountType.rawValue, - accountIndex, - addrBuf.baseAddress, - amountBuf.baseAddress, - UInt(recipients.count), - resolver.handle, - &txBytesPtr, - &txLen - ).check() - } - } - } - - guard let ptr = txBytesPtr, txLen > 0 else { - throw PlatformWalletError.unknown("FFI returned success but tx buffer was empty") - } - defer { core_wallet_free_tx_bytes(ptr, txLen) } - - return Data(bytes: ptr, count: Int(txLen)) - } - /// Broadcast a raw signed transaction. /// /// Returns the transaction ID as a hex string. diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/ViewModels/SendViewModel.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/ViewModels/SendViewModel.swift index af2a8bd63d2..b0741fa116f 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/ViewModels/SendViewModel.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/ViewModels/SendViewModel.swift @@ -447,19 +447,36 @@ class SendViewModel: ObservableObject { error = "Core wallet not available" return } - // Build the ordered output list (primary + any extra - // rows). `coreRecipients` is nil unless every row is a - // valid on-network Core address with a > 0 duffs amount — - // the same condition `canSend` gates on, re-checked here - // so a stale enabled-Send tap can't slip an invalid batch - // through. Coin selection + multi-output tx building are - // entirely Rust-side; we only marshal the parallel - // address/amount arrays into `sendToAddresses`. + // `coreRecipients` is nil unless every row is a valid + // on-network Core address with a > 0 duffs amount — the + // same condition `canSend` gates on, re-checked here so a + // stale enabled-Send tap can't slip an invalid batch + // through. guard let recipients = coreRecipients else { error = "Invalid recipient or amount" return } - let _ = try core.sendToAddresses(recipients: recipients) + // Coin selection, funding, and signing are Rust-side; we + // marshal the outputs into the builder, fund + sign from + // the sender account, then broadcast the signed tx. + let builder = try CoreTransactionBuilder() + for recipient in recipients { + try builder.addOutput( + address: recipient.address, + amountDuffs: recipient.amountDuffs + ) + } + try builder.setFunding( + wallet: core, + accountType: .bip44, + accountIndex: senderAccountIndex + ) + let (signedTx, _) = try builder.buildSigned( + wallet: core, + accountType: .bip44, + accountIndex: senderAccountIndex + ) + let _ = try core.broadcastTransaction(signedTx) successMessage = recipients.count > 1 ? "Payment sent to \(recipients.count) recipients" : "Payment sent" From 909484a7885dbc3933764132e42b9eff254e099f Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Tue, 30 Jun 2026 03:33:12 -0700 Subject: [PATCH 2/2] refactor(platform-wallet-ffi): expose the new core TransactionBuilder API --- Cargo.lock | 753 ++++++++---------- Cargo.toml | 16 +- .../src/core_wallet/addresses.rs | 26 + .../src/core_wallet/broadcast.rs | 18 +- .../src/core_wallet/transaction_builder.rs | 301 +++++-- .../src/wallet/core/wallet.rs | 55 ++ .../src/mnemonic_resolver_core_signer.rs | 16 +- .../CoreWallet/CoreTransactionBuilder.swift | 100 ++- .../CoreWallet/ManagedCoreWallet.swift | 21 +- .../Core/ViewModels/SendViewModel.swift | 4 +- 10 files changed, 795 insertions(+), 515 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dfc157014b0..10f3768b49e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,7 +120,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -131,14 +131,14 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "apple-native-keyring-store" @@ -162,9 +162,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" dependencies = [ "rustversion", ] @@ -189,9 +189,9 @@ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" [[package]] name = "assert_cmd" @@ -244,7 +244,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -255,7 +255,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -294,9 +294,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" dependencies = [ "aws-lc-sys", "zeroize", @@ -304,14 +304,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" dependencies = [ "cc", "cmake", "dunce", "fs_extra", + "pkg-config", ] [[package]] @@ -422,7 +423,7 @@ checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -449,9 +450,9 @@ checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" [[package]] name = "base58ck" -version = "0.1.100" +version = "0.1.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec5dc7e09f7bb15f0062da7c03086d6b71a2c84e0af4fccbbc7d8c6559847816" +checksum = "365c0acd5b2e8dd0111a46c4faea83fb3cfb6e39a49a7c73a06e090db7b2eff0" dependencies = [ "bitcoin_hashes", ] @@ -546,7 +547,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex 1.3.0", - "syn 2.0.117", + "syn 2.0.118", "which", ] @@ -559,13 +560,13 @@ dependencies = [ "bitflags 2.13.0", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.13.0", "proc-macro2", "quote", "regex", "rustc-hash 2.1.2", "shlex 1.3.0", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -622,20 +623,41 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2d6094e2a1ba3c93b5a596fe5a10d1a10c3c6e06785cde89f693a044c01aa40" +dependencies = [ + "bitcoin-internals", +] + +[[package]] +name = "bitcoin-internals" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a30a22d1f112dde8e16be7b45c63645dc165cef254f835b3e1e9553e485cfa64" +dependencies = [ + "hex-conservative 0.3.2", +] + [[package]] name = "bitcoin-io" -version = "0.1.100" +version = "0.1.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11301df0b06f22dea7bb1916403fdd88a371031e495c49b8f96931b28189e175" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] [[package]] name = "bitcoin_hashes" -version = "0.14.100" +version = "0.14.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9901a56e133a1fc86eeb1113e2591f45f4682451ca893bff494d2f88918e3f" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" dependencies = [ "bitcoin-io", - "hex-conservative", + "hex-conservative 0.2.2", ] [[package]] @@ -652,9 +674,9 @@ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium", @@ -803,9 +825,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +checksum = "2f3f6da4992df95bbcd9af42a6c7dcb994498fc9048230405f3b36ff7cd3f145" dependencies = [ "borsh-derive", "bytes", @@ -814,15 +836,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +checksum = "3ae8fb4fb5740e4b2c4884ff95f5f32f5e8479db1e8fd8eb49ddbe09eb09bb7c" dependencies = [ "once_cell", "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -837,13 +859,13 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79" dependencies = [ "memchr", "regex-automata", - "serde", + "serde_core", ] [[package]] @@ -900,9 +922,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" dependencies = [ "serde", ] @@ -946,7 +968,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.117", + "syn 2.0.118", "tempfile", "toml 0.8.23", ] @@ -965,16 +987,16 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.117", + "syn 2.0.118", "tempfile", "toml 0.9.12+spec-1.1.0", ] [[package]] name = "cc" -version = "1.2.64" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "jobserver", @@ -1016,9 +1038,9 @@ dependencies = [ [[package]] name = "chacha20" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -1161,7 +1183,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1523,7 +1545,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1575,7 +1597,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1588,7 +1610,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1599,7 +1621,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1610,7 +1632,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core 0.23.0", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1636,13 +1658,8 @@ dependencies = [ [[package]] name = "dash-network" -<<<<<<< HEAD -version = "0.44.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" -======= -version = "0.43.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" ->>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=afcff1566c81abb0a9cf64b99b0ee675169fddfc#afcff1566c81abb0a9cf64b99b0ee675169fddfc" dependencies = [ "bincode", "bincode_derive", @@ -1652,13 +1669,8 @@ dependencies = [ [[package]] name = "dash-network-seeds" -<<<<<<< HEAD -version = "0.44.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" -======= -version = "0.43.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" ->>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=afcff1566c81abb0a9cf64b99b0ee675169fddfc#afcff1566c81abb0a9cf64b99b0ee675169fddfc" dependencies = [ "dash-network", ] @@ -1684,7 +1696,7 @@ version = "4.0.0" dependencies = [ "heck 0.5.0", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1734,13 +1746,8 @@ dependencies = [ [[package]] name = "dash-spv" -<<<<<<< HEAD -version = "0.44.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" -======= -version = "0.43.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" ->>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=afcff1566c81abb0a9cf64b99b0ee675169fddfc#afcff1566c81abb0a9cf64b99b0ee675169fddfc" dependencies = [ "async-trait", "chrono", @@ -1768,13 +1775,8 @@ dependencies = [ [[package]] name = "dashcore" -<<<<<<< HEAD -version = "0.44.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" -======= -version = "0.43.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" ->>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=afcff1566c81abb0a9cf64b99b0ee675169fddfc#afcff1566c81abb0a9cf64b99b0ee675169fddfc" dependencies = [ "anyhow", "base64-compat", @@ -1799,23 +1801,13 @@ dependencies = [ [[package]] name = "dashcore-private" -<<<<<<< HEAD -version = "0.44.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=afcff1566c81abb0a9cf64b99b0ee675169fddfc#afcff1566c81abb0a9cf64b99b0ee675169fddfc" [[package]] name = "dashcore-rpc" -version = "0.44.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" -======= -version = "0.43.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" - -[[package]] -name = "dashcore-rpc" -version = "0.43.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" ->>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=afcff1566c81abb0a9cf64b99b0ee675169fddfc#afcff1566c81abb0a9cf64b99b0ee675169fddfc" dependencies = [ "dashcore-rpc-json", "hex", @@ -1827,13 +1819,8 @@ dependencies = [ [[package]] name = "dashcore-rpc-json" -<<<<<<< HEAD -version = "0.44.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" -======= -version = "0.43.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" ->>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=afcff1566c81abb0a9cf64b99b0ee675169fddfc#afcff1566c81abb0a9cf64b99b0ee675169fddfc" dependencies = [ "bincode", "dashcore", @@ -1847,13 +1834,8 @@ dependencies = [ [[package]] name = "dashcore_hashes" -<<<<<<< HEAD -version = "0.44.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" -======= -version = "0.43.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" ->>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=afcff1566c81abb0a9cf64b99b0ee675169fddfc#afcff1566c81abb0a9cf64b99b0ee675169fddfc" dependencies = [ "bincode", "dashcore-private", @@ -1934,6 +1916,38 @@ dependencies = [ "keyring-core", ] +[[package]] +name = "defmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "delegate" version = "0.13.5" @@ -1942,7 +1956,7 @@ checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1972,7 +1986,7 @@ checksum = "74ef43543e701c01ad77d3a5922755c6a1d71b22d942cb8042be4994b380caff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1983,7 +1997,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2012,7 +2026,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "unicode-xid", ] @@ -2026,7 +2040,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2060,7 +2074,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2158,7 +2172,7 @@ version = "4.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2420,14 +2434,14 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "env_filter" -version = "1.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ "log", "regex", @@ -2435,9 +2449,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ "anstream", "anstyle", @@ -2788,7 +2802,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2871,16 +2885,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", "rand_core 0.10.1", - "wasip2", - "wasip3", ] [[package]] @@ -2891,7 +2903,7 @@ checksum = "6cf442baaabe4213ce7d1239afc26c039180b6456da2cededa316ae2c8a77a77" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2906,13 +2918,8 @@ dependencies = [ [[package]] name = "git-state" -<<<<<<< HEAD -version = "0.44.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" -======= -version = "0.43.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" ->>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=afcff1566c81abb0a9cf64b99b0ee675169fddfc#afcff1566c81abb0a9cf64b99b0ee675169fddfc" [[package]] name = "glob" @@ -3406,6 +3413,15 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hex-conservative" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830e599c2904b08f0834ee6337d8fe8f0ed4a63b5d9e7a7f49c0ffa06d08d360" +dependencies = [ + "arrayvec", +] + [[package]] name = "hex-literal" version = "1.1.0" @@ -3711,12 +3727,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -3746,9 +3756,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b915661dd01db3f05050265b2477bcc6527b3792388e2749b41623cc592be67d" +checksum = "fe112b004901c62c2faa11f4f75e9864e0cc5af8da71c9115d184a3aa888749f" dependencies = [ "crossbeam-deque", "globset", @@ -3901,10 +3911,11 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.28" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" +checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" dependencies = [ + "defmt", "jiff-static", "log", "portable-atomic", @@ -3914,13 +3925,13 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.28" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" +checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3950,7 +3961,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3969,7 +3980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4078,13 +4089,8 @@ dependencies = [ [[package]] name = "key-wallet" -<<<<<<< HEAD -version = "0.44.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" -======= -version = "0.43.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" ->>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=afcff1566c81abb0a9cf64b99b0ee675169fddfc#afcff1566c81abb0a9cf64b99b0ee675169fddfc" dependencies = [ "aes", "async-trait", @@ -4112,13 +4118,8 @@ dependencies = [ [[package]] name = "key-wallet-ffi" -<<<<<<< HEAD -version = "0.44.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" -======= -version = "0.43.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" ->>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=afcff1566c81abb0a9cf64b99b0ee675169fddfc#afcff1566c81abb0a9cf64b99b0ee675169fddfc" dependencies = [ "cbindgen 0.29.4", "dash-network", @@ -4133,13 +4134,8 @@ dependencies = [ [[package]] name = "key-wallet-manager" -<<<<<<< HEAD -version = "0.44.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=991c6ebe24d7ea8ba7d900a052b25be8c5498409#991c6ebe24d7ea8ba7d900a052b25be8c5498409" -======= -version = "0.43.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=03096dd03460784d4c3ee72e674127cbf32a6c3b#03096dd03460784d4c3ee72e674127cbf32a6c3b" ->>>>>>> 2711b7420c (refactor(platform-wallet-ffi): expose the new core TransactionBuilder API) +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=afcff1566c81abb0a9cf64b99b0ee675169fddfc#afcff1566c81abb0a9cf64b99b0ee675169fddfc" dependencies = [ "async-trait", "bincode", @@ -4187,12 +4183,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "lhash" version = "1.1.0" @@ -4302,9 +4292,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lru" @@ -4537,7 +4527,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4656,7 +4646,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4716,7 +4706,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4811,7 +4801,7 @@ dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4860,7 +4850,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5093,7 +5083,7 @@ checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5143,7 +5133,7 @@ version = "4.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "virtue 0.0.17", ] @@ -5171,7 +5161,7 @@ name = "platform-value-convertible" version = "4.0.0" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5190,7 +5180,7 @@ version = "4.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5454,7 +5444,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5476,6 +5466,28 @@ dependencies = [ "toml_edit 0.25.12+spec-1.1.0", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -5546,7 +5558,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" dependencies = [ "heck 0.4.1", - "itertools 0.10.5", + "itertools 0.14.0", "log", "multimap", "petgraph", @@ -5556,7 +5568,7 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn 2.0.117", + "syn 2.0.118", "tempfile", ] @@ -5567,10 +5579,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5580,10 +5592,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5693,9 +5705,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quick_cache" -version = "0.6.23" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3db184a8b66cfe87f0263a1de147a6b554c864d1767c6f7fa4eb0e5497b565" +checksum = "b9c6658afe513a3b484e3abfdaa0d03ef3c0bbf017542c178dd55f94eb3051f9" dependencies = [ "ahash 0.8.12", "equivalent", @@ -5705,9 +5717,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.9" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", @@ -5725,9 +5737,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" dependencies = [ "aws-lc-rs", "bytes", @@ -5756,14 +5768,14 @@ dependencies = [ "once_cell", "socket2 0.5.10", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -5813,8 +5825,8 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "chacha20 0.10.0", - "getrandom 0.4.2", + "chacha20 0.10.1", + "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -5891,9 +5903,9 @@ dependencies = [ [[package]] name = "rapidhash" -version = "4.4.1" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +checksum = "32b266a82f4aa99bb5c25e28d11cc44ace63d91adbcbcee4d323e2ae3d49ef37" dependencies = [ "rustversion", ] @@ -5972,7 +5984,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -6013,7 +6025,7 @@ dependencies = [ "quote", "refinery-core", "regex", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -6512,7 +6524,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74a5a6f027e892c7a035c6fddb50435a1fbf5a734ffc0c2a9fed4d0221440519" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -6564,9 +6576,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.40" +version = "0.23.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" dependencies = [ "aws-lc-rs", "log", @@ -6592,9 +6604,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" dependencies = [ "web-time", "zeroize", @@ -6902,7 +6914,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -6938,7 +6950,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -7016,7 +7028,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -7028,7 +7040,7 @@ dependencies = [ "darling 0.23.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -7063,7 +7075,7 @@ checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -7236,7 +7248,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -7357,7 +7369,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -7369,7 +7381,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -7406,9 +7418,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -7432,7 +7444,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -7475,7 +7487,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix 1.1.4", "windows-sys 0.52.0", @@ -7561,7 +7573,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -7572,7 +7584,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "test-case-core", ] @@ -7602,7 +7614,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -7613,7 +7625,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -7636,9 +7648,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.49" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", "num-conv", @@ -7656,9 +7668,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.29" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -7735,7 +7747,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -8004,7 +8016,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -8029,7 +8041,7 @@ dependencies = [ "prost-build", "prost-types 0.14.4", "quote", - "syn 2.0.117", + "syn 2.0.118", "tempfile", "tonic-build", ] @@ -8171,7 +8183,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -8244,7 +8256,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad06847b7afb65c7866a36664b75c40b895e318cea4f71299f013fb22965329d" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -8448,11 +8460,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.3" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "rand 0.10.1", "wasm-bindgen", @@ -8507,7 +8519,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -8578,16 +8590,7 @@ version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] @@ -8638,7 +8641,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "wasm-bindgen-shared", ] @@ -8681,7 +8684,7 @@ checksum = "f579cdd0123ac74b94e1a4a72bd963cf30ebac343f2df347da0b8df24cdebed2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -8755,16 +8758,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - [[package]] name = "wasm-logger" version = "0.2.0" @@ -8776,18 +8769,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.14.0", - "wasm-encoder", - "wasmparser", -] - [[package]] name = "wasm-sdk" version = "4.0.0" @@ -8837,18 +8818,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.13.0", - "hashbrown 0.15.5", - "indexmap 2.14.0", - "semver", -] - [[package]] name = "web-sys" version = "0.3.85" @@ -8871,18 +8840,18 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" dependencies = [ "rustls-pki-types", ] [[package]] name = "webpki-roots" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" dependencies = [ "rustls-pki-types", ] @@ -8957,7 +8926,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -8968,7 +8937,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -9025,7 +8994,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -9034,7 +9003,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", ] [[package]] @@ -9052,14 +9030,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -9068,48 +9063,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.5.40" @@ -9137,100 +9180,12 @@ dependencies = [ "memchr", ] -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap 2.14.0", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.13.0", - "indexmap 2.14.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.14.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "withdrawals-contract" version = "4.0.0" @@ -9261,9 +9216,9 @@ dependencies = [ [[package]] name = "xxhash-rust" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" +checksum = "4d93c89cdc2d3a63c3ec48ffe926931bdc069eafa8e4402fe6d8f790c9d1e576" [[package]] name = "yansi" @@ -9290,7 +9245,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] @@ -9332,7 +9287,7 @@ checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -9352,7 +9307,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] @@ -9374,7 +9329,7 @@ checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -9430,7 +9385,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -9494,9 +9449,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" +checksum = "5431d5661c32445236631278f27946e444ddafe4684cac70b185272d4f9c52d5" [[package]] name = "zmij" diff --git a/Cargo.toml b/Cargo.toml index 84d8a4f2b94..da7ec6e8986 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,14 +50,14 @@ members = [ ] [workspace.dependencies] -dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "a8a096838b829cf5bec3c2374a23511640a0c35c" } -dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "a8a096838b829cf5bec3c2374a23511640a0c35c" } -dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "a8a096838b829cf5bec3c2374a23511640a0c35c" } -key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "a8a096838b829cf5bec3c2374a23511640a0c35c" } -key-wallet-ffi = { git = "https://github.com/dashpay/rust-dashcore", rev = "a8a096838b829cf5bec3c2374a23511640a0c35c" } -key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "a8a096838b829cf5bec3c2374a23511640a0c35c" } -dash-network = { git = "https://github.com/dashpay/rust-dashcore", rev = "a8a096838b829cf5bec3c2374a23511640a0c35c" } -dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "a8a096838b829cf5bec3c2374a23511640a0c35c" } +dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "afcff1566c81abb0a9cf64b99b0ee675169fddfc" } +dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "afcff1566c81abb0a9cf64b99b0ee675169fddfc" } +dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "afcff1566c81abb0a9cf64b99b0ee675169fddfc" } +key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "afcff1566c81abb0a9cf64b99b0ee675169fddfc" } +key-wallet-ffi = { git = "https://github.com/dashpay/rust-dashcore", rev = "afcff1566c81abb0a9cf64b99b0ee675169fddfc" } +key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "afcff1566c81abb0a9cf64b99b0ee675169fddfc" } +dash-network = { git = "https://github.com/dashpay/rust-dashcore", rev = "afcff1566c81abb0a9cf64b99b0ee675169fddfc" } +dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "afcff1566c81abb0a9cf64b99b0ee675169fddfc" } tokio-metrics = "0.5" diff --git a/packages/rs-platform-wallet-ffi/src/core_wallet/addresses.rs b/packages/rs-platform-wallet-ffi/src/core_wallet/addresses.rs index ccd16e52798..01d8953ad14 100644 --- a/packages/rs-platform-wallet-ffi/src/core_wallet/addresses.rs +++ b/packages/rs-platform-wallet-ffi/src/core_wallet/addresses.rs @@ -1,9 +1,11 @@ //! FFI bindings for CoreWallet address derivation. +use super::transaction_builder::CoreAccountTypeFFI; use crate::error::*; use crate::handle::*; use crate::runtime::runtime; use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; +use key_wallet::wallet::managed_wallet_info::transaction_building::AccountTypePreference; use std::ffi::CString; use std::os::raw::c_char; @@ -51,6 +53,30 @@ pub unsafe extern "C" fn core_wallet_next_change_address( PlatformWalletFFIResult::ok() } +/// Widen the gap limit for an account, generating the addresses the wider +/// limit now requires. +/// +/// # Safety +/// `handle` must be a valid core-wallet handle. +#[no_mangle] +pub unsafe extern "C" fn core_wallet_set_gap_limit( + handle: Handle, + account_type: CoreAccountTypeFFI, + account_index: u32, + gap_limit: u32, +) -> PlatformWalletFFIResult { + let source: AccountTypePreference = account_type.into(); + + let option = CORE_WALLET_STORAGE.with_item(handle, |wallet| { + runtime().block_on(wallet.set_gap_limit(source, account_index, gap_limit)) + }); + + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + + PlatformWalletFFIResult::ok() +} + /// Free an address string returned by `core_wallet_next_receive_address` /// or `core_wallet_next_change_address`. #[no_mangle] diff --git a/packages/rs-platform-wallet-ffi/src/core_wallet/broadcast.rs b/packages/rs-platform-wallet-ffi/src/core_wallet/broadcast.rs index 76c8f674ca2..f13d12d8264 100644 --- a/packages/rs-platform-wallet-ffi/src/core_wallet/broadcast.rs +++ b/packages/rs-platform-wallet-ffi/src/core_wallet/broadcast.rs @@ -1,32 +1,38 @@ //! FFI bindings for CoreWallet transaction broadcasting. +use super::transaction_builder::FFICoreTransaction; use crate::error::*; use crate::handle::*; use crate::runtime::runtime; use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; use std::os::raw::c_char; -/// Broadcast a signed transaction to the network. +/// Broadcast a transaction built by `core_wallet_tx_builder_build_signed`. +/// +/// # Safety +/// `handle` must be a valid core-wallet handle; `tx` a valid; +/// `out_txid` must be writable. #[no_mangle] pub unsafe extern "C" fn core_wallet_broadcast_transaction( handle: Handle, - tx_bytes: *const u8, - tx_bytes_len: usize, + tx: *const FFICoreTransaction, out_txid: *mut *mut c_char, ) -> PlatformWalletFFIResult { - check_ptr!(tx_bytes); + check_ptr!(tx); check_ptr!(out_txid); - let bytes = std::slice::from_raw_parts(tx_bytes, tx_bytes_len); let tx: dashcore::Transaction = - unwrap_result_or_return!(dashcore::consensus::deserialize(bytes)); + unwrap_result_or_return!(dashcore::consensus::deserialize((*tx).bytes())); let option = CORE_WALLET_STORAGE.with_item(handle, |wallet| { runtime().block_on(wallet.broadcast_transaction(&tx)) }); + let result = unwrap_option_or_return!(option); + let txid = unwrap_result_or_return!(result); let c_str = unwrap_result_or_return!(std::ffi::CString::new(txid.to_string())); *out_txid = c_str.into_raw(); + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/core_wallet/transaction_builder.rs b/packages/rs-platform-wallet-ffi/src/core_wallet/transaction_builder.rs index 4d1cfe1b14c..9e9106eea89 100644 --- a/packages/rs-platform-wallet-ffi/src/core_wallet/transaction_builder.rs +++ b/packages/rs-platform-wallet-ffi/src/core_wallet/transaction_builder.rs @@ -1,9 +1,12 @@ +use crate::core_wallet_types::OutPointFFI; use crate::error::*; use crate::handle::{Handle, CORE_WALLET_STORAGE}; use crate::runtime::runtime; +use crate::types::{FFINetwork, Network}; use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; use dashcore::blockdata::transaction::special_transaction::TransactionPayload; -use dashcore::{Address as DashAddress, Transaction}; +use dashcore::hashes::Hash; +use dashcore::{Address as DashAddress, OutPoint, Transaction, Txid}; use key_wallet::account::ManagedAccountCollection; use key_wallet::managed_account::managed_account_trait::ManagedAccountTrait; use key_wallet::managed_account::ManagedCoreFundsAccount; @@ -16,9 +19,36 @@ use rs_sdk_ffi::{MnemonicResolverCoreSigner, MnemonicResolverHandle}; use std::os::raw::{c_char, c_void}; use std::str::FromStr; +/// Opaque, C-compatible transaction builder. `inner` is a heap-boxed +/// key-wallet `TransactionBuilder`; `network` is the wallet network output +/// and change addresses are validated against. +/// +/// NOT thread-safe: a single builder must be used from one thread at a time. +/// The setters mutate `*inner` in place (`take_builder`/`store_builder`) +/// without synchronization. #[repr(C)] pub struct FFITransactionBuilder { inner: *mut c_void, + network: FFINetwork, +} + +/// Broadcast it with `core_wallet_broadcast_transaction`, then release it +/// with `core_wallet_transaction_free`. +#[repr(C)] +pub struct FFICoreTransaction { + tx_bytes: *mut u8, + tx_len: usize, + fee: u64, +} + +impl FFICoreTransaction { + pub(crate) fn bytes(&self) -> &[u8] { + if self.tx_bytes.is_null() || self.tx_len == 0 { + &[] + } else { + unsafe { std::slice::from_raw_parts(self.tx_bytes, self.tx_len) } + } + } } #[repr(C)] @@ -87,22 +117,42 @@ fn managed_account_mut( } } -unsafe fn apply( - builder: *mut FFITransactionBuilder, - f: impl FnOnce(TransactionBuilder) -> TransactionBuilder, -) { - let tb = &mut *((*builder).inner as *mut TransactionBuilder); - let taken = std::mem::replace(tb, TransactionBuilder::new()); - *tb = f(taken); +impl FFITransactionBuilder { + /// The inner builder taken out by value, leaving an empty one in its + /// place. Pair with [`FFITransactionBuilder::store_builder`] to apply a + /// fluent (`self -> Self`) method. + /// + /// # Safety + /// `self.inner` must point at a live `TransactionBuilder` + unsafe fn take_builder(&self) -> TransactionBuilder { + std::mem::take(&mut *(self.inner as *mut TransactionBuilder)) + } + + /// Store `builder` back into the inner slot. + /// + /// # Safety + /// `self.inner` must point at a live `TransactionBuilder` + unsafe fn store_builder(&self, builder: TransactionBuilder) { + *(self.inner as *mut TransactionBuilder) = builder; + } } -/// Free with `core_wallet_tx_builder_destroy`. +/// Create a new transaction builder for `network`. Free with +/// `core_wallet_tx_builder_destroy` (or `core_wallet_tx_builder_build_signed`, +/// which consumes it). +/// +/// # Safety +/// The returned pointer is owned by the caller. #[no_mangle] -pub unsafe extern "C" fn core_wallet_tx_builder_new() -> *mut FFITransactionBuilder { +pub unsafe extern "C" fn core_wallet_tx_builder_new( + network: FFINetwork, +) -> *mut FFITransactionBuilder { let inner = Box::into_raw(Box::new(TransactionBuilder::new())) as *mut c_void; - Box::into_raw(Box::new(FFITransactionBuilder { inner })) + Box::into_raw(Box::new(FFITransactionBuilder { inner, network })) } +/// # Safety +/// `builder` must be a valid, non-destroyed pointer; `address` a valid NUL-terminated C string. #[no_mangle] pub unsafe extern "C" fn core_wallet_tx_builder_add_output( builder: *mut FFITransactionBuilder, @@ -113,13 +163,27 @@ pub unsafe extern "C" fn core_wallet_tx_builder_add_output( check_ptr!(address); let addr_str = unwrap_result_or_return!(std::ffi::CStr::from_ptr(address).to_str()); - let address = unwrap_result_or_return!(DashAddress::from_str(addr_str)).assume_checked(); + let network: Network = (*builder).network.into(); + let parsed = unwrap_result_or_return!(DashAddress::from_str(addr_str)); + let address = match parsed.require_network(network) { + Ok(a) => a, + Err(e) => { + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, + format!("output address network mismatch: {e}"), + ); + } + }; - apply(builder, |b| b.add_output(&address, amount)); + let b = (*builder).take_builder(); + let b = b.add_output(&address, amount); + (*builder).store_builder(b); PlatformWalletFFIResult::ok() } +/// # Safety +/// `builder` must be a valid, non-destroyed pointer; `address` a valid NUL-terminated C string. #[no_mangle] pub unsafe extern "C" fn core_wallet_tx_builder_set_change_address( builder: *mut FFITransactionBuilder, @@ -129,13 +193,27 @@ pub unsafe extern "C" fn core_wallet_tx_builder_set_change_address( check_ptr!(address); let addr_str = unwrap_result_or_return!(std::ffi::CStr::from_ptr(address).to_str()); - let address = unwrap_result_or_return!(DashAddress::from_str(addr_str)).assume_checked(); + let network: Network = (*builder).network.into(); + let parsed = unwrap_result_or_return!(DashAddress::from_str(addr_str)); + let address = match parsed.require_network(network) { + Ok(a) => a, + Err(e) => { + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, + format!("change address network mismatch: {e}"), + ); + } + }; - apply(builder, |b| b.set_change_address(address)); + let b = (*builder).take_builder(); + let b = b.set_change_address(address); + (*builder).store_builder(b); PlatformWalletFFIResult::ok() } +/// # Safety +/// `builder` must be a valid, non-destroyed pointer. #[no_mangle] pub unsafe extern "C" fn core_wallet_tx_builder_set_fee_rate( builder: *mut FFITransactionBuilder, @@ -143,11 +221,15 @@ pub unsafe extern "C" fn core_wallet_tx_builder_set_fee_rate( ) -> PlatformWalletFFIResult { check_ptr!(builder); - apply(builder, |b| b.set_fee_rate(FeeRate::new(sat_per_kb))); + let b = (*builder).take_builder(); + let b = b.set_fee_rate(FeeRate::new(sat_per_kb)); + (*builder).store_builder(b); PlatformWalletFFIResult::ok() } +/// # Safety +/// `builder` must be a valid, non-destroyed pointer. #[no_mangle] pub unsafe extern "C" fn core_wallet_tx_builder_set_selection_strategy( builder: *mut FFITransactionBuilder, @@ -155,11 +237,15 @@ pub unsafe extern "C" fn core_wallet_tx_builder_set_selection_strategy( ) -> PlatformWalletFFIResult { check_ptr!(builder); - apply(builder, |b| b.set_selection_strategy(strategy.into())); + let b = (*builder).take_builder(); + let b = b.set_selection_strategy(strategy.into()); + (*builder).store_builder(b); PlatformWalletFFIResult::ok() } +/// # Safety +/// `builder` must be a valid, non-destroyed pointer. #[no_mangle] pub unsafe extern "C" fn core_wallet_tx_builder_set_current_height( builder: *mut FFITransactionBuilder, @@ -167,12 +253,18 @@ pub unsafe extern "C" fn core_wallet_tx_builder_set_current_height( ) -> PlatformWalletFFIResult { check_ptr!(builder); - apply(builder, |b| b.set_current_height(height)); + let b = (*builder).take_builder(); + let b = b.set_current_height(height); + (*builder).store_builder(b); PlatformWalletFFIResult::ok() } /// `payload_bytes` is a bincode-encoded `TransactionPayload`. +/// +/// # Safety +/// `builder` must be a valid, non-destroyed pointer; `payload_bytes` a readable buffer of +/// `payload_len` bytes. #[no_mangle] pub unsafe extern "C" fn core_wallet_tx_builder_set_special_payload( builder: *mut FFITransactionBuilder, @@ -194,12 +286,17 @@ pub unsafe extern "C" fn core_wallet_tx_builder_set_special_payload( } }; - apply(builder, |b| b.set_special_payload(payload)); + let b = (*builder).take_builder(); + let b = b.set_special_payload(payload); + (*builder).store_builder(b); PlatformWalletFFIResult::ok() } /// Fund the builder from the wallet account, setting inputs and change. +/// +/// # Safety +/// `builder` must be a valid, non-destroyed pointer; `wallet` a valid core-wallet handle. #[no_mangle] pub unsafe extern "C" fn core_wallet_tx_builder_set_funding( builder: *mut FFITransactionBuilder, @@ -211,13 +308,9 @@ pub unsafe extern "C" fn core_wallet_tx_builder_set_funding( let wallet = unwrap_option_or_return!(CORE_WALLET_STORAGE.with_item(wallet, |w| w.clone())); let wallet_id = wallet.wallet_id(); - let source: AccountTypePreference = account_type.into(); - let tb = &mut *((*builder).inner as *mut TransactionBuilder); - let taken = std::mem::replace(tb, TransactionBuilder::new()); - - let funded = runtime().block_on(async { + let result = runtime().block_on(async { let mut wm = wallet.wallet_manager().write().await; let (w, info) = wm .get_wallet_and_info_mut(&wallet_id) @@ -235,18 +328,18 @@ pub unsafe extern "C" fn core_wallet_tx_builder_set_funding( let managed = managed_account_mut(&mut info.core_wallet.accounts, source, account_index) .ok_or_else(|| format!("managed account {source:?} #{account_index} not found"))?; - Ok::<_, String>( - taken - .set_current_height(height) - .set_funding(managed, account), - ) + // Resolution succeeded — only now consume the builder so a lookup + // failure above can never leave it emptied. + let taken = (*builder).take_builder(); + let funded = taken + .set_current_height(height) + .set_funding(managed, account); + (*builder).store_builder(funded); + Ok::<_, String>(()) }); - match funded { - Ok(b) => { - *tb = b; - PlatformWalletFFIResult::ok() - } + match result { + Ok(()) => PlatformWalletFFIResult::ok(), Err(e) => PlatformWalletFFIResult::err( PlatformWalletFFIResultCode::ErrorWalletOperation, format!("set_funding failed: {e}"), @@ -254,10 +347,85 @@ pub unsafe extern "C" fn core_wallet_tx_builder_set_funding( } } +/// Add a caller-chosen subset of the account's UTXOs as inputs. `outpoints` +/// are selected from the account's own UTXO set (the same ones +/// `platform_wallet_account_utxos` returns). An outpoint not owned by the +/// account is an error +/// +/// # Safety +/// `builder` must be a valid, non-destroyed pointer; `wallet` a valid core-wallet handle; +/// `outpoints` a readable array of `outpoints_len` elements. +#[no_mangle] +pub unsafe extern "C" fn core_wallet_tx_builder_add_inputs_from_outpoints( + builder: *mut FFITransactionBuilder, + wallet: Handle, + account_type: CoreAccountTypeFFI, + account_index: u32, + outpoints: *const OutPointFFI, + outpoints_len: usize, +) -> PlatformWalletFFIResult { + check_ptr!(builder); + + let wallet = unwrap_option_or_return!(CORE_WALLET_STORAGE.with_item(wallet, |w| w.clone())); + let wallet_id = wallet.wallet_id(); + let source: AccountTypePreference = account_type.into(); + + let requested: Vec = if outpoints_len == 0 { + Vec::new() + } else { + check_ptr!(outpoints); + std::slice::from_raw_parts(outpoints, outpoints_len) + .iter() + .map(|op| OutPoint { + txid: Txid::from_byte_array(op.txid), + vout: op.vout, + }) + .collect() + }; + + let result = runtime().block_on(async { + let wm = wallet.wallet_manager().read().await; + let info = wm + .get_wallet_info(&wallet_id) + .ok_or_else(|| "wallet not found".to_string())?; + + let managed = managed_account(&info.core_wallet.accounts, source, account_index) + .ok_or_else(|| format!("managed account {source:?} #{account_index} not found"))?; + + let mut selected = Vec::with_capacity(requested.len()); + for op in &requested { + let utxo = managed + .utxos + .get(op) + .cloned() + .ok_or_else(|| format!("outpoint {}:{} not in account", op.txid, op.vout))?; + selected.push(utxo); + } + + // Validation succeeded — only now consume the builder. + let taken = (*builder).take_builder(); + (*builder).store_builder(taken.add_inputs(selected)); + Ok::<_, String>(()) + }); + + match result { + Ok(()) => PlatformWalletFFIResult::ok(), + Err(e) => PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, + format!("add_inputs_from_outpoints failed: {e}"), + ), + } +} + /// Build and sign, resolving signing paths from the wallet account. Returns -/// consensus-serialized signed bytes and the fee; does not broadcast. +/// consensus-serialized signed bytes and the fee. /// /// This function also frees the builder +/// +/// # Safety +/// `builder` must be a valid, non-destroyed pointer; `wallet` a valid core-wallet handle; +/// `core_signer_handle` a valid, non-destroyed resolver handle; `out_tx` a +/// writable pointer the caller later frees with `core_wallet_transaction_free`. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn core_wallet_tx_builder_build_signed( @@ -266,30 +434,26 @@ pub unsafe extern "C" fn core_wallet_tx_builder_build_signed( account_type: CoreAccountTypeFFI, account_index: u32, core_signer_handle: *mut MnemonicResolverHandle, - out_tx_bytes: *mut *mut u8, - out_tx_len: *mut usize, - out_fee: *mut u64, + out_tx: *mut FFICoreTransaction, ) -> PlatformWalletFFIResult { check_ptr!(builder); + // `build` consumes the builder: reclaim both heap boxes up front so they + // are freed on every return path below + let ffi = Box::from_raw(builder); + let inner = *Box::from_raw(ffi.inner as *mut TransactionBuilder); + check_ptr!(core_signer_handle); - check_ptr!(out_tx_bytes); - check_ptr!(out_tx_len); - check_ptr!(out_fee); + check_ptr!(out_tx); let wallet = unwrap_option_or_return!(CORE_WALLET_STORAGE.with_item(wallet, |w| w.clone())); let wallet_id = wallet.wallet_id(); let source: AccountTypePreference = account_type.into(); let signer = MnemonicResolverCoreSigner::new(core_signer_handle, wallet_id, wallet.network()); - let inner = std::mem::replace( - &mut *((*builder).inner as *mut TransactionBuilder), - TransactionBuilder::new(), - ); - let build = runtime().block_on(async { - let mut wm = wallet.wallet_manager().write().await; + let wm = wallet.wallet_manager().read().await; let info = wm - .get_wallet_info_mut(&wallet_id) + .get_wallet_info(&wallet_id) .ok_or_else(|| "wallet not found".to_string())?; let height = info.core_wallet.last_processed_height(); @@ -317,25 +481,46 @@ pub unsafe extern "C" fn core_wallet_tx_builder_build_signed( let serialized = dashcore::consensus::serialize(&tx); let len = serialized.len(); - *out_tx_bytes = Box::into_raw(serialized.into_boxed_slice()) as *mut u8; - *out_tx_len = len; - *out_fee = fee; + *out_tx = FFICoreTransaction { + tx_bytes: Box::into_raw(serialized.into_boxed_slice()) as *mut u8, + tx_len: len, + fee, + }; PlatformWalletFFIResult::ok() } +/// Destroy a transaction builder created by `core_wallet_tx_builder_new`. +/// +/// # Safety +/// `builder` must not have already been destroyed or built (or null). #[no_mangle] pub unsafe extern "C" fn core_wallet_tx_builder_destroy(builder: *mut FFITransactionBuilder) { - if !builder.is_null() { - let b = Box::from_raw(builder); - drop(Box::from_raw(b.inner as *mut TransactionBuilder)); + if builder.is_null() { + return; } + + let b = Box::from_raw(builder); + let _ = Box::from_raw(b.inner as *mut TransactionBuilder); } -/// Free transaction bytes returned by `core_wallet_tx_builder_build_signed`. +/// Free a transaction returned by `core_wallet_tx_builder_build_signed`. +/// Idempotent: the fields are nulled, so a second call is a no-op. +/// +/// # Safety +/// `tx` must be a valid pointer to an `FFICoreTransaction` from +/// `core_wallet_tx_builder_build_signed` (or null). #[no_mangle] -pub unsafe extern "C" fn core_wallet_free_tx_bytes(bytes: *mut u8, len: usize) { - if !bytes.is_null() && len > 0 { - let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(bytes, len)); +pub unsafe extern "C" fn core_wallet_transaction_free(tx: *mut FFICoreTransaction) { + if tx.is_null() { + return; + } + + let tx = &mut *tx; + if !tx.tx_bytes.is_null() && tx.tx_len > 0 { + let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(tx.tx_bytes, tx.tx_len)); } + + tx.tx_bytes = std::ptr::null_mut(); + tx.tx_len = 0; } diff --git a/packages/rs-platform-wallet/src/wallet/core/wallet.rs b/packages/rs-platform-wallet/src/wallet/core/wallet.rs index fc0e89e9074..5921256e5f5 100644 --- a/packages/rs-platform-wallet/src/wallet/core/wallet.rs +++ b/packages/rs-platform-wallet/src/wallet/core/wallet.rs @@ -7,6 +7,9 @@ use super::balance::WalletBalance; use dashcore::Address as DashAddress; use tokio::sync::RwLock; +use key_wallet::managed_account::address_pool::KeySource; +use key_wallet::managed_account::managed_account_trait::ManagedAccountTrait; +use key_wallet::wallet::managed_wallet_info::transaction_building::AccountTypePreference; use key_wallet_manager::WalletManager; use crate::broadcaster::TransactionBroadcaster; @@ -67,6 +70,58 @@ impl CoreWallet { &self.wallet_manager } + pub async fn set_gap_limit( + &self, + account_type: AccountTypePreference, + account_index: u32, + gap_limit: u32, + ) -> Result<(), PlatformWalletError> { + let mut wm = self.wallet_manager.write().await; + let (wallet, info) = wm.get_wallet_and_info_mut(&self.wallet_id).ok_or_else(|| { + PlatformWalletError::WalletNotFound("Wallet not found in wallet manager".to_string()) + })?; + + let xpub = match account_type { + AccountTypePreference::BIP44 => wallet.get_bip44_account(account_index), + AccountTypePreference::BIP32 => wallet.get_bip32_account(account_index), + AccountTypePreference::CoinJoin => wallet.get_coinjoin_account(account_index), + } + .map(|a| a.account_xpub) + .ok_or_else(|| { + PlatformWalletError::WalletNotFound(format!( + "wallet account {account_type:?} #{account_index} not found" + )) + })?; + + let account = match account_type { + AccountTypePreference::BIP44 => info + .core_wallet + .accounts + .standard_bip44_accounts + .get_mut(&account_index), + AccountTypePreference::BIP32 => info + .core_wallet + .accounts + .standard_bip32_accounts + .get_mut(&account_index), + AccountTypePreference::CoinJoin => info + .core_wallet + .accounts + .coinjoin_accounts + .get_mut(&account_index), + } + .ok_or_else(|| { + PlatformWalletError::WalletNotFound(format!( + "managed account {account_type:?} #{account_index} not found" + )) + })?; + + account + .set_gap_limit(gap_limit, &KeySource::Public(xpub)) + .map(|_| ()) + .map_err(|e| PlatformWalletError::AddressOperation(e.to_string())) + } + /// Get the next unused BIP-44 external (receive) address for a specific account. pub async fn next_receive_address_for_account( &self, diff --git a/packages/rs-sdk-ffi/src/mnemonic_resolver_core_signer.rs b/packages/rs-sdk-ffi/src/mnemonic_resolver_core_signer.rs index d7dc7c7a8fc..fd88fe6eb7e 100644 --- a/packages/rs-sdk-ffi/src/mnemonic_resolver_core_signer.rs +++ b/packages/rs-sdk-ffi/src/mnemonic_resolver_core_signer.rs @@ -64,7 +64,7 @@ use std::ffi::c_void; use std::os::raw::c_char; use async_trait::async_trait; -use key_wallet::bip32::{DerivationPath, ExtendedPrivKey, ExtendedPubKey}; +use key_wallet::bip32::{DerivationPath, ExtendedPrivKey}; use key_wallet::dashcore::secp256k1::{self, Secp256k1}; use key_wallet::signer::{Signer, SignerMethod}; use key_wallet::Network; @@ -363,20 +363,6 @@ impl Signer for MnemonicResolverCoreSigner { secret.non_secure_erase(); Ok(pubkey) } - - async fn extended_public_key( - &self, - path: &DerivationPath, - ) -> Result { - let mut derived = self.derive_extended_priv(path)?; - - let secp = Secp256k1::new(); - let xpub = ExtendedPubKey::from_priv(&secp, &derived); - - derived.private_key.non_secure_erase(); - - Ok(xpub) - } } #[cfg(test)] diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/CoreTransactionBuilder.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/CoreTransactionBuilder.swift index cf37dd60512..c5ceae00832 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/CoreTransactionBuilder.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/CoreTransactionBuilder.swift @@ -1,6 +1,26 @@ import Foundation import DashSDKFFI +/// A built, signed core transaction. Broadcast it via +/// `ManagedCoreWallet.broadcastTransaction`; its bytes are freed when this +/// object is released. +public final class CoreTransaction { + var ffi: FFICoreTransaction + + init(ffi: FFICoreTransaction) { self.ffi = ffi } + + deinit { withUnsafeMutablePointer(to: &ffi) { core_wallet_transaction_free($0) } } + + /// Network fee in duffs. + public var fee: UInt64 { ffi.fee } + + /// Consensus-serialized signed transaction bytes (copied out). + public var data: Data { + guard let p = ffi.tx_bytes, ffi.tx_len > 0 else { return Data() } + return Data(bytes: p, count: Int(ffi.tx_len)) + } +} + /// key-wallet transaction builder over FFI. Build step by step, `buildSigned`, /// then broadcast separately via `ManagedCoreWallet.broadcastTransaction`. public final class CoreTransactionBuilder { @@ -40,16 +60,23 @@ public final class CoreTransactionBuilder { } private let handle: UnsafeMutablePointer - - public init() throws { - guard let handle = core_wallet_tx_builder_new() else { + /// `buildSigned` consumes and frees the builder on the Rust side; once + /// consumed, `deinit` must not destroy it again. + private var consumed = false + + /// `network` is the wallet network; output and change addresses are + /// validated against it. + public init(network: Network) throws { + guard let handle = core_wallet_tx_builder_new(network.ffiValue) else { throw PlatformWalletError.nullPointer("core_wallet_tx_builder_new returned NULL") } self.handle = handle } deinit { - core_wallet_tx_builder_destroy(handle) + if !consumed { + core_wallet_tx_builder_destroy(handle) + } } /// Fund from the account's UTXOs and set its change address. @@ -65,6 +92,41 @@ public final class CoreTransactionBuilder { return self } + /// Add a chosen subset of the account's UTXOs (as returned by + /// `PlatformWalletManager.accountUtxos`) as inputs. Each must belong to + /// the account. + @discardableResult + public func addInputs( + wallet: ManagedCoreWallet, + accountType: AccountType, + accountIndex: UInt32, + utxos: [PlatformWalletManager.AccountUtxo] + ) throws -> CoreTransactionBuilder { + var ffiOutpoints = [OutPointFFI]() + ffiOutpoints.reserveCapacity(utxos.count) + for utxo in utxos { + guard utxo.outpointTxid.count == 32 else { + throw PlatformWalletError.unknown( + "outpoint txid must be 32 bytes, got \(utxo.outpointTxid.count)" + ) + } + var entry = OutPointFFI() + withUnsafeMutableBytes(of: &entry.txid) { dst in + _ = utxo.outpointTxid.copyBytes(to: dst.bindMemory(to: UInt8.self)) + } + entry.vout = utxo.outpointVout + ffiOutpoints.append(entry) + } + + try ffiOutpoints.withUnsafeBufferPointer { buf in + try core_wallet_tx_builder_add_inputs_from_outpoints( + handle, wallet.handle, accountType.ffi, accountIndex, + buf.baseAddress, UInt(buf.count) + ).check() + } + return self + } + @discardableResult public func addOutput(address: String, amountDuffs: UInt64) throws -> CoreTransactionBuilder { let c = strdup(address) @@ -113,35 +175,37 @@ public final class CoreTransactionBuilder { } /// Build and sign against the account; returns the signed transaction - /// and fee without broadcasting. Frees the builder + /// without broadcasting. Consumes the builder — it is freed on the Rust + /// side and this instance must not be reused afterwards. public func buildSigned( wallet: ManagedCoreWallet, accountType: AccountType, accountIndex: UInt32 - ) throws -> (transaction: Data, fee: UInt64) { - var txBytesPtr: UnsafeMutablePointer? = nil - var txLen: UInt = 0 - var fee: UInt64 = 0 + ) throws -> CoreTransaction { + guard !consumed else { + throw PlatformWalletError.unknown("CoreTransactionBuilder already consumed") + } + var out = FFICoreTransaction(tx_bytes: nil, tx_len: 0, fee: 0) let resolver = MnemonicResolver() - try withExtendedLifetime(resolver) { - try core_wallet_tx_builder_build_signed( + let result = withExtendedLifetime(resolver) { + core_wallet_tx_builder_build_signed( handle, wallet.handle, accountType.ffi, accountIndex, resolver.handle, - &txBytesPtr, - &txLen, - &fee - ).check() + &out + ) } + // The builder is freed by the FFI on every path — never destroy it again. + consumed = true + try result.check() - guard let ptr = txBytesPtr, txLen > 0 else { + guard out.tx_bytes != nil, out.tx_len > 0 else { throw PlatformWalletError.unknown("FFI returned success but tx buffer was empty") } - defer { core_wallet_free_tx_bytes(ptr, txLen) } - return (Data(bytes: ptr, count: Int(txLen)), fee) + return CoreTransaction(ffi: out) } } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/ManagedCoreWallet.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/ManagedCoreWallet.swift index 1b3a97b940c..6954c4a2dda 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/ManagedCoreWallet.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/ManagedCoreWallet.swift @@ -86,20 +86,23 @@ public class ManagedCoreWallet { return String(cString: ptr) } + public func setGapLimit( + accountType: CoreTransactionBuilder.AccountType, + accountIndex: UInt32, + gapLimit: UInt32 + ) throws { + try core_wallet_set_gap_limit(handle, accountType.ffi, accountIndex, gapLimit).check() + } + // MARK: - Transactions - /// Broadcast a raw signed transaction. + /// Broadcast a transaction built by `CoreTransactionBuilder.buildSigned`. /// /// Returns the transaction ID as a hex string. - public func broadcastTransaction(_ txData: Data) throws -> String { + public func broadcastTransaction(_ tx: CoreTransaction) throws -> String { var txidPtr: UnsafeMutablePointer? = nil - try txData.withUnsafeBytes { txBuf in - try core_wallet_broadcast_transaction( - handle, - txBuf.baseAddress?.assumingMemoryBound(to: UInt8.self), - UInt(txData.count), - &txidPtr - ).check() + try withUnsafePointer(to: tx.ffi) { txPtr in + try core_wallet_broadcast_transaction(handle, txPtr, &txidPtr).check() } guard let ptr = txidPtr else { diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/ViewModels/SendViewModel.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/ViewModels/SendViewModel.swift index b0741fa116f..29b65824477 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/ViewModels/SendViewModel.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/ViewModels/SendViewModel.swift @@ -459,7 +459,7 @@ class SendViewModel: ObservableObject { // Coin selection, funding, and signing are Rust-side; we // marshal the outputs into the builder, fund + sign from // the sender account, then broadcast the signed tx. - let builder = try CoreTransactionBuilder() + let builder = try CoreTransactionBuilder(network: core.network()) for recipient in recipients { try builder.addOutput( address: recipient.address, @@ -471,7 +471,7 @@ class SendViewModel: ObservableObject { accountType: .bip44, accountIndex: senderAccountIndex ) - let (signedTx, _) = try builder.buildSigned( + let signedTx = try builder.buildSigned( wallet: core, accountType: .bip44, accountIndex: senderAccountIndex