From 5751a5e5aa9e48068570ce7dce31e8b6618ed195 Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Tue, 14 Apr 2026 11:52:50 -0700 Subject: [PATCH] add value and address fields to OutputDetail and FFIOutputDetail --- dash-spv-ffi/src/callbacks.rs | 20 +++++++++++++++---- key-wallet-ffi/src/managed_account.rs | 19 +++++++++++++++--- key-wallet-ffi/src/types.rs | 14 +++++++++++++ key-wallet/src/managed_account/mod.rs | 6 +++++- .../src/managed_account/transaction_record.rs | 3 +++ 5 files changed, 54 insertions(+), 8 deletions(-) diff --git a/dash-spv-ffi/src/callbacks.rs b/dash-spv-ffi/src/callbacks.rs index 549f3f83d..92f2151fc 100644 --- a/dash-spv-ffi/src/callbacks.rs +++ b/dash-spv-ffi/src/callbacks.rs @@ -17,7 +17,7 @@ use key_wallet_ffi::types::{ FFITransactionType, }; use key_wallet_manager::WalletEvent; -use std::ffi::CString; +use std::ffi::{self, CString}; use std::os::raw::{c_char, c_void}; // ============================================================================ @@ -728,9 +728,21 @@ impl FFIWalletEventCallbacks { let output_details: Vec = record .output_details .iter() - .map(|d| FFIOutputDetail { - index: d.index, - role: FFIOutputRole::from(d.role), + .map(|d| { + let address = if let Some(address) = &d.address { + ffi::CString::new(address.to_string()) + .unwrap_or_default() + .into_raw() + } else { + std::ptr::null_mut() + }; + + FFIOutputDetail { + index: d.index, + role: FFIOutputRole::from(d.role), + value: d.value, + address, + } }) .collect(); diff --git a/key-wallet-ffi/src/managed_account.rs b/key-wallet-ffi/src/managed_account.rs index 107f1d2bf..9f43c0bcc 100644 --- a/key-wallet-ffi/src/managed_account.rs +++ b/key-wallet-ffi/src/managed_account.rs @@ -4,6 +4,7 @@ //! ManagedAccount instances from the key-wallet crate. FFIManagedCoreAccount is a //! simple wrapper around `Arc` without additional fields. +use std::ffi::{self}; use std::os::raw::{c_char, c_uint}; use std::sync::Arc; @@ -788,9 +789,19 @@ pub unsafe extern "C" fn managed_core_account_get_transactions( let output_slice: Box<[FFIOutputDetail]> = record .output_details .iter() - .map(|d| FFIOutputDetail { - index: d.index, - role: FFIOutputRole::from(d.role), + .map(|d| { + let address = if let Some(address) = &d.address { + ffi::CString::new(address.to_string()).unwrap_or_default().into_raw() + } else { + std::ptr::null_mut() + }; + + FFIOutputDetail { + index: d.index, + role: FFIOutputRole::from(d.role), + value: d.value, + address, + } }) .collect::>() .into_boxed_slice(); @@ -2038,6 +2049,8 @@ mod tests { let output_slice = vec![FFIOutputDetail { index: 0, role: FFIOutputRole::Received, + value: 100000, + address: std::ptr::null_mut(), }] .into_boxed_slice(); r0.output_details_count = output_slice.len(); diff --git a/key-wallet-ffi/src/types.rs b/key-wallet-ffi/src/types.rs index 9408e55d4..f2394384c 100644 --- a/key-wallet-ffi/src/types.rs +++ b/key-wallet-ffi/src/types.rs @@ -6,6 +6,7 @@ use key_wallet::managed_account::transaction_record::{OutputRole, TransactionDir use key_wallet::transaction_checking::transaction_router::TransactionType; use key_wallet::transaction_checking::{BlockInfo, TransactionContext}; use key_wallet::{Network, Wallet}; +use std::ffi; use std::os::raw::c_char; use std::sync::Arc; @@ -926,10 +927,23 @@ pub struct FFIInputDetail { } /// FFI-compatible output detail +/// Address is optional, will be null for unspendable or sent output roles #[repr(C)] pub struct FFIOutputDetail { pub index: u32, pub role: FFIOutputRole, + pub value: u64, + pub address: *mut std::os::raw::c_char, +} + +impl Drop for FFIOutputDetail { + fn drop(&mut self) { + if self.address != std::ptr::null_mut() { + unsafe { + let _ = ffi::CString::from_raw(self.address); + } + } + } } #[cfg(test)] diff --git a/key-wallet/src/managed_account/mod.rs b/key-wallet/src/managed_account/mod.rs index a209a67a8..07797820c 100644 --- a/key-wallet/src/managed_account/mod.rs +++ b/key-wallet/src/managed_account/mod.rs @@ -486,7 +486,8 @@ impl ManagedCoreAccount { // Build output details — annotate every output with its role let mut output_details = Vec::new(); for (idx, output) in tx.output.iter().enumerate() { - let role = match &resolved_outputs[idx] { + let address = resolved_outputs[idx].clone(); + let role = match &address { Some(addr) if receive_addrs.contains(addr) => OutputRole::Received, Some(addr) if change_addrs.contains(addr) => OutputRole::Change, Some(_) if has_inputs => OutputRole::Sent, @@ -501,9 +502,12 @@ impl ManagedCoreAccount { } } }; + output_details.push(OutputDetail { index: idx as u32, role, + value: output.value, + address, }); } diff --git a/key-wallet/src/managed_account/transaction_record.rs b/key-wallet/src/managed_account/transaction_record.rs index 478c1970e..e3b604acd 100644 --- a/key-wallet/src/managed_account/transaction_record.rs +++ b/key-wallet/src/managed_account/transaction_record.rs @@ -37,6 +37,9 @@ pub struct OutputDetail { pub index: u32, /// Role of this output from the wallet's perspective pub role: OutputRole, + /// Output value in satoshis + pub value: u64, + pub address: Option
, } /// Role of a transaction output from the wallet's perspective