From 51b63ae158db00ec1a53f984691b76a5885f362f Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Tue, 28 Apr 2026 10:03:53 -0700 Subject: [PATCH 1/2] refactor rs-platform-wallet-ffi error framework --- Cargo.lock | 1 + packages/rs-platform-wallet-ffi/Cargo.toml | 5 + .../src/asset_lock/build.rs | 175 ++-- .../src/asset_lock/manager.rs | 109 +- .../src/asset_lock/sync.rs | 145 +-- .../rs-platform-wallet-ffi/src/contact.rs | 355 ++----- .../src/contact_request.rs | 494 ++------- .../src/core_wallet/addresses.rs | 81 +- .../src/core_wallet/broadcast.rs | 159 +-- .../src/core_wallet/wallet.rs | 74 +- .../rs-platform-wallet-ffi/src/dashpay.rs | 637 +++--------- .../src/dashpay_profile.rs | 546 ++-------- .../src/data_contract.rs | 247 +---- .../rs-platform-wallet-ffi/src/derivation.rs | 194 +--- .../src/derive_identity_key_at_slot.rs | 358 +------ packages/rs-platform-wallet-ffi/src/dpns.rs | 944 +++++------------- packages/rs-platform-wallet-ffi/src/error.rs | 374 +++++-- .../src/established_contact.rs | 519 ++-------- .../src/identity_derive_and_persist.rs | 149 +-- .../src/identity_discovery.rs | 98 +- .../src/identity_key_preview.rs | 210 ++-- .../src/identity_keys_from_mnemonic.rs | 339 +------ .../src/identity_manager.rs | 323 ++---- ...dentity_registration_funded_with_signer.rs | 202 +--- .../src/identity_registration_with_signer.rs | 687 +++++-------- .../src/identity_sync.rs | 416 +++----- .../src/identity_top_up.rs | 93 +- .../src/identity_transfer.rs | 188 +--- .../src/identity_update.rs | 221 +--- .../src/identity_withdrawal.rs | 168 +--- .../src/managed_identity.rs | 574 +++-------- .../rs-platform-wallet-ffi/src/manager.rs | 232 ++--- .../src/memory_explorer.rs | 379 ++----- .../src/platform_address_sync.rs | 217 ++-- .../fund_from_asset_lock.rs | 152 +-- .../src/platform_addresses/mod.rs | 33 +- .../src/platform_addresses/sync.rs | 35 +- .../src/platform_addresses/transfer.rs | 78 +- .../src/platform_addresses/wallet.rs | 124 +-- .../src/platform_addresses/withdrawal.rs | 80 +- .../src/platform_wallet_info.rs | 359 ++----- packages/rs-platform-wallet-ffi/src/spv.rs | 205 ++-- .../rs-platform-wallet-ffi/src/tokens/burn.rs | 142 +-- .../src/tokens/claim.rs | 137 +-- .../src/tokens/destroy_frozen_funds.rs | 157 +-- .../src/tokens/freeze.rs | 156 +-- .../src/tokens/group_info.rs | 100 +- .../src/tokens/group_queries.rs | 277 ++--- .../rs-platform-wallet-ffi/src/tokens/mint.rs | 166 +-- .../src/tokens/pause.rs | 143 +-- .../src/tokens/purchase.rs | 117 +-- .../src/tokens/resume.rs | 143 +-- .../src/tokens/set_price.rs | 156 +-- .../src/tokens/transfer.rs | 151 +-- .../src/tokens/unfreeze.rs | 157 +-- .../src/tokens/update_config.rs | 336 ++----- packages/rs-platform-wallet-ffi/src/utils.rs | 261 +---- packages/rs-platform-wallet-ffi/src/wallet.rs | 252 ++--- .../rs-platform-wallet-ffi/src/xpub_render.rs | 47 +- .../tests/comprehensive_tests.rs | 378 +++---- .../tests/integration_tests.rs | 127 +-- .../rs-platform-wallet/src/wallet/apply.rs | 2 +- .../AssetLock/ManagedAssetLockManager.swift | 73 +- .../PlatformWallet/ContactRequest.swift | 83 +- .../CoreWallet/ManagedCoreWallet.swift | 78 +- .../PlatformWallet/DashPayService.swift | 4 +- .../PlatformWallet/EstablishedContact.swift | 97 +- .../PlatformWallet/IdentityManager.swift | 50 +- .../PlatformWallet/ManagedIdentity.swift | 261 ++--- .../ManagedPlatformAddressWallet.swift | 23 +- .../ManagedPlatformWallet.swift | 377 +++---- .../PlatformWallet/PlatformWallet.swift | 59 +- .../PlatformWalletManager.swift | 154 ++- .../PlatformWalletManagerAddressSync.swift | 106 +- .../PlatformWalletManagerIdentitySync.swift | 169 ++-- .../PlatformWalletManagerSPV.swift | 42 +- .../PlatformWallet/PlatformWalletResult.swift | 181 ++++ .../PlatformWallet/PlatformWalletTypes.swift | 91 +- .../PlatformWallet/Tokens/TokenActions.swift | 261 ++--- .../Tokens/TokenGroupActionQueries.swift | 41 +- 80 files changed, 4629 insertions(+), 11905 deletions(-) create mode 100644 packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift diff --git a/Cargo.lock b/Cargo.lock index 0e309a219d8..18d01f601be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4883,6 +4883,7 @@ dependencies = [ name = "platform-wallet-ffi" version = "2.1.1" dependencies = [ + "anyhow", "bincode", "bs58", "cbindgen 0.27.0", diff --git a/packages/rs-platform-wallet-ffi/Cargo.toml b/packages/rs-platform-wallet-ffi/Cargo.toml index 43c195e360e..663940a0952 100644 --- a/packages/rs-platform-wallet-ffi/Cargo.toml +++ b/packages/rs-platform-wallet-ffi/Cargo.toml @@ -35,6 +35,11 @@ bincode = { version = "=2.0.1" } # Hex used for error diagnostics that include a wallet_id. hex = "0.4" +# anyhow surfaces from `KeyType::try_from` / `Purpose::try_from` +# / `SecurityLevel::try_from` in dpp; we need the From impl in +# `error.rs` so `unwrap_result_or_return!` can absorb it. +anyhow = { version = "1.0.81" } + # Used by group-action queries to build the JSON response shape that # Swift decodes via Codable. See `tokens/group_queries.rs`. serde_json = "1.0" diff --git a/packages/rs-platform-wallet-ffi/src/asset_lock/build.rs b/packages/rs-platform-wallet-ffi/src/asset_lock/build.rs index bb46ece213f..3174a04cffc 100644 --- a/packages/rs-platform-wallet-ffi/src/asset_lock/build.rs +++ b/packages/rs-platform-wallet-ffi/src/asset_lock/build.rs @@ -3,6 +3,7 @@ use crate::error::*; use crate::handle::*; use crate::runtime::runtime; +use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; /// Build an asset lock transaction. /// @@ -21,54 +22,34 @@ pub unsafe extern "C" fn asset_lock_manager_build_transaction( out_tx_bytes: *mut *mut u8, out_tx_len: *mut usize, out_private_key: *mut [u8; 32], - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_tx_bytes.is_null() || out_tx_len.is_null() || out_private_key.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_tx_bytes); + check_ptr!(out_tx_len); + check_ptr!(out_private_key); + + let option = parse_funding_type(funding_type); + let funding = unwrap_option_or_return!(option); + + let option = ASSET_LOCK_MANAGER_STORAGE.with_item(handle, |manager| { + runtime().block_on(manager.build_asset_lock_transaction( + amount_duffs, + account_index, + funding, + identity_index, + )) + }); + let result = unwrap_option_or_return!(option); + let (tx, key) = 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; + *out_private_key = key.inner.secret_bytes(); - let funding = match parse_funding_type(funding_type) { - Some(f) => f, - None => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("Unknown funding type: {}", funding_type), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; - - ASSET_LOCK_MANAGER_STORAGE - .with_item(handle, |manager| { - match runtime().block_on(manager.build_asset_lock_transaction( - amount_duffs, - account_index, - funding, - identity_index, - )) { - Ok((tx, key)) => { - 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; - *out_private_key = key.inner.secret_bytes(); - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + PlatformWalletFfiResult::ok() } /// Build, broadcast, and wait for an asset lock proof. @@ -91,74 +72,40 @@ pub unsafe extern "C" fn asset_lock_manager_create_funded_proof( out_proof_len: *mut usize, out_private_key: *mut [u8; 32], out_txid: *mut [u8; 32], - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_proof_bytes.is_null() - || out_proof_len.is_null() - || out_private_key.is_null() - || out_txid.is_null() - { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_proof_bytes); + check_ptr!(out_proof_len); + check_ptr!(out_private_key); + check_ptr!(out_txid); + + let funding = unwrap_option_or_return!(parse_funding_type(funding_type)); + + let option = ASSET_LOCK_MANAGER_STORAGE.with_item(handle, |manager| { + runtime().block_on(manager.create_funded_asset_lock_proof( + amount_duffs, + account_index, + funding, + identity_index, + )) + }); + let result = unwrap_option_or_return!(option); + let (proof, key, out_point) = unwrap_result_or_return!(result); + + let bytes = unwrap_result_or_return!(dpp::bincode::encode_to_vec( + &proof, + dpp::bincode::config::standard() + )); + + let len = bytes.len(); + let boxed = bytes.into_boxed_slice(); + *out_proof_bytes = Box::into_raw(boxed) as *mut u8; + *out_proof_len = len; + *out_private_key = key.inner.secret_bytes(); + let mut txid_bytes = [0u8; 32]; + txid_bytes.copy_from_slice(&out_point.txid[..]); + *out_txid = txid_bytes; - let funding = match parse_funding_type(funding_type) { - Some(f) => f, - None => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("Unknown funding type: {}", funding_type), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; - - ASSET_LOCK_MANAGER_STORAGE - .with_item(handle, |manager| { - match runtime().block_on(manager.create_funded_asset_lock_proof( - amount_duffs, - account_index, - funding, - identity_index, - )) { - Ok((proof, key, out_point)) => { - // Serialize proof with bincode - match dpp::bincode::encode_to_vec(&proof, dpp::bincode::config::standard()) { - Ok(bytes) => { - let len = bytes.len(); - let boxed = bytes.into_boxed_slice(); - *out_proof_bytes = Box::into_raw(boxed) as *mut u8; - *out_proof_len = len; - *out_private_key = key.inner.secret_bytes(); - let mut txid_bytes = [0u8; 32]; - txid_bytes.copy_from_slice(&out_point.txid[..]); - *out_txid = txid_bytes; - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorSerialization, - format!("Failed to serialize proof: {}", e), - ); - } - PlatformWalletFFIResult::ErrorSerialization - } - } - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + PlatformWalletFfiResult::ok() } /// Free transaction bytes. diff --git a/packages/rs-platform-wallet-ffi/src/asset_lock/manager.rs b/packages/rs-platform-wallet-ffi/src/asset_lock/manager.rs index 886353562fa..746f04890ab 100644 --- a/packages/rs-platform-wallet-ffi/src/asset_lock/manager.rs +++ b/packages/rs-platform-wallet-ffi/src/asset_lock/manager.rs @@ -3,6 +3,7 @@ use crate::error::*; use crate::handle::*; use crate::runtime::runtime; +use crate::{check_ptr, unwrap_option_or_return}; /// C-compatible tracked asset lock entry. #[repr(C)] @@ -28,12 +29,9 @@ pub struct TrackedAssetLockFFI { /// Destroy an AssetLockManager handle. #[no_mangle] -pub unsafe extern "C" fn asset_lock_manager_destroy( - handle: Handle, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { +pub unsafe extern "C" fn asset_lock_manager_destroy(handle: Handle) -> PlatformWalletFfiResult { ASSET_LOCK_MANAGER_STORAGE.remove(handle); - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } /// List all tracked asset locks. @@ -45,65 +43,54 @@ pub unsafe extern "C" fn asset_lock_manager_list_tracked_locks( handle: Handle, out_locks: *mut *mut TrackedAssetLockFFI, out_count: *mut usize, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_locks.is_null() || out_count.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_locks); + check_ptr!(out_count); - ASSET_LOCK_MANAGER_STORAGE - .with_item(handle, |manager| { - use platform_wallet::AssetLockStatus; + let option = ASSET_LOCK_MANAGER_STORAGE.with_item(handle, |manager| { + use platform_wallet::AssetLockStatus; - let locks = runtime().block_on(manager.list_tracked_locks()); - let entries: Vec = locks - .iter() - .map(|lock| { - let mut txid = [0u8; 32]; - txid.copy_from_slice(&lock.out_point.txid[..]); - TrackedAssetLockFFI { - txid, - vout: lock.out_point.vout, - account_index: lock.account_index, - funding_type: match lock.funding_type { - key_wallet::wallet::managed_wallet_info::asset_lock_builder::AssetLockFundingType::IdentityRegistration => 0, - key_wallet::wallet::managed_wallet_info::asset_lock_builder::AssetLockFundingType::IdentityTopUp => 1, - key_wallet::wallet::managed_wallet_info::asset_lock_builder::AssetLockFundingType::IdentityTopUpNotBound => 2, - key_wallet::wallet::managed_wallet_info::asset_lock_builder::AssetLockFundingType::IdentityInvitation => 3, - key_wallet::wallet::managed_wallet_info::asset_lock_builder::AssetLockFundingType::AssetLockAddressTopUp => 4, - key_wallet::wallet::managed_wallet_info::asset_lock_builder::AssetLockFundingType::AssetLockShieldedAddressTopUp => 5, - }, - identity_index: lock.identity_index, - amount: lock.amount, - status: match lock.status { - AssetLockStatus::Built => 0, - AssetLockStatus::Broadcast => 1, - AssetLockStatus::InstantSendLocked => 2, - AssetLockStatus::ChainLocked => 3, - }, - has_proof: lock.proof.is_some(), - } - }) - .collect(); + let locks = runtime().block_on(manager.list_tracked_locks()); + let entries: Vec = locks + .iter() + .map(|lock| { + let mut txid = [0u8; 32]; + txid.copy_from_slice(&lock.out_point.txid[..]); + TrackedAssetLockFFI { + txid, + vout: lock.out_point.vout, + account_index: lock.account_index, + funding_type: match lock.funding_type { + key_wallet::wallet::managed_wallet_info::asset_lock_builder::AssetLockFundingType::IdentityRegistration => 0, + key_wallet::wallet::managed_wallet_info::asset_lock_builder::AssetLockFundingType::IdentityTopUp => 1, + key_wallet::wallet::managed_wallet_info::asset_lock_builder::AssetLockFundingType::IdentityTopUpNotBound => 2, + key_wallet::wallet::managed_wallet_info::asset_lock_builder::AssetLockFundingType::IdentityInvitation => 3, + key_wallet::wallet::managed_wallet_info::asset_lock_builder::AssetLockFundingType::AssetLockAddressTopUp => 4, + key_wallet::wallet::managed_wallet_info::asset_lock_builder::AssetLockFundingType::AssetLockShieldedAddressTopUp => 5, + }, + identity_index: lock.identity_index, + amount: lock.amount, + status: match lock.status { + AssetLockStatus::Built => 0, + AssetLockStatus::Broadcast => 1, + AssetLockStatus::InstantSendLocked => 2, + AssetLockStatus::ChainLocked => 3, + }, + has_proof: lock.proof.is_some(), + } + }) + .collect(); + entries + }); + let entries = unwrap_option_or_return!(option); - *out_count = entries.len(); - if entries.is_empty() { - *out_locks = std::ptr::null_mut(); - } else { - *out_locks = - Box::into_raw(entries.into_boxed_slice()) as *mut TrackedAssetLockFFI; - } - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid asset-lock manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + *out_count = entries.len(); + if entries.is_empty() { + *out_locks = std::ptr::null_mut(); + } else { + *out_locks = Box::into_raw(entries.into_boxed_slice()) as *mut TrackedAssetLockFFI; + } + PlatformWalletFfiResult::ok() } /// Free tracked locks array. diff --git a/packages/rs-platform-wallet-ffi/src/asset_lock/sync.rs b/packages/rs-platform-wallet-ffi/src/asset_lock/sync.rs index 19b3f6b38ff..2c855118868 100644 --- a/packages/rs-platform-wallet-ffi/src/asset_lock/sync.rs +++ b/packages/rs-platform-wallet-ffi/src/asset_lock/sync.rs @@ -3,6 +3,7 @@ use crate::error::*; use crate::handle::*; use crate::runtime::runtime; +use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; use std::time::Duration; fn parse_outpoint(txid: *const [u8; 32], vout: u32) -> dashcore::OutPoint { @@ -30,55 +31,30 @@ pub unsafe extern "C" fn asset_lock_manager_resume( out_proof_bytes: *mut *mut u8, out_proof_len: *mut usize, out_private_key: *mut [u8; 32], - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if txid.is_null() - || out_proof_bytes.is_null() - || out_proof_len.is_null() - || out_private_key.is_null() - { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(txid); + check_ptr!(out_proof_bytes); + check_ptr!(out_proof_len); + check_ptr!(out_private_key); let out_point = parse_outpoint(txid, vout); let timeout = Duration::from_secs(timeout_secs); - ASSET_LOCK_MANAGER_STORAGE - .with_item(handle, |manager| { - match runtime().block_on(manager.resume_asset_lock(&out_point, timeout)) { - Ok((proof, key)) => { - match dpp::bincode::encode_to_vec(&proof, dpp::bincode::config::standard()) { - Ok(bytes) => { - let len = bytes.len(); - let boxed = bytes.into_boxed_slice(); - *out_proof_bytes = Box::into_raw(boxed) as *mut u8; - *out_proof_len = len; - *out_private_key = key.inner.secret_bytes(); - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorSerialization, - format!("Failed to serialize proof: {}", e), - ); - } - PlatformWalletFFIResult::ErrorSerialization - } - } - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = ASSET_LOCK_MANAGER_STORAGE.with_item(handle, |manager| { + runtime().block_on(manager.resume_asset_lock(&out_point, timeout)) + }); + let result = unwrap_option_or_return!(option); + let (proof, key) = unwrap_result_or_return!(result); + let bytes = unwrap_result_or_return!(dpp::bincode::encode_to_vec( + &proof, + dpp::bincode::config::standard() + )); + let len = bytes.len(); + let boxed = bytes.into_boxed_slice(); + *out_proof_bytes = Box::into_raw(boxed) as *mut u8; + *out_proof_len = len; + *out_private_key = key.inner.secret_bytes(); + PlatformWalletFfiResult::ok() } /// Recover a tracked asset lock from a serialized transaction. @@ -100,73 +76,42 @@ pub unsafe extern "C" fn asset_lock_manager_recover( vout: u32, proof_bytes: *const u8, proof_len: usize, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if tx_bytes.is_null() || txid.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(tx_bytes); + check_ptr!(txid); // Parse transaction let tx_data = std::slice::from_raw_parts(tx_bytes, tx_bytes_len); - let tx: dashcore::Transaction = match dashcore::consensus::deserialize(tx_data) { - Ok(t) => t, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorDeserialization, - format!("Failed to deserialize transaction: {}", e), - ); - } - return PlatformWalletFFIResult::ErrorDeserialization; - } - }; + let tx: dashcore::Transaction = + unwrap_result_or_return!(dashcore::consensus::deserialize(tx_data)); - let funding = match super::build::parse_funding_type(funding_type) { - Some(f) => f, - None => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("Unknown funding type: {}", funding_type), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; + let funding = unwrap_option_or_return!(super::build::parse_funding_type(funding_type)); let out_point = parse_outpoint(txid, vout); // Parse optional proof let proof = if !proof_bytes.is_null() && proof_len > 0 { let data = std::slice::from_raw_parts(proof_bytes, proof_len); - match dpp::bincode::decode_from_slice(data, dpp::bincode::config::standard()) { - Ok((p, _)) => Some(p), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorDeserialization, - format!("Failed to deserialize proof: {}", e), - ); - } - return PlatformWalletFFIResult::ErrorDeserialization; - } - } + let (p, _) = unwrap_result_or_return!(dpp::bincode::decode_from_slice( + data, + dpp::bincode::config::standard() + )); + Some(p) } else { None }; - ASSET_LOCK_MANAGER_STORAGE - .with_item(handle, |manager| { - manager.recover_asset_lock_blocking( - tx, - amount_duffs, - account_index, - funding, - identity_index, - out_point, - proof, - ); - PlatformWalletFFIResult::Success - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = ASSET_LOCK_MANAGER_STORAGE.with_item(handle, |manager| { + manager.recover_asset_lock_blocking( + tx, + amount_duffs, + account_index, + funding, + identity_index, + out_point, + proof, + ); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/contact.rs b/packages/rs-platform-wallet-ffi/src/contact.rs index 3151561485f..c969a8973f1 100644 --- a/packages/rs-platform-wallet-ffi/src/contact.rs +++ b/packages/rs-platform-wallet-ffi/src/contact.rs @@ -3,45 +3,26 @@ use crate::error::*; use crate::handle::*; use crate::identity_manager::ffi_noop_persister; use crate::types::*; +use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; /// Get all sent contact request IDs #[no_mangle] pub unsafe extern "C" fn managed_identity_get_sent_contact_request_ids( identity_handle: Handle, out_array: *mut IdentifierArray, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_array.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - let ids: Vec = - identity.sent_contact_requests.keys().cloned().collect(); - let array = IdentifierArray::new(ids); - unsafe { *out_array = array }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + check_ptr!(out_array); + + let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { + identity + .sent_contact_requests + .keys() + .cloned() + .collect::>() + }); + let ids = unwrap_option_or_return!(option); + unsafe { *out_array = IdentifierArray::new(ids) }; + PlatformWalletFfiResult::ok() } /// Get all incoming contact request IDs @@ -49,39 +30,19 @@ pub unsafe extern "C" fn managed_identity_get_sent_contact_request_ids( pub unsafe extern "C" fn managed_identity_get_incoming_contact_request_ids( identity_handle: Handle, out_array: *mut IdentifierArray, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_array.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - let ids: Vec = - identity.incoming_contact_requests.keys().cloned().collect(); - let array = IdentifierArray::new(ids); - unsafe { *out_array = array }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + check_ptr!(out_array); + + let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { + identity + .incoming_contact_requests + .keys() + .cloned() + .collect::>() + }); + let ids = unwrap_option_or_return!(option); + unsafe { *out_array = IdentifierArray::new(ids) }; + PlatformWalletFfiResult::ok() } /// Get all established contact IDs @@ -89,39 +50,19 @@ pub unsafe extern "C" fn managed_identity_get_incoming_contact_request_ids( pub unsafe extern "C" fn managed_identity_get_established_contact_ids( identity_handle: Handle, out_array: *mut IdentifierArray, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_array.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - let ids: Vec = - identity.established_contacts.keys().cloned().collect(); - let array = IdentifierArray::new(ids); - unsafe { *out_array = array }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + check_ptr!(out_array); + + let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { + identity + .established_contacts + .keys() + .cloned() + .collect::>() + }); + let ids = unwrap_option_or_return!(option); + unsafe { *out_array = IdentifierArray::new(ids) }; + PlatformWalletFfiResult::ok() } /// Check if a contact is established. `contact_id` is a `*const u8` @@ -131,51 +72,16 @@ pub unsafe extern "C" fn managed_identity_is_contact_established( identity_handle: Handle, contact_id: *const u8, out_is_established: *mut bool, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_is_established.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_is_established); - let id = match unsafe { read_identifier(contact_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identifier: {}", e), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - unsafe { *out_is_established = identity.established_contacts.contains_key(&id) }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let id = unwrap_result_or_return!(unsafe { read_identifier(contact_id) }); + + let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { + identity.established_contacts.contains_key(&id) + }); + *out_is_established = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Send a contact request from this identity to another @@ -185,41 +91,16 @@ pub unsafe extern "C" fn managed_identity_is_contact_established( pub unsafe extern "C" fn managed_identity_send_contact_request( identity_handle: Handle, request_handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { +) -> PlatformWalletFfiResult { let request_result = CONTACT_REQUEST_STORAGE.with_item(request_handle, |req| req.clone()); - let request = match request_result { - Some(r) => r, - None => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact request handle", - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidHandle; - } - }; - - MANAGED_IDENTITY_STORAGE - .with_item_mut(identity_handle, |identity| { - identity.add_sent_contact_request(request, &ffi_noop_persister()); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let request = unwrap_option_or_return!(request_result); + + let option = MANAGED_IDENTITY_STORAGE.with_item_mut(identity_handle, |identity| { + identity.add_sent_contact_request(request, &ffi_noop_persister()); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Accept an incoming contact request @@ -229,41 +110,16 @@ pub unsafe extern "C" fn managed_identity_send_contact_request( pub unsafe extern "C" fn managed_identity_accept_contact_request( identity_handle: Handle, request_handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { +) -> PlatformWalletFfiResult { let request_result = CONTACT_REQUEST_STORAGE.with_item(request_handle, |req| req.clone()); - let request = match request_result { - Some(r) => r, - None => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact request handle", - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidHandle; - } - }; - - MANAGED_IDENTITY_STORAGE - .with_item_mut(identity_handle, |identity| { - identity.add_incoming_contact_request(request, &ffi_noop_persister()); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let request = unwrap_option_or_return!(request_result); + + let option = MANAGED_IDENTITY_STORAGE.with_item_mut(identity_handle, |identity| { + identity.add_incoming_contact_request(request, &ffi_noop_persister()); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Reject an incoming contact request @@ -272,50 +128,21 @@ pub unsafe extern "C" fn managed_identity_accept_contact_request( pub unsafe extern "C" fn managed_identity_reject_contact_request( identity_handle: Handle, sender_id: *const u8, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - let id = match unsafe { read_identifier(sender_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identifier: {}", e), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - - MANAGED_IDENTITY_STORAGE - .with_item_mut(identity_handle, |identity| { - if identity.remove_incoming_contact_request(&id).0.is_some() { - PlatformWalletFFIResult::Success - } else { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorContactNotFound, - "Contact request not found", - ); - } - } - PlatformWalletFFIResult::ErrorContactNotFound - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let id = unwrap_result_or_return!(unsafe { read_identifier(sender_id) }); + + let option = MANAGED_IDENTITY_STORAGE.with_item_mut(identity_handle, |identity| { + identity.remove_incoming_contact_request(&id).0.is_some() + }); + let removed = unwrap_option_or_return!(option); + if removed { + PlatformWalletFfiResult::ok() + } else { + PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorContactNotFound, + "Contact request not found", + ) + } } #[cfg(test)] @@ -366,11 +193,9 @@ mod tests { items: std::ptr::null_mut(), count: 0, }; - let mut error = PlatformWalletFFIError::success(); - let result = - managed_identity_get_sent_contact_request_ids(handle, &mut array, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = managed_identity_get_sent_contact_request_ids(handle, &mut array); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(array.count, 0); // Should be empty for new identity // Cleanup @@ -390,11 +215,9 @@ mod tests { items: std::ptr::null_mut(), count: 0, }; - let mut error = PlatformWalletFFIError::success(); - let result = - managed_identity_get_incoming_contact_request_ids(handle, &mut array, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = managed_identity_get_incoming_contact_request_ids(handle, &mut array); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(array.count, 0); // Cleanup @@ -414,11 +237,9 @@ mod tests { items: std::ptr::null_mut(), count: 0, }; - let mut error = PlatformWalletFFIError::success(); - let result = - managed_identity_get_established_contact_ids(handle, &mut array, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = managed_identity_get_established_contact_ids(handle, &mut array); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(array.count, 0); // Cleanup @@ -436,16 +257,14 @@ mod tests { let contact_id = Identifier::random(); let id_bytes: [u8; 32] = contact_id.to_buffer(); - let mut error = PlatformWalletFFIError::success(); let mut is_established = true; let result = managed_identity_is_contact_established( handle, id_bytes.as_ptr(), &mut is_established, - &mut error, ); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert!(!is_established); // Cleanup diff --git a/packages/rs-platform-wallet-ffi/src/contact_request.rs b/packages/rs-platform-wallet-ffi/src/contact_request.rs index b5f6c5c051f..7dc4e3e3c7d 100644 --- a/packages/rs-platform-wallet-ffi/src/contact_request.rs +++ b/packages/rs-platform-wallet-ffi/src/contact_request.rs @@ -5,6 +5,7 @@ use crate::error::*; use crate::handle::*; use crate::types::*; +use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; use platform_wallet::ContactRequest; // Storage for contact requests @@ -25,49 +26,12 @@ pub unsafe extern "C" fn contact_request_create( core_height_created_at: u32, created_at: u64, out_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if encrypted_public_key_bytes.is_null() || out_handle.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(encrypted_public_key_bytes); + check_ptr!(out_handle); - let sender = match unsafe { read_identifier(sender_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid sender identifier: {}", e), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - - let recipient = match unsafe { read_identifier(recipient_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid recipient identifier: {}", e), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let sender = unwrap_result_or_return!(unsafe { read_identifier(sender_id) }); + let recipient = unwrap_result_or_return!(unsafe { read_identifier(recipient_id) }); let encrypted_key = unsafe { std::slice::from_raw_parts(encrypted_public_key_bytes, encrypted_public_key_len) } @@ -87,7 +51,7 @@ pub unsafe extern "C" fn contact_request_create( let handle = CONTACT_REQUEST_STORAGE.insert(contact_request); unsafe { *out_handle = handle }; - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } /// Create a contact request handle from a managed identity's sent request @@ -96,64 +60,19 @@ pub unsafe extern "C" fn managed_identity_get_sent_contact_request( identity_handle: Handle, recipient_id: *const u8, out_request_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_request_handle.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_request_handle); - let id = match unsafe { read_identifier(recipient_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identifier: {}", e), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - if let Some(request) = identity.sent_contact_requests.get(&id) { - let handle = CONTACT_REQUEST_STORAGE.insert(request.clone()); - unsafe { *out_request_handle = handle }; - PlatformWalletFFIResult::Success - } else { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorContactNotFound, - "Contact request not found", - ); - } - } - PlatformWalletFFIResult::ErrorContactNotFound - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let id = unwrap_result_or_return!(unsafe { read_identifier(recipient_id) }); + + let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { + identity.sent_contact_requests.get(&id).cloned() + }); + let option = unwrap_option_or_return!(option); + let request = unwrap_option_or_return!(option); + + unsafe { *out_request_handle = CONTACT_REQUEST_STORAGE.insert(request) }; + PlatformWalletFfiResult::ok() } /// Create a contact request handle from a managed identity's incoming request @@ -162,64 +81,18 @@ pub unsafe extern "C" fn managed_identity_get_incoming_contact_request( identity_handle: Handle, sender_id: *const u8, out_request_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_request_handle.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - - let id = match unsafe { read_identifier(sender_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identifier: {}", e), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - if let Some(request) = identity.incoming_contact_requests.get(&id) { - let handle = CONTACT_REQUEST_STORAGE.insert(request.clone()); - unsafe { *out_request_handle = handle }; - PlatformWalletFFIResult::Success - } else { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorContactNotFound, - "Contact request not found", - ); - } - } - PlatformWalletFFIResult::ErrorContactNotFound - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + check_ptr!(out_request_handle); + + let id = unwrap_result_or_return!(unsafe { read_identifier(sender_id) }); + + let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { + identity.incoming_contact_requests.get(&id).cloned() + }); + let inner = unwrap_option_or_return!(option); + let request = unwrap_option_or_return!(inner); + unsafe { *out_request_handle = CONTACT_REQUEST_STORAGE.insert(request) }; + PlatformWalletFfiResult::ok() } /// Get sender ID from contact request into a 32-byte out-buffer. @@ -227,36 +100,13 @@ pub unsafe extern "C" fn managed_identity_get_incoming_contact_request( pub unsafe extern "C" fn contact_request_get_sender_id( request_handle: Handle, out_id: *mut u8, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_id.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_id); - CONTACT_REQUEST_STORAGE - .with_item(request_handle, |request| { - unsafe { write_identifier(out_id, &request.sender_id) }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact request handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = CONTACT_REQUEST_STORAGE.with_item(request_handle, |request| request.sender_id); + let id = unwrap_option_or_return!(option); + unsafe { write_identifier(out_id, &id) }; + PlatformWalletFfiResult::ok() } /// Get recipient ID from contact request into a 32-byte out-buffer. @@ -264,36 +114,13 @@ pub unsafe extern "C" fn contact_request_get_sender_id( pub unsafe extern "C" fn contact_request_get_recipient_id( request_handle: Handle, out_id: *mut u8, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_id.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_id); - CONTACT_REQUEST_STORAGE - .with_item(request_handle, |request| { - unsafe { write_identifier(out_id, &request.recipient_id) }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact request handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = CONTACT_REQUEST_STORAGE.with_item(request_handle, |request| request.recipient_id); + let id = unwrap_option_or_return!(option); + unsafe { write_identifier(out_id, &id) }; + PlatformWalletFfiResult::ok() } /// Get sender key index from contact request @@ -301,36 +128,13 @@ pub unsafe extern "C" fn contact_request_get_recipient_id( pub unsafe extern "C" fn contact_request_get_sender_key_index( request_handle: Handle, out_index: *mut u32, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_index.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_index); - CONTACT_REQUEST_STORAGE - .with_item(request_handle, |request| { - unsafe { *out_index = request.sender_key_index }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact request handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = + CONTACT_REQUEST_STORAGE.with_item(request_handle, |request| request.sender_key_index); + *out_index = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Get recipient key index from contact request @@ -338,36 +142,13 @@ pub unsafe extern "C" fn contact_request_get_sender_key_index( pub unsafe extern "C" fn contact_request_get_recipient_key_index( request_handle: Handle, out_index: *mut u32, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_index.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_index); - CONTACT_REQUEST_STORAGE - .with_item(request_handle, |request| { - unsafe { *out_index = request.recipient_key_index }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact request handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = + CONTACT_REQUEST_STORAGE.with_item(request_handle, |request| request.recipient_key_index); + *out_index = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Get account reference from contact request @@ -375,36 +156,13 @@ pub unsafe extern "C" fn contact_request_get_recipient_key_index( pub unsafe extern "C" fn contact_request_get_account_reference( request_handle: Handle, out_account_ref: *mut u32, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_account_ref.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_account_ref); - CONTACT_REQUEST_STORAGE - .with_item(request_handle, |request| { - unsafe { *out_account_ref = request.account_reference }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact request handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = + CONTACT_REQUEST_STORAGE.with_item(request_handle, |request| request.account_reference); + *out_account_ref = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Get encrypted public key from contact request @@ -413,43 +171,21 @@ pub unsafe extern "C" fn contact_request_get_encrypted_public_key( request_handle: Handle, out_bytes: *mut *mut u8, out_len: *mut usize, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_bytes.is_null() || out_len.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + check_ptr!(out_bytes); + check_ptr!(out_len); + + let option = CONTACT_REQUEST_STORAGE.with_item(request_handle, |request| { + request.encrypted_public_key.clone() + }); + let bytes = unwrap_option_or_return!(option).into_boxed_slice(); + let len = bytes.len(); + let ptr = Box::into_raw(bytes) as *mut u8; + unsafe { + *out_bytes = ptr; + *out_len = len; } - - CONTACT_REQUEST_STORAGE - .with_item(request_handle, |request| { - let bytes = request.encrypted_public_key.clone().into_boxed_slice(); - let len = bytes.len(); - let ptr = Box::into_raw(bytes) as *mut u8; - - unsafe { - *out_bytes = ptr; - *out_len = len; - } - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact request handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + PlatformWalletFfiResult::ok() } /// Get creation timestamp from contact request @@ -457,45 +193,24 @@ pub unsafe extern "C" fn contact_request_get_encrypted_public_key( pub unsafe extern "C" fn contact_request_get_created_at( request_handle: Handle, out_timestamp: *mut u64, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_timestamp.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_timestamp); - CONTACT_REQUEST_STORAGE - .with_item(request_handle, |request| { - unsafe { *out_timestamp = request.created_at }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact request handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = CONTACT_REQUEST_STORAGE.with_item(request_handle, |request| request.created_at); + *out_timestamp = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Destroy contact request handle #[no_mangle] -pub extern "C" fn contact_request_destroy(request_handle: Handle) -> PlatformWalletFFIResult { +pub extern "C" fn contact_request_destroy(request_handle: Handle) -> PlatformWalletFfiResult { if CONTACT_REQUEST_STORAGE.remove(request_handle).is_some() { - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } else { - PlatformWalletFFIResult::ErrorInvalidHandle + PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidHandle, + "Invalid contact request handle", + ) } } @@ -523,56 +238,47 @@ mod tests { ); let handle = CONTACT_REQUEST_STORAGE.insert(request); - let mut error = PlatformWalletFFIError::success(); // Test sender ID let mut out_id = [0u8; 32]; - let result = contact_request_get_sender_id(handle, out_id.as_mut_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = contact_request_get_sender_id(handle, out_id.as_mut_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(out_id, [1u8; 32]); // Test recipient ID - let result = contact_request_get_recipient_id(handle, out_id.as_mut_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = contact_request_get_recipient_id(handle, out_id.as_mut_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(out_id, [2u8; 32]); // Test sender key index let mut sender_key_idx = 0u32; - let result = - contact_request_get_sender_key_index(handle, &mut sender_key_idx, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = contact_request_get_sender_key_index(handle, &mut sender_key_idx); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(sender_key_idx, 0); // Test recipient key index let mut recipient_key_idx = 0u32; - let result = - contact_request_get_recipient_key_index(handle, &mut recipient_key_idx, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = contact_request_get_recipient_key_index(handle, &mut recipient_key_idx); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(recipient_key_idx, 1); // Test account reference let mut account_ref = 0u32; - let result = - contact_request_get_account_reference(handle, &mut account_ref, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = contact_request_get_account_reference(handle, &mut account_ref); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(account_ref, 42); // Test created_at let mut created_at = 0u64; - let result = contact_request_get_created_at(handle, &mut created_at, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = contact_request_get_created_at(handle, &mut created_at); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(created_at, 1_700_000_000); // Test encrypted public key let mut bytes_ptr: *mut u8 = std::ptr::null_mut(); let mut len: usize = 0; - let result = contact_request_get_encrypted_public_key( - handle, - &mut bytes_ptr, - &mut len, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = contact_request_get_encrypted_public_key(handle, &mut bytes_ptr, &mut len); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(len, 96); assert!(!bytes_ptr.is_null()); 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 e73b62c7863..318a6daa6eb 100644 --- a/packages/rs-platform-wallet-ffi/src/core_wallet/addresses.rs +++ b/packages/rs-platform-wallet-ffi/src/core_wallet/addresses.rs @@ -3,6 +3,7 @@ use crate::error::*; use crate::handle::*; use crate::runtime::runtime; +use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; use std::ffi::CString; use std::os::raw::c_char; @@ -15,37 +16,17 @@ pub unsafe extern "C" fn core_wallet_next_receive_address( handle: Handle, account_index: u32, out_address: *mut *mut c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_address.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_address); - CORE_WALLET_STORAGE - .with_item(handle, |wallet| { - match runtime().block_on(wallet.next_receive_address_for_account(account_index)) { - Ok(addr) => { - let addr_str = addr.to_string(); - match CString::new(addr_str) { - Ok(c_str) => { - *out_address = c_str.into_raw(); - PlatformWalletFFIResult::Success - } - Err(_) => PlatformWalletFFIResult::ErrorUtf8Conversion, - } - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = CORE_WALLET_STORAGE.with_item(handle, |wallet| { + runtime().block_on(wallet.next_receive_address_for_account(account_index)) + }); + let result = unwrap_option_or_return!(option); + let addr = unwrap_result_or_return!(result); + let c_str = unwrap_result_or_return!(CString::new(addr.to_string())); + *out_address = c_str.into_raw(); + PlatformWalletFfiResult::ok() } /// Get the next unused BIP-44 internal (change) address for a specific account. @@ -57,37 +38,17 @@ pub unsafe extern "C" fn core_wallet_next_change_address( handle: Handle, account_index: u32, out_address: *mut *mut c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_address.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_address); - CORE_WALLET_STORAGE - .with_item(handle, |wallet| { - match runtime().block_on(wallet.next_change_address_for_account(account_index)) { - Ok(addr) => { - let addr_str = addr.to_string(); - match CString::new(addr_str) { - Ok(c_str) => { - *out_address = c_str.into_raw(); - PlatformWalletFFIResult::Success - } - Err(_) => PlatformWalletFFIResult::ErrorUtf8Conversion, - } - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = CORE_WALLET_STORAGE.with_item(handle, |wallet| { + runtime().block_on(wallet.next_change_address_for_account(account_index)) + }); + let result = unwrap_option_or_return!(option); + let addr = unwrap_result_or_return!(result); + let c_str = unwrap_result_or_return!(CString::new(addr.to_string())); + *out_address = c_str.into_raw(); + PlatformWalletFfiResult::ok() } /// Free an address string returned by `core_wallet_next_receive_address` 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 46a20555c83..9a6af8467cf 100644 --- a/packages/rs-platform-wallet-ffi/src/core_wallet/broadcast.rs +++ b/packages/rs-platform-wallet-ffi/src/core_wallet/broadcast.rs @@ -3,78 +3,36 @@ 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; use std::str::FromStr; /// Broadcast a signed transaction to the network. -/// -/// `tx_bytes` is the raw serialized transaction. -/// -/// On success, `out_txid` is set to a heap-allocated hex string of the txid. -/// Free with `core_wallet_free_address`. #[no_mangle] pub unsafe extern "C" fn core_wallet_broadcast_transaction( handle: Handle, tx_bytes: *const u8, tx_bytes_len: usize, out_txid: *mut *mut c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if tx_bytes.is_null() || out_txid.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(tx_bytes); + check_ptr!(out_txid); let bytes = std::slice::from_raw_parts(tx_bytes, tx_bytes_len); - let tx: dashcore::Transaction = match dashcore::consensus::deserialize(bytes) { - Ok(tx) => tx, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorDeserialization, - format!("Failed to deserialize transaction: {}", e), - ); - } - return PlatformWalletFFIResult::ErrorDeserialization; - } - }; + let tx: dashcore::Transaction = + unwrap_result_or_return!(dashcore::consensus::deserialize(bytes)); - CORE_WALLET_STORAGE - .with_item(handle, |wallet| { - match runtime().block_on(wallet.broadcast_transaction(&tx)) { - Ok(txid) => { - let txid_hex = txid.to_string(); - match std::ffi::CString::new(txid_hex) { - Ok(c_str) => { - *out_txid = c_str.into_raw(); - PlatformWalletFFIResult::Success - } - Err(_) => PlatformWalletFFIResult::ErrorUtf8Conversion, - } - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + 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() } /// Build, sign, and broadcast a payment to the given addresses. -/// -/// Uses key-wallet's TransactionBuilder for UTXO selection and signing. -/// -/// `addresses` is an array of C strings (recipient addresses). -/// `amounts` is an array of u64 values (amounts in duffs). -/// Both arrays must have `count` elements. -/// -/// On success, `out_tx_bytes` and `out_tx_len` are set to the serialized -/// signed transaction. Free with `core_wallet_free_tx_bytes`. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn core_wallet_send_to_addresses( @@ -86,46 +44,22 @@ pub unsafe extern "C" fn core_wallet_send_to_addresses( count: usize, out_tx_bytes: *mut *mut u8, out_tx_len: *mut usize, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if (addresses.is_null() || amounts.is_null()) && count > 0 { - return PlatformWalletFFIResult::ErrorNullPointer; - } - if out_tx_bytes.is_null() || out_tx_len.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + if count > 0 { + check_ptr!(addresses); + check_ptr!(amounts); } + check_ptr!(out_tx_bytes); + check_ptr!(out_tx_len); - // Parse addresses and amounts into Vec<(Address, u64)>. 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 = match std::ffi::CStr::from_ptr(addr_ptrs[i]).to_str() { - Ok(s) => s, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("Invalid UTF-8 in address at index {}", i), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - }; + let c_str = unwrap_result_or_return!(std::ffi::CStr::from_ptr(addr_ptrs[i]).to_str()); - let addr = match dashcore::Address::from_str(c_str) { - Ok(a) => a.assume_checked(), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("Invalid address at index {}: {}", i, e), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; + let addr = unwrap_result_or_return!(dashcore::Address::from_str(c_str)).assume_checked(); outputs.push((addr, amount_slice[i])); } @@ -135,43 +69,24 @@ pub unsafe extern "C" fn core_wallet_send_to_addresses( 0 => StandardAccountType::BIP44Account, 1 => StandardAccountType::BIP32Account, _ => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("Unknown account type: {}", account_type), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + format!("Unknown account type: {account_type}"), + ); } }; - CORE_WALLET_STORAGE - .with_item(handle, |wallet| { - match runtime().block_on(wallet.send_to_addresses( - std_account_type, - account_index, - outputs, - )) { - Ok(tx) => { - 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::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = CORE_WALLET_STORAGE.with_item(handle, |wallet| { + runtime().block_on(wallet.send_to_addresses(std_account_type, account_index, outputs)) + }); + 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`. diff --git a/packages/rs-platform-wallet-ffi/src/core_wallet/wallet.rs b/packages/rs-platform-wallet-ffi/src/core_wallet/wallet.rs index 694c9d2da9e..8044e40229b 100644 --- a/packages/rs-platform-wallet-ffi/src/core_wallet/wallet.rs +++ b/packages/rs-platform-wallet-ffi/src/core_wallet/wallet.rs @@ -2,15 +2,13 @@ use crate::error::*; use crate::handle::*; +use crate::{check_ptr, unwrap_option_or_return}; /// Destroy a CoreWallet handle. #[no_mangle] -pub unsafe extern "C" fn core_wallet_destroy( - handle: Handle, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { +pub unsafe extern "C" fn core_wallet_destroy(handle: Handle) -> PlatformWalletFfiResult { CORE_WALLET_STORAGE.remove(handle); - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } /// Get lock-free balance (spendable, unconfirmed, immature, locked). @@ -23,26 +21,26 @@ pub unsafe extern "C" fn core_wallet_get_balance( out_unconfirmed: *mut u64, out_immature: *mut u64, out_locked: *mut u64, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - CORE_WALLET_STORAGE - .with_item(handle, |wallet| { - let balance = wallet.balance(); - if !out_confirmed.is_null() { - *out_confirmed = balance.confirmed(); - } - if !out_unconfirmed.is_null() { - *out_unconfirmed = balance.unconfirmed(); - } - if !out_immature.is_null() { - *out_immature = balance.immature(); - } - if !out_locked.is_null() { - *out_locked = balance.locked(); - } - PlatformWalletFFIResult::Success - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) +) -> PlatformWalletFfiResult { + let option = CORE_WALLET_STORAGE.with_item(handle, |wallet| { + let b = wallet.balance(); + (b.confirmed(), b.unconfirmed(), b.immature(), b.locked()) + }); + let (confirmed, unconfirmed, immature, locked) = unwrap_option_or_return!(option); + + if !out_confirmed.is_null() { + *out_confirmed = confirmed; + } + if !out_unconfirmed.is_null() { + *out_unconfirmed = unconfirmed; + } + if !out_immature.is_null() { + *out_immature = immature; + } + if !out_locked.is_null() { + *out_locked = locked; + } + PlatformWalletFfiResult::ok() } /// Get the network this wallet operates on. @@ -52,21 +50,15 @@ pub unsafe extern "C" fn core_wallet_get_balance( pub unsafe extern "C" fn core_wallet_get_network( handle: Handle, out_network: *mut u32, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_network.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_network); - CORE_WALLET_STORAGE - .with_item(handle, |wallet| { - *out_network = match wallet.network() { - key_wallet::Network::Mainnet => 0, - key_wallet::Network::Testnet => 1, - key_wallet::Network::Devnet => 2, - key_wallet::Network::Regtest => 3, - }; - PlatformWalletFFIResult::Success - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = CORE_WALLET_STORAGE.with_item(handle, |wallet| match wallet.network() { + key_wallet::Network::Mainnet => 0u32, + key_wallet::Network::Testnet => 1, + key_wallet::Network::Devnet => 2, + key_wallet::Network::Regtest => 3, + }); + *out_network = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/dashpay.rs b/packages/rs-platform-wallet-ffi/src/dashpay.rs index 6334ea7d609..fd6d120aa88 100644 --- a/packages/rs-platform-wallet-ffi/src/dashpay.rs +++ b/packages/rs-platform-wallet-ffi/src/dashpay.rs @@ -43,6 +43,7 @@ use crate::established_contact::ESTABLISHED_CONTACT_STORAGE; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::*; +use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; // --------------------------------------------------------------------------- // Managed identity lookup @@ -68,84 +69,19 @@ pub unsafe extern "C" fn platform_wallet_get_managed_identity( wallet_handle: Handle, identity_id: *const u8, out_managed_identity_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_managed_identity_handle.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_managed_identity_handle is null", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - let id = match unsafe { read_identifier(identity_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity identifier: {e}"), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - // `blocking_read` is safe here — the caller is a non- - // tokio FFI thread. Matches the pattern used by - // `platform_wallet_get_dashpay_profile`. - let wm = wallet.wallet_manager().blocking_read(); - let info = match wm.get_wallet_info(&wallet.wallet_id()) { - Some(i) => i, - None => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Wallet info not found for wallet handle", - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidHandle; - } - }; - match info.identity_manager.managed_identity(&id).cloned() { - Some(managed) => { - let handle = MANAGED_IDENTITY_STORAGE.insert(managed); - unsafe { *out_managed_identity_handle = handle }; - PlatformWalletFFIResult::Success - } - None => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorIdentityNotFound, - format!("Identity {id} not found in wallet"), - ); - } - } - PlatformWalletFFIResult::ErrorIdentityNotFound - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + check_ptr!(out_managed_identity_handle); + let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); + + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let wm = wallet.wallet_manager().blocking_read(); + let info = wm.get_wallet_info(&wallet.wallet_id())?; + info.identity_manager.managed_identity(&id).cloned() + }); + let inner = unwrap_option_or_return!(option); + let managed = unwrap_option_or_return!(inner); + unsafe { *out_managed_identity_handle = MANAGED_IDENTITY_STORAGE.insert(managed) }; + PlatformWalletFfiResult::ok() } // --------------------------------------------------------------------------- @@ -163,8 +99,7 @@ pub struct ContactRequestHandleArray { } impl ContactRequestHandleArray { - /// Construct an empty array (null pointer, zero count). Used as - /// the default / failure return. + /// Construct an empty array (null pointer, zero count). pub fn empty() -> Self { Self { handles: std::ptr::null_mut(), @@ -172,8 +107,7 @@ impl ContactRequestHandleArray { } } - /// Copy a slice of contact requests into the global handle - /// storage and return a fresh array of handles. + /// Copy a slice of contact requests into the global handle storage. fn from_requests(requests: Vec) -> Self { if requests.is_empty() { return Self::empty(); @@ -236,55 +170,17 @@ pub unsafe extern "C" fn platform_wallet_contact_request_handle_array_free( pub unsafe extern "C" fn platform_wallet_sync_contact_requests( wallet_handle: Handle, out_array: *mut ContactRequestHandleArray, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_array.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_array is null", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity = wallet.identity().clone(); - let result = block_on_worker(async move { identity.sync_contact_requests().await }); - match result { - Ok(list) => { - let array = ContactRequestHandleArray::from_requests(list); - unsafe { *out_array = array }; - PlatformWalletFFIResult::Success - } - Err(e) => { - unsafe { *out_array = ContactRequestHandleArray::empty() }; - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("sync_contact_requests failed: {e}"), - ); - } - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + check_ptr!(out_array); + + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity = wallet.identity().clone(); + block_on_worker(async move { identity.sync_contact_requests().await }) + }); + let result = unwrap_option_or_return!(option); + let list = unwrap_result_or_return!(result); + unsafe { *out_array = ContactRequestHandleArray::from_requests(list) }; + PlatformWalletFfiResult::ok() } // --------------------------------------------------------------------------- @@ -304,7 +200,9 @@ pub unsafe extern "C" fn platform_wallet_sync_contact_requests( /// /// Returns a handle into `CONTACT_REQUEST_STORAGE` via /// `out_request_handle`. Release via -/// [`crate::contact_request_destroy`]. +/// [`crate::contact_request_destroy`]. `signer_handle` must be a +/// valid, non-destroyed handle produced by +/// `dash_sdk_signer_create_with_ctx`; caller retains ownership. /// /// CAVEAT — ECDH derivation: the Rust side still derives the /// sender's ECDH private key from the wallet seed for the contact @@ -312,10 +210,6 @@ pub unsafe extern "C" fn platform_wallet_sync_contact_requests( /// will fail at that step. See the docstring on /// [`IdentityWallet::send_contact_request_with_external_signer`](platform_wallet::IdentityWallet::send_contact_request_with_external_signer) /// for the planned follow-up to push ECDH across the FFI as well. -/// -/// # Safety -/// `signer_handle` must be a valid, non-destroyed handle produced by -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_send_contact_request_with_signer( @@ -327,57 +221,16 @@ pub unsafe extern "C" fn platform_wallet_send_contact_request_with_signer( auto_accept_proof_len: usize, signer_handle: *mut SignerHandle, out_request_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_request_handle.is_null() || signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_request_handle or signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_request_handle); + check_ptr!(signer_handle); - let sender = match read_identifier(sender_identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid sender identifier: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let recipient = match read_identifier(recipient_identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid recipient identifier: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let sender = unwrap_result_or_return!(read_identifier(sender_identity_id)); + let recipient = unwrap_result_or_return!(read_identifier(recipient_identity_id)); let label = if account_label.is_null() { None } else { - match CStr::from_ptr(account_label).to_str() { - Ok(s) => Some(s.to_string()), - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "account_label is not valid UTF-8", - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - } + Some(unwrap_result_or_return!(CStr::from_ptr(account_label).to_str()).to_string()) }; let proof: Option> = if auto_accept_proof.is_null() || auto_accept_proof_len == 0 { None @@ -387,43 +240,21 @@ pub unsafe extern "C" fn platform_wallet_send_contact_request_with_signer( let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity - .send_contact_request_with_external_signer( - &sender, &recipient, label, proof, signer, - ) - .await - }); - match result { - Ok(request) => { - let handle = CONTACT_REQUEST_STORAGE.insert(request); - *out_request_handle = handle; - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("send_contact_request_with_signer failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity + .send_contact_request_with_external_signer( + &sender, &recipient, label, proof, signer, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + let request = unwrap_result_or_return!(result); + *out_request_handle = CONTACT_REQUEST_STORAGE.insert(request); + PlatformWalletFfiResult::ok() } /// Accept an incoming contact request using an externally-supplied @@ -437,78 +268,36 @@ pub unsafe extern "C" fn platform_wallet_send_contact_request_with_signer( /// `request_handle` must be a live handle from /// `CONTACT_REQUEST_STORAGE` (typically obtained via /// `managed_identity_get_incoming_contact_request` or -/// [`platform_wallet_sync_contact_requests`]). -/// -/// Same ECDH caveat applies as for -/// [`platform_wallet_send_contact_request_with_signer`]. +/// [`platform_wallet_sync_contact_requests`]). Same ECDH caveat +/// applies as for [`platform_wallet_send_contact_request_with_signer`]. #[no_mangle] pub unsafe extern "C" fn platform_wallet_accept_contact_request_with_signer( wallet_handle: Handle, request_handle: Handle, signer_handle: *mut SignerHandle, out_established_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_established_handle.is_null() || signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_established_handle or signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_established_handle); + check_ptr!(signer_handle); - let request = match CONTACT_REQUEST_STORAGE.with_item(request_handle, |req| req.clone()) { - Some(r) => r, - None => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact request handle", - ); - } - return PlatformWalletFFIResult::ErrorInvalidHandle; - } - }; + let request_option = CONTACT_REQUEST_STORAGE.with_item(request_handle, |req| req.clone()); + let request = unwrap_option_or_return!(request_option); let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity - .accept_contact_request_with_external_signer(&request, signer) - .await - }); - match result { - Ok(contact) => { - let handle = ESTABLISHED_CONTACT_STORAGE.insert(contact); - *out_established_handle = handle; - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("accept_contact_request_with_signer failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity + .accept_contact_request_with_external_signer(&request, signer) + .await }) + }); + let result = unwrap_option_or_return!(option); + let contact = unwrap_result_or_return!(result); + *out_established_handle = ESTABLISHED_CONTACT_STORAGE.insert(contact); + PlatformWalletFfiResult::ok() } // --------------------------------------------------------------------------- @@ -526,69 +315,17 @@ pub unsafe extern "C" fn platform_wallet_reject_contact_request( wallet_handle: Handle, our_identity_id: *const u8, contact_identity_id: *const u8, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - let our_id = match unsafe { read_identifier(our_identity_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity identifier: {e}"), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let contact_id = match unsafe { read_identifier(contact_identity_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid contact identifier: {e}"), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity = wallet.identity().clone(); - let result = block_on_worker(async move { - identity.reject_contact_request(&our_id, &contact_id).await - }); - match result { - Ok(()) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("reject_contact_request failed: {e}"), - ); - } - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let our_id = unwrap_result_or_return!(unsafe { read_identifier(our_identity_id) }); + let contact_id = unwrap_result_or_return!(unsafe { read_identifier(contact_identity_id) }); + + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity = wallet.identity().clone(); + block_on_worker(async move { identity.reject_contact_request(&our_id, &contact_id).await }) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } // --------------------------------------------------------------------------- @@ -596,96 +333,30 @@ pub unsafe extern "C" fn platform_wallet_reject_contact_request( // --------------------------------------------------------------------------- /// Query Platform for contact requests sent by `identity_id`. -/// Returns handles into `CONTACT_REQUEST_STORAGE`. Release the -/// array via [`platform_wallet_contact_request_handle_array_free`] -/// and each handle via [`crate::contact_request_destroy`]. #[no_mangle] pub unsafe extern "C" fn platform_wallet_fetch_sent_contact_requests( wallet_handle: Handle, identity_id: *const u8, out_array: *mut ContactRequestHandleArray, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_array.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_array is null", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - let id = match unsafe { read_identifier(identity_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity identifier: {e}"), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity = wallet.identity().clone(); - let result = block_on_worker(async move { identity.sent_contact_requests(&id).await }); - match result { - Ok(list) => { - let array = ContactRequestHandleArray::from_requests(list); - unsafe { *out_array = array }; - PlatformWalletFFIResult::Success - } - Err(e) => { - unsafe { *out_array = ContactRequestHandleArray::empty() }; - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("fetch_sent_contact_requests failed: {e}"), - ); - } - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + check_ptr!(out_array); + let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); + + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity = wallet.identity().clone(); + block_on_worker(async move { identity.sent_contact_requests(&id).await }) + }); + let result = unwrap_option_or_return!(option); + let list = unwrap_result_or_return!(result); + unsafe { *out_array = ContactRequestHandleArray::from_requests(list) }; + PlatformWalletFfiResult::ok() } // --------------------------------------------------------------------------- // Send payment // --------------------------------------------------------------------------- -/// Send a Dash payment from `from_identity_id` to -/// `to_contact_identity_id` (must be an established DashPay -/// contact). `amount_duffs` is the payment amount in duffs -/// (1 DASH = 100,000,000 duffs). -/// -/// On success, `out_txid` is populated with the 32-byte -/// transaction id of the broadcast Core transaction. The Rust -/// side also records a [`PaymentEntry`] on -/// [`ManagedIdentity`](platform_wallet::ManagedIdentity) -/// via the persister, so the Swift persister observes the -/// update through the identity changeset callback. -/// -/// Memo field is optional; pass `memo = null` to omit. +/// Send a Dash payment from `from_identity_id` to `to_contact_identity_id`. #[no_mangle] pub unsafe extern "C" fn platform_wallet_send_dashpay_payment( wallet_handle: Handle, @@ -694,112 +365,30 @@ pub unsafe extern "C" fn platform_wallet_send_dashpay_payment( amount_duffs: u64, memo: *const c_char, out_txid: *mut [u8; 32], - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_txid.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_txid is null", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_txid); - let from_id = match unsafe { read_identifier(from_identity_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid from_identity identifier: {e}"), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let to_id = match unsafe { read_identifier(to_contact_identity_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid to_identity identifier: {e}"), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let from_id = unwrap_result_or_return!(unsafe { read_identifier(from_identity_id) }); + let to_id = unwrap_result_or_return!(unsafe { read_identifier(to_contact_identity_id) }); let memo_str: Option = if memo.is_null() { None } else { - match unsafe { CStr::from_ptr(memo) }.to_str() { - Ok(s) => Some(s.to_string()), - Err(_) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "memo is not valid UTF-8", - ); - } - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - } + Some(unwrap_result_or_return!(unsafe { CStr::from_ptr(memo) }.to_str()).to_string()) }; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity = wallet.identity().clone(); - let result = block_on_worker(async move { - identity - .send_payment(&from_id, &to_id, amount_duffs, memo_str) - .await - }); - match result { - Ok((txid, _entry)) => { - // `send_payment` returns `(Txid, PaymentEntry)`. - // The `PaymentEntry` is already recorded on the - // sender's `ManagedIdentity` via the persister, so - // it'll flow to Swift through the identity - // changeset callback. Here we just surface the - // `Txid` — copy the 32-byte little-endian - // representation into the out-param. - use dashcore::hashes::Hash; - let bytes = txid.to_raw_hash().to_byte_array(); - unsafe { - std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_txid.cast::(), 32); - } - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("send_dashpay_payment failed: {e}"), - ); - } - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity = wallet.identity().clone(); + block_on_worker(async move { + identity + .send_payment(&from_id, &to_id, amount_duffs, memo_str) + .await }) + }); + let result = unwrap_option_or_return!(option); + let (txid, _entry) = unwrap_result_or_return!(result); + use dashcore::hashes::Hash; + let bytes = txid.to_raw_hash().to_byte_array(); + unsafe { + std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_txid.cast::(), 32); + } + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/dashpay_profile.rs b/packages/rs-platform-wallet-ffi/src/dashpay_profile.rs index e809dc3ccb7..c7c4facf6d6 100644 --- a/packages/rs-platform-wallet-ffi/src/dashpay_profile.rs +++ b/packages/rs-platform-wallet-ffi/src/dashpay_profile.rs @@ -1,30 +1,4 @@ //! FFI bindings for DashPay profile read/write. -//! -//! Exposes three classes of entry point: -//! -//! 1. **Local cache read** — `managed_identity_get_dashpay_profile` -//! returns whatever [`DashPayProfile`](platform_wallet::DashPayProfile) -//! is currently cached on a [`ManagedIdentity`] in -//! `MANAGED_IDENTITY_STORAGE`. Sync, no network. -//! -//! 2. **Platform sync** — `platform_wallet_sync_dashpay_profiles` -//! queries the DashPay contract for profile documents owned by each -//! managed identity and updates the local cache. Blocks the calling -//! thread; drives the work on an 8 MB tokio worker thread (see -//! `runtime::block_on_worker`) because proof verification recurses -//! deeply. -//! -//! 3. **Platform write** — -//! `platform_wallet_create_or_update_dashpay_profile_with_signer` -//! broadcasts a `profile` document create / replace transition -//! via the supplied signer, then refreshes the local cache. Also -//! blocks + runs on the worker. -//! -//! Optional C-string inputs (`display_name`, `public_message`, -//! `avatar_url`) accept `null` for "field not provided". Avatar bytes -//! use the standard `(ptr, len)` pair with `(null, 0)` meaning "no -//! avatar"; platform-wallet computes the SHA-256 hash + dHash -//! fingerprint from the bytes before dropping them. use std::ffi::CStr; use std::os::raw::c_char; @@ -33,44 +7,26 @@ use std::ptr; use platform_wallet::{DashPayProfile, ProfileUpdate}; use rs_sdk_ffi::{SignerHandle, VTableSigner}; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::*; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; /// Flat FFI view of a [`DashPayProfile`]. -/// -/// `display_name` / `public_message` / `avatar_url` are heap-allocated -/// C strings that the caller releases with -/// [`dashpay_profile_ffi_free`]; any of them may be null when the -/// underlying `Option` is `None`. -/// -/// `avatar_hash` and `avatar_fingerprint` are inline so no separate -/// allocation is needed; their `_is_some` flag tells the caller -/// whether to read them. #[repr(C)] pub struct DashPayProfileFFI { - /// UTF-8 NUL-terminated display name, or `null`. pub display_name: *mut c_char, - /// UTF-8 NUL-terminated public message / bio, or `null`. pub public_message: *mut c_char, - /// UTF-8 NUL-terminated avatar URL, or `null`. pub avatar_url: *mut c_char, - /// `true` iff `avatar_hash` carries a valid SHA-256 digest. pub avatar_hash_is_some: bool, - /// SHA-256 of the avatar image bytes. Ignore unless - /// `avatar_hash_is_some`. pub avatar_hash: [u8; 32], - /// `true` iff `avatar_fingerprint` carries a valid dHash. pub avatar_fingerprint_is_some: bool, - /// Perceptual dHash fingerprint (64 bits). Ignore unless - /// `avatar_fingerprint_is_some`. pub avatar_fingerprint: [u8; 8], } impl DashPayProfileFFI { - /// Build an all-null / default-zeroed instance — the caller uses - /// this as the out-param initial value. pub fn empty() -> Self { Self { display_name: ptr::null_mut(), @@ -83,14 +39,6 @@ impl DashPayProfileFFI { } } - /// Convert a cached [`DashPayProfile`] into its FFI shape, - /// heap-allocating the string fields. The returned struct takes - /// ownership of those allocations; the caller must release them - /// via [`dashpay_profile_ffi_free`]. - /// - /// The DashPay contract folds `bio` and `publicMessage` onto a - /// single on-chain field, so the FFI only surfaces - /// `public_message` to avoid a spurious duplicate in Swift. fn from_profile(profile: &DashPayProfile) -> Self { let display_name = option_string_to_c(profile.display_name.as_deref()); let public_message = option_string_to_c(profile.public_message.as_deref()); @@ -117,233 +65,90 @@ impl DashPayProfileFFI { } } -/// Allocate a C string from a Rust string slice, or return `null` when -/// the input is `None`. Used by [`DashPayProfileFFI::from_profile`] — -/// the caller releases ownership via [`dashpay_profile_ffi_free`]. fn option_string_to_c(s: Option<&str>) -> *mut c_char { match s { Some(value) => match std::ffi::CString::new(value) { Ok(c_str) => c_str.into_raw(), - // Strings containing interior NULs get dropped silently — - // there's nothing valid to return, and profile fields - // should never contain NUL in practice. Err(_) => ptr::null_mut(), }, None => ptr::null_mut(), } } -/// Decode an optional UTF-8 C string input parameter. -/// -/// Returns: -/// - `Ok(None)` when the pointer is null (absent field). -/// - `Ok(Some(String))` when valid UTF-8. -/// - `Err(&'static str)` describing which field failed UTF-8 -/// validation so the FFI can surface a specific error message. -unsafe fn decode_opt_c_str( - ptr: *const c_char, - field: &'static str, -) -> Result, String> { +unsafe fn decode_opt_c_str(ptr: *const c_char) -> Result, PlatformWalletFfiResult> { if ptr.is_null() { return Ok(None); } - match unsafe { CStr::from_ptr(ptr) }.to_str() { - Ok(s) => Ok(Some(s.to_string())), - Err(_) => Err(format!("{field} is not valid UTF-8")), - } + let s = CStr::from_ptr(ptr).to_str()?; + Ok(Some(s.to_string())) } /// Read the cached DashPay profile for a [`ManagedIdentity`] handle. -/// -/// `out_has_profile` reflects whether the managed identity has a -/// profile cached; `out_profile` is only populated when the flag is -/// `true`. On success the caller owns any non-null strings inside -/// `out_profile` and must release them with -/// [`dashpay_profile_ffi_free`]. -/// -/// No network traffic — consult `platform_wallet_sync_dashpay_profiles` -/// first if you want the freshest data. #[no_mangle] pub unsafe extern "C" fn managed_identity_get_dashpay_profile( identity_handle: Handle, out_profile: *mut DashPayProfileFFI, out_has_profile: *mut bool, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_profile.is_null() || out_has_profile.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided for profile out-params", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + check_ptr!(out_profile); + check_ptr!(out_has_profile); + + let option = MANAGED_IDENTITY_STORAGE + .with_item(identity_handle, |identity| identity.dashpay_profile.clone()); + let profile_opt = unwrap_option_or_return!(option); + match profile_opt { + Some(profile) => unsafe { + *out_profile = DashPayProfileFFI::from_profile(&profile); + *out_has_profile = true; + }, + None => unsafe { + *out_profile = DashPayProfileFFI::empty(); + *out_has_profile = false; + }, } - - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - match &identity.dashpay_profile { - Some(profile) => { - unsafe { - *out_profile = DashPayProfileFFI::from_profile(profile); - *out_has_profile = true; - } - PlatformWalletFFIResult::Success - } - None => { - unsafe { - *out_profile = DashPayProfileFFI::empty(); - *out_has_profile = false; - } - PlatformWalletFFIResult::Success - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid managed identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + PlatformWalletFfiResult::ok() } -/// Read the cached DashPay profile for a specific identity owned by -/// a [`PlatformWallet`](platform_wallet::PlatformWallet) handle. -/// -/// Convenience for UI layers that track identities by ID and don't -/// hold a live [`ManagedIdentity`] handle. Looks the identity up via -/// the wallet's `PlatformWalletInfo` under a read lock, then copies -/// the cached profile into an FFI struct. `out_has_profile` reflects -/// whether the identity has a profile cached; `out_profile` is only -/// populated when the flag is `true`. -/// -/// Returns `ErrorIdentityNotFound` when the identity is unknown to -/// this wallet. +/// Read the cached DashPay profile for a specific identity owned by a wallet. #[no_mangle] pub unsafe extern "C" fn platform_wallet_get_dashpay_profile( wallet_handle: Handle, identity_id: *const u8, out_profile: *mut DashPayProfileFFI, out_has_profile: *mut bool, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_profile.is_null() || out_has_profile.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided for profile out-params", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + check_ptr!(out_profile); + check_ptr!(out_has_profile); + + let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); + + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let wm = wallet.wallet_manager().blocking_read(); + let info = wm.get_wallet_info(&wallet.wallet_id())?; + info.identity_manager.managed_identity(&id).cloned() + }); + let inner = unwrap_option_or_return!(option); + let managed = unwrap_option_or_return!(inner); + match managed.dashpay_profile { + Some(profile) => unsafe { + *out_profile = DashPayProfileFFI::from_profile(&profile); + *out_has_profile = true; + }, + None => unsafe { + *out_profile = DashPayProfileFFI::empty(); + *out_has_profile = false; + }, } - - let id = match unsafe { read_identifier(identity_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity identifier: {e}"), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - // `blocking_read` is safe here — this FFI entry point is - // invoked from a (non-tokio) caller thread, matching the - // same pattern used by - // `try_match_incoming_dashpay_address`. - let wm = wallet.wallet_manager().blocking_read(); - let info = match wm.get_wallet_info(&wallet.wallet_id()) { - Some(i) => i, - None => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Wallet info not found for wallet handle", - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidHandle; - } - }; - - let managed = match info.identity_manager.managed_identity(&id) { - Some(m) => m, - None => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorIdentityNotFound, - format!("Identity {id} not found in wallet"), - ); - } - } - return PlatformWalletFFIResult::ErrorIdentityNotFound; - } - }; - - match &managed.dashpay_profile { - Some(profile) => { - unsafe { - *out_profile = DashPayProfileFFI::from_profile(profile); - *out_has_profile = true; - } - PlatformWalletFFIResult::Success - } - None => { - unsafe { - *out_profile = DashPayProfileFFI::empty(); - *out_has_profile = false; - } - PlatformWalletFFIResult::Success - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + PlatformWalletFfiResult::ok() } -/// Release strings owned by a [`DashPayProfileFFI`] previously populated -/// by this FFI. Safe to call on `empty()` profiles — each string pointer -/// is checked for `null` before freeing. -/// -/// Pointer-only signature: `DashPayProfileFFI` is well over the -/// 16-byte AAPCS64 / Swift register-pass cliff, so by-value cannot -/// be trusted across the `@_silgen_name` boundary. After free the -/// three nullable string pointers are reset so a double-free no-ops. +/// Release strings owned by a [`DashPayProfileFFI`]. #[no_mangle] pub unsafe extern "C" fn dashpay_profile_ffi_free(profile: *mut DashPayProfileFFI) { if profile.is_null() { return; } let profile = unsafe { &mut *profile }; - // Each field is independently heap-owned. `platform_wallet_string_free` - // is a no-op on null. crate::platform_wallet_string_free(profile.display_name); crate::platform_wallet_string_free(profile.public_message); crate::platform_wallet_string_free(profile.avatar_url); @@ -354,81 +159,24 @@ pub unsafe extern "C" fn dashpay_profile_ffi_free(profile: *mut DashPayProfileFF /// Fetch DashPay profile documents for every managed identity on the /// wallet and refresh the local cache. -/// -/// `out_synced_count` is populated with the number of identities for -/// which a profile document was found and cached. Identities with no -/// on-chain profile have their cached profile cleared (if any). -/// -/// Blocks until the sync completes — the actual async work runs on an -/// 8 MB tokio worker thread via `block_on_worker`. #[no_mangle] pub unsafe extern "C" fn platform_wallet_sync_dashpay_profiles( wallet_handle: Handle, out_synced_count: *mut u32, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - // Cheap Arc clone — same generic specialization other - // identity-side FFI entry points use to hand the work to a - // tokio worker without dragging the wallet handle along. - let identity = wallet.identity().clone(); - let result = block_on_worker(async move { identity.sync_profiles().await }); - match result { - Ok(count) => { - if !out_synced_count.is_null() { - unsafe { *out_synced_count = count }; - } - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("sync_dashpay_profiles failed: {e}"), - ); - } - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity = wallet.identity().clone(); + block_on_worker(async move { identity.sync_profiles().await }) + }); + let result = unwrap_option_or_return!(option); + let count = unwrap_result_or_return!(result); + if !out_synced_count.is_null() { + unsafe { *out_synced_count = count }; + } + PlatformWalletFfiResult::ok() } -/// Create or update a DashPay profile using an externally-supplied -/// signer. -/// -/// The document state-transition signature crosses the FFI through -/// the supplied `signer_handle` (typically `KeychainSigner.handle`). -/// Required for external-signable wallets and the architecturally -/// correct path per `swift-sdk/CLAUDE.md` — the wallet's own seed -/// never participates in document signing. -/// -/// `do_create` picks between the two operation paths on the Rust -/// side: `true` calls `create_profile_with_external_signer` (errors -/// when a profile already exists for the identity); `false` calls -/// `update_profile_with_external_signer` (errors when no profile -/// is on Platform yet). -/// -/// On success the caller owns strings inside `out_profile` and must -/// free them with [`dashpay_profile_ffi_free`]. -/// -/// # Safety -/// Standard FFI null/lifetime rules apply to all input pointers. -/// `signer_handle` must be a valid, non-destroyed handle produced by -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. +/// Create or update a DashPay profile using an externally-supplied signer. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_create_or_update_dashpay_profile_with_signer( @@ -442,70 +190,15 @@ pub unsafe extern "C" fn platform_wallet_create_or_update_dashpay_profile_with_s do_create: bool, signer_handle: *mut SignerHandle, out_profile: *mut DashPayProfileFFI, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_profile.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_profile is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_profile); + check_ptr!(signer_handle); - let id = match read_identifier(identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity identifier: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let id = unwrap_result_or_return!(read_identifier(identity_id)); - let display_name = match decode_opt_c_str(display_name, "display_name") { - Ok(v) => v, - Err(msg) => { - if !out_error.is_null() { - *out_error = - PlatformWalletFFIError::new(PlatformWalletFFIResult::ErrorUtf8Conversion, msg); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - }; - let public_message = match decode_opt_c_str(public_message, "public_message") { - Ok(v) => v, - Err(msg) => { - if !out_error.is_null() { - *out_error = - PlatformWalletFFIError::new(PlatformWalletFFIResult::ErrorUtf8Conversion, msg); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - }; - let avatar_url = match decode_opt_c_str(avatar_url, "avatar_url") { - Ok(v) => v, - Err(msg) => { - if !out_error.is_null() { - *out_error = - PlatformWalletFFIError::new(PlatformWalletFFIResult::ErrorUtf8Conversion, msg); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - }; + let display_name = unwrap_result_or_return!(decode_opt_c_str(display_name)); + let public_message = unwrap_result_or_return!(decode_opt_c_str(public_message)); + let avatar_url = unwrap_result_or_return!(decode_opt_c_str(avatar_url)); let avatar_bytes_vec: Option> = if avatar_bytes.is_null() || avatar_bytes_len == 0 { None @@ -515,55 +208,32 @@ pub unsafe extern "C" fn platform_wallet_create_or_update_dashpay_profile_with_s let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, move |wallet| { - let identity = wallet.identity().clone(); - let input = ProfileUpdate { - display_name, - public_message, - avatar_url, - avatar_bytes: avatar_bytes_vec, - }; - - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - if do_create { - identity - .create_profile_with_external_signer(&id, input, signer) - .await - } else { - identity - .update_profile_with_external_signer(&id, input, signer) - .await - } - }); + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, move |wallet| { + let identity = wallet.identity().clone(); + let input = ProfileUpdate { + display_name, + public_message, + avatar_url, + avatar_bytes: avatar_bytes_vec, + }; - match result { - Ok(profile) => { - *out_profile = DashPayProfileFFI::from_profile(&profile); - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - let tag = if do_create { "create" } else { "update" }; - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("{tag}_dashpay_profile_with_signer failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + if do_create { + identity + .create_profile_with_external_signer(&id, input, signer) + .await + } else { + identity + .update_profile_with_external_signer(&id, input, signer) + .await } - PlatformWalletFFIResult::ErrorInvalidHandle }) + }); + let result = unwrap_option_or_return!(option); + let profile = unwrap_result_or_return!(result); + *out_profile = DashPayProfileFFI::from_profile(&profile); + PlatformWalletFfiResult::ok() } #[cfg(test)] @@ -590,16 +260,10 @@ mod tests { let handle = MANAGED_IDENTITY_STORAGE.insert(managed); let mut out = DashPayProfileFFI::empty(); - let mut has_profile = true; // start true so we can observe the write - let mut error = PlatformWalletFFIError::success(); - - let result = managed_identity_get_dashpay_profile( - handle, - &mut out, - &mut has_profile, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + let mut has_profile = true; + + let result = managed_identity_get_dashpay_profile(handle, &mut out, &mut has_profile); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert!(!has_profile); assert!(out.display_name.is_null()); assert!(out.public_message.is_null()); @@ -630,15 +294,9 @@ mod tests { let mut out = DashPayProfileFFI::empty(); let mut has_profile = false; - let mut error = PlatformWalletFFIError::success(); - - let result = managed_identity_get_dashpay_profile( - handle, - &mut out, - &mut has_profile, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + + let result = managed_identity_get_dashpay_profile(handle, &mut out, &mut has_profile); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert!(has_profile); let display = std::ffi::CStr::from_ptr(out.display_name).to_str().unwrap(); @@ -664,18 +322,12 @@ mod tests { unsafe { let mut out = DashPayProfileFFI::empty(); let mut has_profile = false; - let mut error = PlatformWalletFFIError::success(); - let result = managed_identity_get_dashpay_profile( - 9_999_999, /* bogus handle */ - &mut out, - &mut has_profile, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::ErrorInvalidHandle); + let result = + managed_identity_get_dashpay_profile(9_999_999, &mut out, &mut has_profile); + assert_eq!(result.code, PlatformWalletFfiResultCode::NotFound); dashpay_profile_ffi_free(&mut out); - platform_wallet_ffi_error_free(&mut error); } } } diff --git a/packages/rs-platform-wallet-ffi/src/data_contract.rs b/packages/rs-platform-wallet-ffi/src/data_contract.rs index b490f85ea7f..2e6b5374cc1 100644 --- a/packages/rs-platform-wallet-ffi/src/data_contract.rs +++ b/packages/rs-platform-wallet-ffi/src/data_contract.rs @@ -1,24 +1,5 @@ //! FFI bindings for data-contract create / update operations on -//! `IdentityWallet`. Thin C-ABI shells over the -//! `IdentityWallet::create_data_contract_with_signer` etc. methods -//! in `platform-wallet`. -//! -//! Replaces the contract-create surface that used to live in -//! `rs-sdk-ffi::data_contract::dash_sdk_data_contract_create` + -//! `dash_sdk_data_contract_put_to_platform_and_wait`. The previous -//! path went through the rs-sdk-ffi tokio runtime, which uses iOS's -//! ~512 KB default thread stack — proof verification in `rs-drive` -//! recurses through GroveDB deeply enough to blow past that and -//! crash with `EXC_BAD_ACCESS` at the function prologue of -//! `grovedb_query::proofs::encoding::Op::decode`. The -//! platform-wallet runtime (`runtime.rs`) configures a 8 MB worker -//! stack precisely for this case. -//! -//! Architectural note: contract creation belongs on platform-wallet -//! (per `swift-sdk/CLAUDE.md`'s "high-level operations go through -//! platform-wallet" rule) because it spans an identity (the owner), -//! needs the wallet's signer, and changes local state the persister -//! tracks. +//! `IdentityWallet`. use std::ffi::CStr; use std::os::raw::c_char; @@ -28,45 +9,14 @@ use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::prelude::Identifier; use rs_sdk_ffi::{SignerHandle, VTableSigner}; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::read_identifier; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; -/// Create + broadcast a new data contract owned by -/// `owner_identity_id`. Returns the 32-byte contract id in -/// `out_contract_id` on success. -/// -/// JSON shapes (all optional except documents): -/// - `documents_schema_json`: object keyed by document type name, -/// each value a JSON Schema. Pass `"{}"` for token-only contracts. -/// - `tokens_schema_json`: object keyed by stringified slot index -/// (`"0"`, `"1"`, …), each value a `TokenConfiguration` JSON. -/// Caller must include `$formatVersion: "0"` tags at the -/// `TokenConfiguration` / `TokenConfigurationConvention` / -/// `TokenConfigurationLocalization` levels — the V1 struct's -/// tagged enums require them. -/// - `groups_schema_json`: object keyed by stringified group -/// position, each value a `Group` JSON. -/// - `keywords_json`: JSON array of strings. -/// - `description`: plain (non-JSON) string. -/// - `config_json`: `DataContractConfig` JSON. The library injects -/// `$formatVersion: "0"` if missing so a Swift-shaped flags dict -/// round-trips cleanly. -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle -/// registry. -/// - `owner_identity_id` must point at a 32-byte buffer for the -/// duration of the call. -/// - `documents_schema_json` must be a valid NUL-terminated UTF-8 -/// C string. -/// - The other JSON pointers may be NULL; when non-NULL each must -/// be NUL-terminated UTF-8. -/// - `signer_handle` must be a valid, non-destroyed handle from -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. -/// - `out_contract_id` must be a valid, writable 32-byte buffer. -/// - `out_error` may be NULL. +/// Create + broadcast a new data contract owned by `owner_identity_id`. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_create_data_contract_with_signer( @@ -80,166 +30,59 @@ pub unsafe extern "C" fn platform_wallet_create_data_contract_with_signer( config_json: *const c_char, signer_handle: *mut SignerHandle, out_contract_id: *mut u8, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - if documents_schema_json.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "documents_schema_json is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - if out_contract_id.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_contract_id is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); + check_ptr!(documents_schema_json); + check_ptr!(out_contract_id); - // Owner identity id (32 bytes). - let owner_id = match read_identifier(owner_identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid owner_identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let owner_id = unwrap_result_or_return!(read_identifier(owner_identity_id)); - // Decode each JSON-string parameter on the calling thread — - // the resulting `&str` references stay borrowed from the - // caller's buffers, so we can hand them straight to the - // library function without allocating. - let documents_str = match CStr::from_ptr(documents_schema_json).to_str() { - Ok(s) => s, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("documents_schema_json is not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - }; + let documents_str = unwrap_result_or_return!(CStr::from_ptr(documents_schema_json).to_str()); - // Optional JSON pointers — `read_optional_str` returns - // `Ok(None)` for NULL or empty so the library's `Option<&str>` - // contract is honoured. - let tokens_str = match read_optional_str(tokens_schema_json, "tokens_schema_json", out_error) { - Ok(s) => s, - Err(code) => return code, - }; - let groups_str = match read_optional_str(groups_schema_json, "groups_schema_json", out_error) { - Ok(s) => s, - Err(code) => return code, - }; - let keywords_str = match read_optional_str(keywords_json, "keywords_json", out_error) { - Ok(s) => s, - Err(code) => return code, - }; - let description_str = match read_optional_str(description, "description", out_error) { - Ok(s) => s, - Err(code) => return code, - }; - let config_str = match read_optional_str(config_json, "config_json", out_error) { - Ok(s) => s, - Err(code) => return code, - }; + let tokens_str = unwrap_result_or_return!(read_optional_str(tokens_schema_json)); + let groups_str = unwrap_result_or_return!(read_optional_str(groups_schema_json)); + let keywords_str = unwrap_result_or_return!(read_optional_str(keywords_json)); + let description_str = unwrap_result_or_return!(read_optional_str(description)); + let config_str = unwrap_result_or_return!(read_optional_str(config_json)); - // The signer reference outlives the awaited work because the - // FFI caller pins the `KeychainSigner` (and thus the signer - // handle) for the duration of this call. let signer_addr = signer_handle as usize; let owner_id_for_async = owner_id; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - let result: Result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .create_data_contract_with_signer( - &owner_id_for_async, - documents_str, - tokens_str, - groups_str, - keywords_str, - description_str, - config_str, - signer, - ) - .await - .map(|contract| contract.id()) - }); - match result { - Ok(contract_id) => { - let bytes = contract_id.to_buffer(); - let dst = slice::from_raw_parts_mut(out_contract_id, 32); - dst.copy_from_slice(&bytes); - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("create_data_contract_with_signer failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + let result: Result = block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .create_data_contract_with_signer( + &owner_id_for_async, + documents_str, + tokens_str, + groups_str, + keywords_str, + description_str, + config_str, + signer, + ) + .await + .map(|contract| contract.id()) + }); + result + }); + let result = unwrap_option_or_return!(option); + let contract_id = unwrap_result_or_return!(result); + let bytes = contract_id.to_buffer(); + let dst = slice::from_raw_parts_mut(out_contract_id, 32); + dst.copy_from_slice(&bytes); + PlatformWalletFfiResult::ok() } -/// Decode an optional NUL-terminated UTF-8 C string. NULL or empty -/// pointer yields `Ok(None)`. Non-empty pointer that fails UTF-8 -/// validation yields `Err(error_code)` after stamping `out_error`. +/// Decode an optional NUL-terminated UTF-8 C string. unsafe fn read_optional_str<'a>( ptr: *const c_char, - field_name: &str, - out_error: *mut PlatformWalletFFIError, -) -> Result, PlatformWalletFFIResult> { +) -> Result, PlatformWalletFfiResult> { if ptr.is_null() { return Ok(None); } - match CStr::from_ptr(ptr).to_str() { - Ok(s) => Ok(if s.is_empty() { None } else { Some(s) }), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("{field_name} is not valid UTF-8: {e}"), - ); - } - Err(PlatformWalletFFIResult::ErrorUtf8Conversion) - } - } + let s = CStr::from_ptr(ptr).to_str()?; + Ok(if s.is_empty() { None } else { Some(s) }) } diff --git a/packages/rs-platform-wallet-ffi/src/derivation.rs b/packages/rs-platform-wallet-ffi/src/derivation.rs index e1548df16a4..70dba1a32a1 100644 --- a/packages/rs-platform-wallet-ffi/src/derivation.rs +++ b/packages/rs-platform-wallet-ffi/src/derivation.rs @@ -1,16 +1,4 @@ //! Stateless derivation helpers. -//! -//! These helpers compose `key_wallet` primitives -//! (`Mnemonic::to_seed` → `ExtendedPrivKey::new_master` → -//! `derive_priv`) behind a single FFI call so the Swift side can obtain -//! an extended private key at a BIP-32 derivation path **without -//! mutating any persisted wallet state**. -//! -//! The wallet struct itself remains `WatchOnly` / `ExternalSignable` — -//! no key material is ever written into `Wallet::wallet_type`. All -//! intermediates here (seed, master key, derived key) are wrapped in -//! `Zeroizing` so they are scrubbed from memory as soon as the FFI -//! function returns. use std::ffi::CStr; use std::os::raw::c_char; @@ -23,15 +11,8 @@ use key_wallet::Network; use zeroize::Zeroizing; use crate::error::*; +use crate::{check_ptr, unwrap_result_or_return}; -/// Parse a BIP-39 mnemonic against every supported wordlist in turn, -/// returning the first language that yields a valid mnemonic. -/// -/// `key_wallet::Mnemonic` only exposes language-tagged constructors, -/// so user-mnemonic FFI entry points must walk the language list -/// themselves to avoid rejecting a French / Japanese / etc. phrase as -/// "invalid English". BIP-39 wordlists are mutually exclusive per -/// phrase (per the spec), so the first match is unambiguous. fn parse_mnemonic_any_language(phrase: &str) -> Result { const LANGUAGES: [Language; 10] = [ Language::English, @@ -55,42 +36,6 @@ fn parse_mnemonic_any_language(phrase: &str) -> Result { /// Derive a 32-byte ECDSA private key at a BIP-32 derivation path from /// a mnemonic phrase. -/// -/// On success `out_secret_key` (32 bytes) receives the secp256k1 -/// private key and `out_chain_code` (32 bytes) receives the BIP-32 -/// chain code. The corresponding compressed public key (33 bytes) is -/// computed from the private key and written to `out_public_key` so -/// callers that only need the pubkey (identity registration: pubkey -/// only) never have to touch the private material. -/// -/// # Inputs -/// * `mnemonic` — UTF-8 null-terminated BIP-39 mnemonic phrase. -/// * `passphrase` — optional UTF-8 null-terminated BIP-39 passphrase. -/// Pass `null` for no passphrase. -/// * `network` — `0` = Mainnet, `1` = Testnet, `2` = Devnet, `3` = -/// Regtest. Drives the xpriv version bytes; the derived secret -/// key itself is network-independent but the master key's chain -/// code derivation uses the HD seed directly. -/// * `path_utf8` — UTF-8 null-terminated BIP-32 path string, e.g. -/// `m/9'/5'/0'/0'/0'`. Parsed via [`DerivationPath::from_str`]. -/// * `out_secret_key` — 32 byte buffer receiving the private key. -/// Caller must ensure the buffer is 32 bytes. -/// * `out_chain_code` — 32 byte buffer receiving the chain code. -/// * `out_public_key` — 33 byte buffer receiving the compressed -/// secp256k1 pubkey. Pass `null` to skip. -/// * `out_error` — error detail populated on failure. May be null. -/// -/// # Safety -/// All pointers must either be null (only where marked optional) or -/// point at valid memory of the documented sizes. The function does -/// not retain any pointer beyond the call. -/// -/// # Zeroization -/// The derived master xpriv, the derived xpriv at `path`, and the -/// intermediate seed are wrapped in [`Zeroizing`] inside this -/// function. The only material left in memory after return is the -/// 32-byte secret bytes written into the caller's buffer (caller's -/// responsibility to manage). #[no_mangle] pub unsafe extern "C" fn platform_wallet_derive_ext_priv_key_from_mnemonic( mnemonic: *const c_char, @@ -100,64 +45,19 @@ pub unsafe extern "C" fn platform_wallet_derive_ext_priv_key_from_mnemonic( out_secret_key: *mut u8, out_chain_code: *mut u8, out_public_key: *mut u8, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - // ---- Validate pointers -------------------------------------------------- - if mnemonic.is_null() - || path_utf8.is_null() - || out_secret_key.is_null() - || out_chain_code.is_null() - { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "null pointer for required argument", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(mnemonic); + check_ptr!(path_utf8); + check_ptr!(out_secret_key); + check_ptr!(out_chain_code); - // ---- Decode inputs ------------------------------------------------------ - let mnemonic_str = match CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "mnemonic is not valid UTF-8", - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - }; + let mnemonic_str = unwrap_result_or_return!(CStr::from_ptr(mnemonic).to_str()); let passphrase_str: &str = if passphrase.is_null() { "" } else { - match CStr::from_ptr(passphrase).to_str() { - Ok(s) => s, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "passphrase is not valid UTF-8", - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - } - }; - let path_str = match CStr::from_ptr(path_utf8).to_str() { - Ok(s) => s, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "path is not valid UTF-8", - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } + unwrap_result_or_return!(CStr::from_ptr(passphrase).to_str()) }; + let path_str = unwrap_result_or_return!(CStr::from_ptr(path_utf8).to_str()); let network = match network { 0 => Network::Mainnet, @@ -165,82 +65,24 @@ pub unsafe extern "C" fn platform_wallet_derive_ext_priv_key_from_mnemonic( 2 => Network::Devnet, 3 => Network::Regtest, _ => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - "invalid network (expected 0..=3)", - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + "invalid network (expected 0..=3)", + ); } }; - let path = match DerivationPath::from_str(path_str) { - Ok(p) => p, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("invalid derivation path: {}", e), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; + let path = unwrap_result_or_return!(DerivationPath::from_str(path_str)); - // ---- Derive ------------------------------------------------------------- - // Auto-detect the BIP-39 language so non-English phrases (Spanish, - // French, Italian, Japanese, Korean, Chinese {Simplified, - // Traditional}, Czech, Portuguese) are accepted instead of being - // rejected as "invalid English". - let mnemonic_obj = match parse_mnemonic_any_language(mnemonic_str) { - Ok(m) => m, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("invalid mnemonic: {}", e), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; + let mnemonic_obj = unwrap_result_or_return!(parse_mnemonic_any_language(mnemonic_str)); - // Seed is 64 bytes; Zeroizing scrubs on drop. ExtendedPrivKey - // itself does not implement Zeroize (upstream limitation), so we - // only wrap the byte-array outputs below. Intermediate xpriv - // values live for a few microseconds in this function and are - // dropped at end-of-scope; not ideal but materially bounded. let seed = Zeroizing::new(mnemonic_obj.to_seed(passphrase_str)); - let master = match ExtendedPrivKey::new_master(network, &*seed) { - Ok(m) => m, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("failed to derive master key: {}", e), - ); - } - return PlatformWalletFFIResult::ErrorWalletOperation; - } - }; + let master = unwrap_result_or_return!(ExtendedPrivKey::new_master(network, &*seed)); let secp = Secp256k1::new(); - let derived = match master.derive_priv(&secp, &path) { - Ok(d) => d, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("failed to derive private key at path: {}", e), - ); - } - return PlatformWalletFFIResult::ErrorWalletOperation; - } - }; + let derived = unwrap_result_or_return!(master.derive_priv(&secp, &path)); - // ---- Write outputs ------------------------------------------------------ let secret = Zeroizing::new(derived.private_key.secret_bytes()); std::ptr::copy_nonoverlapping(secret.as_ptr(), out_secret_key, 32); @@ -251,5 +93,5 @@ pub unsafe extern "C" fn platform_wallet_derive_ext_priv_key_from_mnemonic( std::ptr::copy_nonoverlapping(pubkey_bytes.as_ptr(), out_public_key, 33); } - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/derive_identity_key_at_slot.rs b/packages/rs-platform-wallet-ffi/src/derive_identity_key_at_slot.rs index c9a28feaac5..ea28ee1e1e7 100644 --- a/packages/rs-platform-wallet-ffi/src/derive_identity_key_at_slot.rs +++ b/packages/rs-platform-wallet-ffi/src/derive_identity_key_at_slot.rs @@ -1,23 +1,4 @@ //! Single-slot mnemonic-driven identity-authentication key derivation. -//! -//! Companion to -//! [`crate::identity_keys_from_mnemonic::dash_sdk_derive_identity_keys_from_mnemonic`] -//! — same derivation logic, but returns ONE row at an arbitrary -//! `(identity_index, key_index)` slot instead of `0..key_count`. -//! -//! Use case: adding a new key to an existing identity. The Swift -//! caller picks `key_index = max(existing_key_ids) + 1`, calls this -//! function to derive the keypair, persists the private bytes to -//! Keychain, then submits an `updateIdentity(addPublicKeys:)` state -//! transition with the returned public key. The mnemonic-driven -//! shape (rather than wallet-handle) keeps watch-only wallets -//! supported — Rust never needs an in-process xpriv loaded. -//! -//! Derivation passes through the library function -//! [`platform_wallet::wallet::identity::network::identity_handle:: -//! derive_ecdsa_identity_auth_keypair_from_master`] so the path -//! builder + secp256k1 derive are not duplicated here. Only the -//! C-ABI marshalling lives in this file. use std::ffi::{c_void, CString}; use std::os::raw::c_char; @@ -34,40 +15,10 @@ use crate::derive_and_persist_callbacks::{ use crate::error::*; use crate::identity_key_preview::IdentityKeyPreviewFFI; use crate::identity_keys_from_mnemonic::{map_network, parse_mnemonic_any_language}; +use crate::{check_ptr, unwrap_result_or_return}; /// Derive a single ECDSA identity-authentication keypair at -/// `(identity_index, key_index)` from a BIP-39 mnemonic. Returns -/// the row in `*out_row`; release with -/// [`dash_sdk_derive_identity_key_at_slot_free`]. -/// -/// Currently ECDSA-only — `key_type` callers (BLS, EdDSA) need a -/// different derivation curve and aren't wired through yet. The -/// caller chooses ECDSA-secp256k1 vs ECDSA-hash160 at the -/// `IdentityPublicKey` construction step (downstream of this -/// function) since both share the same DIP-9 path and the same -/// 33-byte compressed public key bytes. -/// -/// # Parameters -/// - `mnemonic_cstr` / `passphrase_cstr`: the BIP-39 inputs. -/// `passphrase_cstr` may be NULL. -/// - `network`: selects the DIP-9 coin-type slot (mainnet vs testnet). -/// - `identity_index`: hardened identity index slot. -/// - `key_index`: hardened key index slot. The caller chooses this -/// (typically `max(existing_key_ids) + 1`). -/// - `out_row`: populated on success with one -/// [`IdentityKeyPreviewFFI`]. Release with the paired free -/// function. -/// - `out_error`: populated on failure with the usual error detail. -/// -/// On error `*out_row` is left zeroed. -/// -/// # Safety -/// - `mnemonic_cstr` must be a valid, NUL-terminated UTF-8 C string -/// for the duration of the call. -/// - `passphrase_cstr` may be NULL; otherwise must be valid + NUL- -/// terminated. -/// - `out_row` must be a valid, writable pointer. -/// - `out_error` may be NULL. +/// `(identity_index, key_index)` from a BIP-39 mnemonic. #[no_mangle] pub unsafe extern "C" fn dash_sdk_derive_identity_key_at_slot( mnemonic_cstr: *const std::os::raw::c_char, @@ -76,62 +27,19 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_key_at_slot( identity_index: u32, key_index: u32, out_row: *mut IdentityKeyPreviewFFI, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { +) -> PlatformWalletFfiResult { use std::ffi::CStr; - if out_row.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_row is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - // Pre-zero so a failed call leaves the caller staring at known - // empty state, never uninitialized memory. Mirrors the - // mnemonic-loop variant's behaviour. + check_ptr!(out_row); *out_row = IdentityKeyPreviewFFI::empty(); - if mnemonic_cstr.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "mnemonic_cstr is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } + check_ptr!(mnemonic_cstr); - // ---- UTF-8 inputs -------------------------------------------------------- - let mnemonic_str = match CStr::from_ptr(mnemonic_cstr).to_str() { - Ok(s) => s, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("mnemonic_cstr is not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - }; + let mnemonic_str = unwrap_result_or_return!(CStr::from_ptr(mnemonic_cstr).to_str()); let passphrase_str: &str = if passphrase_cstr.is_null() { "" } else { - match CStr::from_ptr(passphrase_cstr).to_str() { - Ok(s) => s, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("passphrase_cstr is not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - } + unwrap_result_or_return!(CStr::from_ptr(passphrase_cstr).to_str()) }; derive_at_slot_inner( @@ -141,13 +49,9 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_key_at_slot( identity_index, key_index, out_row, - out_error, ) } -/// Inner implementation shared by the mnemonic-string and resolver- -/// based entry points. Assumes the caller has already pre-zeroed -/// `*out_row` (so a failed call returns a known empty struct). unsafe fn derive_at_slot_inner( mnemonic_str: &str, passphrase_str: &str, @@ -155,74 +59,21 @@ unsafe fn derive_at_slot_inner( identity_index: u32, key_index: u32, out_row: *mut IdentityKeyPreviewFFI, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - // ---- Mnemonic + seed ----------------------------------------------------- - let mnemonic = match parse_mnemonic_any_language(mnemonic_str) { - Ok(m) => m, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("invalid mnemonic: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; - // 64-byte seed wrapped in `Zeroizing` so it gets scrubbed when - // this function returns (success or failure). +) -> PlatformWalletFfiResult { + let mnemonic = unwrap_result_or_return!(parse_mnemonic_any_language(mnemonic_str)); let seed: Zeroizing<[u8; 64]> = Zeroizing::new(mnemonic.to_seed(passphrase_str)); - // ---- Master xpriv -------------------------------------------------------- let kw_network = map_network(network); - let master = match ExtendedPrivKey::new_master(kw_network, seed.as_ref()) { - Ok(m) => m, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("ExtendedPrivKey::new_master failed: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorWalletOperation; - } - }; + let master = unwrap_result_or_return!(ExtendedPrivKey::new_master(kw_network, seed.as_ref())); - // ---- Library-side derivation -------------------------------------------- - // The library does the actual path build + secp256k1 derive - // pass; we only hand it the master and the slot indices. - let derived = match derive_ecdsa_identity_auth_keypair_from_master( + let derived = unwrap_result_or_return!(derive_ecdsa_identity_auth_keypair_from_master( &master, kw_network, identity_index, key_index, - ) { - Ok(d) => d, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("derivation failed: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorWalletOperation; - } - }; + )); - // ---- Marshal into IdentityKeyPreviewFFI --------------------------------- - let path_cstring = match CString::new(derived.derivation_path.to_string()) { - Ok(s) => s, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("derivation path contained NUL byte: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - }; + let path_cstring = unwrap_result_or_return!(CString::new(derived.derivation_path.to_string())); let pub_bytes_vec = derived.public_key.to_vec(); let mut pub_box: Box<[u8]> = pub_bytes_vec.into_boxed_slice(); @@ -230,30 +81,18 @@ unsafe fn derive_at_slot_inner( let pub_len = pub_box.len(); std::mem::forget(pub_box); - // WIF for the keychain-explorer / debugging UI. Build a temporary - // secp256k1 SecretKey from the derived bytes — copying through - // `DashPrivateKey` matches the loop variant's WIF shape. let secret_key = match dashcore::secp256k1::SecretKey::from_slice(derived.private_key.as_ref()) { Ok(k) => k, Err(e) => { - // Roll back the path / pubkey allocations before bailing. - // Reclaim the boxed slice through the same allocator - // shape it was created with (`into_boxed_slice` → - // `Box<[u8]>`). Reconstructing a `Vec` here is UB - // whenever `len < capacity` for the original source - // vector. drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut( pub_ptr, pub_len, ))); drop(path_cstring); - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("SecretKey::from_slice failed: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorWalletOperation; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, + format!("SecretKey::from_slice failed: {e}"), + ); } }; let dash_private = DashPrivateKey { @@ -264,37 +103,17 @@ unsafe fn derive_at_slot_inner( let wif_cstring = match CString::new(dash_private.to_wif()) { Ok(s) => s, Err(e) => { - // Reclaim the boxed slice through the same allocator - // shape it was created with (`into_boxed_slice` → - // `Box<[u8]>`). Reconstructing a `Vec` here is UB - // whenever `len < capacity` for the original source - // vector. drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut( pub_ptr, pub_len, ))); drop(path_cstring); - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("WIF string contained NUL byte: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorUtf8Conversion, + format!("WIF string contained NUL byte: {e}"), + ); } }; - // Copy the secret bytes out of `Zeroizing` into the FFI row. - // The original `derived` value drops at scope exit and zeroizes - // the source buffer; the FFI row's `private_key_bytes` is the - // caller's responsibility to scrub via the paired `_free`. - // - // Stage the inline 32-byte copy through `Zeroizing<[u8; 32]>` - // so the **stack** copy gets scrubbed when this function - // returns. `[u8; 32]` is `Copy`, so writing into the FFI row - // duplicates the bytes — the row's bytes are the caller's - // problem, but the local buffer would otherwise linger on the - // stack with the live secret until the frame is overwritten - // by the next call. let mut private_key_bytes_buf: Zeroizing<[u8; 32]> = Zeroizing::new([0u8; 32]); private_key_bytes_buf.copy_from_slice(derived.private_key.as_ref()); @@ -307,35 +126,10 @@ unsafe fn derive_at_slot_inner( private_key_bytes: *private_key_bytes_buf, }; - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } -/// Resolver-based variant of -/// [`dash_sdk_derive_identity_key_at_slot`]. -/// -/// Replaces the raw-mnemonic entry point with the same callback -/// pattern that -/// [`crate::dash_sdk_derive_and_persist_identity_keys`] uses: the -/// caller hands in a [`MnemonicResolverHandle`] keyed by -/// `wallet_id_bytes`, and Rust pulls the BIP-39 mnemonic across -/// the FFI from Swift's iOS Keychain on demand. The mnemonic -/// never lives in a Swift `String` outside the resolver -/// trampoline's stack frame — closes the -/// `swift-sdk/CLAUDE.md` "no mnemonic round-tripping" rule that -/// the raw-cstring entry point still violates. -/// -/// Use this in preference to -/// [`dash_sdk_derive_identity_key_at_slot`] from Swift. The -/// raw-cstring variant is retained for tests + any non-iOS caller -/// that already has the mnemonic in hand. -/// -/// # Safety -/// - `wallet_id_bytes` must be valid for 32 readable bytes. -/// - `mnemonic_resolver_handle` must be a non-null handle -/// produced by `dash_sdk_mnemonic_resolver_create`, and remain -/// valid for the duration of the call. -/// - `out_row` must be a valid, writable pointer. -/// - `out_error` may be NULL. +/// Resolver-based variant of [`dash_sdk_derive_identity_key_at_slot`]. #[no_mangle] pub unsafe extern "C" fn dash_sdk_derive_identity_key_at_slot_with_resolver( network: DashSDKNetwork, @@ -344,31 +138,13 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_key_at_slot_with_resolver( identity_index: u32, key_index: u32, out_row: *mut IdentityKeyPreviewFFI, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_row.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_row is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_row); *out_row = IdentityKeyPreviewFFI::empty(); - if wallet_id_bytes.is_null() || mnemonic_resolver_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "wallet_id_bytes and mnemonic_resolver_handle are required", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } + check_ptr!(wallet_id_bytes); + check_ptr!(mnemonic_resolver_handle); - // Stack-resident, zeroized-on-drop buffer the resolver writes - // into. Same shape as `dash_sdk_derive_and_persist_identity_keys`. let mut mnemonic_buf: Zeroizing<[u8; MNEMONIC_RESOLVER_BUFFER_CAPACITY]> = Zeroizing::new([0u8; MNEMONIC_RESOLVER_BUFFER_CAPACITY]); let mut mnemonic_len: usize = 0; @@ -385,52 +161,27 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_key_at_slot_with_resolver( match rc { x if x == mnemonic_resolver_result::SUCCESS => {} x if x == mnemonic_resolver_result::NOT_FOUND => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - "mnemonic resolver: no mnemonic stored for the supplied wallet_id", - ); - } - return PlatformWalletFFIResult::ErrorWalletOperation; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, + "mnemonic resolver: no mnemonic stored for the supplied wallet_id", + ); } x if x == mnemonic_resolver_result::BUFFER_TOO_SMALL => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - "mnemonic resolver: mnemonic exceeded the FFI buffer capacity", - ); - } - return PlatformWalletFFIResult::ErrorWalletOperation; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, + "mnemonic resolver: mnemonic exceeded the FFI buffer capacity", + ); } _ => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - "mnemonic resolver: failed (other / Keychain access error)", - ); - } - return PlatformWalletFFIResult::ErrorWalletOperation; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, + "mnemonic resolver: failed (other / Keychain access error)", + ); } } - let mnemonic_str = match std::str::from_utf8(&mnemonic_buf[..mnemonic_len]) { - Ok(s) => s, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("mnemonic resolver: not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - }; + let mnemonic_str = unwrap_result_or_return!(std::str::from_utf8(&mnemonic_buf[..mnemonic_len])); - // Passphrase isn't part of the resolver vtable today; the - // existing `dash_sdk_derive_and_persist_identity_keys` makes - // the same assumption (BIP-39 wallets don't carry a passphrase - // in this app). When that changes, extend the resolver to - // surface it the same way the mnemonic is surfaced. derive_at_slot_inner( mnemonic_str, "", @@ -438,19 +189,10 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_key_at_slot_with_resolver( identity_index, key_index, out_row, - out_error, ) } -/// Free a row populated by -/// [`dash_sdk_derive_identity_key_at_slot`]. Zeroes the inline -/// 32-byte secret + the WIF buffer in place before releasing the -/// allocations, matching the `_free` paired with the loop variant. -/// -/// # Safety -/// `out_row` must point to a row populated by -/// [`dash_sdk_derive_identity_key_at_slot`] and must not have been -/// freed already. +/// Free a row populated by [`dash_sdk_derive_identity_key_at_slot`]. #[no_mangle] pub unsafe extern "C" fn dash_sdk_derive_identity_key_at_slot_free( out_row: *mut IdentityKeyPreviewFFI, @@ -465,12 +207,6 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_key_at_slot_free( row.derivation_path = std::ptr::null_mut(); } if !row.public_key.is_null() && row.public_key_len > 0 { - // Mirror the allocator shape from - // `dash_sdk_derive_identity_key_at_slot` — the buffer was - // created via `Vec::into_boxed_slice` + `Box::into_raw`, - // so `Box::from_raw(slice_from_raw_parts_mut(...))` is the - // matching dispose. `Vec::from_raw_parts(ptr, len, len)` - // is UB whenever the source vec had `cap > len`. let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut( row.public_key, row.public_key_len, @@ -479,24 +215,12 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_key_at_slot_free( row.public_key_len = 0; } if !row.private_key_wif.is_null() { - // Reconstruct the owned `CString` first, THEN scrub through - // its owned buffer. Earlier revisions zeroed via - // `write_bytes(ptr, 0, strlen)` *before* `CString::from_raw` - // — that is undefined behaviour: `CString::from_raw` - // recomputes the length internally with `strlen`, so a - // pre-zeroed buffer makes it free a 1-byte allocation - // against the original `(len + 1)`-byte allocation - // (rust-lang/rust#68456). Reconstructing first preserves - // the original length in `CString::as_bytes().len()` so the - // allocator sees a matching size on drop. let cstring = CString::from_raw(row.private_key_wif); let bytes_len = cstring.as_bytes().len(); std::ptr::write_bytes(cstring.as_ptr() as *mut u8, 0, bytes_len); drop(cstring); row.private_key_wif = std::ptr::null_mut(); } - // Inline 32-byte secret — zero in place. The struct itself is - // owned by the caller, so we don't free anything here. for byte in row.private_key_bytes.iter_mut() { *byte = 0; } diff --git a/packages/rs-platform-wallet-ffi/src/dpns.rs b/packages/rs-platform-wallet-ffi/src/dpns.rs index be27e83bf8b..33ffd464d9f 100644 --- a/packages/rs-platform-wallet-ffi/src/dpns.rs +++ b/packages/rs-platform-wallet-ffi/src/dpns.rs @@ -1,29 +1,5 @@ //! FFI bindings for DPNS name operations on the platform-wallet //! [`IdentityWallet`](platform_wallet::IdentityWallet). -//! -//! Entry points: -//! -//! 1. [`platform_wallet_register_dpns_name_with_signer`] — register a -//! DPNS name using an externally-supplied `SignerHandle` (the -//! iOS-side `KeychainSigner` in the SwiftExampleApp case). The -//! wallet's own seed never participates in signing, which -//! unblocks watch-only wallets where the seed lives in iOS -//! Keychain rather than the in-process `WalletManager`. -//! -//! 2. [`platform_wallet_resolve_dpns_name`] — resolve a DPNS name -//! to an identity id. Async; no persistence side-effects. -//! -//! 3. [`platform_wallet_search_dpns_names`] — prefix search over -//! Platform's DPNS documents. Async; returns a heap-allocated -//! array of `DpnsSearchResultFFI` releasable via -//! [`dpns_search_results_free`]. -//! -//! Replaces the direct `dash_sdk_dpns_*` paths the iOS app was -//! using for DPNS writes — those paths are still functional but -//! bypass the identity manager + changeset layer, leaving -//! `ManagedIdentity.dpns_names` and `PersistentIdentity.dpnsName` -//! out of sync with on-chain state until the next sync. Routing -//! through this module fixes the drift. use std::ffi::{CStr, CString}; use std::os::raw::c_char; @@ -31,10 +7,12 @@ use std::ptr; use rs_sdk_ffi::{SignerHandle, VTableSigner}; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::*; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; /// Flat FFI result from [`platform_wallet_search_dpns_names`]. /// @@ -43,9 +21,7 @@ use crate::types::*; /// array. `identity_id` is a 32-byte inline buffer. #[repr(C)] pub struct DpnsSearchResultFFI { - /// Identity that owns the DPNS name. pub identity_id: [u8; 32], - /// Fully-qualified label (e.g. "alice.dash"). pub label: *mut c_char, } @@ -65,21 +41,9 @@ pub struct DpnsSearchResultFFI { /// On success the just-registered name is appended to /// `ManagedIdentity.dpns_names` on the Rust side and an identity /// changeset is queued so the Swift persister observes the update via -/// `on_persist_identities_fn`. -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle -/// registry. -/// - `identity_id` must point at a valid 32-byte buffer for the -/// duration of the call. -/// - `name` must be a NUL-terminated UTF-8 C-string for the duration -/// of the call. -/// - `signer_handle` must be a valid, non-destroyed handle produced by -/// `dash_sdk_signer_create_with_ctx` (typically `KeychainSigner.handle`). -/// The caller retains ownership; this function does NOT destroy it. -/// - `out_full_domain_name` and `out_error` must be writable for the -/// duration of the call. `out_error` may be left null only when the -/// caller is willing to lose the diagnostic message. +/// `on_persist_identities_fn`. `signer_handle` must be a valid, +/// non-destroyed handle produced by `dash_sdk_signer_create_with_ctx` +/// (typically `KeychainSigner.handle`); the caller retains ownership. #[no_mangle] pub unsafe extern "C" fn platform_wallet_register_dpns_name_with_signer( wallet_handle: Handle, @@ -87,199 +51,67 @@ pub unsafe extern "C" fn platform_wallet_register_dpns_name_with_signer( name: *const c_char, signer_handle: *mut SignerHandle, out_full_domain_name: *mut *mut c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if name.is_null() || out_full_domain_name.is_null() || signer_handle.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "name, out_full_domain_name, or signer_handle is null", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(name); + check_ptr!(out_full_domain_name); + check_ptr!(signer_handle); - let id = match unsafe { read_identifier(identity_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity identifier: {e}"), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let name_str = match unsafe { CStr::from_ptr(name) }.to_str() { - Ok(s) => s.to_string(), - Err(_) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "name is not valid UTF-8", - ); - } - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - }; + let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); + let name_str = unwrap_result_or_return!(unsafe { CStr::from_ptr(name) }.to_str()).to_string(); - // Round-trip the signer pointer through `usize` so the spawned - // future has a `Send + 'static` capture (raw pointers are `!Send`, - // but `usize` is). The underlying `VTableSigner`'s `Inner::Callback - // { ctx, vtable }` is `Send + Sync` (see the unsafe impls in - // `rs-sdk-ffi/src/signer.rs`). let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - // SAFETY: caller guarantees `signer_handle` is valid and - // outlives this call; `signer_addr` is the same pointer - // reinterpreted as `usize` for the `Send` capture below. - let result = block_on_worker(async move { - let signer: &VTableSigner = unsafe { &*(signer_addr as *const VTableSigner) }; - identity_wallet - .register_name_with_external_signer(&id, &name_str, signer) - .await - }); - match result { - Ok(full_name) => match CString::new(full_name) { - Ok(cstr) => { - unsafe { *out_full_domain_name = cstr.into_raw() }; - PlatformWalletFFIResult::Success - } - Err(_) => { - // Defensive — DPNS labels never carry an - // interior NUL today, but guard against a - // future encoding change. - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorSerialization, - "full domain name contained NUL", - ); - } - } - PlatformWalletFFIResult::ErrorSerialization - } - }, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("register_dpns_name_with_signer failed: {e}"), - ); - } - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = unsafe { &*(signer_addr as *const VTableSigner) }; + identity_wallet + .register_name_with_external_signer(&id, &name_str, signer) + .await }) + }); + let result = unwrap_option_or_return!(option); + let full_name = unwrap_result_or_return!(result); + let cstr = unwrap_result_or_return!(CString::new(full_name)); + unsafe { *out_full_domain_name = cstr.into_raw() }; + PlatformWalletFfiResult::ok() } /// Resolve a DPNS name (`"alice"` or `"alice.dash"`) to an identity id. /// /// `out_found` reports whether the lookup returned a hit. When `true`, -/// `out_identity_id` is populated. +/// `out_identity_id` is populated; when `false`, `out_identity_id` is +/// zeroed. #[no_mangle] pub unsafe extern "C" fn platform_wallet_resolve_dpns_name( wallet_handle: Handle, name: *const c_char, out_identity_id: *mut u8, out_found: *mut bool, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if name.is_null() || out_identity_id.is_null() || out_found.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided to resolve_dpns_name", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + check_ptr!(name); + check_ptr!(out_identity_id); + check_ptr!(out_found); + + let name_str = unwrap_result_or_return!(unsafe { CStr::from_ptr(name) }.to_str()).to_string(); + + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity = wallet.identity().clone(); + block_on_worker(async move { identity.resolve_name(&name_str).await }) + }); + let result = unwrap_option_or_return!(option); + let resolved = unwrap_result_or_return!(result); + match resolved { + Some(id) => unsafe { + write_identifier(out_identity_id, &id); + *out_found = true; + }, + None => unsafe { + std::ptr::write_bytes(out_identity_id, 0u8, 32); + *out_found = false; + }, } - - let name_str = match unsafe { CStr::from_ptr(name) }.to_str() { - Ok(s) => s.to_string(), - Err(_) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "name is not valid UTF-8", - ); - } - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - }; - - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity = wallet.identity().clone(); - let result = block_on_worker(async move { identity.resolve_name(&name_str).await }); - match result { - Ok(Some(id)) => { - unsafe { - write_identifier(out_identity_id, &id); - *out_found = true; - } - PlatformWalletFFIResult::Success - } - Ok(None) => { - unsafe { - // Zero out the 32-byte buffer for a clean - // "not found" return value. - std::ptr::write_bytes(out_identity_id, 0u8, 32); - *out_found = false; - } - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("resolve_dpns_name failed: {e}"), - ); - } - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + PlatformWalletFfiResult::ok() } /// Prefix search over DPNS documents on Platform. @@ -298,109 +130,53 @@ pub unsafe extern "C" fn platform_wallet_search_dpns_names( limit: u32, out_results: *mut *mut DpnsSearchResultFFI, out_count: *mut usize, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if prefix.is_null() || out_results.is_null() || out_count.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided to search_dpns_names", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(prefix); + check_ptr!(out_results); + check_ptr!(out_count); - let prefix_str = match unsafe { CStr::from_ptr(prefix) }.to_str() { - Ok(s) => s.to_string(), - Err(_) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "prefix is not valid UTF-8", - ); - } - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - }; - // Rust-side takes `Option`; `0` means "default cap". + let prefix_str = + unwrap_result_or_return!(unsafe { CStr::from_ptr(prefix) }.to_str()).to_string(); let sdk_limit = if limit == 0 { None } else { Some(limit) }; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity = wallet.identity().clone(); - let result = - block_on_worker(async move { identity.search_names(&prefix_str, sdk_limit).await }); - match result { - Ok(list) => { - // Build the FFI array — each entry owns its label - // C-string via `CString::into_raw`. On the free - // side, `dpns_search_results_free` walks the array - // to reclaim every label before releasing the - // array itself. - use dash_sdk::platform::dpns_usernames::DpnsUsername; - if list.is_empty() { - unsafe { - *out_results = ptr::null_mut(); - *out_count = 0; - } - return PlatformWalletFFIResult::Success; - } - let mut buf: Vec = Vec::with_capacity(list.len()); - for u in list { - // DpnsUsername carries label + normalized_label - // + full_name + owner_id; we surface the full - // user-visible "alice.dash" plus the owning - // identity id. - let DpnsUsername { - full_name, - owner_id, - .. - } = u; - let c = CString::new(full_name) - .map(|c| c.into_raw()) - .unwrap_or(ptr::null_mut()); - buf.push(DpnsSearchResultFFI { - identity_id: owner_id.to_buffer(), - label: c, - }); - } - let count = buf.len(); - let boxed = buf.into_boxed_slice(); - let array_ptr = Box::into_raw(boxed) as *mut DpnsSearchResultFFI; - unsafe { - *out_results = array_ptr; - *out_count = count; - } - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("search_dpns_names failed: {e}"), - ); - } - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity = wallet.identity().clone(); + block_on_worker(async move { identity.search_names(&prefix_str, sdk_limit).await }) + }); + let result = unwrap_option_or_return!(option); + let list = unwrap_result_or_return!(result); + + use dash_sdk::platform::dpns_usernames::DpnsUsername; + if list.is_empty() { + unsafe { + *out_results = ptr::null_mut(); + *out_count = 0; + } + return PlatformWalletFfiResult::ok(); + } + let mut buf: Vec = Vec::with_capacity(list.len()); + for u in list { + let DpnsUsername { + full_name, + owner_id, + .. + } = u; + let c = CString::new(full_name) + .map(|c| c.into_raw()) + .unwrap_or(ptr::null_mut()); + buf.push(DpnsSearchResultFFI { + identity_id: owner_id.to_buffer(), + label: c, + }); + } + let count = buf.len(); + let boxed = buf.into_boxed_slice(); + let array_ptr = Box::into_raw(boxed) as *mut DpnsSearchResultFFI; + unsafe { + *out_results = array_ptr; + *out_count = count; + } + PlatformWalletFfiResult::ok() } /// Release an array previously returned by @@ -422,10 +198,6 @@ pub unsafe extern "C" fn dpns_search_results_free(results: *mut DpnsSearchResult let _ = unsafe { Box::from_raw(slice as *mut [DpnsSearchResultFFI]) }; } -// --------------------------------------------------------------------------- -// DPNS cache sync + read (per-identity) -// --------------------------------------------------------------------------- - /// Fetch DPNS usernames for `identity_id` from Platform and merge /// them into `ManagedIdentity.dpns_names`. Returns the number of /// newly-added labels via `out_added` (unchanged when the cache @@ -442,58 +214,19 @@ pub unsafe extern "C" fn platform_wallet_sync_dpns_names( wallet_handle: Handle, identity_id: *const u8, out_added: *mut u32, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - let id = match unsafe { read_identifier(identity_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity identifier: {e}"), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity = wallet.identity().clone(); - let result = block_on_worker(async move { identity.sync_dpns_names(&id).await }); - match result { - Ok(added) => { - if !out_added.is_null() { - unsafe { *out_added = added }; - } - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("sync_dpns_names failed: {e}"), - ); - } - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); + + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity = wallet.identity().clone(); + block_on_worker(async move { identity.sync_dpns_names(&id).await }) + }); + let result = unwrap_option_or_return!(option); + let added = unwrap_result_or_return!(result); + if !out_added.is_null() { + unsafe { *out_added = added }; + } + PlatformWalletFfiResult::ok() } /// Heap-allocated array of DPNS labels returned by @@ -520,66 +253,40 @@ impl DpnsNameArray { /// Returns the labels from /// [`ManagedIdentity.dpns_names`](platform_wallet::ManagedIdentity). /// Empty when nothing has been synced yet — follow with -/// [`platform_wallet_sync_dpns_names`] to populate. -/// -/// Release the returned array via [`dpns_name_array_free`]. +/// [`platform_wallet_sync_dpns_names`] to populate. Release the +/// returned array via [`dpns_name_array_free`]. #[no_mangle] pub unsafe extern "C" fn managed_identity_get_dpns_names( identity_handle: Handle, out_array: *mut DpnsNameArray, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_array.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_array is null", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + check_ptr!(out_array); + + let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { + identity + .dpns_names + .iter() + .map(|i| i.label.clone()) + .collect::>() + }); + let labels = unwrap_option_or_return!(option); + if labels.is_empty() { + unsafe { *out_array = DpnsNameArray::empty() }; + return PlatformWalletFfiResult::ok(); } - - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - if identity.dpns_names.is_empty() { - unsafe { *out_array = DpnsNameArray::empty() }; - return PlatformWalletFFIResult::Success; - } - // Build a vector of owned C-string pointers; the array - // itself is heap-allocated and released with every - // label by `dpns_name_array_free`. - let mut labels: Vec<*mut c_char> = Vec::with_capacity(identity.dpns_names.len()); - for info in &identity.dpns_names { - let c = match CString::new(info.label.clone()) { - Ok(c) => c.into_raw(), - // NUL in label is unreachable in practice; - // keep the entry but surface as a null pointer - // so the caller's iteration doesn't crash. - Err(_) => std::ptr::null_mut(), - }; - labels.push(c); - } - let count = labels.len(); - let boxed = labels.into_boxed_slice(); - let ptr = Box::into_raw(boxed) as *mut *mut c_char; - unsafe { - *out_array = DpnsNameArray { labels: ptr, count }; - } - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid managed identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let mut raw_labels: Vec<*mut c_char> = Vec::with_capacity(labels.len()); + for label in labels { + let c = match CString::new(label) { + Ok(c) => c.into_raw(), + Err(_) => std::ptr::null_mut(), + }; + raw_labels.push(c); + } + let count = raw_labels.len(); + let boxed = raw_labels.into_boxed_slice(); + let ptr = Box::into_raw(boxed) as *mut *mut c_char; + unsafe { *out_array = DpnsNameArray { labels: ptr, count } }; + PlatformWalletFfiResult::ok() } /// Release an array previously returned by @@ -613,10 +320,6 @@ pub unsafe extern "C" fn dpns_name_array_free(array: *mut DpnsNameArray) { array.count = 0; } -// --------------------------------------------------------------------------- -// Contest vote state (ephemeral — not cached) -// --------------------------------------------------------------------------- - /// One contender row in [`ContestVoteStateFFI`]. Plain scalar struct /// (no owned allocations) — reclaimed wholesale when the parent's /// `contenders_ptr` array is freed. @@ -628,9 +331,6 @@ pub struct ContestContenderFFI { } /// Winner-kind discriminant for [`ContestVoteStateFFI`]. -/// -/// `winner_identity_id` is only populated when `winner_kind` is -/// `WonByIdentity` (1); ignore the field for `None` / `Locked`. #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ContestWinnerKindFFI { @@ -645,30 +345,21 @@ pub enum ContestWinnerKindFFI { /// Caller owns `label` + `contenders_ptr`; both are released by /// [`contest_vote_state_ffi_free`]. Safe to call free on an /// all-null snapshot (the default / "not found" state). +/// `winner_identity_id` is only populated when `winner_kind` is +/// `WonByIdentity` (1); ignore the field for `None` / `Locked`. #[repr(C)] pub struct ContestVoteStateFFI { - /// Heap-owned NUL-terminated UTF-8 label. `null` only on an - /// empty/default-initialized struct. pub label: *mut c_char, - /// Voting end time in milliseconds since epoch. pub end_time_ms: u64, - /// Heap-owned contender array. `null` with `contenders_count = 0` - /// when the contest has no listed contenders yet. pub contenders_ptr: *mut ContestContenderFFI, pub contenders_count: usize, pub abstain_votes: u32, pub lock_votes: u32, - /// Winner discriminant. Maps to [`ContestWinnerKindFFI`]. pub winner_kind: u8, - /// Populated only when `winner_kind == WonByIdentity`. pub winner_identity_id: [u8; 32], } impl ContestVoteStateFFI { - /// All-null/zeroed snapshot used as the out-param initial - /// value. Writing an empty before the FFI call means the "not - /// found" path leaves a well-defined struct that - /// [`contest_vote_state_ffi_free`] can safely no-op on. pub fn empty() -> Self { Self { label: std::ptr::null_mut(), @@ -683,13 +374,12 @@ impl ContestVoteStateFFI { } } -/// Fetch the current vote state for a DPNS contest `identity_id` -/// is contending for. `out_found` signals whether the lookup -/// returned a hit; `out_state` is populated only when `out_found` -/// is `true`. Release `out_state` with -/// [`contest_vote_state_ffi_free`] whether or not `out_found` was -/// set — free is a no-op on the empty / zeroed struct that the -/// "not found" path leaves behind. +/// Fetch the current vote state for a DPNS contest `identity_id` is +/// contending for. `out_found` signals whether the lookup returned a +/// hit; `out_state` is populated only when `out_found` is `true`. +/// Release `out_state` with [`contest_vote_state_ffi_free`] whether +/// or not `out_found` was set — free is a no-op on the empty / +/// zeroed struct that the "not found" path leaves behind. #[no_mangle] pub unsafe extern "C" fn platform_wallet_fetch_contest_vote_state( wallet_handle: Handle, @@ -697,140 +387,83 @@ pub unsafe extern "C" fn platform_wallet_fetch_contest_vote_state( label: *const c_char, out_state: *mut ContestVoteStateFFI, out_found: *mut bool, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if label.is_null() || out_state.is_null() || out_found.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided to fetch_contest_vote_state", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + check_ptr!(label); + check_ptr!(out_state); + check_ptr!(out_found); + + let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); + let label_str = unwrap_result_or_return!(unsafe { CStr::from_ptr(label) }.to_str()).to_string(); + + // Pre-clear the out struct so a downstream early-return leaves + // the caller looking at well-defined empty state, never garbage. + unsafe { + *out_state = ContestVoteStateFFI::empty(); + *out_found = false; } - let id = match unsafe { read_identifier(identity_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity identifier: {e}"), - ); + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity = wallet.identity().clone(); + block_on_worker(async move { identity.contest_vote_state(&id, &label_str).await }) + }); + let result = unwrap_option_or_return!(option); + let state_opt = unwrap_result_or_return!(result); + match state_opt { + Some(state) => { + use platform_wallet::wallet::identity::network::ContestWinner; + + let label_c = CString::new(state.label) + .map(|c| c.into_raw()) + .unwrap_or(std::ptr::null_mut()); + + let (contenders_ptr, contenders_count) = if state.contenders.is_empty() { + (std::ptr::null_mut(), 0usize) + } else { + let buf: Vec = state + .contenders + .into_iter() + .map(|c| ContestContenderFFI { + identity_id: c.identity_id.to_buffer(), + vote_tally: c.vote_tally, + }) + .collect(); + let count = buf.len(); + let boxed = buf.into_boxed_slice(); + let ptr = Box::into_raw(boxed) as *mut ContestContenderFFI; + (ptr, count) + }; + + let (winner_kind, winner_identity_id) = match state.winner { + ContestWinner::None => (ContestWinnerKindFFI::None as u8, [0u8; 32]), + ContestWinner::WonByIdentity(id) => { + (ContestWinnerKindFFI::WonByIdentity as u8, id.to_buffer()) } + ContestWinner::Locked => (ContestWinnerKindFFI::Locked as u8, [0u8; 32]), + }; + + unsafe { + *out_state = ContestVoteStateFFI { + label: label_c, + end_time_ms: state.end_time_ms, + contenders_ptr, + contenders_count, + abstain_votes: state.abstain_votes, + lock_votes: state.lock_votes, + winner_kind, + winner_identity_id, + }; + *out_found = true; } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; + PlatformWalletFfiResult::ok() } - }; - let label_str = match unsafe { CStr::from_ptr(label) }.to_str() { - Ok(s) => s.to_string(), - Err(_) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "label is not valid UTF-8", - ); - } + None => { + unsafe { + *out_state = ContestVoteStateFFI::empty(); + *out_found = false; } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + PlatformWalletFfiResult::ok() } - }; - - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity = wallet.identity().clone(); - let result = - block_on_worker(async move { identity.contest_vote_state(&id, &label_str).await }); - match result { - Ok(Some(state)) => { - use platform_wallet::wallet::identity::network::ContestWinner; - - // Heap-alloc the label + contenders; ownership - // moves to the caller, released by - // `contest_vote_state_ffi_free`. - let label_c = CString::new(state.label) - .map(|c| c.into_raw()) - .unwrap_or(std::ptr::null_mut()); - - let (contenders_ptr, contenders_count) = if state.contenders.is_empty() { - (std::ptr::null_mut(), 0usize) - } else { - let buf: Vec = state - .contenders - .into_iter() - .map(|c| ContestContenderFFI { - identity_id: c.identity_id.to_buffer(), - vote_tally: c.vote_tally, - }) - .collect(); - let count = buf.len(); - let boxed = buf.into_boxed_slice(); - let ptr = Box::into_raw(boxed) as *mut ContestContenderFFI; - (ptr, count) - }; - - let (winner_kind, winner_identity_id) = match state.winner { - ContestWinner::None => (ContestWinnerKindFFI::None as u8, [0u8; 32]), - ContestWinner::WonByIdentity(id) => { - (ContestWinnerKindFFI::WonByIdentity as u8, id.to_buffer()) - } - ContestWinner::Locked => (ContestWinnerKindFFI::Locked as u8, [0u8; 32]), - }; - - unsafe { - *out_state = ContestVoteStateFFI { - label: label_c, - end_time_ms: state.end_time_ms, - contenders_ptr, - contenders_count, - abstain_votes: state.abstain_votes, - lock_votes: state.lock_votes, - winner_kind, - winner_identity_id, - }; - *out_found = true; - } - PlatformWalletFFIResult::Success - } - Ok(None) => { - unsafe { - *out_state = ContestVoteStateFFI::empty(); - *out_found = false; - } - PlatformWalletFFIResult::Success - } - Err(e) => { - unsafe { - *out_state = ContestVoteStateFFI::empty(); - *out_found = false; - } - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("fetch_contest_vote_state failed: {e}"), - ); - } - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + } } /// Release heap allocations owned by a [`ContestVoteStateFFI`] — @@ -860,132 +493,63 @@ pub unsafe extern "C" fn contest_vote_state_ffi_free(state: *mut ContestVoteStat state.contenders_count = 0; } -// --------------------------------------------------------------------------- -// Contested DPNS names -// --------------------------------------------------------------------------- - /// Fetch the non-resolved contested DPNS names `identity_id` is a /// contender for and replace /// [`ManagedIdentity.contested_dpns_names`](platform_wallet::ManagedIdentity) -/// wholesale with the canonical set. Writes a full snapshot via -/// the persister (not dedup-append) so resolved contests disappear -/// from the local cache on sync. Returns the new count via -/// `out_count`. +/// wholesale with the canonical set. Writes a full snapshot via the +/// persister (not dedup-append) so resolved contests disappear from +/// the local cache on sync. Returns the new count via `out_count`. #[no_mangle] pub unsafe extern "C" fn platform_wallet_sync_contested_dpns_names( wallet_handle: Handle, identity_id: *const u8, out_count: *mut u32, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - let id = match unsafe { read_identifier(identity_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity identifier: {e}"), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity = wallet.identity().clone(); - let result = - block_on_worker(async move { identity.sync_contested_dpns_names(&id).await }); - match result { - Ok(labels) => { - if !out_count.is_null() { - unsafe { *out_count = labels.len() as u32 }; - } - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("sync_contested_dpns_names failed: {e}"), - ); - } - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); + + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity = wallet.identity().clone(); + block_on_worker(async move { identity.sync_contested_dpns_names(&id).await }) + }); + let result = unwrap_option_or_return!(option); + let labels = unwrap_result_or_return!(result); + if !out_count.is_null() { + unsafe { *out_count = labels.len() as u32 }; + } + PlatformWalletFfiResult::ok() } /// Read the cached contested DPNS labels for a [`ManagedIdentity`] /// handle. Returns an empty [`DpnsNameArray`] when the cache hasn't /// been populated; follow with -/// [`platform_wallet_sync_contested_dpns_names`] to refresh. -/// Release via [`dpns_name_array_free`]. +/// [`platform_wallet_sync_contested_dpns_names`] to refresh. Release +/// via [`dpns_name_array_free`]. #[no_mangle] pub unsafe extern "C" fn managed_identity_get_contested_dpns_names( identity_handle: Handle, out_array: *mut DpnsNameArray, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_array.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_array is null", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + check_ptr!(out_array); + + let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { + identity.contested_dpns_names.clone() + }); + let labels = unwrap_option_or_return!(option); + if labels.is_empty() { + unsafe { *out_array = DpnsNameArray::empty() }; + return PlatformWalletFfiResult::ok(); } - - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - if identity.contested_dpns_names.is_empty() { - unsafe { *out_array = DpnsNameArray::empty() }; - return PlatformWalletFFIResult::Success; - } - let mut labels: Vec<*mut c_char> = - Vec::with_capacity(identity.contested_dpns_names.len()); - for label in &identity.contested_dpns_names { - let c = match CString::new(label.clone()) { - Ok(c) => c.into_raw(), - Err(_) => std::ptr::null_mut(), - }; - labels.push(c); - } - let count = labels.len(); - let boxed = labels.into_boxed_slice(); - let ptr = Box::into_raw(boxed) as *mut *mut c_char; - unsafe { - *out_array = DpnsNameArray { labels: ptr, count }; - } - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid managed identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let mut raw_labels: Vec<*mut c_char> = Vec::with_capacity(labels.len()); + for label in labels { + let c = match CString::new(label) { + Ok(c) => c.into_raw(), + Err(_) => std::ptr::null_mut(), + }; + raw_labels.push(c); + } + let count = raw_labels.len(); + let boxed = raw_labels.into_boxed_slice(); + let ptr = Box::into_raw(boxed) as *mut *mut c_char; + unsafe { *out_array = DpnsNameArray { labels: ptr, count } }; + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/error.rs b/packages/rs-platform-wallet-ffi/src/error.rs index 8ff194195f3..c1a3d0832a3 100644 --- a/packages/rs-platform-wallet-ffi/src/error.rs +++ b/packages/rs-platform-wallet-ffi/src/error.rs @@ -1,10 +1,68 @@ +use platform_wallet::PlatformWalletError; use std::ffi::CString; use std::os::raw::c_char; -/// FFI Result type +#[macro_export] +macro_rules! deref_ptr { + ($ptr:expr) => {{ + if $ptr.is_null() { + return $crate::error::PlatformWalletFfiResult::err( + $crate::error::PlatformWalletFfiResultCode::ErrorNullPointer, + format!("{} ptr is null", stringify!($ptr)), + ); + } + unsafe { &*$ptr } + }}; +} + +#[macro_export] +macro_rules! deref_ptr_mut { + ($ptr:expr) => {{ + if $ptr.is_null() { + return $crate::error::PlatformWalletFfiResult::err( + $crate::error::PlatformWalletFfiResultCode::ErrorNullPointer, + format!("{} ptr is null", stringify!($ptr)), + ); + } + unsafe { &mut *$ptr } + }}; +} + +#[macro_export] +macro_rules! check_ptr { + ($ptr:expr) => {{ + if $ptr.is_null() { + return $crate::error::PlatformWalletFfiResult::err( + $crate::error::PlatformWalletFfiResultCode::ErrorNullPointer, + format!("{} ptr is null", stringify!($ptr)), + ); + } + }}; +} + +#[macro_export] +macro_rules! unwrap_result_or_return { + ($expr:expr) => {{ + match $expr { + Ok(v) => v, + Err(e) => return e.into(), + } + }}; +} + +#[macro_export] +macro_rules! unwrap_option_or_return { + ($expr:expr) => {{ + let Some(v) = $expr else { + return $expr.into(); + }; + v + }}; +} + #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PlatformWalletFFIResult { +pub enum PlatformWalletFfiResultCode { Success = 0, ErrorInvalidHandle = 1, ErrorInvalidParameter = 2, @@ -18,62 +76,264 @@ pub enum PlatformWalletFFIResult { ErrorInvalidIdentifier = 10, ErrorMemoryAllocation = 11, ErrorUtf8Conversion = 12, + + NotFound = 98, // Used exclusively for all the Option that are retuned as errors ErrorUnknown = 99, } -/// Error information structure +/// Must be freed with ['platform_wallet_ffi_result_free'] #[repr(C)] -pub struct PlatformWalletFFIError { - pub code: PlatformWalletFFIResult, +#[derive(Debug)] +pub struct PlatformWalletFfiResult { + pub code: PlatformWalletFfiResultCode, pub message: *mut c_char, } -impl PlatformWalletFFIError { - pub fn new(code: PlatformWalletFFIResult, message: impl Into) -> Self { - let msg = message.into(); - let c_msg = CString::new(msg).unwrap_or_else(|_| CString::new("Invalid UTF-8").unwrap()); - Self { - code, - message: c_msg.into_raw(), +impl Drop for PlatformWalletFfiResult { + fn drop(&mut self) { + if !self.message.is_null() { + unsafe { + let _ = CString::from_raw(self.message); + } + self.message = std::ptr::null_mut(); } } +} - pub fn success() -> Self { +impl PlatformWalletFfiResult { + pub const fn ok() -> Self { Self { - code: PlatformWalletFFIResult::Success, + code: PlatformWalletFfiResultCode::Success, message: std::ptr::null_mut(), } } + + pub fn err(code: PlatformWalletFfiResultCode, message: impl Into) -> Self { + let msg = message.into(); + let c_msg = CString::new(msg).unwrap_or_else(|_| CString::new("").unwrap()); + Self { + code, + message: c_msg.into_raw(), + } + } } -/// Free error message. +/// Free the Rust-owned message held by an error result. +/// +/// Idempotent — the message pointer is nulled after free so a +/// second call is a no-op. Safe to pass a `Success` result +/// (message is already NULL). /// -/// Pointer-only signature: callers pass `&mut error` instead of the -/// struct by value. The previous by-value form straddled the -/// 16-byte AAPCS64 / Swift-ABI cliff for `@_silgen_name` calls and -/// was the kind of code we removed in the EXC_BAD_ACCESS sweep. +/// # Safety +/// `result` must point to a valid `PlatformWalletFfiResult` +/// produced by this crate. Mutates the struct through the pointer. #[no_mangle] -pub unsafe extern "C" fn platform_wallet_ffi_error_free(error: *mut PlatformWalletFFIError) { - if error.is_null() { +pub unsafe extern "C" fn platform_wallet_ffi_result_free(result: *mut PlatformWalletFfiResult) { + if result.is_null() { return; } - let error = unsafe { &mut *error }; - if !error.message.is_null() { - unsafe { - let _ = CString::from_raw(error.message); + let result = &mut *result; + + // Same logic we have in the Drop implementation, we can't rely on the Drop implementation + // bcs the struct is always in the stack, and we cannot take ownership of it + if !result.message.is_null() { + let _ = CString::from_raw(result.message); + result.message = std::ptr::null_mut(); + } +} + +impl From> for PlatformWalletFfiResult { + fn from(value: Option) -> Self { + match value { + Some(_) => Self::ok(), + None => Self::err( + PlatformWalletFfiResultCode::NotFound, + format!("requested {} not found", std::any::type_name::()), + ), } - error.message = std::ptr::null_mut(); } } -/// Convert Rust error to FFI error -pub trait ToFFIError { - fn to_ffi_error(&self) -> PlatformWalletFFIError; +impl From for PlatformWalletFfiResult { + fn from(error: PlatformWalletError) -> Self { + PlatformWalletFfiResult::err(PlatformWalletFfiResultCode::ErrorUnknown, error.to_string()) + } +} + +impl From for PlatformWalletFfiResult { + fn from(error: dashcore::consensus::encode::Error) -> Self { + PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorDeserialization, + error.to_string(), + ) + } +} + +impl From for PlatformWalletFfiResult { + fn from(e: std::ffi::NulError) -> Self { + Self::err( + PlatformWalletFfiResultCode::ErrorUtf8Conversion, + format!("string contained an interior NUL byte: {e}"), + ) + } +} + +impl From for PlatformWalletFfiResult { + fn from(e: std::str::Utf8Error) -> Self { + Self::err( + PlatformWalletFfiResultCode::ErrorUtf8Conversion, + format!("invalid UTF-8: {e}"), + ) + } +} + +impl From for PlatformWalletFfiResult { + fn from(e: std::string::FromUtf8Error) -> Self { + Self::err( + PlatformWalletFfiResultCode::ErrorUtf8Conversion, + format!("invalid UTF-8: {e}"), + ) + } +} + +impl From for PlatformWalletFfiResult { + fn from(e: bs58::decode::Error) -> Self { + Self::err( + PlatformWalletFfiResultCode::ErrorInvalidIdentifier, + format!("base58 decode failed: {e}"), + ) + } } -impl ToFFIError for E { - fn to_ffi_error(&self) -> PlatformWalletFFIError { - PlatformWalletFFIError::new(PlatformWalletFFIResult::ErrorUnknown, self.to_string()) +impl From for PlatformWalletFfiResult { + fn from(e: hex::FromHexError) -> Self { + Self::err( + PlatformWalletFfiResultCode::ErrorInvalidIdentifier, + format!("hex decode failed: {e}"), + ) + } +} + +impl From for PlatformWalletFfiResult { + fn from(e: serde_json::Error) -> Self { + let code = if e.is_data() || e.is_syntax() { + PlatformWalletFfiResultCode::ErrorDeserialization + } else { + PlatformWalletFfiResultCode::ErrorSerialization + }; + Self::err(code, format!("JSON error: {e}")) + } +} + +impl From for PlatformWalletFfiResult { + fn from(e: bincode::error::EncodeError) -> Self { + Self::err( + PlatformWalletFfiResultCode::ErrorSerialization, + format!("bincode encode failed: {e}"), + ) + } +} + +impl From for PlatformWalletFfiResult { + fn from(e: bincode::error::DecodeError) -> Self { + Self::err( + PlatformWalletFfiResultCode::ErrorDeserialization, + format!("bincode decode failed: {e}"), + ) + } +} + +impl From for PlatformWalletFfiResult { + fn from(e: dpp::ProtocolError) -> Self { + let msg = e.to_string(); + let code = if msg.contains("identifier") { + PlatformWalletFfiResultCode::ErrorInvalidIdentifier + } else if msg.contains("deserialization") || msg.contains("decode") { + PlatformWalletFfiResultCode::ErrorDeserialization + } else if msg.contains("serialization") || msg.contains("encode") { + PlatformWalletFfiResultCode::ErrorSerialization + } else { + PlatformWalletFfiResultCode::ErrorWalletOperation + }; + Self::err(code, format!("DPP protocol error: {msg}")) + } +} + +impl From<&str> for PlatformWalletFfiResult { + fn from(e: &str) -> Self { + Self::err(PlatformWalletFfiResultCode::ErrorInvalidParameter, e) + } +} + +impl From for PlatformWalletFfiResult { + fn from(e: key_wallet::bip32::Error) -> Self { + Self::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, + format!("bip32 derivation failed: {e}"), + ) + } +} + +impl From for PlatformWalletFfiResult { + fn from(e: key_wallet::Error) -> Self { + Self::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, + format!("key-wallet error: {e}"), + ) + } +} + +impl From for PlatformWalletFfiResult { + fn from(e: dashcore::address::Error) -> Self { + Self::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + format!("address parse failed: {e}"), + ) + } +} + +impl From for PlatformWalletFfiResult { + fn from(e: dashcore::key::Error) -> Self { + Self::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + format!("dashcore key error: {e}"), + ) + } +} + +impl From for PlatformWalletFfiResult { + fn from(e: dpp::platform_value::Error) -> Self { + Self::err( + PlatformWalletFfiResultCode::ErrorSerialization, + format!("platform_value error: {e}"), + ) + } +} + +impl From for PlatformWalletFfiResult { + fn from(e: platform_wallet::changeset::PersistenceError) -> Self { + Self::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, + format!("persistence error: {e}"), + ) + } +} + +impl From> for PlatformWalletFfiResult { + fn from(e: Box) -> Self { + Self::err( + PlatformWalletFfiResultCode::ErrorUnknown, + format!("unclassified error: {e}"), + ) + } +} + +impl From for PlatformWalletFfiResult { + fn from(e: anyhow::Error) -> Self { + Self::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + e.to_string(), + ) } } @@ -82,38 +342,38 @@ mod tests { use super::*; #[test] - fn test_error_creation() { - unsafe { - let mut error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Test error", - ); - assert_eq!(error.code, PlatformWalletFFIResult::ErrorInvalidHandle); - assert!(!error.message.is_null()); - - // Clean up - platform_wallet_ffi_error_free(&mut error); - assert!(error.message.is_null()); - } + fn ok_has_null_message() { + let r = PlatformWalletFfiResult::ok(); + assert_eq!(r.code, PlatformWalletFfiResultCode::Success); + assert!(r.message.is_null()); } #[test] - fn test_success_error() { - let error = PlatformWalletFFIError::success(); - assert_eq!(error.code, PlatformWalletFFIResult::Success); - assert!(error.message.is_null()); + fn err_carries_message() { + let mut r = + PlatformWalletFfiResult::err(PlatformWalletFfiResultCode::ErrorDeserialization, "boom"); + assert_ne!(r.code, PlatformWalletFfiResultCode::Success); + assert!(!r.message.is_null()); + unsafe { platform_wallet_ffi_result_free(&mut r) }; + assert!(r.message.is_null()); } #[test] - fn test_error_free() { + fn free_is_idempotent() { + let mut r = PlatformWalletFfiResult::err(PlatformWalletFfiResultCode::ErrorUnknown, "x"); unsafe { - let mut error = - PlatformWalletFFIError::new(PlatformWalletFFIResult::ErrorUnknown, "Test"); - platform_wallet_ffi_error_free(&mut error); - // Should not crash; message reset to null. - assert!(error.message.is_null()); - // Calling again is a no-op. - platform_wallet_ffi_error_free(&mut error); + platform_wallet_ffi_result_free(&mut r); + platform_wallet_ffi_result_free(&mut r); } + assert!(r.message.is_null()); + } + + #[test] + fn nul_in_message_is_replaced() { + let r = PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorUnknown, + "before\0after", + ); + assert!(!r.message.is_null()); } } diff --git a/packages/rs-platform-wallet-ffi/src/established_contact.rs b/packages/rs-platform-wallet-ffi/src/established_contact.rs index 239e867810b..8b46cfdae31 100644 --- a/packages/rs-platform-wallet-ffi/src/established_contact.rs +++ b/packages/rs-platform-wallet-ffi/src/established_contact.rs @@ -5,6 +5,7 @@ use crate::error::*; use crate::handle::*; use crate::types::*; +use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; use platform_wallet::EstablishedContact; // Storage for established contacts @@ -19,67 +20,21 @@ pub unsafe extern "C" fn managed_identity_get_established_contact( identity_handle: Handle, contact_id: *const u8, out_contact_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_contact_handle.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_contact_handle); - let contact_identifier = match unsafe { read_identifier(contact_id) } { - Ok(id) => id, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identifier: {}", e), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let contact_identifier = unwrap_result_or_return!(unsafe { read_identifier(contact_id) }); - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - match identity.established_contacts.get(&contact_identifier) { - Some(contact) => { - let handle = ESTABLISHED_CONTACT_STORAGE.insert(contact.clone()); - unsafe { *out_contact_handle = handle }; - PlatformWalletFFIResult::Success - } - None => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorContactNotFound, - "Established contact not found", - ); - } - } - PlatformWalletFFIResult::ErrorContactNotFound - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { + identity + .established_contacts + .get(&contact_identifier) + .cloned() + }); + let inner = unwrap_option_or_return!(option); + let contact = unwrap_option_or_return!(inner); + unsafe { *out_contact_handle = ESTABLISHED_CONTACT_STORAGE.insert(contact) }; + PlatformWalletFfiResult::ok() } /// Get the contact identity ID from an established contact into a @@ -88,36 +43,14 @@ pub unsafe extern "C" fn managed_identity_get_established_contact( pub unsafe extern "C" fn established_contact_get_contact_id( contact_handle: Handle, out_id: *mut u8, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_id.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_id); - ESTABLISHED_CONTACT_STORAGE - .with_item(contact_handle, |contact| { - unsafe { write_identifier(out_id, &contact.contact_identity_id) }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = ESTABLISHED_CONTACT_STORAGE + .with_item(contact_handle, |contact| contact.contact_identity_id); + let id = unwrap_option_or_return!(option); + unsafe { write_identifier(out_id, &id) }; + PlatformWalletFfiResult::ok() } /// Get a handle to the outgoing contact request from an established contact @@ -125,38 +58,16 @@ pub unsafe extern "C" fn established_contact_get_contact_id( pub unsafe extern "C" fn established_contact_get_outgoing_request( contact_handle: Handle, out_request_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_request_handle.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + check_ptr!(out_request_handle); + + let option = ESTABLISHED_CONTACT_STORAGE + .with_item(contact_handle, |contact| contact.outgoing_request.clone()); + let req = unwrap_option_or_return!(option); + unsafe { + *out_request_handle = crate::contact_request::CONTACT_REQUEST_STORAGE.insert(req); } - - ESTABLISHED_CONTACT_STORAGE - .with_item(contact_handle, |contact| { - let handle = crate::contact_request::CONTACT_REQUEST_STORAGE - .insert(contact.outgoing_request.clone()); - unsafe { *out_request_handle = handle }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + PlatformWalletFfiResult::ok() } /// Get a handle to the incoming contact request from an established contact @@ -164,38 +75,16 @@ pub unsafe extern "C" fn established_contact_get_outgoing_request( pub unsafe extern "C" fn established_contact_get_incoming_request( contact_handle: Handle, out_request_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_request_handle.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + check_ptr!(out_request_handle); + + let option = ESTABLISHED_CONTACT_STORAGE + .with_item(contact_handle, |contact| contact.incoming_request.clone()); + let req = unwrap_option_or_return!(option); + unsafe { + *out_request_handle = crate::contact_request::CONTACT_REQUEST_STORAGE.insert(req); } - - ESTABLISHED_CONTACT_STORAGE - .with_item(contact_handle, |contact| { - let handle = crate::contact_request::CONTACT_REQUEST_STORAGE - .insert(contact.incoming_request.clone()); - unsafe { *out_request_handle = handle }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + PlatformWalletFfiResult::ok() } /// Get the contact identity ID from an established contact (alias @@ -205,9 +94,8 @@ pub unsafe extern "C" fn established_contact_get_incoming_request( pub unsafe extern "C" fn established_contact_get_contact_identity_id( contact_handle: Handle, out_id: *mut u8, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - unsafe { established_contact_get_contact_id(contact_handle, out_id, out_error) } +) -> PlatformWalletFfiResult { + unsafe { established_contact_get_contact_id(contact_handle, out_id) } } /// Get the alias for an established contact @@ -215,49 +103,17 @@ pub unsafe extern "C" fn established_contact_get_contact_identity_id( pub unsafe extern "C" fn established_contact_get_alias( contact_handle: Handle, out_alias: *mut *mut std::os::raw::c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_alias.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_alias); + *out_alias = std::ptr::null_mut(); - ESTABLISHED_CONTACT_STORAGE - .with_item(contact_handle, |contact| { - if let Some(alias) = &contact.alias { - match std::ffi::CString::new(alias.clone()) { - Ok(c_str) => { - unsafe { *out_alias = c_str.into_raw() }; - PlatformWalletFFIResult::Success - } - Err(_) => { - unsafe { *out_alias = std::ptr::null_mut() }; - PlatformWalletFFIResult::ErrorUtf8Conversion - } - } - } else { - unsafe { *out_alias = std::ptr::null_mut() }; - PlatformWalletFFIResult::Success - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = + ESTABLISHED_CONTACT_STORAGE.with_item(contact_handle, |contact| contact.alias.clone()); + let option = unwrap_option_or_return!(option); + let alias = unwrap_option_or_return!(option); + let c_str = unwrap_result_or_return!(std::ffi::CString::new(alias)); + unsafe { *out_alias = c_str.into_raw() }; + PlatformWalletFfiResult::ok() } /// Set the alias for an established contact @@ -265,69 +121,34 @@ pub unsafe extern "C" fn established_contact_get_alias( pub unsafe extern "C" fn established_contact_set_alias( contact_handle: Handle, alias: *const std::os::raw::c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { +) -> PlatformWalletFfiResult { let alias_str = if alias.is_null() { None } else { unsafe { - match std::ffi::CStr::from_ptr(alias).to_str() { - Ok(s) => Some(s.to_string()), - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "Invalid UTF-8 in alias", - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - } + Some(unwrap_result_or_return!(std::ffi::CStr::from_ptr(alias).to_str()).to_string()) } }; - ESTABLISHED_CONTACT_STORAGE - .with_item_mut(contact_handle, |contact| { - if let Some(a) = alias_str { - contact.set_alias(a); - } - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = ESTABLISHED_CONTACT_STORAGE.with_item_mut(contact_handle, |contact| { + if let Some(a) = alias_str { + contact.set_alias(a); + } + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Clear the alias for an established contact #[no_mangle] pub unsafe extern "C" fn established_contact_clear_alias( contact_handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - ESTABLISHED_CONTACT_STORAGE - .with_item_mut(contact_handle, |contact| { - contact.clear_alias(); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let option = ESTABLISHED_CONTACT_STORAGE.with_item_mut(contact_handle, |contact| { + contact.clear_alias(); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Get the note for an established contact @@ -335,49 +156,16 @@ pub unsafe extern "C" fn established_contact_clear_alias( pub unsafe extern "C" fn established_contact_get_note( contact_handle: Handle, out_note: *mut *mut std::os::raw::c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_note.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_note); - ESTABLISHED_CONTACT_STORAGE - .with_item(contact_handle, |contact| { - if let Some(note) = &contact.note { - match std::ffi::CString::new(note.clone()) { - Ok(c_str) => { - unsafe { *out_note = c_str.into_raw() }; - PlatformWalletFFIResult::Success - } - Err(_) => { - unsafe { *out_note = std::ptr::null_mut() }; - PlatformWalletFFIResult::ErrorUtf8Conversion - } - } - } else { - unsafe { *out_note = std::ptr::null_mut() }; - PlatformWalletFFIResult::Success - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = + ESTABLISHED_CONTACT_STORAGE.with_item(contact_handle, |contact| contact.note.clone()); + let option = unwrap_option_or_return!(option); + let note = unwrap_option_or_return!(option); + let c_str = unwrap_result_or_return!(std::ffi::CString::new(note)); + unsafe { *out_note = c_str.into_raw() }; + PlatformWalletFfiResult::ok() } /// Set the note for an established contact @@ -385,69 +173,34 @@ pub unsafe extern "C" fn established_contact_get_note( pub unsafe extern "C" fn established_contact_set_note( contact_handle: Handle, note: *const std::os::raw::c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { +) -> PlatformWalletFfiResult { let note_str = if note.is_null() { None } else { unsafe { - match std::ffi::CStr::from_ptr(note).to_str() { - Ok(s) => Some(s.to_string()), - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "Invalid UTF-8 in note", - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - } + Some(unwrap_result_or_return!(std::ffi::CStr::from_ptr(note).to_str()).to_string()) } }; - ESTABLISHED_CONTACT_STORAGE - .with_item_mut(contact_handle, |contact| { - if let Some(n) = note_str { - contact.set_note(n); - } - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = ESTABLISHED_CONTACT_STORAGE.with_item_mut(contact_handle, |contact| { + if let Some(n) = note_str { + contact.set_note(n); + } + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Clear the note for an established contact #[no_mangle] pub unsafe extern "C" fn established_contact_clear_note( contact_handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - ESTABLISHED_CONTACT_STORAGE - .with_item_mut(contact_handle, |contact| { - contact.clear_note(); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let option = ESTABLISHED_CONTACT_STORAGE.with_item_mut(contact_handle, |contact| { + contact.clear_note(); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Check if an established contact is hidden @@ -455,96 +208,46 @@ pub unsafe extern "C" fn established_contact_clear_note( pub unsafe extern "C" fn established_contact_is_hidden( contact_handle: Handle, out_is_hidden: *mut bool, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_is_hidden.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_is_hidden); - ESTABLISHED_CONTACT_STORAGE - .with_item(contact_handle, |contact| { - unsafe { *out_is_hidden = contact.is_hidden }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = ESTABLISHED_CONTACT_STORAGE.with_item(contact_handle, |contact| contact.is_hidden); + *out_is_hidden = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Hide an established contact from the contact list #[no_mangle] pub unsafe extern "C" fn established_contact_hide( contact_handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - ESTABLISHED_CONTACT_STORAGE - .with_item_mut(contact_handle, |contact| { - contact.hide(); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let option = ESTABLISHED_CONTACT_STORAGE.with_item_mut(contact_handle, |contact| { + contact.hide(); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Unhide an established contact #[no_mangle] pub unsafe extern "C" fn established_contact_unhide( contact_handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - ESTABLISHED_CONTACT_STORAGE - .with_item_mut(contact_handle, |contact| { - contact.unhide(); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid contact handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let option = ESTABLISHED_CONTACT_STORAGE.with_item_mut(contact_handle, |contact| { + contact.unhide(); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Destroy an established contact handle and free resources #[no_mangle] pub unsafe extern "C" fn established_contact_destroy( contact_handle: Handle, -) -> PlatformWalletFFIResult { - if ESTABLISHED_CONTACT_STORAGE.remove(contact_handle).is_some() { - PlatformWalletFFIResult::Success - } else { - PlatformWalletFFIResult::ErrorInvalidHandle - } +) -> PlatformWalletFfiResult { + let option = ESTABLISHED_CONTACT_STORAGE.remove(contact_handle); + let _ = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } // Tests for this module are in tests/comprehensive_tests.rs diff --git a/packages/rs-platform-wallet-ffi/src/identity_derive_and_persist.rs b/packages/rs-platform-wallet-ffi/src/identity_derive_and_persist.rs index e146a85ae58..6c4539430fd 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_derive_and_persist.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_derive_and_persist.rs @@ -92,6 +92,7 @@ use crate::identity_keys_from_mnemonic::{ identity_auth_derivation_path, map_network, parse_mnemonic_any_language, }; use crate::identity_registration_with_signer::IdentityRegistrationKeyDerivationsFFI; +use crate::{check_ptr, unwrap_result_or_return}; /// DPP `KeyType::ECDSA_SECP256K1` discriminant byte. const KEY_TYPE_ECDSA_SECP256K1: u8 = 0; @@ -136,8 +137,6 @@ const SECURITY_LEVEL_HIGH: u8 = 2; /// [`crate::dash_sdk_derive_identity_keys_from_mnemonic_free`] /// — same memory layout as the lower-level FFI's output, so the /// existing free path handles it correctly. -/// - `out_error`: populated on failure with the usual -/// [`PlatformWalletFFIError`] detail. /// /// On error `*out_pubkeys` is left at the empty zero state. Any /// keys persisted by partial success before the failing iteration @@ -154,7 +153,6 @@ const SECURITY_LEVEL_HIGH: u8 = 2; /// functions and remain valid for the duration of the call. /// - `out_pubkeys` must be a valid, writable /// [`IdentityRegistrationKeyDerivationsFFI`] pointer. -/// - `out_error` may be null. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( @@ -165,16 +163,8 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( mnemonic_resolver_handle: *mut MnemonicResolverHandle, persister_handle: *mut IdentityKeyPersisterHandle, out_pubkeys: *mut IdentityRegistrationKeyDerivationsFFI, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_pubkeys.is_null() { - write_err( - out_error, - PlatformWalletFFIResult::ErrorNullPointer, - "out_pubkeys is null", - ); - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_pubkeys); // Pre-zero so a failed call leaves the caller staring at a known // empty struct, never uninitialized memory. *out_pubkeys = IdentityRegistrationKeyDerivationsFFI { @@ -182,18 +172,12 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( count: 0, }; - if wallet_id_bytes.is_null() || mnemonic_resolver_handle.is_null() || persister_handle.is_null() - { - write_err( - out_error, - PlatformWalletFFIResult::ErrorNullPointer, - "wallet_id_bytes, mnemonic_resolver_handle and persister_handle are required", - ); - return PlatformWalletFFIResult::ErrorNullPointer; - } + check_ptr!(wallet_id_bytes); + check_ptr!(mnemonic_resolver_handle); + check_ptr!(persister_handle); if key_count == 0 { - return PlatformWalletFFIResult::Success; + return PlatformWalletFfiResult::ok(); } // ---- Resolve mnemonic ---------------------------------------------------- @@ -214,64 +198,36 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( match rc { x if x == mnemonic_resolver_result::SUCCESS => {} x if x == mnemonic_resolver_result::NOT_FOUND => { - write_err( - out_error, - PlatformWalletFFIResult::ErrorWalletOperation, + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, "mnemonic resolver: no mnemonic stored for the supplied wallet_id", ); - return PlatformWalletFFIResult::ErrorWalletOperation; } x if x == mnemonic_resolver_result::BUFFER_TOO_SMALL => { - write_err( - out_error, - PlatformWalletFFIResult::ErrorWalletOperation, + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, "mnemonic resolver: mnemonic exceeded the FFI buffer capacity", ); - return PlatformWalletFFIResult::ErrorWalletOperation; } _ => { - write_err( - out_error, - PlatformWalletFFIResult::ErrorWalletOperation, + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, "mnemonic resolver: failed (other / Keychain access error)", ); - return PlatformWalletFFIResult::ErrorWalletOperation; } } if mnemonic_len == 0 || mnemonic_len > MNEMONIC_RESOLVER_BUFFER_CAPACITY { - write_err( - out_error, - PlatformWalletFFIResult::ErrorWalletOperation, + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, "mnemonic resolver: returned invalid length", ); - return PlatformWalletFFIResult::ErrorWalletOperation; } // Validate UTF-8 once over the prefix the resolver claimed to // write. Done in-place so we never construct a `String` // (Swift's String can't be zeroized; ours can). - let mnemonic_str = match std::str::from_utf8(&mnemonic_buf[..mnemonic_len]) { - Ok(s) => s, - Err(e) => { - write_err( - out_error, - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("mnemonic resolver returned non-UTF-8 bytes: {e}"), - ); - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - }; - let mnemonic = match parse_mnemonic_any_language(mnemonic_str) { - Ok(m) => m, - Err(e) => { - write_err( - out_error, - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("mnemonic resolver returned an invalid BIP-39 phrase: {e}"), - ); - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; + let mnemonic_str = unwrap_result_or_return!(std::str::from_utf8(&mnemonic_buf[..mnemonic_len])); + let mnemonic = unwrap_result_or_return!(parse_mnemonic_any_language(mnemonic_str)); // ---- Derive seed + master xpriv ------------------------------------------ // Empty passphrase to mirror the rest of the SDK (no caller @@ -282,17 +238,7 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( drop(mnemonic); let kw_network = map_network(network); - let master = match ExtendedPrivKey::new_master(kw_network, seed.as_ref()) { - Ok(m) => m, - Err(e) => { - write_err( - out_error, - PlatformWalletFFIResult::ErrorWalletOperation, - format!("ExtendedPrivKey::new_master failed: {e}"), - ); - return PlatformWalletFFIResult::ErrorWalletOperation; - } - }; + let master = unwrap_result_or_return!(ExtendedPrivKey::new_master(kw_network, seed.as_ref())); let secp = Secp256k1::new(); // ---- Walk derivation paths, persist, build pubkey-only rows -------------- @@ -321,15 +267,13 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( Ok(p) => p, Err(detail) => { cleanup(rows); - write_err( - out_error, - PlatformWalletFFIResult::ErrorWalletOperation, + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, format!( "derive_and_persist: path build failed at \ (identity={identity_index}, key={key_index}): {detail}" ), ); - return PlatformWalletFFIResult::ErrorWalletOperation; } }; @@ -337,15 +281,13 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( Ok(d) => d, Err(e) => { cleanup(rows); - write_err( - out_error, - PlatformWalletFFIResult::ErrorWalletOperation, + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, format!( "derive_and_persist: derive_priv failed at \ (identity={identity_index}, key={key_index}): {e}" ), ); - return PlatformWalletFFIResult::ErrorWalletOperation; } }; @@ -363,12 +305,10 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( Err(e) => { priv_scalar.zeroize(); cleanup(rows); - write_err( - out_error, - PlatformWalletFFIResult::ErrorUtf8Conversion, + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorUtf8Conversion, format!("derivation path contained NUL byte: {e}"), ); - return PlatformWalletFFIResult::ErrorUtf8Conversion; } }; @@ -414,15 +354,13 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( // it falls out of scope here. drop(path_cstring); cleanup(rows); - write_err( - out_error, - PlatformWalletFFIResult::ErrorWalletOperation, + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, format!( "persister callback returned false at \ (identity={identity_index}, key={key_index})" ), ); - return PlatformWalletFFIResult::ErrorWalletOperation; } // Detach the pubkey buffer for the FFI return. @@ -457,21 +395,7 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( count: items_count, }; - PlatformWalletFFIResult::Success -} - -/// Convenience for stamping an error into `out_error` if it's -/// non-null. Mirrors the inline pattern the rest of the FFI -/// surface uses; kept local since the rest of the file is full of -/// repetitive `if !out_error.is_null()` blocks otherwise. -unsafe fn write_err( - out_error: *mut PlatformWalletFFIError, - code: PlatformWalletFFIResult, - detail: impl Into, -) { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new(code, detail.into()); - } + PlatformWalletFfiResult::ok() } #[cfg(test)] @@ -626,7 +550,6 @@ mod tests { items: std::ptr::null_mut(), count: 0, }; - let mut err = PlatformWalletFFIError::success(); let rc = unsafe { dash_sdk_derive_and_persist_identity_keys( DashSDKNetwork::SDKTestnet, @@ -636,10 +559,9 @@ mod tests { resolver, persister, &mut out, - &mut err, ) }; - assert_eq!(rc, PlatformWalletFFIResult::Success); + assert_eq!(rc.code, PlatformWalletFfiResultCode::Success); assert_eq!(out.count, 3); // SAFETY: `capture` is alive (we're about to free it @@ -692,7 +614,6 @@ mod tests { items: std::ptr::null_mut(), count: 0, }; - let mut err = PlatformWalletFFIError::success(); let rc = unsafe { dash_sdk_derive_and_persist_identity_keys( DashSDKNetwork::SDKTestnet, @@ -702,10 +623,9 @@ mod tests { resolver, persister, &mut out, - &mut err, ) }; - assert_eq!(rc, PlatformWalletFFIResult::ErrorWalletOperation); + assert_eq!(rc.code, PlatformWalletFfiResultCode::ErrorWalletOperation); assert_eq!(out.count, 0); assert!(out.items.is_null()); unsafe { @@ -718,7 +638,6 @@ mod tests { fn rejects_null_inputs() { let (resolver, persister, capture) = make_capturing_handles(); let wallet_id = [0u8; 32]; - let mut err = PlatformWalletFFIError::success(); // Null out_pubkeys. let rc = unsafe { dash_sdk_derive_and_persist_identity_keys( @@ -729,10 +648,9 @@ mod tests { resolver, persister, std::ptr::null_mut(), - &mut err, ) }; - assert_eq!(rc, PlatformWalletFFIResult::ErrorNullPointer); + assert_eq!(rc.code, PlatformWalletFfiResultCode::ErrorNullPointer); // Null wallet id. let mut out = IdentityRegistrationKeyDerivationsFFI { @@ -748,10 +666,9 @@ mod tests { resolver, persister, &mut out, - &mut err, ) }; - assert_eq!(rc, PlatformWalletFFIResult::ErrorNullPointer); + assert_eq!(rc.code, PlatformWalletFfiResultCode::ErrorNullPointer); unsafe { dash_sdk_mnemonic_resolver_destroy(resolver); dash_sdk_identity_key_persister_destroy(persister); @@ -767,7 +684,6 @@ mod tests { items: std::ptr::null_mut(), count: 0, }; - let mut err = PlatformWalletFFIError::success(); let rc = unsafe { dash_sdk_derive_and_persist_identity_keys( DashSDKNetwork::SDKTestnet, @@ -777,10 +693,9 @@ mod tests { resolver, persister, &mut out, - &mut err, ) }; - assert_eq!(rc, PlatformWalletFFIResult::Success); + assert_eq!(rc.code, PlatformWalletFfiResultCode::Success); assert_eq!(out.count, 0); assert_eq!(unsafe { (*capture).rows.lock().unwrap().len() }, 0); unsafe { diff --git a/packages/rs-platform-wallet-ffi/src/identity_discovery.rs b/packages/rs-platform-wallet-ffi/src/identity_discovery.rs index 7a523110996..a1be35c82ad 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_discovery.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_discovery.rs @@ -24,9 +24,11 @@ use std::ptr; use platform_wallet::wallet::identity::network::IdentityDiscoveryOptions; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; /// Heap-allocated array of 32-byte identity ids returned by /// [`platform_wallet_discover_identities`]. Release by handing the @@ -65,37 +67,19 @@ impl DiscoveredIdentityIdsFFI { /// of the newly-discovered identity ids. Release with /// [`platform_wallet_discover_identities_free`]. On error the /// struct is left at its empty-zero state. -/// - `out_error` — populated on failure with the usual /// [`PlatformWalletFFIError`] detail. /// -/// # Returns -/// [`PlatformWalletFFIResult::Success`] on success (possibly with a -/// zero-length `out_found` — the scan completed but matched nothing -/// new), or an error variant. -/// /// # Safety /// `wallet_handle` must come from the platform-wallet handle -/// registry. `out_found` must be a valid, writable pointer. All -/// other out pointers may be null. +/// registry. `out_found` must be a valid, writable pointer. #[no_mangle] pub unsafe extern "C" fn platform_wallet_discover_identities( wallet_handle: Handle, start_index_or_neg1: i64, gap_limit: u32, out_found: *mut DiscoveredIdentityIdsFFI, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_found.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_found is null", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_found); // Pre-clear the out-array so partial failures don't leave the // caller staring at uninitialized memory. @@ -105,8 +89,6 @@ pub unsafe extern "C" fn platform_wallet_discover_identities( start_index: if start_index_or_neg1 < 0 { None } else { - // Clamp to u32 — callers passing something beyond - // u32::MAX are already in pathological territory. Some(start_index_or_neg1.min(u32::MAX as i64) as u32) }, gap_limit: if gap_limit == 0 { @@ -116,58 +98,26 @@ pub unsafe extern "C" fn platform_wallet_discover_identities( }, }; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity = wallet.identity().clone(); - let result = block_on_worker(async move { identity.discover(opts).await }); - match result { - Ok(found) => { - if found.is_empty() { - // `out_found` is already the empty sentinel. - return PlatformWalletFFIResult::Success; - } - - // Serialize discovered identity ids into a - // heap-allocated `[[u8; 32]]` buffer paired with - // its length, then hand ownership back to the - // caller via `out_found`. - use dpp::identity::accessors::IdentityGettersV0; - - let ids: Vec<[u8; 32]> = found.iter().map(|i| *i.id().as_bytes()).collect(); - let mut boxed = ids.into_boxed_slice(); - let count = boxed.len(); - let ptr = boxed.as_mut_ptr(); - std::mem::forget(boxed); + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity = wallet.identity().clone(); + block_on_worker(async move { identity.discover(opts).await }) + }); + let result = unwrap_option_or_return!(option); + let found = unwrap_result_or_return!(result); + if found.is_empty() { + return PlatformWalletFfiResult::ok(); + } - unsafe { - *out_found = DiscoveredIdentityIdsFFI { ids: ptr, count }; - } - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("discover_identities failed: {e}"), - ); - } - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + use dpp::identity::accessors::IdentityGettersV0; + let ids: Vec<[u8; 32]> = found.iter().map(|i| *i.id().as_bytes()).collect(); + let mut boxed = ids.into_boxed_slice(); + let count = boxed.len(); + let ptr = boxed.as_mut_ptr(); + std::mem::forget(boxed); + unsafe { + *out_found = DiscoveredIdentityIdsFFI { ids: ptr, count }; + } + PlatformWalletFfiResult::ok() } /// Release a [`DiscoveredIdentityIdsFFI`] previously populated by diff --git a/packages/rs-platform-wallet-ffi/src/identity_key_preview.rs b/packages/rs-platform-wallet-ffi/src/identity_key_preview.rs index 1c05971d298..0f7ab414369 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_key_preview.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_key_preview.rs @@ -35,6 +35,7 @@ use platform_wallet::{derive_identity_auth_keypair, IDENTITY_GAP_LIMIT, MASTER_K use crate::error::*; use crate::handle::*; +use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; /// One identity-registration-key preview row. /// @@ -132,32 +133,18 @@ impl IdentityKeyPreviewsFFI { /// array. Release with /// [`platform_wallet_preview_identity_registration_keys_free`]. On /// error the struct is left at the empty zero state. -/// - `out_error` — populated on failure with the usual -/// [`PlatformWalletFFIError`] detail. /// /// # Safety /// `wallet_handle` must come from the platform-wallet handle /// registry. `out_previews` must be a valid, writable pointer. -/// `out_error` may be null. #[no_mangle] pub unsafe extern "C" fn platform_wallet_preview_identity_registration_keys( wallet_handle: Handle, start_index: u32, count_or_neg1: i32, out_previews: *mut IdentityKeyPreviewsFFI, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_previews.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_previews is null", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_previews); // Pre-clear so partial failures don't leave the caller staring // at uninitialized memory. @@ -172,77 +159,44 @@ pub unsafe extern "C" fn platform_wallet_preview_identity_registration_keys( if count == 0 { // Empty preview is a valid result — early-out before // touching the wallet manager. - return PlatformWalletFFIResult::Success; + return PlatformWalletFfiResult::ok(); } - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - // Synchronous read of the wallet manager — same pattern - // `platform_wallet_get_dashpay_profile` and - // `platform_wallet_get_managed_identity` use, since FFI - // callers come in on non-tokio threads. - let wm = wallet.wallet_manager().blocking_read(); - let key_wallet = match wm.get_wallet(&wallet.wallet_id()) { - Some(w) => w, - None => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Wallet not found in wallet manager", - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidHandle; - } - }; - let network = key_wallet.network; - - // Build the row vec up-front so an error on row N - // doesn't leak rows 0..N — the `Drop` on the typed - // `Vec` would skip the - // CStrings/Box buffers we hand out raw, so we hand-roll - // cleanup on failure. - let mut rows: Vec = Vec::with_capacity(count as usize); + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + // Synchronous read of the wallet manager — FFI callers come + // in on non-tokio threads. + let wm = wallet.wallet_manager().blocking_read(); + let key_wallet = wm.get_wallet(&wallet.wallet_id()).ok_or_else(|| { + PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidHandle, + "Wallet not found in wallet manager", + ) + })?; + let network = key_wallet.network; - let mut error_pair: Option<(PlatformWalletFFIResult, String)> = None; - for offset in 0..count { - // Saturating add: the discovery scan caps identity - // indices well below u32::MAX in practice; if a - // caller intentionally passes near-max values we - // simply repeat the cap rather than wrap. - let identity_index = start_index.saturating_add(offset); - - let (path, ext_priv, public_key) = match derive_identity_auth_keypair( + // Build a single row. All fallible work runs first; raw- + // pointer detachment (`into_raw`, `mem::forget`) happens at + // the very end so an early `?` cleans up via Drop. + let build_row = + |identity_index: u32| -> Result { + let (path, ext_priv, public_key) = derive_identity_auth_keypair( key_wallet, network, identity_index, MASTER_KEY_INDEX, - ) { - Ok(triple) => triple, - Err(e) => { - error_pair = Some(( - PlatformWalletFFIResult::ErrorWalletOperation, - format!( - "preview_identity_registration_keys derivation failed at \ - identity_index {identity_index}: {e}" - ), - )); - break; - } - }; + )?; - let path_string = path.to_string(); - let path_cstring = match CString::new(path_string) { - Ok(s) => s, - Err(e) => { - error_pair = Some(( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("derivation path contained NUL byte: {e}"), - )); - break; - } + let path_cstring = CString::new(path.to_string())?; + + // WIF: network-aware (mainnet → 0xCC, testnet/devnet/ + // regtest → 0xEF) and compressed. Same construction + // `key_wallet::derive_private_key_as_wif` performs. + let dash_private = DashPrivateKey { + compressed: true, + network, + inner: ext_priv.private_key, }; + let wif_cstring = CString::new(dash_private.to_wif())?; // Compressed secp256k1 pubkey is always 33 bytes. let pub_bytes: [u8; 33] = public_key.serialize(); @@ -251,83 +205,51 @@ pub unsafe extern "C" fn platform_wallet_preview_identity_registration_keys( let pub_len = pub_box.len(); std::mem::forget(pub_box); - // WIF: network-aware (mainnet → 0xCC, testnet/ - // devnet/regtest → 0xEF) and compressed. Same - // construction `key_wallet::derive_private_key_as_wif` - // performs internally. - let dash_private = DashPrivateKey { - compressed: true, - network, - inner: ext_priv.private_key, - }; - let wif_string = dash_private.to_wif(); - let wif_cstring = match CString::new(wif_string) { - Ok(s) => s, - Err(e) => { - // Path C-string + pubkey buffer were - // already detached — clean them up before - // bailing. - unsafe { - drop(Vec::from_raw_parts(pub_ptr, pub_len, pub_len)); - } - drop(path_cstring); - error_pair = Some(( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("WIF string contained NUL byte: {e}"), - )); - break; - } - }; - - rows.push(IdentityKeyPreviewFFI { + Ok(IdentityKeyPreviewFFI { identity_index, derivation_path: path_cstring.into_raw(), public_key: pub_ptr, public_key_len: pub_len, private_key_wif: wif_cstring.into_raw(), private_key_bytes: ext_priv.private_key.secret_bytes(), - }); - } + }) + }; - if let Some((code, msg)) = error_pair { - // Free everything we've successfully appended so - // far — we never hand a partial array back. - free_rows(rows); - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new(code, msg); - } + let mut rows: Vec = Vec::with_capacity(count as usize); + for offset in 0..count { + // Saturating add: the discovery scan caps identity + // indices well below u32::MAX in practice; if a caller + // intentionally passes near-max values we simply repeat + // the cap rather than wrap. + let identity_index = start_index.saturating_add(offset); + match build_row(identity_index) { + Ok(row) => rows.push(row), + Err(e) => { + // Free everything we've successfully appended so + // far — we never hand a partial array back. + // TODO: Implement Drop instead of manually drop so ? op is usable + free_rows(rows); + return Err(e); } - return code; } + } + Ok(rows) + }); + let result = unwrap_option_or_return!(option); + let rows = unwrap_result_or_return!(result); - // Hand the row array off as a raw heap allocation. The - // matching free walks the slice and reclaims each row's - // owned strings + pubkey buffer. - let mut boxed_items = rows.into_boxed_slice(); - let items_ptr = boxed_items.as_mut_ptr(); - let items_count = boxed_items.len(); - std::mem::forget(boxed_items); + let mut boxed_items = rows.into_boxed_slice(); + let items_ptr = boxed_items.as_mut_ptr(); + let items_count = boxed_items.len(); + std::mem::forget(boxed_items); - unsafe { - *out_previews = IdentityKeyPreviewsFFI { - items: items_ptr, - count: items_count, - }; - } - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + unsafe { + *out_previews = IdentityKeyPreviewsFFI { + items: items_ptr, + count: items_count, + }; + } + PlatformWalletFfiResult::ok() } /// Release an [`IdentityKeyPreviewsFFI`] previously populated by diff --git a/packages/rs-platform-wallet-ffi/src/identity_keys_from_mnemonic.rs b/packages/rs-platform-wallet-ffi/src/identity_keys_from_mnemonic.rs index 019bc42d68f..bf2eee79e66 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_keys_from_mnemonic.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_keys_from_mnemonic.rs @@ -1,40 +1,4 @@ //! Mnemonic-driven identity-registration key derivation. -//! -//! Surgical companion to -//! [`crate::identity_registration_with_signer::platform_wallet_derive_identity_keys_for_index`] -//! — same output shape, different input. Where the wallet-handle -//! variant requires a full `key_wallet::Wallet` with an in-process -//! xpriv loaded, this entry point takes the BIP-39 mnemonic directly -//! and never touches the wallet manager. -//! -//! # Why this exists -//! -//! Wallets restored from a Swift-side persisted state (SwiftData -//! row + Keychain mnemonic) load into the Rust process as -//! **watch-only** — the seed is in iOS Keychain, not in the -//! `WalletManager`. Calling -//! [`platform_wallet_derive_identity_keys_for_index`](crate::platform_wallet_derive_identity_keys_for_index) -//! on those wallets fails with `"Cannot derive private keys from -//! watch-only wallet"` because `Wallet::derive_extended_private_key` -//! refuses to operate without the seed. -//! -//! Rather than push the seed across the FFI just to load it into the -//! `WalletManager` (which would defeat the watch-only model that the -//! rest of this crate carefully preserves), the Swift caller hands -//! us the mnemonic for the duration of one call. The mnemonic is -//! parsed, converted to a 64-byte seed inside a [`Zeroizing`] buffer, -//! used to build the master xpriv, and walked through `key_count` -//! derivation paths — all within this function's lifetime. The seed -//! is wrapped; the intermediate `ExtendedPrivKey`s aren't and rely on -//! the underlying `secp256k1::SecretKey` drop path to clear the -//! secret. Only the final 32-byte secret scalars cross the FFI; the -//! `_free` path additionally zeroizes the inline `private_key_bytes` -//! and the WIF buffer in place before the row slab is released. -//! -//! The output rows reuse [`IdentityKeyPreviewFFI`] and the wrapper -//! reuses [`crate::identity_registration_with_signer::IdentityRegistrationKeyDerivationsFFI`] -//! so the Swift marshalling code already written for the wallet-handle -//! variant works unchanged. use std::ffi::CString; @@ -51,15 +15,9 @@ use zeroize::Zeroizing; use crate::error::*; use crate::identity_key_preview::IdentityKeyPreviewFFI; use crate::identity_registration_with_signer::IdentityRegistrationKeyDerivationsFFI; +use crate::{check_ptr, unwrap_result_or_return}; /// Parse a BIP-39 mnemonic against every supported wordlist. -/// -/// Mirrors the auto-detect helper in `rs-sdk-ffi::signer_simple` -/// (`parse_mnemonic_any_language`); kept inline here so this crate -/// doesn't have to take a public dependency on that crate-internal -/// helper. BIP-39 wordlists are mutually exclusive within a single -/// phrase, so the first language that yields a valid mnemonic is the -/// right one. pub(crate) fn parse_mnemonic_any_language(phrase: &str) -> Result { const LANGUAGES: [Language; 10] = [ Language::English, @@ -82,9 +40,6 @@ pub(crate) fn parse_mnemonic_any_language(phrase: &str) -> Result key_wallet::Network { match network { DashSDKNetwork::SDKMainnet => key_wallet::Network::Mainnet, @@ -97,17 +52,6 @@ pub(crate) fn map_network(network: DashSDKNetwork) -> key_wallet::Network { /// Build the DIP-9 identity-authentication derivation path /// `m/9'/coin'/5'/0'/0'/identity_index'/key_index'`. -/// -/// Coin type is selected by `network` (mainnet vs testnet). The hardened -/// `0'` slot in position 4 is `KeyDerivationType::ECDSA`; this helper -/// hardcodes ECDSA because that's the only key type the identity- -/// registration path supports today (matches `derive_identity_auth_keypair` -/// in `platform-wallet`, which is the source of truth for live -/// registrations / discovery / preview). -/// -/// Re-derived locally instead of borrowed from `platform-wallet` because -/// `platform_wallet::identity_auth_derivation_path` is `pub(crate)` and -/// this entry point sits outside that crate. pub(crate) fn identity_auth_derivation_path( network: key_wallet::Network, identity_index: u32, @@ -118,8 +62,6 @@ pub(crate) fn identity_auth_derivation_path( _ => IDENTITY_AUTHENTICATION_PATH_TESTNET, } .into(); - // KeyDerivationType::ECDSA = 0 — hardcoded to match the only path - // shape `derive_identity_auth_keypair` ever produces. let key_type_index: u32 = 0; Ok(base_path.extend([ ChildNumber::from_hardened_idx(key_type_index) @@ -132,60 +74,7 @@ pub(crate) fn identity_auth_derivation_path( } /// Derive `key_count` identity-registration keys from -/// `(mnemonic, passphrase, network)` at DIP-9 paths -/// `m/9'/coin'/5'/0'/0'/identity_index'/key_index'` for -/// `key_index` in `0..key_count`. -/// -/// Returns rows of `(pubkey_bytes, derivation_path_cstr, -/// private_key_bytes, private_key_wif)` via the shared -/// [`IdentityRegistrationKeyDerivationsFFI`] / [`IdentityKeyPreviewFFI`] -/// shape — same memory layout as the wallet-handle variant -/// [`platform_wallet_derive_identity_keys_for_index`](crate::platform_wallet_derive_identity_keys_for_index) -/// so the Swift marshalling and free path are identical. -/// -/// The 64-byte mnemonic seed is wrapped in [`Zeroizing`] for the -/// duration of the call. Intermediate `ExtendedPrivKey` values -/// (`master`, `derived`) are *not* wrapped — they currently rely on -/// the underlying `secp256k1::SecretKey`'s drop path to scrub itself -/// rather than an explicit `Zeroizing` wrapper. Only the final -/// 32-byte secret scalars cross the FFI boundary for the caller to -/// persist into Keychain; on the `_free` path both the inline -/// `private_key_bytes` and the WIF buffer are zeroized in place -/// before the row slab is released. -/// -/// # Why this exists -/// Identity-key derivation that previously routed through the wallet -/// handle ([`platform_wallet_derive_identity_keys_for_index`](crate::platform_wallet_derive_identity_keys_for_index)) -/// fails for restored watch-only wallets because Rust has no xpriv -/// loaded for them. This entry point bypasses the wallet entirely — -/// the caller supplies the mnemonic. -/// -/// # Parameters -/// - `mnemonic_cstr`: null-terminated UTF-8 BIP-39 phrase. Auto- -/// detects language from the supported wordlists. -/// - `passphrase_cstr`: null-terminated UTF-8 BIP-39 passphrase. May -/// be null, in which case the empty passphrase is used. -/// - `network`: selects the coin-type slot in the derivation path -/// AND the WIF version byte. -/// - `identity_index`: hardened identity index slot. -/// - `key_count`: number of consecutive `key_index` slots to derive, -/// starting at 0. -/// - `out_rows`: populated on success with a heap-allocated array. -/// Release with [`dash_sdk_derive_identity_keys_from_mnemonic_free`]. -/// - `out_error`: populated on failure with the usual -/// [`PlatformWalletFFIError`] detail. -/// -/// On error `*out_rows` is left at its zero state. -/// -/// # Safety -/// - `mnemonic_cstr` must be a valid, null-terminated UTF-8 C string -/// for the duration of the call. -/// - `passphrase_cstr` may be null; otherwise must be a valid -/// null-terminated UTF-8 C string. -/// - `out_rows` must be a valid, writable pointer to a -/// `IdentityRegistrationKeyDerivationsFFI`. Caller retains -/// ownership of the outer struct; this function fills it in place. -/// - `out_error` may be null. +/// `(mnemonic, passphrase, network)`. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( @@ -195,114 +84,37 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( identity_index: u32, key_count: u32, out_rows: *mut IdentityRegistrationKeyDerivationsFFI, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { +) -> PlatformWalletFfiResult { use std::ffi::CStr; - if out_rows.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_rows is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - // Pre-zero so a failed call leaves the caller staring at a known - // empty struct, never uninitialized memory. + check_ptr!(out_rows); *out_rows = IdentityRegistrationKeyDerivationsFFI { items: std::ptr::null_mut(), count: 0, }; - if mnemonic_cstr.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "mnemonic_cstr is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } + check_ptr!(mnemonic_cstr); if key_count == 0 { - return PlatformWalletFFIResult::Success; + return PlatformWalletFfiResult::ok(); } - // ---- UTF-8 inputs -------------------------------------------------------- - let mnemonic_str = match CStr::from_ptr(mnemonic_cstr).to_str() { - Ok(s) => s, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("mnemonic_cstr is not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - }; + let mnemonic_str = unwrap_result_or_return!(CStr::from_ptr(mnemonic_cstr).to_str()); let passphrase_str: &str = if passphrase_cstr.is_null() { "" } else { - match CStr::from_ptr(passphrase_cstr).to_str() { - Ok(s) => s, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("passphrase_cstr is not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - } + unwrap_result_or_return!(CStr::from_ptr(passphrase_cstr).to_str()) }; - // ---- Mnemonic + seed ----------------------------------------------------- - let mnemonic = match parse_mnemonic_any_language(mnemonic_str) { - Ok(m) => m, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("invalid mnemonic: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; - // 64-byte seed wrapped in `Zeroizing` so it gets scrubbed when this - // function returns (success or failure). `to_seed` returns by value; - // wrapping at the call site is the earliest we can intercept it. + let mnemonic = unwrap_result_or_return!(parse_mnemonic_any_language(mnemonic_str)); let seed: Zeroizing<[u8; 64]> = Zeroizing::new(mnemonic.to_seed(passphrase_str)); - // ---- Master xpriv -------------------------------------------------------- let kw_network = map_network(network); - let master = match ExtendedPrivKey::new_master(kw_network, seed.as_ref()) { - Ok(m) => m, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("ExtendedPrivKey::new_master failed: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorWalletOperation; - } - }; + let master = unwrap_result_or_return!(ExtendedPrivKey::new_master(kw_network, seed.as_ref())); let secp = Secp256k1::new(); - // ---- Walk key_count derivation paths ------------------------------------- - // - // Build the row vec up-front so we can unwind ownership of every - // CString / Vec / Box we've already detached if a later iteration - // fails. `Vec::drop` would NOT free those raw pointers — they are - // exposed via `into_raw` / `forget(Box::...)` and only the paired - // free function reclaims them. let mut rows: Vec = Vec::with_capacity(key_count as usize); - // Hand-roll cleanup matching the wallet-handle variant's pattern. let cleanup = |rows: Vec| { for row in rows { if !row.derivation_path.is_null() { @@ -322,37 +134,27 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( Ok(p) => p, Err(detail) => { cleanup(rows); - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!( - "derive_identity_keys_from_mnemonic: path build failed at \ - (identity={identity_index}, key={key_index}): {detail}" - ), - ); - } - return PlatformWalletFFIResult::ErrorWalletOperation; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, + format!( + "derive_identity_keys_from_mnemonic: path build failed at \ + (identity={identity_index}, key={key_index}): {detail}" + ), + ); } }; - // The intermediate `derived` xpriv carries a 32-byte secret in - // the clear; we extract `.private_key.secret_bytes()` into the - // FFI row and let the xpriv fall out of scope at the end of - // the iteration. The seed remains zeroized regardless. let derived = match master.derive_priv(&secp, &path) { Ok(d) => d, Err(e) => { cleanup(rows); - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!( - "derive_identity_keys_from_mnemonic: derive_priv failed at \ - (identity={identity_index}, key={key_index}): {e}" - ), - ); - } - return PlatformWalletFFIResult::ErrorWalletOperation; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, + format!( + "derive_identity_keys_from_mnemonic: derive_priv failed at \ + (identity={identity_index}, key={key_index}): {e}" + ), + ); } }; let extended_pub = ExtendedPubKey::from_priv(&secp, &derived); @@ -362,25 +164,19 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( Ok(s) => s, Err(e) => { cleanup(rows); - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("derivation path contained NUL byte: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorUtf8Conversion, + format!("derivation path contained NUL byte: {e}"), + ); } }; - // Compressed secp256k1 pubkey is exactly 33 bytes. let pub_bytes: [u8; 33] = public_key.serialize(); let mut pub_box: Box<[u8]> = pub_bytes.to_vec().into_boxed_slice(); let pub_ptr = pub_box.as_mut_ptr(); let pub_len = pub_box.len(); std::mem::forget(pub_box); - // WIF for the keychain-explorer / debugging UI. Same network- - // aware shape as the wallet-handle variant produces. let dash_private = DashPrivateKey { compressed: true, network: kw_network, @@ -389,17 +185,13 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( let wif_cstring = match CString::new(dash_private.to_wif()) { Ok(s) => s, Err(e) => { - // Path cstring + pubkey buffer were already detached. drop(Vec::from_raw_parts(pub_ptr, pub_len, pub_len)); drop(path_cstring); cleanup(rows); - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("WIF string contained NUL byte: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorUtf8Conversion, + format!("WIF string contained NUL byte: {e}"), + ); } }; @@ -409,9 +201,6 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( public_key: pub_ptr, public_key_len: pub_len, private_key_wif: wif_cstring.into_raw(), - // 32-byte secret scalar — copied by value into the FFI - // row. `derived` goes out of scope at the end of the - // loop body; the seed is still zeroized at function exit. private_key_bytes: derived.private_key.secret_bytes(), }); } @@ -425,26 +214,11 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( items: items_ptr, count: items_count, }; - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } /// Release a [`IdentityRegistrationKeyDerivationsFFI`] previously /// populated by [`dash_sdk_derive_identity_keys_from_mnemonic`]. -/// -/// Safe to call on a zero / null struct or null outer pointer (no-op). -/// Each row's owned strings (`derivation_path`, `private_key_wif`) -/// and pubkey buffer are reclaimed. -/// -/// Behaviorally identical to -/// [`platform_wallet_derive_identity_keys_for_index_free`](crate::platform_wallet_derive_identity_keys_for_index_free) -/// — they consume the same struct shape — but kept as a separate -/// symbol so the Swift call site can pair allocator with deallocator -/// 1:1 by name. -/// -/// # Safety -/// `rows.items` must have been handed out by -/// [`dash_sdk_derive_identity_keys_from_mnemonic`] (or the wallet-handle -/// variant — same layout) and must not be freed twice. #[no_mangle] pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic_free( rows: *mut IdentityRegistrationKeyDerivationsFFI, @@ -471,16 +245,10 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic_free( let _ = Vec::from_raw_parts(row.public_key, row.public_key_len, row.public_key_len); } if !row.private_key_wif.is_null() { - // The WIF string encodes the same 32-byte secret as - // `private_key_bytes`; scrub the buffer in place before - // dropping so the heap allocation isn't released with - // recoverable key material. let mut wif = CString::from_raw(row.private_key_wif).into_bytes_with_nul(); zeroize::Zeroize::zeroize(&mut wif); row.private_key_wif = std::ptr::null_mut(); } - // Final inline secret scalar — wipe before the row slab is - // returned to the allocator. zeroize::Zeroize::zeroize(&mut row.private_key_bytes); } let _ = Box::from_raw(slice as *mut [IdentityKeyPreviewFFI]); @@ -490,17 +258,9 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic_free( mod tests { use super::*; - /// English BIP-39 test vector (all-zero entropy). Same fixture - /// the upstream `bip39` crate uses for round-trip tests; reused - /// here so the assertions match a well-known mnemonic. const ENGLISH_PHRASE: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; - /// Sanity-check the happy path on testnet with a small key count. - /// We don't assert specific hex bytes — derive determinism is the - /// platform-wallet / key-wallet test surface — but we DO assert - /// the shape contract: row count, pubkey length, path string - /// shape, and that each row carries non-zero secret bytes. #[test] fn derives_three_keys_with_correct_shape() { let mnemonic = std::ffi::CString::new(ENGLISH_PHRASE).unwrap(); @@ -508,19 +268,17 @@ mod tests { items: std::ptr::null_mut(), count: 0, }; - let mut err = PlatformWalletFFIError::success(); let result = unsafe { dash_sdk_derive_identity_keys_from_mnemonic( mnemonic.as_ptr(), - std::ptr::null(), // empty passphrase + std::ptr::null(), DashSDKNetwork::SDKTestnet, 7, 3, &mut out, - &mut err, ) }; - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(out.count, 3); assert!(!out.items.is_null()); @@ -534,9 +292,6 @@ mod tests { let path = unsafe { std::ffi::CStr::from_ptr(row.derivation_path) } .to_str() .unwrap(); - // m/9'/1'/5'/0'/0'/7'/' on testnet (coin_type = 1). - // We just sanity-check the structural prefix and the trailing - // key index — exact path strings are covered by platform-wallet. assert!( path.starts_with("m/9'/1'/5'/0'/0'/7'/"), "unexpected path prefix: {path}" @@ -546,8 +301,6 @@ mod tests { "unexpected path tail: {path}" ); - // Secret scalar must be non-zero; if it were, derivation - // silently no-op'd. assert!( row.private_key_bytes.iter().any(|b| *b != 0), "secret bytes are all zero at row {i}" @@ -555,13 +308,10 @@ mod tests { } unsafe { dash_sdk_derive_identity_keys_from_mnemonic_free(&mut out) }; - // `_free` resets the outer struct. assert!(out.items.is_null()); assert_eq!(out.count, 0); } - /// Mainnet path uses coin_type = 5 instead of 1. Lightweight - /// coverage that the network mapping flows into the path. #[test] fn derives_mainnet_path_uses_coin_type_5() { let mnemonic = std::ffi::CString::new(ENGLISH_PHRASE).unwrap(); @@ -569,7 +319,6 @@ mod tests { items: std::ptr::null_mut(), count: 0, }; - let mut err = PlatformWalletFFIError::success(); let result = unsafe { dash_sdk_derive_identity_keys_from_mnemonic( mnemonic.as_ptr(), @@ -578,10 +327,9 @@ mod tests { 0, 1, &mut out, - &mut err, ) }; - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(out.count, 1); let row = unsafe { &*out.items }; @@ -593,7 +341,6 @@ mod tests { unsafe { dash_sdk_derive_identity_keys_from_mnemonic_free(&mut out) }; } - /// `key_count == 0` is a legal no-op and must not allocate. #[test] fn key_count_zero_is_noop_success() { let mnemonic = std::ffi::CString::new(ENGLISH_PHRASE).unwrap(); @@ -601,7 +348,6 @@ mod tests { items: std::ptr::null_mut(), count: 0, }; - let mut err = PlatformWalletFFIError::success(); let result = unsafe { dash_sdk_derive_identity_keys_from_mnemonic( mnemonic.as_ptr(), @@ -610,18 +356,14 @@ mod tests { 0, 0, &mut out, - &mut err, ) }; - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(out.count, 0); assert!(out.items.is_null()); - // `_free` is a no-op on the empty struct. unsafe { dash_sdk_derive_identity_keys_from_mnemonic_free(&mut out) }; } - /// Garbage mnemonic must surface `ErrorInvalidParameter`, not a - /// crash, and must not leave a partial allocation behind. #[test] fn rejects_invalid_mnemonic() { let mnemonic = std::ffi::CString::new("not a real bip39 phrase at all here").unwrap(); @@ -629,7 +371,6 @@ mod tests { items: std::ptr::null_mut(), count: 0, }; - let mut err = PlatformWalletFFIError::success(); let result = unsafe { dash_sdk_derive_identity_keys_from_mnemonic( mnemonic.as_ptr(), @@ -638,10 +379,12 @@ mod tests { 0, 3, &mut out, - &mut err, ) }; - assert_eq!(result, PlatformWalletFFIResult::ErrorInvalidParameter); + assert_eq!( + result.code, + PlatformWalletFfiResultCode::ErrorInvalidParameter + ); assert!(out.items.is_null()); assert_eq!(out.count, 0); } diff --git a/packages/rs-platform-wallet-ffi/src/identity_manager.rs b/packages/rs-platform-wallet-ffi/src/identity_manager.rs index 009db373b13..44c1833cb3d 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_manager.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_manager.rs @@ -1,6 +1,8 @@ +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::types::*; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; use platform_wallet::wallet::persister::{NoPlatformPersistence, WalletPersister}; use platform_wallet::IdentityManager; use std::sync::Arc; @@ -13,25 +15,14 @@ pub(crate) fn ffi_noop_persister() -> WalletPersister { #[no_mangle] pub unsafe extern "C" fn identity_manager_create( out_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_handle.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_handle); let manager = IdentityManager::default(); let handle = IDENTITY_MANAGER_STORAGE.insert(manager); unsafe { *out_handle = handle }; - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } /// Add a managed identity to the manager. @@ -44,44 +35,17 @@ pub unsafe extern "C" fn identity_manager_create( pub unsafe extern "C" fn identity_manager_add_identity( manager_handle: Handle, identity_handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - let identity_result = +) -> PlatformWalletFfiResult { + let identity_option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| identity.clone()); - - let identity = match identity_result { - Some(i) => i, - None => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidHandle; - } - }; - - IDENTITY_MANAGER_STORAGE - .with_item_mut(manager_handle, |manager| { - match manager.add_out_of_wallet_identity(identity.identity, &ffi_noop_persister()) { - Ok(_) => PlatformWalletFFIResult::Success, - Err(_) => PlatformWalletFFIResult::ErrorWalletOperation, - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let identity = unwrap_option_or_return!(identity_option); + + let option = IDENTITY_MANAGER_STORAGE.with_item_mut(manager_handle, |manager| { + manager.add_out_of_wallet_identity(identity.identity, &ffi_noop_persister()) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } /// Remove an identity from the manager. @@ -94,50 +58,20 @@ pub unsafe extern "C" fn identity_manager_add_identity( pub unsafe extern "C" fn identity_manager_remove_identity( manager_handle: Handle, identity_id: *const u8, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - let id = match unsafe { read_identifier(identity_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identifier: {}", e), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - - IDENTITY_MANAGER_STORAGE - .with_item_mut(manager_handle, |manager| { - if manager.remove_identity(&id, &ffi_noop_persister()).is_ok() { - PlatformWalletFFIResult::Success - } else { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorIdentityNotFound, - "Identity not found", - ); - } - } - PlatformWalletFFIResult::ErrorIdentityNotFound - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); + + let option = IDENTITY_MANAGER_STORAGE.with_item_mut(manager_handle, |manager| { + manager.remove_identity(&id, &ffi_noop_persister()) + }); + let result = unwrap_option_or_return!(option); + if result.is_err() { + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorIdentityNotFound, + "Identity not found", + ); + } + PlatformWalletFfiResult::ok() } /// Get an identity by ID. `identity_id` is a `*const u8` to a @@ -148,67 +82,18 @@ pub unsafe extern "C" fn identity_manager_get_identity( manager_handle: Handle, identity_id: *const u8, out_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_handle.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - - let id = match unsafe { read_identifier(identity_id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identifier: {}", e), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - - IDENTITY_MANAGER_STORAGE - .with_item(manager_handle, |manager| { - match manager.managed_identity(&id) { - Some(identity) => { - let handle = MANAGED_IDENTITY_STORAGE.insert(identity.clone()); - unsafe { *out_handle = handle }; - PlatformWalletFFIResult::Success - } - None => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorIdentityNotFound, - "Identity not found", - ); - } - } - PlatformWalletFFIResult::ErrorIdentityNotFound - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + check_ptr!(out_handle); + + let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); + + let option = IDENTITY_MANAGER_STORAGE.with_item(manager_handle, |manager| { + manager.managed_identity(&id).cloned() + }); + let inner = unwrap_option_or_return!(option); + let identity = unwrap_option_or_return!(inner); + unsafe { *out_handle = MANAGED_IDENTITY_STORAGE.insert(identity) }; + PlatformWalletFfiResult::ok() } /// Get all identity IDs across both buckets. @@ -216,44 +101,21 @@ pub unsafe extern "C" fn identity_manager_get_identity( pub unsafe extern "C" fn identity_manager_get_all_identity_ids( manager_handle: Handle, out_array: *mut IdentifierArray, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { +) -> PlatformWalletFfiResult { use dpp::identity::accessors::IdentityGettersV0; - if out_array.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - - IDENTITY_MANAGER_STORAGE - .with_item(manager_handle, |manager| { - let ids: Vec = manager - .all_identities() - .into_iter() - .map(|i| i.id()) - .collect(); - let array = IdentifierArray::new(ids); - unsafe { *out_array = array }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + check_ptr!(out_array); + + let option = IDENTITY_MANAGER_STORAGE.with_item(manager_handle, |manager| { + manager + .all_identities() + .into_iter() + .map(|i| i.id()) + .collect::>() + }); + let ids = unwrap_option_or_return!(option); + unsafe { *out_array = IdentifierArray::new(ids) }; + PlatformWalletFfiResult::ok() } /// Get the count of identities across both buckets. @@ -261,47 +123,27 @@ pub unsafe extern "C" fn identity_manager_get_all_identity_ids( pub unsafe extern "C" fn identity_manager_get_identity_count( manager_handle: Handle, out_count: *mut usize, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_count.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_count); - IDENTITY_MANAGER_STORAGE - .with_item(manager_handle, |manager| { - unsafe { *out_count = manager.identity_count() }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = + IDENTITY_MANAGER_STORAGE.with_item(manager_handle, |manager| manager.identity_count()); + *out_count = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Destroy IdentityManager and free resources #[no_mangle] pub unsafe extern "C" fn identity_manager_destroy( manager_handle: Handle, -) -> PlatformWalletFFIResult { +) -> PlatformWalletFfiResult { if IDENTITY_MANAGER_STORAGE.remove(manager_handle).is_some() { - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } else { - PlatformWalletFFIResult::ErrorInvalidHandle + PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidHandle, + "Invalid manager handle", + ) } } @@ -347,14 +189,12 @@ mod tests { fn test_create_identity_manager() { unsafe { let mut handle: Handle = NULL_HANDLE; - let mut error = PlatformWalletFFIError::success(); - let result = identity_manager_create(&mut handle, &mut error); + let result = identity_manager_create(&mut handle); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_ne!(handle, NULL_HANDLE); - // Cleanup identity_manager_destroy(handle); } } @@ -363,17 +203,15 @@ mod tests { fn test_get_identity_count() { unsafe { let mut handle: Handle = NULL_HANDLE; - let mut error = PlatformWalletFFIError::success(); - identity_manager_create(&mut handle, &mut error); + identity_manager_create(&mut handle); let mut count: usize = 0; - let result = identity_manager_get_identity_count(handle, &mut count, &mut error); + let result = identity_manager_get_identity_count(handle, &mut count); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(count, 0); - // Cleanup identity_manager_destroy(handle); } } @@ -385,32 +223,25 @@ mod tests { // round-trip and the count reflects the insert. unsafe { let mut manager_handle: Handle = NULL_HANDLE; - let mut error = PlatformWalletFFIError::success(); - identity_manager_create(&mut manager_handle, &mut error); + identity_manager_create(&mut manager_handle); let identity = create_test_identity(); let id_bytes: [u8; 32] = [1u8; 32]; let managed_identity = ManagedIdentity::new(identity, 0); let identity_handle = MANAGED_IDENTITY_STORAGE.insert(managed_identity); - identity_manager_add_identity(manager_handle, identity_handle, &mut error); + identity_manager_add_identity(manager_handle, identity_handle); let mut count: usize = 0; - identity_manager_get_identity_count(manager_handle, &mut count, &mut error); + identity_manager_get_identity_count(manager_handle, &mut count); assert_eq!(count, 1); let mut got: Handle = NULL_HANDLE; - let result = identity_manager_get_identity( - manager_handle, - id_bytes.as_ptr(), - &mut got, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = identity_manager_get_identity(manager_handle, id_bytes.as_ptr(), &mut got); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_ne!(got, NULL_HANDLE); - // Cleanup identity_manager_destroy(manager_handle); } } @@ -419,7 +250,7 @@ mod tests { fn test_destroy_invalid_handle() { unsafe { let result = identity_manager_destroy(9999); - assert_eq!(result, PlatformWalletFFIResult::ErrorInvalidHandle); + assert_eq!(result.code, PlatformWalletFfiResultCode::ErrorInvalidHandle); } } } diff --git a/packages/rs-platform-wallet-ffi/src/identity_registration_funded_with_signer.rs b/packages/rs-platform-wallet-ffi/src/identity_registration_funded_with_signer.rs index cebfc46dfaa..54d4182659a 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_registration_funded_with_signer.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_registration_funded_with_signer.rs @@ -1,30 +1,5 @@ //! Asset-lock-funded identity registration driven by an external //! `SignerHandle`. -//! -//! The single entry point — -//! [`platform_wallet_register_identity_with_funding_signer`] — wraps -//! [`IdentityWallet::register_identity_with_funding_external_signer`](platform_wallet::IdentityWallet::register_identity_with_funding_external_signer) -//! and works the same way the address-funded -//! `platform_wallet_register_identity_with_signer` does: -//! -//! - Caller pre-derives the new identity's authentication public keys -//! via [`crate::dash_sdk_derive_identity_keys_from_mnemonic`] (which -//! works for watch-only wallets, unlike the wallet-handle variant) -//! and ships them in as a flat [`crate::IdentityPubkeyFFI`] array. -//! - Caller pre-persists each key's matching private material to -//! whatever store the supplied `signer_handle` reads from (iOS -//! Keychain in the typical case). -//! - The asset-lock proof + private key are still built on the Rust -//! side from `amount_duffs` (the wallet must have spendable Core -//! UTXOs — same precondition as the legacy variant). -//! - Every IdentityCreate state-transition signature crosses the FFI -//! through `signer_handle`. -//! -//! The two-signer split that -//! `platform_wallet_register_identity_with_signer` (address-funded) -//! uses isn't needed here: the funding side is a Core-chain asset -//! lock signed with `asset_lock_private_key` (built Rust-side), not a -//! `Signer` operation. use std::collections::BTreeMap; use std::convert::TryFrom; @@ -37,33 +12,14 @@ use dpp::platform_value::BinaryData; use platform_wallet::wallet::identity::types::funding::IdentityFundingMethod; use rs_sdk_ffi::{SignerHandle, VTableSigner}; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::identity_registration_with_signer::IdentityPubkeyFFI; use crate::runtime::block_on_worker; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; /// Register a new asset-lock-funded identity using an external signer. -/// -/// `amount_duffs` funds the asset lock from wallet UTXOs; the SDK -/// builds + broadcasts the asset-lock transaction internally and -/// retries with a ChainLock proof on InstantSend rejection (see -/// [`IdentityWallet::register_identity_with_funding_external_signer`](platform_wallet::IdentityWallet::register_identity_with_funding_external_signer) -/// for the IS->CL fallback details). -/// -/// On success `out_identity_id` (32 bytes) and `out_identity_handle` -/// (a fresh `ManagedIdentity` handle in `MANAGED_IDENTITY_STORAGE`) -/// are populated. -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle registry. -/// - `identity_pubkeys` must point at a valid `[IdentityPubkeyFFI; -/// identity_pubkeys_count]` array, and each row's `pubkey_bytes` -/// must be a valid `[u8; pubkey_len]` buffer for the duration of -/// the call. -/// - `signer_handle` must be a valid, non-destroyed handle produced by -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. -/// - `out_identity_id` and `out_identity_handle` must be writable for -/// the duration of the call. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_register_identity_with_funding_signer( @@ -75,83 +31,30 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_funding_signer( signer_handle: *mut SignerHandle, out_identity_id: *mut [u8; 32], out_identity_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() - || identity_pubkeys.is_null() - || identity_pubkeys_count == 0 - || out_identity_id.is_null() - || out_identity_handle.is_null() - { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle, identity_pubkeys, out_identity_id, or out_identity_handle is null/empty", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); + check_ptr!(identity_pubkeys); + check_ptr!(out_identity_id); + check_ptr!(out_identity_handle); + if identity_pubkeys_count == 0 { + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + "identity_pubkeys_count must be >= 1", + ); } - // Materialize identity pubkeys into a BTreeMap - // before entering the wallet-storage closure so a parse failure is - // not gated on whether the wallet handle happens to be valid. let pubkey_rows: &[IdentityPubkeyFFI] = slice::from_raw_parts(identity_pubkeys, identity_pubkeys_count); let mut keys_map: BTreeMap = BTreeMap::new(); for (i, row) in pubkey_rows.iter().enumerate() { - let key_type = match KeyType::try_from(row.key_type) { - Ok(kt) => kt, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!( - "identity_pubkeys[{}].key_type = {} is not a valid KeyType", - i, row.key_type - ), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; - let purpose = match Purpose::try_from(row.purpose) { - Ok(p) => p, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!( - "identity_pubkeys[{}].purpose = {} is not a valid Purpose", - i, row.purpose - ), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; - let security_level = match SecurityLevel::try_from(row.security_level) { - Ok(sl) => sl, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!( - "identity_pubkeys[{}].security_level = {} is not a valid SecurityLevel", - i, row.security_level - ), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; + let key_type = unwrap_result_or_return!(KeyType::try_from(row.key_type)); + let purpose = unwrap_result_or_return!(Purpose::try_from(row.purpose)); + let security_level = unwrap_result_or_return!(SecurityLevel::try_from(row.security_level)); if row.pubkey_bytes.is_null() || row.pubkey_len == 0 { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - format!("identity_pubkeys[{}].pubkey_bytes is null or empty", i), - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorNullPointer, + format!("identity_pubkeys[{i}].pubkey_bytes is null or empty"), + ); } let pubkey_bytes: Vec = slice::from_raw_parts(row.pubkey_bytes, row.pubkey_len).to_vec(); @@ -172,50 +75,27 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_funding_signer( let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .register_identity_with_funding_external_signer( - IdentityFundingMethod::FundWithWallet { amount_duffs }, - identity_index, - keys_map, - signer, - None, - ) - .await - }); - - match result { - Ok(identity) => { - let id_bytes: [u8; 32] = identity.id().to_buffer(); - *out_identity_id = id_bytes; - let managed = platform_wallet::ManagedIdentity::new(identity, identity_index); - let handle = MANAGED_IDENTITY_STORAGE.insert(managed); - *out_identity_handle = handle; - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("register_identity_with_funding_signer failed: {}", e), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .register_identity_with_funding_external_signer( + IdentityFundingMethod::FundWithWallet { amount_duffs }, + identity_index, + keys_map, + signer, + None, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + let identity = unwrap_result_or_return!(result); + let id_bytes: [u8; 32] = identity.id().to_buffer(); + *out_identity_id = id_bytes; + let managed = platform_wallet::ManagedIdentity::new(identity, identity_index); + let handle = MANAGED_IDENTITY_STORAGE.insert(managed); + *out_identity_handle = handle; + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/identity_registration_with_signer.rs b/packages/rs-platform-wallet-ffi/src/identity_registration_with_signer.rs index 5a56ae83d5b..04e689c6885 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_registration_with_signer.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_registration_with_signer.rs @@ -71,11 +71,13 @@ use std::ptr; use std::slice; use zeroize::Zeroize; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::identity_key_preview::IdentityKeyPreviewFFI; use crate::identity_registration::{IdentityFundingInputFFI, IdentityFundingOutputFFI}; use crate::runtime::block_on_worker; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; /// One identity authentication public key the caller has already /// derived (via [`crate::dash_sdk_derive_identity_keys_from_mnemonic`] @@ -142,65 +144,48 @@ pub struct IdentityPubkeyFFI { /// silently can't use. /// /// `purpose` is the parsed `Purpose` discriminant for the row, used -/// only for the encryption / decryption guard above. +/// only for the encryption / decryption guard above. `row_index` and +/// `field_label` only flavour error messages (different callers want +/// different prefixes — `add_public_keys[i]` for update, +/// `identity_pubkeys[i]` for registration). /// -/// `row_index` and `field_label` only flavour error messages -/// (different callers want different prefixes — `add_public_keys[i]` -/// for update, `identity_pubkeys[i]` for registration). On any -/// error, populates `out_error` (when non-null) and returns -/// `Err(result_code)` so the caller can early-return with the same -/// FFI status it was already returning. -/// -/// # Safety -/// Each non-null pointer in the row must remain valid for the -/// duration of the call. `contract_bounds_id` (when not null) must -/// point at >=32 bytes; `contract_bounds_document_type` (when not -/// null) must be a NUL-terminated UTF-8 C string. +/// Returns `Err(PlatformWalletFfiResult)` carrying the FFI error the +/// caller should bubble up (the result already holds the message); +/// caller does `unwrap_result_or_return!(decode_contract_bounds(...))`. pub(crate) unsafe fn decode_contract_bounds( row: &IdentityPubkeyFFI, purpose: Purpose, row_index: usize, field_label: &str, - out_error: *mut PlatformWalletFFIError, -) -> Result, PlatformWalletFFIResult> { +) -> Result, PlatformWalletFfiResult> { match row.contract_bounds_kind { 0 => { if matches!(purpose, Purpose::ENCRYPTION | Purpose::DECRYPTION) { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!( - "{}[{}].contract_bounds_kind = 0 (no bounds) but purpose = {:?} \ - requires bounds — Drive scopes Encryption / Decryption keys to a \ - specific contract (use kind 1 or 2)", - field_label, row_index, purpose - ), - ); - } - return Err(PlatformWalletFFIResult::ErrorInvalidParameter); + return Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + format!( + "{field_label}[{row_index}].contract_bounds_kind = 0 (no bounds) but \ + purpose = {purpose:?} requires bounds — Drive scopes Encryption / \ + Decryption keys to a specific contract (use kind 1 or 2)" + ), + )); } Ok(None) } 1 => { if row.contract_bounds_id.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - format!( - "{}[{}].contract_bounds_id is null but kind == 1 \ - (SingleContract)", - field_label, row_index - ), - ); - } - return Err(PlatformWalletFFIResult::ErrorNullPointer); + return Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorNullPointer, + format!( + "{field_label}[{row_index}].contract_bounds_id is null but kind == 1 \ + (SingleContract)" + ), + )); } let id_bytes: [u8; 32] = match <[u8; 32]>::try_from(slice::from_raw_parts(row.contract_bounds_id, 32)) { Ok(b) => b, - Err(_) => { - unreachable!("from_raw_parts(_, 32) always yields exactly 32 bytes") - } + Err(_) => unreachable!("from_raw_parts(_, 32) always yields exactly 32 bytes"), }; Ok(Some(ContractBounds::SingleContract { id: Identifier::from(id_bytes), @@ -208,38 +193,30 @@ pub(crate) unsafe fn decode_contract_bounds( } 2 => { if row.contract_bounds_id.is_null() || row.contract_bounds_document_type.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - format!( - "{}[{}].contract_bounds_id or .contract_bounds_document_type \ - is null but kind == 2 (SingleContractDocumentType)", - field_label, row_index - ), - ); - } - return Err(PlatformWalletFFIResult::ErrorNullPointer); + return Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorNullPointer, + format!( + "{field_label}[{row_index}].contract_bounds_id or \ + .contract_bounds_document_type is null but kind == 2 \ + (SingleContractDocumentType)" + ), + )); } let id_bytes: [u8; 32] = match <[u8; 32]>::try_from(slice::from_raw_parts(row.contract_bounds_id, 32)) { Ok(b) => b, - Err(_) => { - unreachable!("from_raw_parts(_, 32) always yields exactly 32 bytes") - } + Err(_) => unreachable!("from_raw_parts(_, 32) always yields exactly 32 bytes"), }; let doc_type = match CStr::from_ptr(row.contract_bounds_document_type).to_str() { Ok(s) => s.to_string(), Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!( - "{}[{}].contract_bounds_document_type is not valid UTF-8: {}", - field_label, row_index, e - ), - ); - } - return Err(PlatformWalletFFIResult::ErrorUtf8Conversion); + return Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorUtf8Conversion, + format!( + "{field_label}[{row_index}].contract_bounds_document_type is not \ + valid UTF-8: {e}" + ), + )); } }; Ok(Some(ContractBounds::SingleContractDocumentType { @@ -247,19 +224,13 @@ pub(crate) unsafe fn decode_contract_bounds( document_type_name: doc_type, })) } - other => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!( - "{}[{}].contract_bounds_kind = {} is not a valid discriminant \ - (0=none, 1=SingleContract, 2=SingleContractDocumentType)", - field_label, row_index, other - ), - ); - } - Err(PlatformWalletFFIResult::ErrorInvalidParameter) - } + other => Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + format!( + "{field_label}[{row_index}].contract_bounds_kind = {other} is not a valid \ + discriminant (0=none, 1=SingleContract, 2=SingleContractDocumentType)" + ), + )), } } @@ -289,25 +260,15 @@ pub(crate) unsafe fn decode_contract_bounds( /// unblocks watch-only wallets (where the identity signer is a /// hardware HSM and the address signer reaches into the Keychain) /// and Keychain-backed platform-address keys without another ABI -/// change later. +/// change later. Passing the same pointer for both is supported and +/// expected — the underlying `VTableSigner` impls +/// `Signer` AND `Signer` and +/// dispatches by `key_type` byte. /// /// On success both `out_identity_id` (32 bytes) and /// `out_identity_handle` are populated. The returned handle points at /// a freshly-inserted `ManagedIdentity` in `MANAGED_IDENTITY_STORAGE` /// wrapping the new identity together with `identity_index`. -/// -/// # Safety -/// - All pointer parameters follow the same null / lifetime rules as -/// the mnemonic-based variant. -/// - `identity_pubkeys` must point at a valid `[IdentityPubkeyFFI; -/// identity_pubkeys_count]` array, and each row's `pubkey_bytes` -/// must be a valid `[u8; pubkey_len]` buffer for the duration of -/// the call. The caller retains ownership of every buffer. -/// - `signer_identity_handle` and `signer_address_handle` must each be -/// a valid, non-destroyed handle and must outlive this call. Passing -/// the same pointer for both is supported and expected — the -/// underlying `VTableSigner` impls `Signer` AND -/// `Signer` and dispatches by `key_type` byte. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_register_identity_with_signer( @@ -322,39 +283,26 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_signer( output: *const IdentityFundingOutputFFI, out_identity_id: *mut [u8; 32], out_identity_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - let invariant_violation: Option<&'static str> = if inputs.is_null() { - Some("`inputs` pointer is null") - } else if inputs_count == 0 { - Some("`inputs_count` is zero") - } else if identity_pubkeys.is_null() { - Some("`identity_pubkeys` pointer is null") - } else if identity_pubkeys_count == 0 { - Some("`identity_pubkeys_count` must be >= 1") - } else if signer_identity_handle.is_null() { - Some("`signer_identity_handle` pointer is null") - } else if signer_address_handle.is_null() { - Some("`signer_address_handle` pointer is null") - } else if out_identity_id.is_null() { - Some("`out_identity_id` pointer is null") - } else if out_identity_handle.is_null() { - Some("`out_identity_handle` pointer is null") - } else { - None - }; - if let Some(detail) = invariant_violation { - if !out_error.is_null() { - *out_error = - PlatformWalletFFIError::new(PlatformWalletFFIResult::ErrorNullPointer, detail); - } - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + check_ptr!(inputs); + check_ptr!(identity_pubkeys); + check_ptr!(signer_identity_handle); + check_ptr!(signer_address_handle); + check_ptr!(out_identity_id); + check_ptr!(out_identity_handle); + if inputs_count == 0 { + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + "`inputs_count` is zero", + ); + } + if identity_pubkeys_count == 0 { + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + "`identity_pubkeys_count` must be >= 1", + ); } - // Parse the inputs and optional output exactly the same way the - // mnemonic variant does. Keeping the FFI shape identical lets - // Swift call this function by swapping the symbol name and - // dropping the mnemonic + passphrase arguments. let entries = slice::from_raw_parts(inputs, inputs_count); let mut input_map: BTreeMap = BTreeMap::new(); for entry in entries { @@ -362,20 +310,12 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_signer( 0 => PlatformAddress::P2pkh(entry.hash), 1 => PlatformAddress::P2sh(entry.hash), _ => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - "invalid address_type (expected 0 or 1)", - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + "invalid address_type (expected 0 or 1)", + ); } }; - // Sum duplicate rows for the same address rather than - // overwriting — see the matching comment in - // `identity_top_up.rs`. Caller may legitimately split one - // address across rows; `insert` alone would silently - // under-fund the new identity by the prior contribution. input_map .entry(address) .and_modify(|existing| *existing = existing.saturating_add(entry.credits)) @@ -391,13 +331,10 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_signer( 0 => PlatformAddress::P2pkh(output_ref.hash), 1 => PlatformAddress::P2sh(output_ref.hash), _ => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - "invalid output address_type (expected 0 or 1)", - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + "invalid output address_type (expected 0 or 1)", + ); } }; Some((address, output_ref.credits)) @@ -406,102 +343,26 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_signer( } }; - // Re-acquire each `VTableSigner` behind its handle as a borrowed - // reference inside the future. Round-tripping the pointers through - // `usize` gives the spawned future a `Send + 'static` capture (the - // raw pointer is `!Send`, but `usize` is). The actual signer state - // — `Inner::Callback { ctx, vtable }` — is `Send + Sync` (see the - // unsafe impls in `rs-sdk-ffi/src/signer.rs`). - // - // The two pointers may legitimately alias when the caller is - // sharing one `KeychainSigner` for both roles; the `Signer` - // trait is generic over `K`, so the same `VTableSigner` value is - // viewed as `Signer` *or* `Signer` - // at the call site below depending on which generic parameter - // `register_from_addresses` instantiates. let signer_identity_addr = signer_identity_handle as usize; let signer_address_addr = signer_address_handle as usize; - // Materialize the caller-supplied pubkey rows into a BTreeMap of - // `IdentityPublicKey` once, *before* entering the wallet-storage - // closure. This lookup is independent of the wallet handle (we no - // longer derive from the seed here — Swift derived these via the - // mnemonic-driven FFI which works for watch-only wallets too) and - // a parse failure should not depend on whether the wallet handle - // happens to be valid. Validation errors propagate out the same - // way as before via `out_error`. let pubkey_rows: &[IdentityPubkeyFFI] = slice::from_raw_parts(identity_pubkeys, identity_pubkeys_count); let mut keys_map: BTreeMap = BTreeMap::new(); for (i, row) in pubkey_rows.iter().enumerate() { - let key_type = match KeyType::try_from(row.key_type) { - Ok(kt) => kt, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!( - "identity_pubkeys[{}].key_type = {} is not a valid KeyType discriminant", - i, row.key_type - ), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; - let purpose = match Purpose::try_from(row.purpose) { - Ok(p) => p, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!( - "identity_pubkeys[{}].purpose = {} is not a valid Purpose discriminant", - i, row.purpose - ), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; - let security_level = match SecurityLevel::try_from(row.security_level) { - Ok(sl) => sl, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!( - "identity_pubkeys[{}].security_level = {} is not a valid SecurityLevel discriminant", - i, row.security_level - ), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; + let key_type = unwrap_result_or_return!(KeyType::try_from(row.key_type)); + let purpose = unwrap_result_or_return!(Purpose::try_from(row.purpose)); + let security_level = unwrap_result_or_return!(SecurityLevel::try_from(row.security_level)); if row.pubkey_bytes.is_null() || row.pubkey_len == 0 { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - format!("identity_pubkeys[{}].pubkey_bytes is null or empty", i), - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorNullPointer, + format!("identity_pubkeys[{i}].pubkey_bytes is null or empty"), + ); } let pubkey_bytes: Vec = slice::from_raw_parts(row.pubkey_bytes, row.pubkey_len).to_vec(); - // Decode the optional contract-bounds payload through the - // shared helper. Earlier revisions silently dropped these - // fields here, so Encryption / Decryption keys registered - // via this entry point ended up unbounded on Platform — - // matching the update path's parser closes the gap. The - // helper also rejects unscoped Encryption / Decryption - // keys (Drive requires a contract scope for those). let contract_bounds = - match decode_contract_bounds(row, purpose, i, "identity_pubkeys", out_error) { - Ok(b) => b, - Err(code) => return code, - }; + unwrap_result_or_return!(decode_contract_bounds(row, purpose, i, "identity_pubkeys")); keys_map.insert( row.key_id, IdentityPublicKey::V0(IdentityPublicKeyV0 { @@ -517,75 +378,43 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_signer( ); } - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - - // Pubkey derivation moved up above the wallet-storage - // closure: the caller supplies the pubkeys directly via - // `identity_pubkeys`, so we no longer need to consult the - // wallet manager here. The wallet handle is still - // required for `wallet.identity()` and for the SDK call - // below — those uses are unchanged. - let placeholder = Identity::V0(IdentityV0 { - id: Identifier::default(), - public_keys: keys_map, - balance: 0, - revision: 0, - }); + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); - // SAFETY: the caller guaranteed both signer handles are - // valid and outlive this call. `signer_*_addr` are the - // same pointers reinterpreted as `usize` so they can - // travel into the `'static + Send` future below. - let result = block_on_worker(async move { - let identity_signer: &VTableSigner = - unsafe { &*(signer_identity_addr as *const VTableSigner) }; - let address_signer: &VTableSigner = - unsafe { &*(signer_address_addr as *const VTableSigner) }; + let placeholder = Identity::V0(IdentityV0 { + id: Identifier::default(), + public_keys: keys_map, + balance: 0, + revision: 0, + }); - identity_wallet - .register_from_addresses( - &placeholder, - input_map, - output_map, - identity_index, - identity_signer, - address_signer, - None, - ) - .await - }); + block_on_worker(async move { + let identity_signer: &VTableSigner = + unsafe { &*(signer_identity_addr as *const VTableSigner) }; + let address_signer: &VTableSigner = + unsafe { &*(signer_address_addr as *const VTableSigner) }; - match result { - Ok(identity) => { - let id_bytes: [u8; 32] = identity.id().to_buffer(); - *out_identity_id = id_bytes; - let managed = platform_wallet::ManagedIdentity::new(identity, identity_index); - let handle = MANAGED_IDENTITY_STORAGE.insert(managed); - *out_identity_handle = handle; - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("register_from_addresses failed: {}", e), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + identity_wallet + .register_from_addresses( + &placeholder, + input_map, + output_map, + identity_index, + identity_signer, + address_signer, + None, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + let identity = unwrap_result_or_return!(result); + let id_bytes: [u8; 32] = identity.id().to_buffer(); + *out_identity_id = id_bytes; + let managed = platform_wallet::ManagedIdentity::new(identity, identity_index); + let handle = MANAGED_IDENTITY_STORAGE.insert(managed); + *out_identity_handle = handle; + PlatformWalletFfiResult::ok() } // --------------------------------------------------------------------------- @@ -594,13 +423,15 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_signer( /// Heap-allocated array of [`IdentityKeyPreviewFFI`] rows handed back /// by [`platform_wallet_derive_identity_keys_for_index`]. Same memory -/// layout (and same free function) as [`crate::identity_key_preview::IdentityKeyPreviewsFFI`] -/// so the existing release machinery can reclaim it. +/// layout (and same free function) as +/// [`crate::identity_key_preview::IdentityKeyPreviewsFFI`] so the +/// existing release machinery can reclaim it. /// -/// We deliberately re-use `IdentityKeyPreviewFFI` rather than inventing -/// a new row type so the Swift side can drop the result through the -/// existing `previewIdentityRegistrationKeys`-style marshalling code -/// (just iterated over a different `(identity_index, key_index)` set). +/// We deliberately re-use `IdentityKeyPreviewFFI` rather than +/// inventing a new row type so the Swift side can drop the result +/// through the existing `previewIdentityRegistrationKeys`-style +/// marshalling code (just iterated over a different +/// `(identity_index, key_index)` set). #[repr(C)] pub struct IdentityRegistrationKeyDerivationsFFI { pub items: *mut IdentityKeyPreviewFFI, @@ -620,8 +451,9 @@ impl IdentityRegistrationKeyDerivationsFFI { /// [`platform_wallet_register_identity_with_signer`] call will need /// for `identity_index`, returning one row per key id in `0..key_count`. /// -/// Sister function to [`crate::platform_wallet_preview_identity_registration_keys`]: -/// the preview only walks the MASTER slot at key_id 0 across many +/// Sister function to +/// [`crate::platform_wallet_preview_identity_registration_keys`]: the +/// preview only walks the MASTER slot at key_id 0 across many /// `identity_index` values; this function fixes the `identity_index` /// and walks `key_count` consecutive `key_id`s. Used at registration /// time so the Swift `KeychainSigner` can pre-stash every key the @@ -639,172 +471,131 @@ impl IdentityRegistrationKeyDerivationsFFI { /// in-process `WalletManager`). Most call sites should prefer /// [`crate::dash_sdk_derive_identity_keys_from_mnemonic`], which /// takes the mnemonic directly and works on every wallet shape -/// regardless of how it was loaded into the process. -/// -/// Kept around for any out-of-tree consumer that still binds to the -/// old symbol; new code should not call it. -/// -/// # Safety -/// `wallet_handle` must come from the platform-wallet handle registry. -/// `out_rows` must be a valid, writable pointer. `out_error` may be -/// null. +/// regardless of how it was loaded into the process. Kept around for +/// any out-of-tree consumer that still binds to the old symbol; new +/// code should not call it. #[no_mangle] pub unsafe extern "C" fn platform_wallet_derive_identity_keys_for_index( wallet_handle: Handle, identity_index: u32, key_count: u32, out_rows: *mut IdentityRegistrationKeyDerivationsFFI, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_rows.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_rows is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_rows); *out_rows = IdentityRegistrationKeyDerivationsFFI::empty(); if key_count == 0 { - return PlatformWalletFFIResult::Success; + return PlatformWalletFfiResult::ok(); } - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let wm = wallet.wallet_manager().blocking_read(); - let wallet_id = wallet.wallet_id(); - let key_wallet = match wm.get_wallet(&wallet_id) { - Some(w) => w, - None => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Wallet not found in wallet manager", - ); - } - return PlatformWalletFFIResult::ErrorInvalidHandle; - } - }; - let network = key_wallet.network; + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let wm = wallet.wallet_manager().blocking_read(); + let wallet_id = wallet.wallet_id(); + let key_wallet = match wm.get_wallet(&wallet_id) { + Some(w) => w, + None => { + return Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidHandle, + "Wallet not found in wallet manager", + )); + } + }; + let network = key_wallet.network; - let mut rows: Vec = Vec::with_capacity(key_count as usize); + let mut rows: Vec = Vec::with_capacity(key_count as usize); - // Hand-roll cleanup on failure: each successfully-pushed - // row owns CString / Vec allocations that won't be freed - // by `Vec::drop` (we declare them as raw pointers). - let cleanup = |rows: Vec| { - for row in rows { - if !row.derivation_path.is_null() { - let _ = CString::from_raw(row.derivation_path); - } - if !row.public_key.is_null() { - let _ = Vec::from_raw_parts( - row.public_key, - row.public_key_len, - row.public_key_len, - ); - } - if !row.private_key_wif.is_null() { - let _ = CString::from_raw(row.private_key_wif); - } + let cleanup = |rows: Vec| { + for row in rows { + if !row.derivation_path.is_null() { + let _ = unsafe { CString::from_raw(row.derivation_path) }; } - }; - - for key_id in 0..key_count { - let (path, ext_priv, public_key) = - match derive_identity_auth_keypair(key_wallet, network, identity_index, key_id) - { - Ok(t) => t, - Err(e) => { - cleanup(rows); - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!( - "derive_identity_keys_for_index: derivation failed at \ - (identity={}, key={}): {}", - identity_index, key_id, e - ), - ); - } - return PlatformWalletFFIResult::ErrorWalletOperation; - } + if !row.public_key.is_null() { + let _ = unsafe { + Vec::from_raw_parts(row.public_key, row.public_key_len, row.public_key_len) }; + } + if !row.private_key_wif.is_null() { + let _ = unsafe { CString::from_raw(row.private_key_wif) }; + } + } + }; - let path_cstring = match CString::new(path.to_string()) { - Ok(s) => s, + for key_id in 0..key_count { + let (path, ext_priv, public_key) = + match derive_identity_auth_keypair(key_wallet, network, identity_index, key_id) { + Ok(t) => t, Err(e) => { cleanup(rows); - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("derivation path contained NUL byte: {}", e), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + return Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorWalletOperation, + format!( + "derive_identity_keys_for_index: derivation failed at \ + (identity={identity_index}, key={key_id}): {e}" + ), + )); } }; - let pub_bytes: [u8; 33] = public_key.serialize(); - let mut pub_box: Box<[u8]> = pub_bytes.to_vec().into_boxed_slice(); - let pub_ptr = pub_box.as_mut_ptr(); - let pub_len = pub_box.len(); - std::mem::forget(pub_box); + let path_cstring = match CString::new(path.to_string()) { + Ok(s) => s, + Err(e) => { + cleanup(rows); + return Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorUtf8Conversion, + format!("derivation path contained NUL byte: {e}"), + )); + } + }; - let dash_private = DashPrivateKey { - compressed: true, - network, - inner: ext_priv.private_key, - }; - let wif_cstring = match CString::new(dash_private.to_wif()) { - Ok(s) => s, - Err(e) => { - unsafe { - drop(Vec::from_raw_parts(pub_ptr, pub_len, pub_len)); - } - drop(path_cstring); - cleanup(rows); - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("WIF string contained NUL byte: {}", e), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + let pub_bytes: [u8; 33] = public_key.serialize(); + let mut pub_box: Box<[u8]> = pub_bytes.to_vec().into_boxed_slice(); + let pub_ptr = pub_box.as_mut_ptr(); + let pub_len = pub_box.len(); + std::mem::forget(pub_box); + + let dash_private = DashPrivateKey { + compressed: true, + network, + inner: ext_priv.private_key, + }; + let wif_cstring = match CString::new(dash_private.to_wif()) { + Ok(s) => s, + Err(e) => { + unsafe { + drop(Vec::from_raw_parts(pub_ptr, pub_len, pub_len)); } - }; + drop(path_cstring); + cleanup(rows); + return Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorUtf8Conversion, + format!("WIF string contained NUL byte: {e}"), + )); + } + }; - rows.push(IdentityKeyPreviewFFI { - identity_index, - derivation_path: path_cstring.into_raw(), - public_key: pub_ptr, - public_key_len: pub_len, - private_key_wif: wif_cstring.into_raw(), - private_key_bytes: ext_priv.private_key.secret_bytes(), - }); - } + rows.push(IdentityKeyPreviewFFI { + identity_index, + derivation_path: path_cstring.into_raw(), + public_key: pub_ptr, + public_key_len: pub_len, + private_key_wif: wif_cstring.into_raw(), + private_key_bytes: ext_priv.private_key.secret_bytes(), + }); + } + Ok(rows) + }); + let inner = unwrap_option_or_return!(option); + let rows = unwrap_result_or_return!(inner); - let mut boxed = rows.into_boxed_slice(); - let items_ptr = boxed.as_mut_ptr(); - let items_count = boxed.len(); - std::mem::forget(boxed); + let mut boxed = rows.into_boxed_slice(); + let items_ptr = boxed.as_mut_ptr(); + let items_count = boxed.len(); + std::mem::forget(boxed); - *out_rows = IdentityRegistrationKeyDerivationsFFI { - items: items_ptr, - count: items_count, - }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + *out_rows = IdentityRegistrationKeyDerivationsFFI { + items: items_ptr, + count: items_count, + }; + PlatformWalletFfiResult::ok() } /// Release a [`IdentityRegistrationKeyDerivationsFFI`] previously @@ -812,12 +603,9 @@ pub unsafe extern "C" fn platform_wallet_derive_identity_keys_for_index( /// /// Safe to call on a zero / null struct or null outer pointer (no-op). /// Each row's owned strings (`derivation_path`, `private_key_wif`) -/// and pubkey buffer are reclaimed. -/// -/// # Safety -/// `rows.items` must have been handed out by -/// [`platform_wallet_derive_identity_keys_for_index`] and must not be -/// freed twice. +/// and pubkey buffer are reclaimed. `rows.items` must have been +/// handed out by [`platform_wallet_derive_identity_keys_for_index`] +/// and must not be freed twice. #[no_mangle] pub unsafe extern "C" fn platform_wallet_derive_identity_keys_for_index_free( rows: *mut IdentityRegistrationKeyDerivationsFFI, @@ -838,19 +626,12 @@ pub unsafe extern "C" fn platform_wallet_derive_identity_keys_for_index_free( let _ = Vec::from_raw_parts(row.public_key, row.public_key_len, row.public_key_len); } if !row.private_key_wif.is_null() { - // The WIF string encodes the same 32-byte secret as - // `private_key_bytes`; scrub the buffer in place before - // dropping so the heap allocation isn't released with - // recoverable key material. let mut wif = CString::from_raw(row.private_key_wif).into_bytes_with_nul(); wif.zeroize(); row.private_key_wif = ptr::null_mut(); } - // Final inline secret scalar — wipe before the row slab is - // returned to the allocator. row.private_key_bytes.zeroize(); } - // Reclaim the row array itself. let _ = Box::from_raw(slice as *mut [IdentityKeyPreviewFFI]); } diff --git a/packages/rs-platform-wallet-ffi/src/identity_sync.rs b/packages/rs-platform-wallet-ffi/src/identity_sync.rs index 2b5a7331d5a..1cfd7b0be96 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_sync.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_sync.rs @@ -7,11 +7,10 @@ //! flat snapshot read API for the per-identity cache (single //! identity or whole-store) with a paired free helper. //! -//! All `*const` and `*mut` parameters follow the same convention as -//! the rest of this crate: pointers may be null, the function returns -//! [`PlatformWalletFFIResult::ErrorNullPointer`] if a non-optional -//! pointer is null, and detailed error context lands in the optional -//! `out_error` slot when supplied. +//! Error handling follows the same shape as the rest of this crate: +//! every entry point returns a `PlatformWalletFfiResult` carrying the +//! typed code + Rust-supplied detail; null / invalid inputs surface +//! through the `check_ptr!` and `unwrap_option_or_return!` macros. use std::time::Duration; @@ -20,6 +19,7 @@ use platform_wallet::{IdentityTokenSyncInfo, IdentityTokenSyncState}; use crate::error::*; use crate::handle::*; use crate::runtime::runtime; +use crate::{check_ptr, unwrap_option_or_return}; /// Flattened per-(identity, token) row mirroring /// [`IdentityTokenSyncInfo`]. @@ -64,45 +64,25 @@ impl IdentityTokenSyncInfoFFI { #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_identity_sync_start( handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - let _entered = runtime().enter(); - manager.identity_sync_arc().start(); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + let _entered = runtime().enter(); + manager.identity_sync_arc().start(); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Stop the identity-token sync manager if it is running. #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_identity_sync_stop( handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - manager.identity_sync().stop(); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + manager.identity_sync().stop(); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Whether the identity-token sync background loop is running. @@ -110,26 +90,14 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_stop( pub unsafe extern "C" fn platform_wallet_manager_identity_sync_is_running( handle: Handle, out_running: *mut bool, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_running.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_running); - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - *out_running = manager.identity_sync().is_running(); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = PLATFORM_WALLET_MANAGER_STORAGE + .with_item(handle, |manager| manager.identity_sync().is_running()); + let running = unwrap_option_or_return!(option); + *out_running = running; + PlatformWalletFfiResult::ok() } /// Whether an identity-token sync pass is currently in flight. @@ -137,26 +105,14 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_is_running( pub unsafe extern "C" fn platform_wallet_manager_identity_sync_is_syncing( handle: Handle, out_syncing: *mut bool, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_syncing.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_syncing); - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - *out_syncing = manager.identity_sync().is_syncing(); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = PLATFORM_WALLET_MANAGER_STORAGE + .with_item(handle, |manager| manager.identity_sync().is_syncing()); + let syncing = unwrap_option_or_return!(option); + *out_syncing = syncing; + PlatformWalletFfiResult::ok() } /// Unix seconds of the last completed identity-token sync pass for @@ -170,32 +126,21 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_last_sync_unix_se handle: Handle, identity_id_ptr: *const u8, out_last_sync_unix: *mut u64, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if identity_id_ptr.is_null() || out_last_sync_unix.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(identity_id_ptr); + check_ptr!(out_last_sync_unix); + let mut id_bytes = [0u8; 32]; std::ptr::copy_nonoverlapping(identity_id_ptr, id_bytes.as_mut_ptr(), 32); let identity_id = dpp::prelude::Identifier::from(id_bytes); - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - let mgr = manager.identity_sync_arc(); - let value = runtime() - .block_on(async move { mgr.last_sync_unix_for_identity(&identity_id).await }); - *out_last_sync_unix = value.unwrap_or(0); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + let mgr = manager.identity_sync_arc(); + runtime().block_on(async move { mgr.last_sync_unix_for_identity(&identity_id).await }) + }); + let value = unwrap_option_or_return!(option); + *out_last_sync_unix = value.unwrap_or(0); + PlatformWalletFfiResult::ok() } /// Set the background identity-token sync interval in seconds. @@ -203,24 +148,14 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_last_sync_unix_se pub unsafe extern "C" fn platform_wallet_manager_identity_sync_set_interval( handle: Handle, interval_seconds: u64, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - manager - .identity_sync() - .set_interval(Duration::from_secs(interval_seconds)); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + manager + .identity_sync() + .set_interval(Duration::from_secs(interval_seconds)); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Run one identity-token sync pass across all registered wallets. @@ -233,22 +168,12 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_set_interval( #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_identity_sync_sync_now( handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - runtime().block_on(manager.identity_sync().sync_now()); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + runtime().block_on(manager.identity_sync().sync_now()); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Snapshot the identity-token sync state for one identity. @@ -272,54 +197,41 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_state_for_identit out_rows: *mut *mut IdentityTokenSyncInfoFFI, out_rows_count: *mut usize, out_last_sync_unix: *mut u64, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if identity_id_ptr.is_null() - || out_rows.is_null() - || out_rows_count.is_null() - || out_last_sync_unix.is_null() - { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(identity_id_ptr); + check_ptr!(out_rows); + check_ptr!(out_rows_count); + check_ptr!(out_last_sync_unix); let mut id_bytes = [0u8; 32]; std::ptr::copy_nonoverlapping(identity_id_ptr, id_bytes.as_mut_ptr(), 32); let identity_id = dpp::prelude::Identifier::from(id_bytes); - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - let mgr = manager.identity_sync_arc(); - let row = runtime().block_on(async move { mgr.state_for_identity(&identity_id).await }); - match row { - Some(state) => { - let rows: Vec = state - .tokens - .iter() - .map(|info| IdentityTokenSyncInfoFFI::from_state_row(&state, info)) - .collect(); - let len = rows.len(); - let boxed = rows.into_boxed_slice(); - *out_rows = Box::into_raw(boxed) as *mut IdentityTokenSyncInfoFFI; - *out_rows_count = len; - *out_last_sync_unix = state.last_sync_unix; - } - None => { - *out_rows = std::ptr::null_mut(); - *out_rows_count = 0; - *out_last_sync_unix = 0; - } - } - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + let mgr = manager.identity_sync_arc(); + runtime().block_on(async move { mgr.state_for_identity(&identity_id).await }) + }); + let row = unwrap_option_or_return!(option); + match row { + Some(state) => { + let rows: Vec = state + .tokens + .iter() + .map(|info| IdentityTokenSyncInfoFFI::from_state_row(&state, info)) + .collect(); + let len = rows.len(); + let boxed = rows.into_boxed_slice(); + *out_rows = Box::into_raw(boxed) as *mut IdentityTokenSyncInfoFFI; + *out_rows_count = len; + *out_last_sync_unix = state.last_sync_unix; + } + None => { + *out_rows = std::ptr::null_mut(); + *out_rows_count = 0; + *out_last_sync_unix = 0; + } + } + PlatformWalletFfiResult::ok() } /// Snapshot the identity-token sync state for every cached identity @@ -340,42 +252,31 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_state_all( handle: Handle, out_rows: *mut *mut IdentityTokenSyncInfoFFI, out_rows_count: *mut usize, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_rows.is_null() || out_rows_count.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_rows); + check_ptr!(out_rows_count); - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - let mgr = manager.identity_sync_arc(); - let snapshot = runtime().block_on(async move { mgr.all_state().await }); - let mut rows: Vec = Vec::new(); - for state in snapshot.values() { - for info in &state.tokens { - rows.push(IdentityTokenSyncInfoFFI::from_state_row(state, info)); - } - } - let len = rows.len(); - if len == 0 { - *out_rows = std::ptr::null_mut(); - *out_rows_count = 0; - } else { - let boxed = rows.into_boxed_slice(); - *out_rows = Box::into_raw(boxed) as *mut IdentityTokenSyncInfoFFI; - *out_rows_count = len; - } - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + let mgr = manager.identity_sync_arc(); + runtime().block_on(async move { mgr.all_state().await }) + }); + let snapshot = unwrap_option_or_return!(option); + let mut rows: Vec = Vec::new(); + for state in snapshot.values() { + for info in &state.tokens { + rows.push(IdentityTokenSyncInfoFFI::from_state_row(state, info)); + } + } + let len = rows.len(); + if len == 0 { + *out_rows = std::ptr::null_mut(); + *out_rows_count = 0; + } else { + let boxed = rows.into_boxed_slice(); + *out_rows = Box::into_raw(boxed) as *mut IdentityTokenSyncInfoFFI; + *out_rows_count = len; + } + PlatformWalletFfiResult::ok() } /// Free a heap-owned `IdentityTokenSyncInfoFFI` array returned by one @@ -426,34 +327,25 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_register_identity identity_id_ptr: *const u8, token_ids_ptr: *const u8, token_ids_count: usize, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if identity_id_ptr.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(identity_id_ptr); let mut id_bytes = [0u8; 32]; std::ptr::copy_nonoverlapping(identity_id_ptr, id_bytes.as_mut_ptr(), 32); let identity_id = dpp::prelude::Identifier::from(id_bytes); let Some(token_ids) = read_token_ids(token_ids_ptr, token_ids_count) else { - return PlatformWalletFFIResult::ErrorNullPointer; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorNullPointer, + "token_ids_ptr is null with non-zero count", + ); }; - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - let mgr = manager.identity_sync_arc(); - runtime().block_on(async move { mgr.register_identity(identity_id, token_ids).await }); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + let mgr = manager.identity_sync_arc(); + runtime().block_on(async move { mgr.register_identity(identity_id, token_ids).await }); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Unregister an identity from the token-sync registry. @@ -464,30 +356,18 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_register_identity pub unsafe extern "C" fn platform_wallet_manager_identity_sync_unregister_identity( handle: Handle, identity_id_ptr: *const u8, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if identity_id_ptr.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(identity_id_ptr); let mut id_bytes = [0u8; 32]; std::ptr::copy_nonoverlapping(identity_id_ptr, id_bytes.as_mut_ptr(), 32); let identity_id = dpp::prelude::Identifier::from(id_bytes); - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - let mgr = manager.identity_sync_arc(); - runtime().block_on(async move { mgr.unregister_identity(&identity_id).await }); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + let mgr = manager.identity_sync_arc(); + runtime().block_on(async move { mgr.unregister_identity(&identity_id).await }); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Replace the watched-token list for an already-registered identity. @@ -505,33 +385,23 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_update_watched_to identity_id_ptr: *const u8, token_ids_ptr: *const u8, token_ids_count: usize, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if identity_id_ptr.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(identity_id_ptr); let mut id_bytes = [0u8; 32]; std::ptr::copy_nonoverlapping(identity_id_ptr, id_bytes.as_mut_ptr(), 32); let identity_id = dpp::prelude::Identifier::from(id_bytes); let Some(token_ids) = read_token_ids(token_ids_ptr, token_ids_count) else { - return PlatformWalletFFIResult::ErrorNullPointer; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorNullPointer, + "token_ids_ptr is null with non-zero count", + ); }; - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - let mgr = manager.identity_sync_arc(); - runtime() - .block_on(async move { mgr.update_watched_tokens(identity_id, token_ids).await }); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + let mgr = manager.identity_sync_arc(); + runtime().block_on(async move { mgr.update_watched_tokens(identity_id, token_ids).await }); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/identity_top_up.rs b/packages/rs-platform-wallet-ffi/src/identity_top_up.rs index ad335440f96..02d8dea1888 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_top_up.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_top_up.rs @@ -30,10 +30,12 @@ use dpp::fee::Credits; use dpp::prelude::Identifier; use rs_sdk_ffi::{SignerHandle, VTableSigner}; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::identity_registration::IdentityFundingInputFFI; use crate::runtime::block_on_worker; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; /// Top up an existing identity's credit balance from one or more /// Platform addresses, using an external `SignerHandle` for the @@ -66,27 +68,16 @@ pub unsafe extern "C" fn platform_wallet_top_up_from_addresses_with_signer( inputs_count: usize, signer_address_handle: *mut SignerHandle, out_new_balance: *mut u64, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - let invariant_violation: Option<&'static str> = if identity_id.is_null() { - Some("`identity_id` pointer is null") - } else if inputs.is_null() { - Some("`inputs` pointer is null") - } else if inputs_count == 0 { - Some("`inputs_count` is zero") - } else if signer_address_handle.is_null() { - Some("`signer_address_handle` pointer is null") - } else if out_new_balance.is_null() { - Some("`out_new_balance` pointer is null") - } else { - None - }; - if let Some(detail) = invariant_violation { - if !out_error.is_null() { - *out_error = - PlatformWalletFFIError::new(PlatformWalletFFIResult::ErrorNullPointer, detail); - } - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + check_ptr!(identity_id); + check_ptr!(inputs); + check_ptr!(signer_address_handle); + check_ptr!(out_new_balance); + if inputs_count == 0 { + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + "`inputs_count` is zero", + ); } let identity_id_bytes: [u8; 32] = *identity_id; @@ -99,13 +90,10 @@ pub unsafe extern "C" fn platform_wallet_top_up_from_addresses_with_signer( 0 => PlatformAddress::P2pkh(entry.hash), 1 => PlatformAddress::P2sh(entry.hash), _ => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - "invalid address_type (expected 0 or 1)", - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + "invalid address_type (expected 0 or 1)", + ); } }; // Caller may pass the same address twice (the Swift inputs @@ -128,42 +116,17 @@ pub unsafe extern "C" fn platform_wallet_top_up_from_addresses_with_signer( // is `Send + Sync` — see the unsafe impls in `rs-sdk-ffi/src/signer.rs`. let signer_addr = signer_address_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - - let result = block_on_worker(async move { - let address_signer: &VTableSigner = - unsafe { &*(signer_addr as *const VTableSigner) }; - - identity_wallet - .top_up_from_addresses(&identity_id, input_map, address_signer, None) - .await - }); - - match result { - Ok(new_balance) => { - *out_new_balance = new_balance; - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("top_up_from_addresses failed: {}", e), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let address_signer: &VTableSigner = unsafe { &*(signer_addr as *const VTableSigner) }; + identity_wallet + .top_up_from_addresses(&identity_id, input_map, address_signer, None) + .await }) + }); + let result = unwrap_option_or_return!(option); + let new_balance = unwrap_result_or_return!(result); + *out_new_balance = new_balance; + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/identity_transfer.rs b/packages/rs-platform-wallet-ffi/src/identity_transfer.rs index e56b52d4d91..6788a4bca26 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_transfer.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_transfer.rs @@ -21,10 +21,12 @@ use dpp::address_funds::PlatformAddress; use dpp::fee::Credits; use rs_sdk_ffi::{SignerHandle, VTableSigner}; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::*; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; /// One recipient of a credit transfer-to-addresses call. /// @@ -57,8 +59,6 @@ pub struct PlatformAddressCreditOutputFFI { /// - `signer_handle` must be a valid, non-destroyed handle produced by /// `dash_sdk_signer_create_with_ctx` (typically `KeychainSigner.handle`). /// Caller retains ownership; this function does NOT destroy it. -/// - `out_error` may be null only when the caller is willing to lose -/// the diagnostic message. #[no_mangle] pub unsafe extern "C" fn platform_wallet_transfer_credits_with_signer( wallet_handle: Handle, @@ -66,42 +66,11 @@ pub unsafe extern "C" fn platform_wallet_transfer_credits_with_signer( to_identity_id: *const u8, amount: u64, signer_handle: *mut SignerHandle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); - let from_id = match read_identifier(from_identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid from_identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let to_id = match read_identifier(to_identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid to_identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let from_id = unwrap_result_or_return!(read_identifier(from_identity_id)); + let to_id = unwrap_result_or_return!(read_identifier(to_identity_id)); // Round-trip the signer pointer through `usize` so the spawned // future has a `Send + 'static` capture (raw pointers are `!Send`, @@ -110,37 +79,18 @@ pub unsafe extern "C" fn platform_wallet_transfer_credits_with_signer( // `rs-sdk-ffi/src/signer.rs`). let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .transfer_credits_with_external_signer(&from_id, &to_id, amount, signer, None) - .await - }); - match result { - Ok(()) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("transfer_credits_with_signer failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .transfer_credits_with_external_signer(&from_id, &to_id, amount, signer, None) + .await }) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } /// Transfer credits from `from_identity_id` to a set of @@ -168,39 +118,17 @@ pub unsafe extern "C" fn platform_wallet_transfer_credits_to_addresses_with_sign outputs_count: usize, signer_handle: *mut SignerHandle, out_new_balance: *mut u64, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - if outputs.is_null() || outputs_count == 0 { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "outputs is null or empty", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); + check_ptr!(outputs); + if outputs_count == 0 { + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + "`outputs_count` is zero", + ); } - let from_id = match read_identifier(from_identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid from_identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let from_id = unwrap_result_or_return!(read_identifier(from_identity_id)); let entries = slice::from_raw_parts(outputs, outputs_count); let mut output_map: BTreeMap = BTreeMap::new(); @@ -209,13 +137,10 @@ pub unsafe extern "C" fn platform_wallet_transfer_credits_to_addresses_with_sign 0 => PlatformAddress::P2pkh(entry.hash), 1 => PlatformAddress::P2sh(entry.hash), _ => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - "invalid address_type (expected 0 or 1)", - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + "invalid address_type (expected 0 or 1)", + ); } }; output_map.insert(address, entry.credits); @@ -223,42 +148,21 @@ pub unsafe extern "C" fn platform_wallet_transfer_credits_to_addresses_with_sign let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .transfer_credits_to_addresses_with_external_signer( - &from_id, output_map, signer, None, - ) - .await - }); - match result { - Ok(new_balance) => { - if !out_new_balance.is_null() { - *out_new_balance = new_balance; - } - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("transfer_credits_to_addresses_with_signer failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .transfer_credits_to_addresses_with_external_signer( + &from_id, output_map, signer, None, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + let new_balance = unwrap_result_or_return!(result); + if !out_new_balance.is_null() { + *out_new_balance = new_balance; + } + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/identity_update.rs b/packages/rs-platform-wallet-ffi/src/identity_update.rs index a395e002491..38fdcd20855 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_update.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_update.rs @@ -12,40 +12,17 @@ use dpp::identity::{IdentityPublicKey, KeyType, Purpose, SecurityLevel}; use dpp::platform_value::BinaryData; use rs_sdk_ffi::{SignerHandle, VTableSigner}; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::identity_registration_with_signer::{decode_contract_bounds, IdentityPubkeyFFI}; use crate::runtime::block_on_worker; use crate::types::*; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; /// Update an identity by adding new public keys and/or disabling /// existing key IDs, signing the resulting `IdentityUpdateTransition` /// with the supplied `signer_handle`. -/// -/// The new keys are passed in as flat [`IdentityPubkeyFFI`] rows -/// (mirroring the registration FFI). Caller is responsible for -/// pre-persisting each new key's private material to whatever store -/// the signer reads from (iOS Keychain in the typical case) so the -/// signer can later sign with the newly-added keys; the signer here -/// only signs the update transition itself with an existing MASTER -/// key. -/// -/// Wraps -/// [`IdentityWallet::update_identity_with_external_signer`](platform_wallet::IdentityWallet::update_identity_with_external_signer). -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle registry. -/// - `identity_id` must point at a 32-byte buffer for the duration of -/// the call. -/// - `add_public_keys` must point at a valid `[IdentityPubkeyFFI; -/// add_public_keys_count]` array, and each row's `pubkey_bytes` -/// must be a valid `[u8; pubkey_len]` buffer for the duration of -/// the call. Either `(null, 0)` if no keys are being added. -/// - `disable_public_key_ids` must point at a valid -/// `[u32; disable_public_key_ids_count]` array. Either `(null, 0)` -/// if no keys are being disabled. -/// - `signer_handle` must be a valid, non-destroyed handle produced by -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_update_identity_with_signer( @@ -56,125 +33,52 @@ pub unsafe extern "C" fn platform_wallet_update_identity_with_signer( disable_public_key_ids: *const u32, disable_public_key_ids_count: usize, signer_handle: *mut SignerHandle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); - let id = match read_identifier(identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let id = unwrap_result_or_return!(read_identifier(identity_id)); - // Materialize add_public_keys into Vec before - // entering the wallet-storage closure so a parse failure is not - // gated on whether the wallet handle happens to be valid. - let add_keys: Vec = if add_public_keys.is_null() - || add_public_keys_count == 0 - { - Vec::new() - } else { - let rows: &[IdentityPubkeyFFI] = - slice::from_raw_parts(add_public_keys, add_public_keys_count); - let mut keys: Vec = Vec::with_capacity(rows.len()); - for (i, row) in rows.iter().enumerate() { - let key_type = match KeyType::try_from(row.key_type) { - Ok(kt) => kt, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!( - "add_public_keys[{}].key_type = {} is not a valid KeyType", - i, row.key_type - ), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; - let purpose = match Purpose::try_from(row.purpose) { - Ok(p) => p, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!( - "add_public_keys[{}].purpose = {} is not a valid Purpose", - i, row.purpose - ), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; - let security_level = match SecurityLevel::try_from(row.security_level) { - Ok(sl) => sl, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!( - "add_public_keys[{}].security_level = {} is not a valid SecurityLevel", - i, row.security_level - ), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; - if row.pubkey_bytes.is_null() || row.pubkey_len == 0 { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - format!("add_public_keys[{}].pubkey_bytes is null or empty", i), + let add_keys: Vec = + if add_public_keys.is_null() || add_public_keys_count == 0 { + Vec::new() + } else { + let rows: &[IdentityPubkeyFFI] = + slice::from_raw_parts(add_public_keys, add_public_keys_count); + let mut keys: Vec = Vec::with_capacity(rows.len()); + for (i, row) in rows.iter().enumerate() { + let key_type = unwrap_result_or_return!(KeyType::try_from(row.key_type)); + let purpose = unwrap_result_or_return!(Purpose::try_from(row.purpose)); + let security_level = + unwrap_result_or_return!(SecurityLevel::try_from(row.security_level)); + if row.pubkey_bytes.is_null() || row.pubkey_len == 0 { + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorNullPointer, + format!("add_public_keys[{i}].pubkey_bytes is null or empty"), ); } - return PlatformWalletFFIResult::ErrorNullPointer; - } - let pubkey_bytes: Vec = - slice::from_raw_parts(row.pubkey_bytes, row.pubkey_len).to_vec(); + let pubkey_bytes: Vec = + slice::from_raw_parts(row.pubkey_bytes, row.pubkey_len).to_vec(); - // Decode optional contract-bounds payload. `kind == 0` - // means "no bounds" — Authentication / Transfer keys - // and any caller that just doesn't set them. Encryption - // / Decryption keys MUST carry a value here for Drive - // to scope the key correctly; the helper rejects - // unscoped rows for those purposes. - let contract_bounds = - match decode_contract_bounds(row, purpose, i, "add_public_keys", out_error) { - Ok(b) => b, - Err(code) => return code, - }; + let contract_bounds = unwrap_result_or_return!(decode_contract_bounds( + row, + purpose, + i, + "add_public_keys" + )); - keys.push(IdentityPublicKey::V0(IdentityPublicKeyV0 { - id: row.key_id, - purpose, - security_level, - contract_bounds, - key_type, - read_only: row.read_only, - data: BinaryData::new(pubkey_bytes), - disabled_at: None, - })); - } - keys - }; + keys.push(IdentityPublicKey::V0(IdentityPublicKeyV0 { + id: row.key_id, + purpose, + security_level, + contract_bounds, + key_type, + read_only: row.read_only, + data: BinaryData::new(pubkey_bytes), + disabled_at: None, + })); + } + keys + }; let disable_ids: Vec = if disable_public_key_ids.is_null() || disable_public_key_ids_count == 0 { @@ -185,35 +89,16 @@ pub unsafe extern "C" fn platform_wallet_update_identity_with_signer( let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .update_identity_with_external_signer(&id, add_keys, disable_ids, signer, None) - .await - }); - match result { - Ok(()) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("update_identity_with_signer failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .update_identity_with_external_signer(&id, add_keys, disable_ids, signer, None) + .await }) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/identity_withdrawal.rs b/packages/rs-platform-wallet-ffi/src/identity_withdrawal.rs index 71ea63c66eb..f3a5930bfc3 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_withdrawal.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_withdrawal.rs @@ -1,8 +1,5 @@ //! FFI bindings for identity → Core address withdrawal driven by an //! external `SignerHandle`. -//! -//! The withdrawal state-transition signature crosses the FFI through -//! the supplied `signer_handle` (typically the iOS-side `KeychainSigner`). use std::ffi::CStr; use std::os::raw::c_char; @@ -11,31 +8,14 @@ use std::str::FromStr; use dashcore::Address as DashAddress; use rs_sdk_ffi::{SignerHandle, VTableSigner}; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::*; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; -/// Withdraw `amount` credits from `identity_id` to a Dash address -/// (`to_address` — `Address::from_str`-parseable, e.g. P2PKH base58 -/// like `"yNPbcFfabtNmmxKdGwhHomdYfVs6gikbPf"`) using the supplied -/// `signer_handle` for the identity-state-transition signature. -/// -/// Wraps -/// [`IdentityWallet::withdraw_credits_with_external_signer`](platform_wallet::IdentityWallet::withdraw_credits_with_external_signer). -/// On success the identity's local balance on `ManagedIdentity` is -/// updated (the Rust side performs the credit-debit) and a snapshot -/// changeset is emitted via the persister so the Swift -/// `PersistentIdentity` row refreshes. -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle registry. -/// - `identity_id` must point at a 32-byte buffer for the duration of -/// the call. -/// - `to_address` must be a NUL-terminated UTF-8 C-string for the -/// duration of the call. -/// - `signer_handle` must be a valid, non-destroyed handle produced by -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. +/// Withdraw `amount` credits from `identity_id` to a Dash address. #[no_mangle] pub unsafe extern "C" fn platform_wallet_withdraw_credits_with_signer( wallet_handle: Handle, @@ -43,113 +23,47 @@ pub unsafe extern "C" fn platform_wallet_withdraw_credits_with_signer( amount: u64, to_address: *const c_char, signer_handle: *mut SignerHandle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() || to_address.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle or to_address is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); + check_ptr!(to_address); - let id = match read_identifier(identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let id = unwrap_result_or_return!(read_identifier(identity_id)); - let to_address_str = match CStr::from_ptr(to_address).to_str() { - Ok(s) => s.to_string(), - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "to_address is not valid UTF-8", - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - }; - // `Address::from_str` returns an unchecked address; cross-check - // it against the wallet's active network before `assume_checked` - // so a mainnet-vs-testnet mismatch fails fast at the FFI boundary - // rather than as an opaque protocol-side error. - let to_address_unchecked = match DashAddress::from_str(&to_address_str) { - Ok(a) => a, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("Invalid Dash address: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; + let to_address_str = unwrap_result_or_return!(CStr::from_ptr(to_address).to_str()).to_string(); + + let to_address_unchecked = unwrap_result_or_return!(DashAddress::from_str(&to_address_str)); let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let wallet_network = wallet.platform().network(); - let to_address_parsed = - match to_address_unchecked.clone().require_network(wallet_network) { - Ok(addr) => addr, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!( - "Address network mismatch (wallet network {wallet_network:?}): {e}" - ), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; - let identity_wallet = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .withdraw_credits_with_external_signer( - &id, - amount, - &to_address_parsed, - signer, - None, - ) - .await - }); - match result { - Ok(()) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("withdraw_credits_with_signer failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = + PLATFORM_WALLET_STORAGE.with_item( + wallet_handle, + |wallet| -> Result< + Result<(), platform_wallet::PlatformWalletError>, + PlatformWalletFfiResult, + > { + let wallet_network = wallet.platform().network(); + let to_address_parsed = to_address_unchecked + .clone() + .require_network(wallet_network) + .map_err(PlatformWalletFfiResult::from)?; + let identity_wallet = wallet.identity().clone(); + Ok(block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .withdraw_credits_with_external_signer( + &id, + amount, + &to_address_parsed, + signer, + None, + ) + .await + })) + }, + ); + let inner = unwrap_option_or_return!(option); + let result = unwrap_result_or_return!(inner); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/managed_identity.rs b/packages/rs-platform-wallet-ffi/src/managed_identity.rs index 9ef427b7bec..e8c38573c32 100644 --- a/packages/rs-platform-wallet-ffi/src/managed_identity.rs +++ b/packages/rs-platform-wallet-ffi/src/managed_identity.rs @@ -1,6 +1,7 @@ use crate::error::*; use crate::handle::*; use crate::types::*; +use crate::{check_ptr, deref_ptr, unwrap_option_or_return, unwrap_result_or_return}; use dpp::identity::accessors::IdentityGettersV0; use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; use dpp::serialization::PlatformDeserializable; @@ -13,46 +14,21 @@ pub unsafe extern "C" fn managed_identity_create_from_identity_bytes( identity_bytes: *const std::os::raw::c_uchar, identity_len: usize, out_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if identity_bytes.is_null() || out_handle.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(identity_bytes); + check_ptr!(out_handle); let bytes = unsafe { std::slice::from_raw_parts(identity_bytes, identity_len) }; - // Deserialize Identity from bytes - let identity = match dpp::identity::Identity::deserialize_from_bytes_no_limit(bytes) { - Ok(id) => id, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorDeserialization, - format!("Failed to deserialize identity: {}", e), - ); - } - } - return PlatformWalletFFIResult::ErrorDeserialization; - } - }; + let identity = unwrap_result_or_return!( + dpp::identity::Identity::deserialize_from_bytes_no_limit(bytes) + ); - // Create ManagedIdentity from the deserialized Identity let managed_identity = ManagedIdentity::new(identity, 0); - - // Store in handle storage let handle = MANAGED_IDENTITY_STORAGE.insert(managed_identity); unsafe { *out_handle = handle }; - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } /// Get the identity ID into a 32-byte out-buffer. @@ -60,36 +36,14 @@ pub unsafe extern "C" fn managed_identity_create_from_identity_bytes( pub unsafe extern "C" fn managed_identity_get_id( identity_handle: Handle, out_id: *mut u8, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_id.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - unsafe { write_identifier(out_id, &identity.identity.id()) }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + check_ptr!(out_id); + + let option = + MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| identity.identity.id()); + let id = unwrap_option_or_return!(option); + unsafe { write_identifier(out_id, &id) }; + PlatformWalletFfiResult::ok() } /// Get the identity balance @@ -97,108 +51,41 @@ pub unsafe extern "C" fn managed_identity_get_id( pub unsafe extern "C" fn managed_identity_get_balance( identity_handle: Handle, out_balance: *mut u64, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_balance.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_balance); - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - unsafe { *out_balance = identity.identity.balance() }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = + MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| identity.identity.balance()); + *out_balance = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Get the label. /// /// `ManagedIdentity` no longer carries a `label` field — labels live -/// on Swift `PersistentIdentity.alias` instead. This entry point is -/// retained as a stub returning `null` so the existing Swift wrapper -/// keeps linking; callers should read `PersistentIdentity.alias` -/// directly via SwiftData. +/// on Swift `PersistentIdentity.alias` instead. Stub returning null. #[no_mangle] pub unsafe extern "C" fn managed_identity_get_label( identity_handle: Handle, out_label: *mut *mut c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_label.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_label); - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |_identity| { - unsafe { *out_label = std::ptr::null_mut() }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |_identity| ()); + unwrap_option_or_return!(option); + unsafe { *out_label = std::ptr::null_mut() }; + PlatformWalletFfiResult::ok() } /// Set the label — no-op stub. -/// -/// `ManagedIdentity` no longer carries a `label` field — Swift writes -/// labels directly to `PersistentIdentity.alias`. Existing call sites -/// see a `Success` return so they don't error; the Swift handler -/// updates SwiftData itself. #[no_mangle] pub unsafe extern "C" fn managed_identity_set_label( identity_handle: Handle, _label: *const c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - MANAGED_IDENTITY_STORAGE - .with_item_mut(identity_handle, |_identity| { - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let option = MANAGED_IDENTITY_STORAGE.with_item_mut(identity_handle, |_identity| ()); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Get last updated balance block time @@ -206,94 +93,43 @@ pub unsafe extern "C" fn managed_identity_set_label( pub unsafe extern "C" fn managed_identity_get_last_updated_balance_block_time( identity_handle: Handle, out_block_time: *mut BlockTime, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_block_time.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + check_ptr!(out_block_time); + + let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { + identity.last_updated_balance_block_time + }); + let bt = unwrap_option_or_return!(option); + unsafe { + *out_block_time = match bt { + Some(bt) => bt.into(), + None => BlockTime { + height: 0, + core_height: 0, + timestamp: 0, + }, + }; } - - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - if let Some(bt) = identity.last_updated_balance_block_time { - unsafe { *out_block_time = bt.into() }; - PlatformWalletFFIResult::Success - } else { - // Return zeroed block time if None - unsafe { - *out_block_time = BlockTime { - height: 0, - core_height: 0, - timestamp: 0, - }; - } - PlatformWalletFFIResult::Success - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + PlatformWalletFfiResult::ok() } /// Set last updated balance block time. -/// -/// `block_time` is a pointer to a `BlockTime` struct (24 bytes on -/// 64-bit), passed by reference to dodge the Swift / AAPCS64 ABI -/// mismatch that bites every >16-byte by-value struct. #[no_mangle] pub unsafe extern "C" fn managed_identity_set_last_updated_balance_block_time( identity_handle: Handle, block_time: *const BlockTime, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if block_time.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "block_time is null", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - let bt = unsafe { &*block_time }; +) -> PlatformWalletFfiResult { + let bt = deref_ptr!(block_time); let owned: platform_wallet::BlockTime = platform_wallet::BlockTime { height: bt.height, core_height: bt.core_height, timestamp: bt.timestamp, }; - MANAGED_IDENTITY_STORAGE - .with_item_mut(identity_handle, |identity| { - identity.last_updated_balance_block_time = Some(owned); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = MANAGED_IDENTITY_STORAGE.with_item_mut(identity_handle, |identity| { + identity.last_updated_balance_block_time = Some(owned); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Get last synced keys block time @@ -301,213 +137,115 @@ pub unsafe extern "C" fn managed_identity_set_last_updated_balance_block_time( pub unsafe extern "C" fn managed_identity_get_last_synced_keys_block_time( identity_handle: Handle, out_block_time: *mut BlockTime, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_block_time.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + check_ptr!(out_block_time); + + let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { + identity.last_synced_keys_block_time + }); + let bt = unwrap_option_or_return!(option); + unsafe { + *out_block_time = match bt { + Some(bt) => bt.into(), + None => BlockTime { + height: 0, + core_height: 0, + timestamp: 0, + }, + }; } - - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - if let Some(bt) = identity.last_synced_keys_block_time { - unsafe { *out_block_time = bt.into() }; - PlatformWalletFFIResult::Success - } else { - // Return zeroed block time if None - unsafe { - *out_block_time = BlockTime { - height: 0, - core_height: 0, - timestamp: 0, - }; - } - PlatformWalletFFIResult::Success - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + PlatformWalletFfiResult::ok() } /// Get the identity revision. -/// -/// Revisions advance on every platform-side state transition -/// (key add/remove, balance change, etc.). Value 0 indicates the -/// identity was just created and has not been mutated. #[no_mangle] pub unsafe extern "C" fn managed_identity_get_revision( identity_handle: Handle, out_revision: *mut u64, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_revision.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_revision); - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - unsafe { *out_revision = identity.identity.revision() }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = MANAGED_IDENTITY_STORAGE + .with_item(identity_handle, |identity| identity.identity.revision()); + *out_revision = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Flat C representation of an `IdentityPublicKey` for the Swift -/// wrapper. `disabled_at_is_some` / `disabled_at` together encode -/// the `Option` because C can't express Option -/// directly. `data_ptr` / `data_len` own a heap-allocated copy of -/// the public-key payload; the whole array plus the individual data -/// buffers are released by -/// [`managed_identity_free_public_keys`]. +/// wrapper. #[repr(C)] pub struct IdentityPublicKeyFFI { - /// DPP `KeyID` — matches `IdentityPublicKeyV0::id`. pub key_id: u32, - /// `Purpose` as its `#[repr(u8)]` discriminant. pub purpose: u8, - /// `SecurityLevel` as its `#[repr(u8)]` discriminant. pub security_level: u8, - /// `KeyType` as its `#[repr(u8)]` discriminant. pub key_type: u8, pub read_only: bool, - /// Set iff `disabled_at.is_some()`. pub disabled_at_is_some: bool, - /// Milliseconds-since-epoch timestamp; ignore unless - /// `disabled_at_is_some` is true. pub disabled_at: u64, - /// Heap-owned public-key bytes (compressed secp256k1, BLS, - /// hash160, etc. — depends on `key_type`). Freed with the whole - /// array by `managed_identity_free_public_keys`. pub data_ptr: *mut u8, pub data_len: usize, } /// Snapshot every `IdentityPublicKey` on the identity into a flat -/// heap-allocated array. The caller takes ownership of the array -/// pointer and of the inner `data_ptr` blobs; both are released by -/// a single call to [`managed_identity_free_public_keys`]. -/// -/// On success `out_keys` / `out_count` are populated. `out_count` is -/// zero for an identity with no keys and `out_keys` is `null`. -/// Contract bounds are intentionally omitted — the first-pass use -/// site (address-funded registration) never sets them, and bridging -/// the `ContractBounds` enum would expand the FFI surface. +/// heap-allocated array. Free with [`managed_identity_free_public_keys`]. #[no_mangle] pub unsafe extern "C" fn managed_identity_get_public_keys( identity_handle: Handle, out_keys: *mut *mut IdentityPublicKeyFFI, out_count: *mut usize, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_keys.is_null() || out_count.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } +) -> PlatformWalletFfiResult { + check_ptr!(out_keys); + check_ptr!(out_count); + + let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { + let keys = identity.identity.public_keys(); + let mut buf: Vec = Vec::with_capacity(keys.len()); + for (&key_id, pk) in keys { + let data_bytes = pk.data().as_slice().to_vec(); + let data_len = data_bytes.len(); + let data_boxed = data_bytes.into_boxed_slice(); + let data_ptr = Box::into_raw(data_boxed) as *mut u8; + + let (disabled_some, disabled_val) = match pk.disabled_at() { + Some(ts) => (true, ts), + None => (false, 0u64), + }; + + buf.push(IdentityPublicKeyFFI { + key_id, + purpose: pk.purpose() as u8, + security_level: pk.security_level() as u8, + key_type: pk.key_type() as u8, + read_only: pk.read_only(), + disabled_at_is_some: disabled_some, + disabled_at: disabled_val, + data_ptr, + data_len, + }); } - return PlatformWalletFFIResult::ErrorNullPointer; - } + buf + }); + let buf = unwrap_option_or_return!(option); - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - let keys = identity.identity.public_keys(); - if keys.is_empty() { - unsafe { - *out_keys = std::ptr::null_mut(); - *out_count = 0; - } - return PlatformWalletFFIResult::Success; - } - - let mut buf: Vec = Vec::with_capacity(keys.len()); - for (&key_id, pk) in keys { - let data_bytes = pk.data().as_slice().to_vec(); - let data_len = data_bytes.len(); - let data_boxed = data_bytes.into_boxed_slice(); - let data_ptr = Box::into_raw(data_boxed) as *mut u8; - - let (disabled_some, disabled_val) = match pk.disabled_at() { - Some(ts) => (true, ts), - None => (false, 0u64), - }; - - buf.push(IdentityPublicKeyFFI { - key_id, - purpose: pk.purpose() as u8, - security_level: pk.security_level() as u8, - key_type: pk.key_type() as u8, - read_only: pk.read_only(), - disabled_at_is_some: disabled_some, - disabled_at: disabled_val, - data_ptr, - data_len, - }); - } - - let count = buf.len(); - let boxed = buf.into_boxed_slice(); - let array_ptr = Box::into_raw(boxed) as *mut IdentityPublicKeyFFI; - unsafe { - *out_keys = array_ptr; - *out_count = count; - } - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + if buf.is_empty() { + unsafe { + *out_keys = std::ptr::null_mut(); + *out_count = 0; + } + return PlatformWalletFfiResult::ok(); + } + let count = buf.len(); + let boxed = buf.into_boxed_slice(); + let array_ptr = Box::into_raw(boxed) as *mut IdentityPublicKeyFFI; + unsafe { + *out_keys = array_ptr; + *out_count = count; + } + PlatformWalletFfiResult::ok() } /// Release an array previously returned by -/// [`managed_identity_get_public_keys`]. Walks the array to free -/// every `data_ptr` blob before releasing the array itself. -/// -/// Safe to call with `keys = null` / `count = 0` — both are no-ops. +/// [`managed_identity_get_public_keys`]. #[no_mangle] pub unsafe extern "C" fn managed_identity_free_public_keys( keys: *mut IdentityPublicKeyFFI, @@ -516,7 +254,6 @@ pub unsafe extern "C" fn managed_identity_free_public_keys( if keys.is_null() || count == 0 { return; } - // Reconstruct the slice box so we can iterate + free inner data. let slice = unsafe { std::slice::from_raw_parts_mut(keys, count) }; for entry in slice.iter_mut() { if !entry.data_ptr.is_null() && entry.data_len > 0 { @@ -530,19 +267,18 @@ pub unsafe extern "C" fn managed_identity_free_public_keys( let _ = unsafe { Box::from_raw(slice as *mut [IdentityPublicKeyFFI]) }; } -// Note: managed_identity_to_json is not currently available because -// ManagedIdentity does not implement Serialize. This would require -// significant work to add custom serialization for all internal types. - /// Destroy ManagedIdentity and free resources #[no_mangle] pub unsafe extern "C" fn managed_identity_destroy( identity_handle: Handle, -) -> PlatformWalletFFIResult { +) -> PlatformWalletFfiResult { if MANAGED_IDENTITY_STORAGE.remove(identity_handle).is_some() { - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } else { - PlatformWalletFFIResult::ErrorInvalidHandle + PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidHandle, + "Invalid identity handle", + ) } } @@ -585,27 +321,18 @@ mod tests { #[test] fn test_get_and_set_label_stub_returns_null() { - // `ManagedIdentity` no longer has a label field — both - // entry points are no-op stubs (set succeeds without touching - // anything; get always returns null). Verify the stubs link - // and behave consistently. unsafe { let identity = create_test_identity(); let managed = platform_wallet::ManagedIdentity::new(identity, 0); let handle = MANAGED_IDENTITY_STORAGE.insert(managed); let label = std::ffi::CString::new("Test Identity").unwrap(); - let mut error = PlatformWalletFFIError::success(); - - let result = managed_identity_set_label(handle, label.as_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let _ = managed_identity_set_label(handle, label.as_ptr()); let mut label_ptr: *mut c_char = std::ptr::null_mut(); - let result = managed_identity_get_label(handle, &mut label_ptr, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let _ = managed_identity_get_label(handle, &mut label_ptr); assert!(label_ptr.is_null()); - // Cleanup managed_identity_destroy(handle); } } @@ -618,12 +345,8 @@ mod tests { let handle = MANAGED_IDENTITY_STORAGE.insert(managed); let mut balance: u64 = 0; - let mut error = PlatformWalletFFIError::success(); + let _ = managed_identity_get_balance(handle, &mut balance); - let result = managed_identity_get_balance(handle, &mut balance, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); - - // Cleanup managed_identity_destroy(handle); } } @@ -641,33 +364,18 @@ mod tests { timestamp: 1234567890, }; - let mut error = PlatformWalletFFIError::success(); - - // Set block time - let result = managed_identity_set_last_updated_balance_block_time( - handle, - &block_time, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + let _ = managed_identity_set_last_updated_balance_block_time(handle, &block_time); - // Get block time let mut retrieved_bt = BlockTime { height: 0, core_height: 0, timestamp: 0, }; - let result = managed_identity_get_last_updated_balance_block_time( - handle, - &mut retrieved_bt, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + let _ = managed_identity_get_last_updated_balance_block_time(handle, &mut retrieved_bt); assert_eq!(retrieved_bt.height, 100); assert_eq!(retrieved_bt.core_height, 200); assert_eq!(retrieved_bt.timestamp, 1234567890); - // Cleanup managed_identity_destroy(handle); } } diff --git a/packages/rs-platform-wallet-ffi/src/manager.rs b/packages/rs-platform-wallet-ffi/src/manager.rs index df43838fcb1..dfd249ca249 100644 --- a/packages/rs-platform-wallet-ffi/src/manager.rs +++ b/packages/rs-platform-wallet-ffi/src/manager.rs @@ -1,10 +1,12 @@ //! FFI bindings for PlatformWalletManager (wallet lifecycle management). +use crate::check_ptr; use crate::error::*; use crate::event_handler::{EventHandlerCallbacks, FFIEventHandler}; use crate::handle::*; use crate::persistence::{FFIPersister, PersistenceCallbacks}; use crate::runtime::runtime; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; use dash_sdk::Sdk; use key_wallet::wallet::initialization::WalletAccountCreationOptions; @@ -35,15 +37,11 @@ pub unsafe extern "C" fn platform_wallet_manager_create( persistence: *const PersistenceCallbacks, event_handler: *const EventHandlerCallbacks, out_handle: *mut Handle, - // No detailed error path today — the constructor is infallible - // once the null-pointer check passes. Kept in the ABI for - // forward-compat. - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if sdk_ptr.is_null() || persistence.is_null() || event_handler.is_null() || out_handle.is_null() - { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(sdk_ptr); + check_ptr!(persistence); + check_ptr!(event_handler); + check_ptr!(out_handle); let sdk = Arc::new((*(sdk_ptr as *const Sdk)).clone()); let persister = Arc::new(FFIPersister::new(std::ptr::read(persistence))); @@ -64,7 +62,7 @@ pub unsafe extern "C" fn platform_wallet_manager_create( let handle = PLATFORM_WALLET_MANAGER_STORAGE.insert(manager); *out_handle = handle; - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } /// Create a wallet from raw seed bytes (64 bytes). @@ -80,19 +78,15 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_seed( account_options: u32, out_wallet_handle: *mut Handle, out_wallet_id: *mut [u8; 32], - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if seed_bytes.is_null() || out_wallet_handle.is_null() || out_wallet_id.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(seed_bytes); + check_ptr!(out_wallet_handle); + check_ptr!(out_wallet_id); if seed_len != 64 { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("Seed must be 64 bytes, got {}", seed_len), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + format!("Seed must be 64 bytes, got {seed_len}"), + ); } let network = match network { @@ -101,13 +95,10 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_seed( 2 => Network::Devnet, 3 => Network::Regtest, _ => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidNetwork, - format!("Unknown network: {}", network), - ); - } - return PlatformWalletFFIResult::ErrorInvalidNetwork; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidNetwork, + format!("Unknown network: {network}"), + ); } }; @@ -120,29 +111,16 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_seed( _ => WalletAccountCreationOptions::Default, }; - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(manager_handle, |manager| { - match runtime().block_on(manager.create_wallet_from_seed_bytes(network, seed, accounts)) - { - Ok(wallet) => { - let wallet_id = wallet.wallet_id(); - let wallet_handle = PLATFORM_WALLET_STORAGE.insert(wallet); - *out_wallet_handle = wallet_handle; - *out_wallet_id = wallet_id; - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(manager_handle, |manager| { + runtime().block_on(manager.create_wallet_from_seed_bytes(network, seed, accounts)) + }); + let result = unwrap_option_or_return!(option); + let wallet = unwrap_result_or_return!(result); + let wallet_id = wallet.wallet_id(); + let wallet_handle = PLATFORM_WALLET_STORAGE.insert(wallet); + *out_wallet_handle = wallet_handle; + *out_wallet_id = wallet_id; + PlatformWalletFfiResult::ok() } /// Create a wallet from a BIP39 mnemonic phrase (English). @@ -157,24 +135,12 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_mnemonic( account_options: u32, out_wallet_handle: *mut Handle, out_wallet_id: *mut [u8; 32], - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if mnemonic.is_null() || out_wallet_handle.is_null() || out_wallet_id.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(mnemonic); + check_ptr!(out_wallet_handle); + check_ptr!(out_wallet_id); - let mnemonic_str = match std::ffi::CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "Invalid UTF-8 in mnemonic", - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - }; + let mnemonic_str = unwrap_result_or_return!(std::ffi::CStr::from_ptr(mnemonic).to_str()); let network = match network { 0 => Network::Mainnet, @@ -182,13 +148,10 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_mnemonic( 2 => Network::Devnet, 3 => Network::Regtest, _ => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidNetwork, - format!("Unknown network: {}", network), - ); - } - return PlatformWalletFFIResult::ErrorInvalidNetwork; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidNetwork, + format!("Unknown network: {network}"), + ); } }; @@ -197,32 +160,16 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_mnemonic( _ => WalletAccountCreationOptions::Default, }; - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(manager_handle, |manager| { - match runtime().block_on(manager.create_wallet_from_mnemonic( - mnemonic_str, - network, - accounts, - )) { - Ok(wallet) => { - let wallet_id = wallet.wallet_id(); - let wallet_handle = PLATFORM_WALLET_STORAGE.insert(wallet); - *out_wallet_handle = wallet_handle; - *out_wallet_id = wallet_id; - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(manager_handle, |manager| { + runtime().block_on(manager.create_wallet_from_mnemonic(mnemonic_str, network, accounts)) + }); + let result = unwrap_option_or_return!(option); + let wallet = unwrap_result_or_return!(result); + let wallet_id = wallet.wallet_id(); + let wallet_handle = PLATFORM_WALLET_STORAGE.insert(wallet); + *out_wallet_handle = wallet_handle; + *out_wallet_id = wallet_id; + PlatformWalletFfiResult::ok() } /// Hydrate the manager from its persister. @@ -237,74 +184,55 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_mnemonic( #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_load_from_persistor( manager_handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(manager_handle, |manager| { - match runtime().block_on(manager.load_from_persistor()) { - Ok(()) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) +) -> PlatformWalletFfiResult { + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(manager_handle, |manager| { + runtime().block_on(manager.load_from_persistor()) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } /// Get a `PlatformWallet` handle for a wallet registered in the -/// manager. Returns `ErrorWalletNotFound` if no wallet with the given +/// manager. Returns `NotFound` if no wallet with the given /// id is currently held. #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_get_wallet( manager_handle: Handle, wallet_id: *const [u8; 32], out_wallet_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if wallet_id.is_null() || out_wallet_handle.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(wallet_id); + check_ptr!(out_wallet_handle); let wallet_id_value = *wallet_id; - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(manager_handle, |manager| { - match runtime().block_on(manager.get_wallet(&wallet_id_value)) { - Some(wallet) => { - let handle = PLATFORM_WALLET_STORAGE.insert(wallet); - *out_wallet_handle = handle; - PlatformWalletFFIResult::Success - } - None => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!( - "Wallet {} not found in manager", - hex::encode(wallet_id_value) - ), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(manager_handle, |manager| { + runtime().block_on(manager.get_wallet(&wallet_id_value)) + }); + let inner = unwrap_option_or_return!(option); + match inner { + Some(wallet) => { + let handle = PLATFORM_WALLET_STORAGE.insert(wallet); + *out_wallet_handle = handle; + PlatformWalletFfiResult::ok() + } + None => PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::NotFound, + format!( + "Wallet {} not found in manager", + hex::encode(wallet_id_value) + ), + ), + } } /// Destroy a PlatformWalletManager handle. #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_destroy( handle: Handle, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { +) -> PlatformWalletFfiResult { if let Some(manager) = PLATFORM_WALLET_MANAGER_STORAGE.remove(handle) { manager.platform_address_sync().stop(); } - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/memory_explorer.rs b/packages/rs-platform-wallet-ffi/src/memory_explorer.rs index 1b9073e6e0c..1a760315242 100644 --- a/packages/rs-platform-wallet-ffi/src/memory_explorer.rs +++ b/packages/rs-platform-wallet-ffi/src/memory_explorer.rs @@ -24,35 +24,20 @@ use crate::error::*; use crate::handle::*; use crate::types::*; +use crate::{check_ptr, unwrap_option_or_return}; use dpp::identity::accessors::IdentityGettersV0; use platform_wallet::wallet::identity::state::managed_identity::IdentityStatus; -/// Per-wallet snapshot returned by -/// [`platform_wallet_get_in_memory_summary`]. -/// -/// Caller-owned; written into a slot the caller already allocates so -/// no `_free` is required. +/// Per-wallet snapshot returned by [`platform_wallet_get_in_memory_summary`]. #[repr(C)] pub struct PlatformWalletMemorySummaryFFI { - /// Number of identities the wallet manages (signing-capable; lives - /// in the wallet bucket). pub identities_count: usize, - /// Number of out-of-wallet (read-only / observed) identities. pub watched_count: usize, - /// One past the wallet's highest already-registered identity index, - /// matching the resume position the gap-limit scanner uses next. - /// Zero when the wallet has no managed identities yet. pub last_scanned_index: u32, - /// Number of tracked asset locks the wallet currently holds in - /// memory (`PlatformWalletInfo.tracked_asset_locks`). pub tracked_asset_locks_count: usize, } /// Identity lifecycle status mirror. -/// -/// Maps directly to `platform_wallet::wallet::identity::state:: -/// managed_identity::IdentityStatus`. Exposed as a `#[repr(u8)]` -/// enum so Swift can read it as a plain integer return value. #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum IdentityStatusFFI { @@ -75,309 +60,135 @@ impl From for IdentityStatusFFI { } } -/// List the ids of every identity the wallet currently manages -/// (signing-capable identities in the wallet bucket). -/// -/// Iterates `info.identity_manager.wallet_identities` values via -/// [`IdentifierArray`]. Release with -/// [`crate::platform_wallet_identifier_array_free`]. +/// List the ids of every identity the wallet currently manages. #[no_mangle] pub unsafe extern "C" fn platform_wallet_list_in_memory_identity_ids( wallet_handle: Handle, out_array: *mut IdentifierArray, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_array.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_array is null", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_array); - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let wm = wallet.wallet_manager().blocking_read(); - let info = match wm.get_wallet_info(&wallet.wallet_id()) { - Some(i) => i, - None => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Wallet info not found for wallet handle", - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidHandle; - } - }; - let ids: Vec = info - .identity_manager - .wallet_identities - .values() - .flat_map(|inner| inner.values().map(|m| m.identity.id())) - .collect(); - unsafe { *out_array = IdentifierArray::new(ids) }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let wm = wallet.wallet_manager().blocking_read(); + let info = wm.get_wallet_info(&wallet.wallet_id())?; + let ids: Vec = info + .identity_manager + .wallet_identities + .values() + .flat_map(|inner| inner.values().map(|m| m.identity.id())) + .collect(); + Some(ids) + }); + let inner = unwrap_option_or_return!(option); + let ids = unwrap_option_or_return!(inner); + unsafe { *out_array = IdentifierArray::new(ids) }; + PlatformWalletFfiResult::ok() } -/// List the ids of every out-of-wallet / observed identity the wallet -/// currently holds. -/// -/// Returns the keys of `info.identity_manager.out_of_wallet_identities` -/// via [`IdentifierArray`]. Release with -/// [`crate::platform_wallet_identifier_array_free`]. +/// List the ids of every out-of-wallet / observed identity. #[no_mangle] pub unsafe extern "C" fn platform_wallet_list_in_memory_watched_identity_ids( wallet_handle: Handle, out_array: *mut IdentifierArray, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_array.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_array is null", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_array); - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let wm = wallet.wallet_manager().blocking_read(); - let info = match wm.get_wallet_info(&wallet.wallet_id()) { - Some(i) => i, - None => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Wallet info not found for wallet handle", - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidHandle; - } - }; - let ids: Vec = info - .identity_manager - .out_of_wallet_identities - .keys() - .copied() - .collect(); - unsafe { *out_array = IdentifierArray::new(ids) }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let wm = wallet.wallet_manager().blocking_read(); + let info = wm.get_wallet_info(&wallet.wallet_id())?; + let ids: Vec = info + .identity_manager + .out_of_wallet_identities + .keys() + .copied() + .collect(); + Some(ids) + }); + let inner = unwrap_option_or_return!(option); + let ids = unwrap_option_or_return!(inner); + unsafe { *out_array = IdentifierArray::new(ids) }; + PlatformWalletFfiResult::ok() } /// Populate `out` with a snapshot of the wallet's in-memory state. -/// -/// Caller-owned out-pointer (struct is > 16 bytes — keeps us inside -/// the AAPCS64 / Swift-ABI safe lane). On error the slot is left -/// untouched. #[no_mangle] pub unsafe extern "C" fn platform_wallet_get_in_memory_summary( wallet_handle: Handle, out: *mut PlatformWalletMemorySummaryFFI, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out is null", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let wallet_id = wallet.wallet_id(); - let wm = wallet.wallet_manager().blocking_read(); - let info = match wm.get_wallet_info(&wallet_id) { - Some(i) => i, - None => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Wallet info not found for wallet handle", - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidHandle; - } - }; - // `identities_count` follows the new bucket layout — - // sum the inner-map lengths under the wallet bucket. - let identities_count: usize = info - .identity_manager - .wallet_identities - .values() - .map(|m| m.len()) - .sum(); - let watched_count = info.identity_manager.out_of_wallet_identities.len(); - // Resume position the gap-limit scanner would use next — - // one past the highest already-registered slot for this - // wallet, or 0 when nothing has been registered yet. - let last_scanned_index = info - .identity_manager - .highest_registration_index(&wallet_id) - .map_or(0u32, |i| i + 1); - let tracked_asset_locks_count = info.tracked_asset_locks.len(); +) -> PlatformWalletFfiResult { + check_ptr!(out); - unsafe { - *out = PlatformWalletMemorySummaryFFI { - identities_count, - watched_count, - last_scanned_index, - tracked_asset_locks_count, - }; - } - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let wallet_id = wallet.wallet_id(); + let wm = wallet.wallet_manager().blocking_read(); + let info = wm.get_wallet_info(&wallet_id)?; + // `identities_count` follows the new bucket layout — + // sum the inner-map lengths under the wallet bucket. + let identities_count: usize = info + .identity_manager + .wallet_identities + .values() + .map(|m| m.len()) + .sum(); + let watched_count = info.identity_manager.out_of_wallet_identities.len(); + // Resume position the gap-limit scanner would use next — + // one past the highest already-registered slot for this + // wallet, or 0 when nothing has been registered yet. + let last_scanned_index = info + .identity_manager + .highest_registration_index(&wallet_id) + .map_or(0u32, |i| i + 1); + let tracked_asset_locks_count = info.tracked_asset_locks.len(); + Some(PlatformWalletMemorySummaryFFI { + identities_count, + watched_count, + last_scanned_index, + tracked_asset_locks_count, }) + }); + let inner = unwrap_option_or_return!(option); + let summary = unwrap_option_or_return!(inner); + unsafe { *out = summary }; + PlatformWalletFfiResult::ok() } /// Read the BIP-9 identity index recorded on a managed identity. -/// -/// Mirrors `ManagedIdentity.identity_index` — the position in -/// `m/9'/coin'/5'/0'/key_type'/identity_index'/key_id'` used to -/// derive this identity's keys. Useful for the explorer view to -/// surface "which HD slot owns this id". -/// -/// Out-of-wallet (observed) identities have no derivation context: -/// when the underlying `Option` is `None`, this writes -/// `*out_has_index = false` and `*out_index = 0` and still returns -/// `Success` — both states are valid, not errors. #[no_mangle] pub unsafe extern "C" fn managed_identity_get_identity_index( identity_handle: Handle, out_has_index: *mut bool, out_index: *mut u32, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_has_index.is_null() || out_index.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_has_index or out_index is null", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_has_index); + check_ptr!(out_index); - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - match identity.identity_index { - Some(idx) => unsafe { - *out_has_index = true; - *out_index = idx; - }, - None => unsafe { - *out_has_index = false; - *out_index = 0; - }, - } - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = + MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| identity.identity_index); + let idx = unwrap_option_or_return!(option); + match idx { + Some(i) => unsafe { + *out_has_index = true; + *out_index = i; + }, + None => unsafe { + *out_has_index = false; + *out_index = 0; + }, + } + PlatformWalletFfiResult::ok() } /// Read the lifecycle status of a managed identity. -/// -/// `out_status` receives an [`IdentityStatusFFI`] discriminant. The -/// `Default` for the underlying enum is `Unknown`, so the value is -/// always meaningful even for freshly-created identities. #[no_mangle] pub unsafe extern "C" fn managed_identity_get_status( identity_handle: Handle, out_status: *mut u8, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_status.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_status is null", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_status); - MANAGED_IDENTITY_STORAGE - .with_item(identity_handle, |identity| { - let status: IdentityStatusFFI = identity.status.into(); - unsafe { *out_status = status as u8 }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) + let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { + IdentityStatusFFI::from(identity.status) as u8 + }); + *out_status = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/platform_address_sync.rs b/packages/rs-platform-wallet-ffi/src/platform_address_sync.rs index 056dafb4f4b..c0be333c11b 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_address_sync.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_address_sync.rs @@ -9,6 +9,7 @@ use crate::error::*; use crate::handle::*; use crate::platform_address_types::AddressSyncConfigFFI; use crate::runtime::runtime; +use crate::{check_ptr, unwrap_option_or_return}; /// Flattened sync metrics for one wallet result in a platform-address sync pass. #[repr(C)] @@ -79,45 +80,25 @@ impl Default for PlatformAddressSyncWalletResultFFI { #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_start( handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - let _entered = runtime().enter(); - manager.platform_address_sync_arc().start(); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + let _entered = runtime().enter(); + manager.platform_address_sync_arc().start(); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Stop the platform-address sync manager if it is running. #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_stop( handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - manager.platform_address_sync().stop(); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + manager.platform_address_sync().stop(); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Whether the platform-address sync manager background loop is running. @@ -125,26 +106,14 @@ pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_stop( pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_is_running( handle: Handle, out_running: *mut bool, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_running.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } - - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - *out_running = manager.platform_address_sync().is_running(); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + check_ptr!(out_running); + + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + manager.platform_address_sync().is_running() + }); + *out_running = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Whether a platform-address sync pass is currently in flight. @@ -152,26 +121,14 @@ pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_is_runnin pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_is_syncing( handle: Handle, out_syncing: *mut bool, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_syncing.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } - - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - *out_syncing = manager.platform_address_sync().is_syncing(); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + check_ptr!(out_syncing); + + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + manager.platform_address_sync().is_syncing() + }); + *out_syncing = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Unix seconds of the last completed platform-address sync pass, or 0 if none ran. @@ -179,29 +136,17 @@ pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_is_syncin pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_last_sync_unix_seconds( handle: Handle, out_last_sync_unix: *mut u64, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_last_sync_unix.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } - - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - *out_last_sync_unix = manager - .platform_address_sync() - .last_sync_unix_seconds() - .unwrap_or(0); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + check_ptr!(out_last_sync_unix); + + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + manager + .platform_address_sync() + .last_sync_unix_seconds() + .unwrap_or(0) + }); + *out_last_sync_unix = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Set the background platform-address sync interval in seconds. @@ -209,24 +154,14 @@ pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_last_sync pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_set_interval( handle: Handle, interval_seconds: u64, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - manager - .platform_address_sync() - .set_interval(Duration::from_secs(interval_seconds)); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + manager + .platform_address_sync() + .set_interval(Duration::from_secs(interval_seconds)); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Replace the shared platform-address sync config used on each pass. @@ -236,47 +171,27 @@ pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_set_inter pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_set_config( handle: Handle, config: *const AddressSyncConfigFFI, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - let config = if config.is_null() { - None - } else { - Some((*config).into()) - }; - manager.platform_address_sync().set_config(config); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let cfg = if config.is_null() { + None + } else { + Some((*config).into()) + }; + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + manager.platform_address_sync().set_config(cfg); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Run one platform-address sync pass across all registered wallets. #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_sync_now( handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - runtime().block_on(manager.platform_address_sync().sync_now()); - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid manager handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + runtime().block_on(manager.platform_address_sync().sync_now()); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/platform_addresses/fund_from_asset_lock.rs b/packages/rs-platform-wallet-ffi/src/platform_addresses/fund_from_asset_lock.rs index 361a0ac18b9..cdc7c8859b8 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_addresses/fund_from_asset_lock.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_addresses/fund_from_asset_lock.rs @@ -1,35 +1,16 @@ //! FFI bindings for funding platform addresses from asset locks. +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::platform_address_types::*; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; use dpp::address_funds::PlatformAddress; use rs_sdk_ffi::{SignerHandle, VTableSigner}; use super::runtime; /// Fund platform addresses from a Core L1 asset lock. -/// -/// `asset_lock_proof_bytes` is the bincode-serialized `AssetLockProof`. -/// `private_key_bytes` must point to exactly 32 bytes — this is the -/// asset-lock private key derived from the Core funding flow, used to -/// authorize the asset-lock contribution itself. It is unrelated to -/// the platform-address signer. -/// -/// `signer_address_handle` is a `*mut SignerHandle` produced by -/// `dash_sdk_signer_create_with_ctx` (e.g. via `KeychainSigner.handle`) -/// and is consumed as `Signer` for any -/// previously-funded input address present in `addresses` (entries -/// with `has_balance = true`). Even when the call only carries the -/// freshly-locked output (the single `has_balance = false` entry), -/// the signer slot is still required so the SDK can resolve the -/// trait bound — pass any valid handle (typically the wallet's -/// `KeychainSigner.handle`). The caller retains ownership; this -/// function does NOT destroy it. -/// -/// Exactly one entry in `addresses` must have `has_balance = false`. -/// -/// Free result with `platform_address_wallet_free_changeset`. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_address_wallet_fund_from_asset_lock( @@ -44,40 +25,16 @@ pub unsafe extern "C" fn platform_address_wallet_fund_from_asset_lock( fee_strategy_count: usize, signer_address_handle: *mut SignerHandle, out_changeset: *mut PlatformAddressChangeSetFFI, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_changeset.is_null() - || addresses.is_null() - || asset_lock_proof_bytes.is_null() - || private_key_bytes.is_null() - { - return PlatformWalletFFIResult::ErrorNullPointer; - } - if signer_address_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_address_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_changeset); + check_ptr!(addresses); + check_ptr!(asset_lock_proof_bytes); + check_ptr!(private_key_bytes); + check_ptr!(signer_address_handle); - // Parse addresses let mut address_map = std::collections::BTreeMap::new(); for entry in std::slice::from_raw_parts(addresses, addresses_count) { - let addr = match PlatformAddress::try_from(entry.address) { - Ok(a) => a, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - e, - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; + let addr = unwrap_result_or_return!(PlatformAddress::try_from(entry.address)); let balance = if entry.has_balance { Some(entry.balance) } else { @@ -86,80 +43,33 @@ pub unsafe extern "C" fn platform_address_wallet_fund_from_asset_lock( address_map.insert(addr, balance); } - // Deserialize asset lock proof (bincode-encoded) let proof_bytes = std::slice::from_raw_parts(asset_lock_proof_bytes, asset_lock_proof_len); - let asset_lock_proof: dpp::prelude::AssetLockProof = - match dpp::bincode::decode_from_slice(proof_bytes, dpp::bincode::config::standard()) { - Ok((proof, _)) => proof, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorDeserialization, - format!("Failed to deserialize AssetLockProof: {}", e), - ); - } - return PlatformWalletFFIResult::ErrorDeserialization; - } - }; + let (asset_lock_proof, _): (dpp::prelude::AssetLockProof, usize) = unwrap_result_or_return!( + dpp::bincode::decode_from_slice(proof_bytes, dpp::bincode::config::standard(),) + ); - // Parse private key (network is irrelevant for raw key bytes) - let key_bytes = std::slice::from_raw_parts(private_key_bytes, 32); - let key_array: &[u8; 32] = match key_bytes.try_into() { - Ok(arr) => arr, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - "Private key must be exactly 32 bytes", - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; - let private_key = - match dashcore::PrivateKey::from_byte_array(key_array, dashcore::Network::Mainnet) { - Ok(k) => k, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("Invalid private key: {}", e), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; + let key_array = &*(private_key_bytes as *const [u8; 32]); + let private_key = unwrap_result_or_return!(dashcore::PrivateKey::from_byte_array( + key_array, + dashcore::Network::Mainnet, + )); let fee = parse_fee_strategy(fee_strategy, fee_strategy_count); - // SAFETY: caller guarantees `signer_address_handle` is a valid, - // non-destroyed handle that outlives this call. let address_signer: &VTableSigner = &*(signer_address_handle as *const VTableSigner); - PLATFORM_ADDRESS_WALLET_STORAGE - .with_item(handle, |wallet| { - match runtime().block_on(wallet.fund_from_asset_lock( - account_index, - address_map, - asset_lock_proof, - private_key, - fee, - address_signer, - )) { - Ok(changeset) => { - *out_changeset = PlatformAddressChangeSetFFI::from(&changeset); - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = PLATFORM_ADDRESS_WALLET_STORAGE.with_item(handle, |wallet| { + runtime().block_on(wallet.fund_from_asset_lock( + account_index, + address_map, + asset_lock_proof, + private_key, + fee, + address_signer, + )) + }); + let result = unwrap_option_or_return!(option); + let changeset = unwrap_result_or_return!(result); + *out_changeset = PlatformAddressChangeSetFFI::from(&changeset); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/platform_addresses/mod.rs b/packages/rs-platform-wallet-ffi/src/platform_addresses/mod.rs index 9ba5f991ef4..9d58e17ece4 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_addresses/mod.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_addresses/mod.rs @@ -23,6 +23,10 @@ use crate::runtime::runtime; /// Parse an `InputSelectionType` + raw arrays into a Rust `InputSelection`. /// +/// On error, returns the populated `PlatformWalletFfiResult` so the caller +/// can early-return it directly: +/// `let sel = unwrap_result_or_return!(parse_input_selection(...));` +/// /// # Safety /// Pointers must be valid for their respective counts. pub(crate) unsafe fn parse_input_selection( @@ -31,36 +35,25 @@ pub(crate) unsafe fn parse_input_selection( explicit_inputs_count: usize, nonce_inputs: *const ExplicitInputWithNonceFFI, nonce_inputs_count: usize, - out_error: *mut PlatformWalletFFIError, -) -> Result { +) -> Result { match input_type { InputSelectionType::Auto => Ok(InputSelection::Auto), InputSelectionType::Explicit => { match parse_explicit_inputs(explicit_inputs, explicit_inputs_count) { Ok(m) => Ok(InputSelection::Explicit(m)), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - e, - ); - } - Err(PlatformWalletFFIResult::ErrorInvalidParameter) - } + Err(e) => Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + e, + )), } } InputSelectionType::ExplicitWithNonces => { match parse_explicit_inputs_with_nonces(nonce_inputs, nonce_inputs_count) { Ok(m) => Ok(InputSelection::ExplicitWithNonces(m)), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - e, - ); - } - Err(PlatformWalletFFIResult::ErrorInvalidParameter) - } + Err(e) => Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + e, + )), } } } diff --git a/packages/rs-platform-wallet-ffi/src/platform_addresses/sync.rs b/packages/rs-platform-wallet-ffi/src/platform_addresses/sync.rs index b1bfe627712..d086fd7a88b 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_addresses/sync.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_addresses/sync.rs @@ -1,8 +1,10 @@ //! FFI bindings for platform address sync operations. +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::platform_address_types::*; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; use super::runtime; @@ -19,11 +21,8 @@ pub unsafe extern "C" fn platform_address_wallet_sync_balances( has_config: bool, config: *const AddressSyncConfigFFI, out_result: *mut AddressSyncResultFFI, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_result.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_result); let config_opt = if has_config && !config.is_null() { Some(dash_sdk::platform::address_sync::AddressSyncConfig::from( @@ -33,23 +32,11 @@ pub unsafe extern "C" fn platform_address_wallet_sync_balances( None }; - PLATFORM_ADDRESS_WALLET_STORAGE - .with_item(handle, |wallet| { - match runtime().block_on(wallet.sync_balances(config_opt)) { - Ok(result) => { - *out_result = AddressSyncResultFFI::from(&result); - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = PLATFORM_ADDRESS_WALLET_STORAGE.with_item(handle, |wallet| { + runtime().block_on(wallet.sync_balances(config_opt)) + }); + let result = unwrap_option_or_return!(option); + let sync = unwrap_result_or_return!(result); + *out_result = AddressSyncResultFFI::from(&sync); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/platform_addresses/transfer.rs b/packages/rs-platform-wallet-ffi/src/platform_addresses/transfer.rs index 8a7c867a696..294df2e4ac3 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_addresses/transfer.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_addresses/transfer.rs @@ -1,8 +1,10 @@ //! FFI bindings for platform address transfer operations. +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::platform_address_types::*; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; use rs_sdk_ffi::{SignerHandle, VTableSigner}; use super::{parse_input_selection, runtime}; @@ -32,43 +34,19 @@ pub unsafe extern "C" fn platform_address_wallet_transfer( fee_strategy_count: usize, signer_address_handle: *mut SignerHandle, out_changeset: *mut PlatformAddressChangeSetFFI, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_changeset.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } - if signer_address_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_address_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_changeset); + check_ptr!(signer_address_handle); - let output_map = match parse_outputs(outputs, outputs_count) { - Ok(m) => m, - Err(e) => { - if !out_error.is_null() { - *out_error = - PlatformWalletFFIError::new(PlatformWalletFFIResult::ErrorInvalidParameter, e); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; + let output_map = unwrap_result_or_return!(parse_outputs(outputs, outputs_count)); - let input_selection = match parse_input_selection( + let input_selection = unwrap_result_or_return!(parse_input_selection( input_type, explicit_inputs, explicit_inputs_count, nonce_inputs, nonce_inputs_count, - out_error, - ) { - Ok(s) => s, - Err(code) => return code, - }; + )); let fee = parse_fee_strategy(fee_strategy, fee_strategy_count); @@ -76,30 +54,18 @@ pub unsafe extern "C" fn platform_address_wallet_transfer( // non-destroyed handle that outlives this call. let address_signer: &VTableSigner = &*(signer_address_handle as *const VTableSigner); - PLATFORM_ADDRESS_WALLET_STORAGE - .with_item(handle, |wallet| { - match runtime().block_on(wallet.transfer( - account_index, - input_selection, - output_map, - fee, - None, // platform_version = latest - address_signer, - )) { - Ok(changeset) => { - *out_changeset = PlatformAddressChangeSetFFI::from(&changeset); - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = PLATFORM_ADDRESS_WALLET_STORAGE.with_item(handle, |wallet| { + runtime().block_on(wallet.transfer( + account_index, + input_selection, + output_map, + fee, + None, // platform_version = latest + address_signer, + )) + }); + let result = unwrap_option_or_return!(option); + let changeset = unwrap_result_or_return!(result); + *out_changeset = PlatformAddressChangeSetFFI::from(&changeset); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/platform_addresses/wallet.rs b/packages/rs-platform-wallet-ffi/src/platform_addresses/wallet.rs index b7b1b5fea83..6784f56158d 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_addresses/wallet.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_addresses/wallet.rs @@ -1,8 +1,10 @@ //! Handle management, queries, and memory deallocation for PlatformAddressWallet. +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::platform_address_types::*; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; use super::runtime; @@ -14,10 +16,9 @@ use super::runtime; #[no_mangle] pub unsafe extern "C" fn platform_address_wallet_destroy( handle: Handle, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { +) -> PlatformWalletFfiResult { PLATFORM_ADDRESS_WALLET_STORAGE.remove(handle); - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } /// Add a provider for a new account index. @@ -25,24 +26,13 @@ pub unsafe extern "C" fn platform_address_wallet_destroy( pub unsafe extern "C" fn platform_address_wallet_add_provider( handle: Handle, account_index: u32, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_ADDRESS_WALLET_STORAGE - .with_item(handle, |wallet| { - match runtime().block_on(wallet.add_provider(account_index)) { - Ok(()) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) +) -> PlatformWalletFfiResult { + let option = PLATFORM_ADDRESS_WALLET_STORAGE.with_item(handle, |wallet| { + runtime().block_on(wallet.add_provider(account_index)) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } /// Restore sync state from persisted values. @@ -56,18 +46,16 @@ pub unsafe extern "C" fn platform_address_wallet_restore_sync_state( sync_height: u64, sync_timestamp: u64, last_known_recent_block: u64, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_ADDRESS_WALLET_STORAGE - .with_item(handle, |wallet| { - runtime().block_on(wallet.restore_sync_state( - sync_height, - sync_timestamp, - last_known_recent_block, - )); - PlatformWalletFFIResult::Success - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) +) -> PlatformWalletFfiResult { + let option = PLATFORM_ADDRESS_WALLET_STORAGE.with_item(handle, |wallet| { + runtime().block_on(wallet.restore_sync_state( + sync_height, + sync_timestamp, + last_known_recent_block, + )); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } // --------------------------------------------------------------------------- @@ -79,19 +67,13 @@ pub unsafe extern "C" fn platform_address_wallet_restore_sync_state( pub unsafe extern "C" fn platform_address_wallet_total_credits( handle: Handle, out_credits: *mut u64, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_credits.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_credits); - PLATFORM_ADDRESS_WALLET_STORAGE - .with_item(handle, |wallet| { - let credits = runtime().block_on(wallet.total_credits()); - *out_credits = credits; - PlatformWalletFFIResult::Success - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = PLATFORM_ADDRESS_WALLET_STORAGE + .with_item(handle, |wallet| runtime().block_on(wallet.total_credits())); + *out_credits = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Get all platform addresses with their cached balances. @@ -103,35 +85,31 @@ pub unsafe extern "C" fn platform_address_wallet_addresses_with_balances( handle: Handle, out_entries: *mut *mut AddressBalanceEntryFFI, out_count: *mut usize, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_entries.is_null() || out_count.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_entries); + check_ptr!(out_count); - PLATFORM_ADDRESS_WALLET_STORAGE - .with_item(handle, |wallet| { - let balances = runtime().block_on(wallet.addresses_with_balances()); - let entries: Vec = balances - .into_iter() - .map(|(address, balance)| AddressBalanceEntryFFI { - address: address.into(), - balance, - nonce: 0, - account_index: 0, - address_index: 0, - }) - .collect(); - *out_count = entries.len(); - if entries.is_empty() { - *out_entries = std::ptr::null_mut(); - } else { - *out_entries = - Box::into_raw(entries.into_boxed_slice()) as *mut AddressBalanceEntryFFI; - } - PlatformWalletFFIResult::Success - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = PLATFORM_ADDRESS_WALLET_STORAGE.with_item(handle, |wallet| { + let balances = runtime().block_on(wallet.addresses_with_balances()); + balances + .into_iter() + .map(|(address, balance)| AddressBalanceEntryFFI { + address: address.into(), + balance, + nonce: 0, + account_index: 0, + address_index: 0, + }) + .collect::>() + }); + let entries = unwrap_option_or_return!(option); + *out_count = entries.len(); + if entries.is_empty() { + *out_entries = std::ptr::null_mut(); + } else { + *out_entries = Box::into_raw(entries.into_boxed_slice()) as *mut AddressBalanceEntryFFI; + } + PlatformWalletFfiResult::ok() } // --------------------------------------------------------------------------- diff --git a/packages/rs-platform-wallet-ffi/src/platform_addresses/withdrawal.rs b/packages/rs-platform-wallet-ffi/src/platform_addresses/withdrawal.rs index 8c332a8d5f9..c3c30755aeb 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_addresses/withdrawal.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_addresses/withdrawal.rs @@ -1,22 +1,16 @@ //! FFI bindings for platform address withdrawal operations. +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::platform_address_types::*; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; use dpp::identity::core_script::CoreScript; use rs_sdk_ffi::{SignerHandle, VTableSigner}; use super::{parse_input_selection, runtime}; /// Withdraw platform credits to a Core L1 address. -/// -/// `signer_address_handle` is a `*mut SignerHandle` produced by -/// `dash_sdk_signer_create_with_ctx` (e.g. via `KeychainSigner.handle`) -/// and is consumed as `Signer` for each input -/// address. The caller retains ownership of the handle; this function -/// does NOT destroy it. -/// -/// Free result with `platform_address_wallet_free_changeset`. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_address_wallet_withdraw( @@ -34,67 +28,39 @@ pub unsafe extern "C" fn platform_address_wallet_withdraw( fee_strategy_count: usize, signer_address_handle: *mut SignerHandle, out_changeset: *mut PlatformAddressChangeSetFFI, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_changeset.is_null() || output_script.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } - if signer_address_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_address_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_changeset); + check_ptr!(output_script); + check_ptr!(signer_address_handle); let script_bytes = std::slice::from_raw_parts(output_script, output_script_len); let core_script = CoreScript::from_bytes(script_bytes.to_vec()); - let input_selection = match parse_input_selection( + let input_selection = unwrap_result_or_return!(parse_input_selection( input_type, explicit_inputs, explicit_inputs_count, nonce_inputs, nonce_inputs_count, - out_error, - ) { - Ok(s) => s, - Err(code) => return code, - }; + )); let fee = parse_fee_strategy(fee_strategy, fee_strategy_count); - // SAFETY: caller guarantees `signer_address_handle` is a valid, - // non-destroyed handle that outlives this call. let address_signer: &VTableSigner = &*(signer_address_handle as *const VTableSigner); - PLATFORM_ADDRESS_WALLET_STORAGE - .with_item(handle, |wallet| { - match runtime().block_on(wallet.withdraw( - account_index, - input_selection, - core_script, - core_fee_per_byte, - fee, - None, // platform_version = latest - address_signer, - )) { - Ok(changeset) => { - *out_changeset = PlatformAddressChangeSetFFI::from(&changeset); - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = PLATFORM_ADDRESS_WALLET_STORAGE.with_item(handle, |wallet| { + runtime().block_on(wallet.withdraw( + account_index, + input_selection, + core_script, + core_fee_per_byte, + fee, + None, + address_signer, + )) + }); + let result = unwrap_option_or_return!(option); + let changeset = unwrap_result_or_return!(result); + *out_changeset = PlatformAddressChangeSetFFI::from(&changeset); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/platform_wallet_info.rs b/packages/rs-platform-wallet-ffi/src/platform_wallet_info.rs index 542b5ab4d0b..f6988be33f5 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_wallet_info.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_wallet_info.rs @@ -1,6 +1,7 @@ use crate::error::*; use crate::handle::*; use crate::types::Network; +use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; use key_wallet::wallet::initialization::WalletAccountCreationOptions; use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; use platform_wallet::PlatformWalletInfo; @@ -16,19 +17,9 @@ pub unsafe extern "C" fn platform_wallet_info_create_from_seed( seed_bytes: *const c_uchar, seed_len: usize, out_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if seed_bytes.is_null() || out_handle.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(seed_bytes); + check_ptr!(out_handle); let network = match network { 0 => Network::Mainnet, @@ -36,29 +27,19 @@ pub unsafe extern "C" fn platform_wallet_info_create_from_seed( 2 => Network::Devnet, 3 => Network::Regtest, _ => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidNetwork, - format!("Unknown network: {}", network), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidNetwork; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidNetwork, + format!("Unknown network: {network}"), + ); } }; // Validate seed length (should be 64 bytes for BIP39) if seed_len != 64 { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("Invalid seed length: expected 64 bytes, got {}", seed_len), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidParameter; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + format!("Invalid seed length: expected 64 bytes, got {seed_len}"), + ); } let seed_slice = unsafe { std::slice::from_raw_parts(seed_bytes, seed_len) }; @@ -67,59 +48,31 @@ pub unsafe extern "C" fn platform_wallet_info_create_from_seed( let mut seed_array = [0u8; 64]; seed_array.copy_from_slice(seed_slice); - // Create wallet from seed - let wallet = match key_wallet::Wallet::from_seed_bytes( + let wallet = unwrap_result_or_return!(key_wallet::Wallet::from_seed_bytes( seed_array, network, - WalletAccountCreationOptions::None, // No accounts initially - ) { - Ok(w) => w, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("Failed to create wallet from seed: {}", e), - ); - } - } - return PlatformWalletFFIResult::ErrorWalletOperation; - } - }; + WalletAccountCreationOptions::None, + )); - // Create PlatformWalletInfo from the wallet let platform_wallet = PlatformWalletInfo::from_wallet(&wallet, 0); // Store in handle storage let handle = WALLET_INFO_STORAGE.insert(platform_wallet); unsafe { *out_handle = handle }; - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } /// Create a new PlatformWalletInfo from mnemonic. -/// -/// `network` encoding matches other entry points in this crate: -/// 0 = Mainnet, 1 = Testnet, 2 = Devnet, 3 = Regtest. #[no_mangle] pub unsafe extern "C" fn platform_wallet_info_create_from_mnemonic( network: u32, mnemonic: *const c_char, passphrase: *const c_char, out_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if mnemonic.is_null() || out_handle.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(mnemonic); + check_ptr!(out_handle); let network = match network { 0 => Network::Mainnet, @@ -127,111 +80,40 @@ pub unsafe extern "C" fn platform_wallet_info_create_from_mnemonic( 2 => Network::Devnet, 3 => Network::Regtest, _ => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidNetwork, - format!("Unknown network: {}", network), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidNetwork; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidNetwork, + format!("Unknown network: {network}"), + ); } }; - let mnemonic_str = unsafe { - match std::ffi::CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "Invalid UTF-8 in mnemonic", - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - } - }; + let mnemonic_str = + unwrap_result_or_return!(unsafe { std::ffi::CStr::from_ptr(mnemonic).to_str() }); let passphrase_str = if passphrase.is_null() { None } else { - unsafe { - match std::ffi::CStr::from_ptr(passphrase).to_str() { - Ok(s) => Some(s), - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "Invalid UTF-8 in passphrase", - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - } - } + Some(unwrap_result_or_return!(unsafe { + std::ffi::CStr::from_ptr(passphrase).to_str() + })) }; - // Parse mnemonic string - let mnemonic_obj = match mnemonic_str.parse::() { - Ok(m) => m, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("Failed to parse mnemonic: {}", e), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; + let mnemonic_obj = unwrap_result_or_return!(mnemonic_str.parse::()); // Create wallet from mnemonic with or without passphrase let wallet = if let Some(pass) = passphrase_str { - match key_wallet::Wallet::from_mnemonic_with_passphrase( + unwrap_result_or_return!(key_wallet::Wallet::from_mnemonic_with_passphrase( mnemonic_obj, pass.to_string(), network, - WalletAccountCreationOptions::None, // No accounts initially - ) { - Ok(w) => w, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!( - "Failed to create wallet from mnemonic with passphrase: {}", - e - ), - ); - } - } - return PlatformWalletFFIResult::ErrorWalletOperation; - } - } + WalletAccountCreationOptions::None, + )) } else { - match key_wallet::Wallet::from_mnemonic( + unwrap_result_or_return!(key_wallet::Wallet::from_mnemonic( mnemonic_obj, network, - WalletAccountCreationOptions::None, // No accounts initially - ) { - Ok(w) => w, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("Failed to create wallet from mnemonic: {}", e), - ); - } - } - return PlatformWalletFFIResult::ErrorWalletOperation; - } - } + WalletAccountCreationOptions::None, + )) }; // Create PlatformWalletInfo from the wallet @@ -241,7 +123,7 @@ pub unsafe extern "C" fn platform_wallet_info_create_from_mnemonic( let handle = WALLET_INFO_STORAGE.insert(platform_wallet); unsafe { *out_handle = handle }; - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } /// Get the identity manager @@ -249,37 +131,16 @@ pub unsafe extern "C" fn platform_wallet_info_create_from_mnemonic( pub unsafe extern "C" fn platform_wallet_info_get_identity_manager( wallet_handle: Handle, out_handle: *mut Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_handle.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - - WALLET_INFO_STORAGE - .with_item(wallet_handle, |wallet_info| { - let handle = IDENTITY_MANAGER_STORAGE.insert(wallet_info.identity_manager.clone()); - unsafe { *out_handle = handle }; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) +) -> PlatformWalletFfiResult { + check_ptr!(out_handle); + + let option = WALLET_INFO_STORAGE.with_item(wallet_handle, |wallet_info| { + wallet_info.identity_manager.clone() + }); + let manager = unwrap_option_or_return!(option); + let handle = IDENTITY_MANAGER_STORAGE.insert(manager); + unsafe { *out_handle = handle }; + PlatformWalletFfiResult::ok() } /// Set the identity manager @@ -287,112 +148,53 @@ pub unsafe extern "C" fn platform_wallet_info_get_identity_manager( pub unsafe extern "C" fn platform_wallet_info_set_identity_manager( wallet_handle: Handle, manager_handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - let manager_result = +) -> PlatformWalletFfiResult { + let manager_option = IDENTITY_MANAGER_STORAGE.with_item(manager_handle, |manager| manager.clone()); + let manager = unwrap_option_or_return!(manager_option); - let manager = match manager_result { - Some(m) => m, - None => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid identity manager handle", - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidHandle; - } - }; - - WALLET_INFO_STORAGE - .with_item_mut(wallet_handle, |wallet_info| { - wallet_info.identity_manager = manager; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid wallet handle", - ); - } - } - PlatformWalletFFIResult::ErrorInvalidHandle - }) -} - -/// Serialize PlatformWalletInfo to JSON -/// TODO: Requires serde support on PlatformWalletInfo -#[allow(dead_code)] -fn platform_wallet_info_to_json( - _wallet_handle: Handle, - out_json: *mut *mut c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_json.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - - // TODO: Implement once PlatformWalletInfo has Serialize derived - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorSerialization, - "Serialization not yet implemented", - ); - } - } - PlatformWalletFFIResult::ErrorSerialization + let option = WALLET_INFO_STORAGE.with_item_mut(wallet_handle, |wallet_info| { + wallet_info.identity_manager = manager; + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Destroy PlatformWalletInfo and free resources #[no_mangle] pub unsafe extern "C" fn platform_wallet_info_destroy( wallet_handle: Handle, -) -> PlatformWalletFFIResult { +) -> PlatformWalletFfiResult { if WALLET_INFO_STORAGE.remove(wallet_handle).is_some() { - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } else { - PlatformWalletFFIResult::ErrorInvalidHandle + PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidHandle, + "Invalid wallet handle", + ) } } #[cfg(test)] mod tests { use super::*; - use crate::platform_wallet_string_free; #[test] fn test_create_from_seed() { unsafe { let seed = [0u8; 64]; let mut handle: Handle = NULL_HANDLE; - let mut error = PlatformWalletFFIError::success(); let result = platform_wallet_info_create_from_seed( 1, // Testnet seed.as_ptr(), seed.len(), &mut handle, - &mut error, ); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_ne!(handle, NULL_HANDLE); - // Cleanup platform_wallet_info_destroy(handle); } } @@ -401,52 +203,21 @@ mod tests { fn test_create_from_mnemonic() { unsafe { let mnemonic = std::ffi::CString::new( - "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" - ).unwrap(); + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + ).unwrap(); let mut handle: Handle = NULL_HANDLE; - let mut error = PlatformWalletFFIError::success(); let result = platform_wallet_info_create_from_mnemonic( 1, // Testnet mnemonic.as_ptr(), std::ptr::null(), &mut handle, - &mut error, ); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_ne!(handle, NULL_HANDLE); - // Cleanup - platform_wallet_info_destroy(handle); - } - } - - #[test] - #[ignore] // Stubbed - requires serde support on PlatformWalletInfo - fn test_to_json() { - unsafe { - let seed = [0u8; 64]; - let mut handle: Handle = NULL_HANDLE; - let mut error = PlatformWalletFFIError::success(); - - platform_wallet_info_create_from_seed( - 1, // Testnet - seed.as_ptr(), - seed.len(), - &mut handle, - &mut error, - ); - - let mut json_ptr: *mut c_char = std::ptr::null_mut(); - let result = platform_wallet_info_to_json(handle, &mut json_ptr, &mut error); - - assert_eq!(result, PlatformWalletFFIResult::Success); - assert!(!json_ptr.is_null()); - - // Cleanup - platform_wallet_string_free(json_ptr); platform_wallet_info_destroy(handle); } } @@ -455,7 +226,7 @@ mod tests { fn test_destroy_invalid_handle() { unsafe { let result = platform_wallet_info_destroy(9999); - assert_eq!(result, PlatformWalletFFIResult::ErrorInvalidHandle); + assert_eq!(result.code, PlatformWalletFfiResultCode::ErrorInvalidHandle); } } } diff --git a/packages/rs-platform-wallet-ffi/src/spv.rs b/packages/rs-platform-wallet-ffi/src/spv.rs index 4faf84f4c6c..a9949fdc417 100644 --- a/packages/rs-platform-wallet-ffi/src/spv.rs +++ b/packages/rs-platform-wallet-ffi/src/spv.rs @@ -1,11 +1,4 @@ //! FFI bindings for PlatformWalletManager's SPV runtime. -//! -//! Exposes a minimal subset of the SPV client API needed by the UI: -//! sync progress polling, lifecycle controls (stop, clear storage), -//! and masternode toggle. The `start` entry point mirrors the old -//! `dash_spv_ffi_client_new` + `run` flow — it accepts a minimal -//! config (network + data dir + peer overrides) and fires up the -//! SpvRuntime on a background task until stopped. use std::ffi::CStr; use std::os::raw::c_char; @@ -15,15 +8,8 @@ use platform_wallet::spv::{ClientConfig, ProgressPercentage, SyncProgress, SyncS use crate::error::*; use crate::handle::*; use crate::runtime::runtime; +use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; -// --------------------------------------------------------------------------- -// Sync progress -// --------------------------------------------------------------------------- - -/// Sync state enum mirroring dash_spv::sync::SyncState. -/// -/// 0 = WaitForEvents, 1 = WaitingForConnections, 2 = Syncing, -/// 3 = Synced, 4 = Error. pub const SPV_SYNC_STATE_WAIT_FOR_EVENTS: u32 = 0; pub const SPV_SYNC_STATE_WAITING_FOR_CONNECTIONS: u32 = 1; pub const SPV_SYNC_STATE_SYNCING: u32 = 2; @@ -31,10 +17,6 @@ pub const SPV_SYNC_STATE_SYNCED: u32 = 3; pub const SPV_SYNC_STATE_ERROR: u32 = 4; /// Flattened sync progress summary for FFI. -/// -/// Each `has_*` flag indicates whether the corresponding manager has -/// started reporting progress. When `has_*` is false, the numeric fields -/// for that manager should be ignored. #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct FFISpvSyncProgress { @@ -148,35 +130,22 @@ fn progress_to_ffi(p: &SyncProgress) -> FFISpvSyncProgress { } /// Poll the current sync progress. -/// -/// Writes a snapshot of SPV sync state into `out_progress`. If the SPV -/// client is not running, all `has_*` flags are false and the state is -/// `WaitForEvents`. -/// -/// # Safety -/// Caller must ensure `handle` is a valid manager handle and `out_progress` -/// is a valid pointer. #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_sync_progress( handle: Handle, out_progress: *mut FFISpvSyncProgress, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_progress.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_progress); - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - let progress = runtime().block_on(manager.spv().sync_progress()); - let ffi = match progress { - Some(p) => progress_to_ffi(&p), - None => FFISpvSyncProgress::default(), - }; - *out_progress = ffi; - PlatformWalletFFIResult::Success - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + runtime().block_on(manager.spv().sync_progress()) + }); + let progress = unwrap_option_or_return!(option); + *out_progress = match progress { + Some(p) => progress_to_ffi(&p), + None => FFISpvSyncProgress::default(), + }; + PlatformWalletFfiResult::ok() } /// Whether the SPV client is currently running. @@ -184,39 +153,15 @@ pub unsafe extern "C" fn platform_wallet_manager_sync_progress( pub unsafe extern "C" fn platform_wallet_manager_spv_is_running( handle: Handle, out_running: *mut bool, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_running.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - *out_running = manager.spv().is_started(); - PlatformWalletFFIResult::Success - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) +) -> PlatformWalletFfiResult { + check_ptr!(out_running); + let option = + PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| manager.spv().is_started()); + *out_running = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } -// --------------------------------------------------------------------------- -// Lifecycle -// --------------------------------------------------------------------------- - /// Start SPV sync in the background. -/// -/// `data_dir` — path to the SPV storage directory (must exist and be writable). -/// `network` — 0=Mainnet, 1=Testnet, 2=Devnet, 3=Regtest. -/// `user_agent` — optional user agent string (null to use default). -/// `peers` — optional null-terminated array of peer addresses ("ip:port" or -/// just "ip"); pass `peers=null, peer_count=0` for DNS discovery. -/// `restrict_to_configured_peers` — if true, only connect to listed peers. -/// `start_from_height` — checkpoint height to start syncing from (0 = genesis). -/// `masternode_sync_enabled` — whether to sync masternode lists. -/// -/// Returns immediately after spawning the background task. -/// -/// # Safety -/// `handle` must be a valid manager handle. String pointers must be valid -/// null-terminated UTF-8 for the duration of this call. #[no_mangle] #[allow(clippy::field_reassign_with_default)] pub unsafe extern "C" fn platform_wallet_manager_spv_start( @@ -229,22 +174,13 @@ pub unsafe extern "C" fn platform_wallet_manager_spv_start( restrict_to_configured_peers: bool, start_from_height: u32, masternode_sync_enabled: bool, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if data_dir.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } - let data_dir_str = match CStr::from_ptr(data_dir).to_str() { - Ok(s) => s.to_string(), - Err(_) => return PlatformWalletFFIResult::ErrorUtf8Conversion, - }; +) -> PlatformWalletFfiResult { + check_ptr!(data_dir); + let data_dir_str = unwrap_result_or_return!(CStr::from_ptr(data_dir).to_str()).to_string(); let user_agent_str = if user_agent.is_null() { None } else { - match CStr::from_ptr(user_agent).to_str() { - Ok(s) => Some(s.to_string()), - Err(_) => return PlatformWalletFFIResult::ErrorUtf8Conversion, - } + Some(unwrap_result_or_return!(CStr::from_ptr(user_agent).to_str()).to_string()) }; let net = match network { @@ -252,7 +188,12 @@ pub unsafe extern "C" fn platform_wallet_manager_spv_start( 1 => dashcore::Network::Testnet, 2 => dashcore::Network::Devnet, 3 => dashcore::Network::Regtest, - _ => return PlatformWalletFFIResult::ErrorInvalidNetwork, + _ => { + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidNetwork, + format!("Unknown network: {network}"), + ); + } }; let mut peer_list: Vec = Vec::new(); @@ -268,72 +209,54 @@ pub unsafe extern "C" fn platform_wallet_manager_spv_start( } } - let _ = out_error; - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - let mut config = ClientConfig::default(); - config.network = net; - config.storage_path = std::path::PathBuf::from(&data_dir_str); - if let Some(ua) = user_agent_str.clone() { - config.user_agent = Some(ua); - } - if start_from_height > 0 { - config.start_from_height = Some(start_from_height); - } - config.enable_masternodes = masternode_sync_enabled; - config.restrict_to_configured_peers = restrict_to_configured_peers; - for p in &peer_list { - if let Ok(addr) = p.parse() { - config.peers.push(addr); - } + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + let mut config = ClientConfig::default(); + config.network = net; + config.storage_path = std::path::PathBuf::from(&data_dir_str); + if let Some(ua) = user_agent_str.clone() { + config.user_agent = Some(ua); + } + if start_from_height > 0 { + config.start_from_height = Some(start_from_height); + } + config.enable_masternodes = masternode_sync_enabled; + config.restrict_to_configured_peers = restrict_to_configured_peers; + for p in &peer_list { + if let Ok(addr) = p.parse() { + config.peers.push(addr); } + } - // Enter the runtime so `spawn_in_background` can call `tokio::spawn`. - let _guard = runtime().enter(); - manager.spv_arc().spawn_in_background(config); - PlatformWalletFFIResult::Success - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let _guard = runtime().enter(); + manager.spv_arc().spawn_in_background(config); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Stop the SPV client. #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_spv_stop( handle: Handle, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - runtime().block_on(async { - let _ = manager.spv().stop().await; - }); - PlatformWalletFFIResult::Success - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) +) -> PlatformWalletFfiResult { + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + runtime().block_on(async { + let _ = manager.spv().stop().await; + }); + }); + unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Clear all persisted SPV storage (headers, filters, state). -/// -/// The SPV client must be running. #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_spv_clear_storage( handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(handle, |manager| { - match runtime().block_on(manager.spv().clear_storage()) { - Ok(()) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) +) -> PlatformWalletFfiResult { + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { + runtime().block_on(manager.spv().clear_storage()) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/burn.rs b/packages/rs-platform-wallet-ffi/src/tokens/burn.rs index ab33b27de5f..4ed96981fe9 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/burn.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/burn.rs @@ -1,35 +1,20 @@ //! FFI binding for `IdentityWallet::token_burn_with_external_signer`. -//! -//! Burn supports group-gated execution; the caller passes a flat -//! `(group_info_kind, position, action_id, action_is_proposer)` tuple -//! that we decode into `Option` via -//! `super::group_info::decode_group_info`. use std::ffi::CStr; use std::os::raw::c_char; use rs_sdk_ffi::{SignerHandle, VTableSigner}; -use super::group_info::{decode_group_info, GroupInfoDecode}; +use super::group_info::decode_group_info; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::read_identifier; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; /// Burn `amount` of token at `token_position` on `token_contract_id`, /// debiting the caller's balance. -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle registry. -/// - `identity_id`, `token_contract_id` must each point at exactly 32 -/// readable bytes. -/// - `public_note` may be NULL; when non-NULL it must be a -/// NUL-terminated UTF-8 C string. -/// - `group_info_action_id` must point at 32 bytes when -/// `group_info_kind == 2`; ignored otherwise (may be NULL). -/// - `signer_handle` must be a valid, non-destroyed handle from -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. -/// - `out_error` may be NULL. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_token_burn( @@ -45,111 +30,52 @@ pub unsafe extern "C" fn platform_wallet_token_burn( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); - let id = match read_identifier(identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let contract_id = match read_identifier(token_contract_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid token_contract_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let id = unwrap_result_or_return!(read_identifier(identity_id)); + let contract_id = unwrap_result_or_return!(read_identifier(token_contract_id)); let public_note_str = if public_note.is_null() { None } else { - match CStr::from_ptr(public_note).to_str() { - Ok("") => None, - Ok(s) => Some(s.to_owned()), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("public_note is not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + { + let s = unwrap_result_or_return!(CStr::from_ptr(public_note).to_str()); + if s.is_empty() { + None + } else { + Some(s.to_owned()) } } }; - let group_info = match decode_group_info( + let group_info = unwrap_result_or_return!(decode_group_info( group_info_kind, group_info_position, group_info_action_id, group_info_action_is_proposer, - out_error, - ) { - GroupInfoDecode::Ok(value) => value, - GroupInfoDecode::Err(code) => return code, - }; + )); let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .token_burn_with_external_signer( - id, - contract_id, - token_position, - amount, - public_note_str, - group_info, - signer, - ) - .await - }); - match result { - Ok(_) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("token_burn failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .token_burn_with_external_signer( + id, + contract_id, + token_position, + amount, + public_note_str, + group_info, + signer, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/claim.rs b/packages/rs-platform-wallet-ffi/src/tokens/claim.rs index bde927e3a5a..cf43ce328a1 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/claim.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/claim.rs @@ -1,8 +1,5 @@ //! FFI binding for `IdentityWallet::token_claim_with_external_signer`. //! -//! Claim is not group-gated — there's no `group_info_*` payload here. -//! It does take a distribution-type discriminant, surfaced as a `u8`. -//! //! Distribution-type mapping (mirrors //! `dpp::data_contract::associated_token::token_distribution_key::TokenDistributionType`): //! @@ -19,23 +16,15 @@ use std::os::raw::c_char; use dpp::data_contract::associated_token::token_distribution_key::TokenDistributionType; use rs_sdk_ffi::{SignerHandle, VTableSigner}; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::read_identifier; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; /// Claim a distribution payout for `identity_id` from the token at /// `token_position` on `token_contract_id`. -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle registry. -/// - `identity_id`, `token_contract_id` must each point at exactly 32 -/// readable bytes. -/// - `public_note` may be NULL; when non-NULL it must be a -/// NUL-terminated UTF-8 C string. -/// - `signer_handle` must be a valid, non-destroyed handle from -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. -/// - `out_error` may be NULL. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_token_claim( @@ -47,113 +36,55 @@ pub unsafe extern "C" fn platform_wallet_token_claim( public_note: *const c_char, _signing_key_id: u32, signer_handle: *mut SignerHandle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); - let id = match read_identifier(identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let contract_id = match read_identifier(token_contract_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid token_contract_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let id = unwrap_result_or_return!(read_identifier(identity_id)); + let contract_id = unwrap_result_or_return!(read_identifier(token_contract_id)); let dist_type = match distribution_type { 0 => TokenDistributionType::PreProgrammed, 1 => TokenDistributionType::Perpetual, other => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("Invalid distribution_type: {other} (expected 0 or 1)"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + format!("Invalid distribution_type: {other} (expected 0 or 1)"), + ); } }; let public_note_str = if public_note.is_null() { None } else { - match CStr::from_ptr(public_note).to_str() { - Ok("") => None, - Ok(s) => Some(s.to_owned()), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("public_note is not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + { + let s = unwrap_result_or_return!(CStr::from_ptr(public_note).to_str()); + if s.is_empty() { + None + } else { + Some(s.to_owned()) } } }; let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .token_claim_with_external_signer( - id, - contract_id, - token_position, - dist_type, - public_note_str, - signer, - ) - .await - }); - match result { - Ok(_) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("token_claim failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .token_claim_with_external_signer( + id, + contract_id, + token_position, + dist_type, + public_note_str, + signer, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/destroy_frozen_funds.rs b/packages/rs-platform-wallet-ffi/src/tokens/destroy_frozen_funds.rs index beccb3bcebb..3ae20ae32e8 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/destroy_frozen_funds.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/destroy_frozen_funds.rs @@ -1,34 +1,19 @@ //! FFI binding for `IdentityWallet::token_destroy_frozen_funds_with_external_signer`. -//! -//! Destroy-frozen-funds is group-capable; same shape as Freeze / -//! Unfreeze. The Rust builder takes no `amount` — destroying always -//! affects the full frozen balance of `frozen_identity_id`. use std::ffi::CStr; use std::os::raw::c_char; use rs_sdk_ffi::{SignerHandle, VTableSigner}; -use super::group_info::{decode_group_info, GroupInfoDecode}; +use super::group_info::decode_group_info; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::read_identifier; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; -/// Destroy the entire frozen balance of `frozen_identity_id` for the -/// token at `token_position` on `token_contract_id`. -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle registry. -/// - `identity_id`, `token_contract_id`, `frozen_identity_id` must each -/// point at exactly 32 readable bytes. -/// - `public_note` may be NULL; when non-NULL it must be a -/// NUL-terminated UTF-8 C string. -/// - `group_info_action_id` must point at 32 bytes when -/// `group_info_kind == 2`; ignored otherwise (may be NULL). -/// - `signer_handle` must be a valid, non-destroyed handle from -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. -/// - `out_error` may be NULL. +/// Destroy the entire frozen balance of `frozen_identity_id`. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_token_destroy_frozen_funds( @@ -44,123 +29,53 @@ pub unsafe extern "C" fn platform_wallet_token_destroy_frozen_funds( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); - let id = match read_identifier(identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let contract_id = match read_identifier(token_contract_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid token_contract_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let frozen_id = match read_identifier(frozen_identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid frozen_identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let id = unwrap_result_or_return!(read_identifier(identity_id)); + let contract_id = unwrap_result_or_return!(read_identifier(token_contract_id)); + let frozen_id = unwrap_result_or_return!(read_identifier(frozen_identity_id)); let public_note_str = if public_note.is_null() { None } else { - match CStr::from_ptr(public_note).to_str() { - Ok("") => None, - Ok(s) => Some(s.to_owned()), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("public_note is not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + { + let s = unwrap_result_or_return!(CStr::from_ptr(public_note).to_str()); + if s.is_empty() { + None + } else { + Some(s.to_owned()) } } }; - let group_info = match decode_group_info( + let group_info = unwrap_result_or_return!(decode_group_info( group_info_kind, group_info_position, group_info_action_id, group_info_action_is_proposer, - out_error, - ) { - GroupInfoDecode::Ok(value) => value, - GroupInfoDecode::Err(code) => return code, - }; + )); let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .token_destroy_frozen_funds_with_external_signer( - id, - contract_id, - token_position, - frozen_id, - public_note_str, - group_info, - signer, - ) - .await - }); - match result { - Ok(_) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("token_destroy_frozen_funds failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .token_destroy_frozen_funds_with_external_signer( + id, + contract_id, + token_position, + frozen_id, + public_note_str, + group_info, + signer, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/freeze.rs b/packages/rs-platform-wallet-ffi/src/tokens/freeze.rs index 65ac058d880..0866dad6e77 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/freeze.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/freeze.rs @@ -1,36 +1,20 @@ //! FFI binding for `IdentityWallet::token_freeze_with_external_signer`. -//! -//! Freeze is group-capable; the caller passes the same flat -//! `(group_info_kind, position, action_id, action_is_proposer)` tuple -//! that Burn / Mint use, decoded by `super::group_info::decode_group_info`. -//! Unlike Burn there is no `amount` — the action freezes the full -//! balance of `frozen_identity_id`. use std::ffi::CStr; use std::os::raw::c_char; use rs_sdk_ffi::{SignerHandle, VTableSigner}; -use super::group_info::{decode_group_info, GroupInfoDecode}; +use super::group_info::decode_group_info; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::read_identifier; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; /// Freeze the entire balance of `frozen_identity_id` for the token at /// `token_position` on `token_contract_id`. -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle registry. -/// - `identity_id`, `token_contract_id`, `frozen_identity_id` must each -/// point at exactly 32 readable bytes. -/// - `public_note` may be NULL; when non-NULL it must be a -/// NUL-terminated UTF-8 C string. -/// - `group_info_action_id` must point at 32 bytes when -/// `group_info_kind == 2`; ignored otherwise (may be NULL). -/// - `signer_handle` must be a valid, non-destroyed handle from -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. -/// - `out_error` may be NULL. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_token_freeze( @@ -46,123 +30,53 @@ pub unsafe extern "C" fn platform_wallet_token_freeze( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); - let id = match read_identifier(identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let contract_id = match read_identifier(token_contract_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid token_contract_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let frozen_id = match read_identifier(frozen_identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid frozen_identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let id = unwrap_result_or_return!(read_identifier(identity_id)); + let contract_id = unwrap_result_or_return!(read_identifier(token_contract_id)); + let frozen_id = unwrap_result_or_return!(read_identifier(frozen_identity_id)); let public_note_str = if public_note.is_null() { None } else { - match CStr::from_ptr(public_note).to_str() { - Ok("") => None, - Ok(s) => Some(s.to_owned()), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("public_note is not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + { + let s = unwrap_result_or_return!(CStr::from_ptr(public_note).to_str()); + if s.is_empty() { + None + } else { + Some(s.to_owned()) } } }; - let group_info = match decode_group_info( + let group_info = unwrap_result_or_return!(decode_group_info( group_info_kind, group_info_position, group_info_action_id, group_info_action_is_proposer, - out_error, - ) { - GroupInfoDecode::Ok(value) => value, - GroupInfoDecode::Err(code) => return code, - }; + )); let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .token_freeze_with_external_signer( - id, - contract_id, - token_position, - frozen_id, - public_note_str, - group_info, - signer, - ) - .await - }); - match result { - Ok(_) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("token_freeze failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .token_freeze_with_external_signer( + id, + contract_id, + token_position, + frozen_id, + public_note_str, + group_info, + signer, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/group_info.rs b/packages/rs-platform-wallet-ffi/src/tokens/group_info.rs index 56008e277fc..74989b01fe8 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/group_info.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/group_info.rs @@ -14,60 +14,38 @@ use dpp::group::{GroupStateTransitionInfo, GroupStateTransitionInfoStatus}; -use crate::error::{PlatformWalletFFIError, PlatformWalletFFIResult}; +use crate::error::{PlatformWalletFfiResult, PlatformWalletFfiResultCode}; use crate::types::read_identifier; -/// Outcome of decoding a group-info FFI tuple. -pub(crate) enum GroupInfoDecode { - /// Successfully decoded — may be `None` for kind == 0. - Ok(Option), - /// Decoding failed; `out_error` has been stamped (when non-null) - /// and the caller should bail out with this code. - Err(PlatformWalletFFIResult), -} - /// Decode a flat `(kind, position, action_id, action_is_proposer)` /// tuple from an FFI caller into an `Option`. /// +/// On error returns `Err(PlatformWalletFfiResult)` carrying the FFI +/// error the caller should bubble up. +/// /// # Safety /// - `action_id` may be NULL when `kind != 2`. When `kind == 2` it must /// point at a 32-byte buffer for the duration of the call. -/// - `out_error` may be NULL. pub(crate) unsafe fn decode_group_info( kind: u8, position: u16, action_id: *const u8, action_is_proposer: bool, - out_error: *mut PlatformWalletFFIError, -) -> GroupInfoDecode { +) -> Result, PlatformWalletFfiResult> { match kind { - 0 => GroupInfoDecode::Ok(None), - 1 => GroupInfoDecode::Ok(Some( + 0 => Ok(None), + 1 => Ok(Some( GroupStateTransitionInfoStatus::GroupStateTransitionInfoProposer(position), )), 2 => { if action_id.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "group_info_action_id is null but kind == 2 (other-signer)", - ); - } - return GroupInfoDecode::Err(PlatformWalletFFIResult::ErrorNullPointer); + return Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorNullPointer, + "group_info_action_id is null but kind == 2 (other-signer)", + )); } - let action_identifier = match read_identifier(action_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid group_info_action_id: {e}"), - ); - } - return GroupInfoDecode::Err(PlatformWalletFFIResult::ErrorInvalidIdentifier); - } - }; - GroupInfoDecode::Ok(Some( + let action_identifier = read_identifier(action_id)?; + Ok(Some( GroupStateTransitionInfoStatus::GroupStateTransitionInfoOtherSigner( GroupStateTransitionInfo { group_contract_position: position, @@ -77,15 +55,10 @@ pub(crate) unsafe fn decode_group_info( ), )) } - other => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("Invalid group_info_kind: {other} (expected 0, 1, or 2)"), - ); - } - GroupInfoDecode::Err(PlatformWalletFFIResult::ErrorInvalidParameter) - } + other => Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + format!("Invalid group_info_kind: {other} (expected 0, 1, or 2)"), + )), } } @@ -96,9 +69,9 @@ mod tests { #[test] fn test_decode_kind_zero() { unsafe { - let result = decode_group_info(0, 0, std::ptr::null(), false, std::ptr::null_mut()); + let result = decode_group_info(0, 0, std::ptr::null(), false); match result { - GroupInfoDecode::Ok(None) => {} + Ok(None) => {} _ => panic!("expected Ok(None)"), } } @@ -107,11 +80,9 @@ mod tests { #[test] fn test_decode_proposer() { unsafe { - let result = decode_group_info(1, 7, std::ptr::null(), false, std::ptr::null_mut()); + let result = decode_group_info(1, 7, std::ptr::null(), false); match result { - GroupInfoDecode::Ok(Some( - GroupStateTransitionInfoStatus::GroupStateTransitionInfoProposer(pos), - )) => { + Ok(Some(GroupStateTransitionInfoStatus::GroupStateTransitionInfoProposer(pos))) => { assert_eq!(pos, 7); } _ => panic!("expected Proposer(7)"), @@ -122,14 +93,8 @@ mod tests { #[test] fn test_decode_other_signer_null_action_id() { unsafe { - let mut err = PlatformWalletFFIError::success(); - let result = decode_group_info(2, 0, std::ptr::null(), false, &mut err); - match result { - GroupInfoDecode::Err(code) => { - assert_eq!(code, PlatformWalletFFIResult::ErrorNullPointer); - } - _ => panic!("expected Err(NullPointer)"), - } + let result = decode_group_info(2, 0, std::ptr::null(), false); + assert!(matches!(result, Err(_)), "expected Err(NullPointer)"); } } @@ -137,12 +102,11 @@ mod tests { fn test_decode_other_signer_ok() { unsafe { let action_id_bytes = [9u8; 32]; - let result = - decode_group_info(2, 3, action_id_bytes.as_ptr(), true, std::ptr::null_mut()); + let result = decode_group_info(2, 3, action_id_bytes.as_ptr(), true); match result { - GroupInfoDecode::Ok(Some( - GroupStateTransitionInfoStatus::GroupStateTransitionInfoOtherSigner(info), - )) => { + Ok(Some(GroupStateTransitionInfoStatus::GroupStateTransitionInfoOtherSigner( + info, + ))) => { assert_eq!(info.group_contract_position, 3); assert!(info.action_is_proposer); assert_eq!(info.action_id.to_buffer(), action_id_bytes); @@ -155,14 +119,8 @@ mod tests { #[test] fn test_decode_invalid_kind() { unsafe { - let mut err = PlatformWalletFFIError::success(); - let result = decode_group_info(99, 0, std::ptr::null(), false, &mut err); - match result { - GroupInfoDecode::Err(code) => { - assert_eq!(code, PlatformWalletFFIResult::ErrorInvalidParameter); - } - _ => panic!("expected Err(InvalidParameter)"), - } + let result = decode_group_info(99, 0, std::ptr::null(), false); + assert!(matches!(result, Err(_)), "expected Err(InvalidParameter)"); } } } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/group_queries.rs b/packages/rs-platform-wallet-ffi/src/tokens/group_queries.rs index 36bfc3ecfa3..4d21c09d2ca 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/group_queries.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/group_queries.rs @@ -25,37 +25,26 @@ use platform_wallet::wallet::tokens::{ }; use serde_json::{json, Value}; -use crate::error::{PlatformWalletFFIError, PlatformWalletFFIResult}; +use crate::check_ptr; +use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::read_identifier; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; /// Decode a raw `u8` status from the FFI caller into the rs-dpp enum. -/// Mirrors `GroupActionStatus::try_from(u8)` but stamps `out_error` -/// on failure rather than returning `anyhow::Error`. -unsafe fn decode_status( - status: u8, - out_error: *mut PlatformWalletFFIError, -) -> Result { +fn decode_status(status: u8) -> Result { match status { 0 => Ok(GroupActionStatus::ActionActive), 1 => Ok(GroupActionStatus::ActionClosed), - other => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("Invalid group action status: {other} (expected 0 or 1)"), - ); - } - Err(PlatformWalletFFIResult::ErrorInvalidParameter) - } + other => Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + format!("Invalid group action status: {other} (expected 0 or 1)"), + )), } } -/// Render a group-action `params` payload as a JSON object. Each -/// variant matches the discriminator names emitted in -/// [`render_action_entry`] below. `kind` is owned so the `Other` -/// variant can carry a runtime name without a static-lifetime trick. +/// Render a group-action `params` payload as a JSON object. fn render_params(params: &GroupActionParams) -> (String, Value) { fn id(b: &dpp::prelude::Identifier) -> String { bs58::encode(b.as_bytes()).into_string() @@ -175,37 +164,11 @@ fn render_signer_entry(entry: &GroupActionSignerEntry) -> Value { /// Allocate a JSON `Value` as a NUL-terminated C string and write it /// to `out_json`. Caller frees with `platform_wallet_free_string`. -unsafe fn emit_json( - value: Value, - out_json: *mut *mut c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - let serialized = match serde_json::to_string(&value) { - Ok(s) => s, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorSerialization, - format!("Failed to serialize group action JSON: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorSerialization; - } - }; - let cstring = match std::ffi::CString::new(serialized) { - Ok(s) => s, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorSerialization, - format!("Group action JSON contained a NUL byte: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorSerialization; - } - }; +unsafe fn emit_json(value: Value, out_json: *mut *mut c_char) -> PlatformWalletFfiResult { + let serialized = unwrap_result_or_return!(serde_json::to_string(&value)); + let cstring = unwrap_result_or_return!(std::ffi::CString::new(serialized)); *out_json = cstring.into_raw(); - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } /// Fetch group-action proposals on `(token_contract_id, group_contract_position)` @@ -241,7 +204,6 @@ unsafe fn emit_json( /// - `out_json` must be a writable `*mut *mut c_char`. On success the /// caller owns the string and must free it via /// `platform_wallet_free_string`. -/// - `out_error` may be NULL. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_token_pending_group_actions( @@ -252,113 +214,47 @@ pub unsafe extern "C" fn platform_wallet_token_pending_group_actions( start_at_action_id: *const u8, limit: u16, out_json: *mut *mut c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_json.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_json is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_json); *out_json = std::ptr::null_mut(); - let contract_id = match read_identifier(token_contract_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid token_contract_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let contract_id = unwrap_result_or_return!(read_identifier(token_contract_id)); - let status_enum = match decode_status(status, out_error) { - Ok(s) => s, - Err(code) => return code, - }; + let status_enum = unwrap_result_or_return!(decode_status(status)); let start_at = if start_at_action_id.is_null() { None } else { - match read_identifier(start_at_action_id) { - Ok(i) => Some((i, true)), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid start_at_action_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - } + Some(( + unwrap_result_or_return!(read_identifier(start_at_action_id)), + true, + )) }; let limit_opt = if limit == 0 { None } else { Some(limit) }; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let sdk = wallet.sdk_arc(); - let result = block_on_worker(async move { - pending_group_actions_external( - sdk.as_ref(), - contract_id, - group_contract_position, - status_enum, - start_at, - limit_opt, - ) - .await - }); - match result { - Ok(entries) => { - let array: Vec = entries.iter().map(render_action_entry).collect(); - emit_json(Value::Array(array), out_json, out_error) - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("token_pending_group_actions failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let sdk = wallet.sdk_arc(); + block_on_worker(async move { + pending_group_actions_external( + sdk.as_ref(), + contract_id, + group_contract_position, + status_enum, + start_at, + limit_opt, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + let entries = unwrap_result_or_return!(result); + let array: Vec = entries.iter().map(render_action_entry).collect(); + emit_json(Value::Array(array), out_json) } /// Fetch the signers of a specific group-action proposal. Writes a /// JSON array to `out_json`. -/// -/// JSON element shape: -/// ```json -/// { "identityId": "", "power": } -/// ``` -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle registry. -/// - `token_contract_id` and `action_id` must each point at exactly -/// 32 readable bytes. -/// - `out_json` must be a writable `*mut *mut c_char`. On success the -/// caller owns the string and must free it via -/// `platform_wallet_free_string`. -/// - `out_error` may be NULL. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_token_group_action_signers( @@ -368,85 +264,30 @@ pub unsafe extern "C" fn platform_wallet_token_group_action_signers( status: u8, action_id: *const u8, out_json: *mut *mut c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_json.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "out_json is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_json); *out_json = std::ptr::null_mut(); - let contract_id = match read_identifier(token_contract_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid token_contract_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let action_id_decoded = match read_identifier(action_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid action_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let contract_id = unwrap_result_or_return!(read_identifier(token_contract_id)); + let action_id_decoded = unwrap_result_or_return!(read_identifier(action_id)); - let status_enum = match decode_status(status, out_error) { - Ok(s) => s, - Err(code) => return code, - }; + let status_enum = unwrap_result_or_return!(decode_status(status)); - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let sdk = wallet.sdk_arc(); - let result = block_on_worker(async move { - group_action_signers_external( - sdk.as_ref(), - contract_id, - group_contract_position, - status_enum, - action_id_decoded, - ) - .await - }); - match result { - Ok(entries) => { - let array: Vec = entries.iter().map(render_signer_entry).collect(); - emit_json(Value::Array(array), out_json, out_error) - } - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("token_group_action_signers failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let sdk = wallet.sdk_arc(); + block_on_worker(async move { + group_action_signers_external( + sdk.as_ref(), + contract_id, + group_contract_position, + status_enum, + action_id_decoded, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + let entries = unwrap_result_or_return!(result); + let array: Vec = entries.iter().map(render_signer_entry).collect(); + emit_json(Value::Array(array), out_json) } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/mint.rs b/packages/rs-platform-wallet-ffi/src/tokens/mint.rs index 21b5393b66e..63d92d72566 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/mint.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/mint.rs @@ -1,41 +1,19 @@ //! FFI binding for `IdentityWallet::token_mint_with_external_signer`. -//! -//! Mint supports group-gated execution; the caller passes a flat -//! `(group_info_kind, position, action_id, action_is_proposer)` tuple -//! that we decode into `Option` via -//! `super::group_info::decode_group_info`. -//! -//! Mint also takes an optional recipient identity id. When NULL the -//! tokens are issued to `identity_id` (mint-to-self). When non-NULL -//! the tokens are issued to that identity (subject to the contract's -//! `mintingAllowChoosingDestination` rule, enforced server-side). use std::ffi::CStr; use std::os::raw::c_char; use rs_sdk_ffi::{SignerHandle, VTableSigner}; -use super::group_info::{decode_group_info, GroupInfoDecode}; +use super::group_info::decode_group_info; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::read_identifier; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; /// Mint `amount` of token at `token_position` on `token_contract_id`. -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle registry. -/// - `identity_id`, `token_contract_id` must each point at exactly 32 -/// readable bytes. -/// - `issued_to_identity_id` may be NULL (mint-to-self); when non-NULL -/// it must point at exactly 32 readable bytes. -/// - `public_note` may be NULL; when non-NULL it must be a -/// NUL-terminated UTF-8 C string. -/// - `group_info_action_id` must point at 32 bytes when -/// `group_info_kind == 2`; ignored otherwise (may be NULL). -/// - `signer_handle` must be a valid, non-destroyed handle from -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. -/// - `out_error` may be NULL. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_token_mint( @@ -52,129 +30,61 @@ pub unsafe extern "C" fn platform_wallet_token_mint( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); - let id = match read_identifier(identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let contract_id = match read_identifier(token_contract_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid token_contract_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let id = unwrap_result_or_return!(read_identifier(identity_id)); + let contract_id = unwrap_result_or_return!(read_identifier(token_contract_id)); let recipient = if issued_to_identity_id.is_null() { None } else { - match read_identifier(issued_to_identity_id) { - Ok(i) => Some(i), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid issued_to_identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - } + Some(unwrap_result_or_return!(read_identifier( + issued_to_identity_id + ))) }; let public_note_str = if public_note.is_null() { None } else { - match CStr::from_ptr(public_note).to_str() { - Ok("") => None, - Ok(s) => Some(s.to_owned()), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("public_note is not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + { + let s = unwrap_result_or_return!(CStr::from_ptr(public_note).to_str()); + if s.is_empty() { + None + } else { + Some(s.to_owned()) } } }; - let group_info = match decode_group_info( + let group_info = unwrap_result_or_return!(decode_group_info( group_info_kind, group_info_position, group_info_action_id, group_info_action_is_proposer, - out_error, - ) { - GroupInfoDecode::Ok(value) => value, - GroupInfoDecode::Err(code) => return code, - }; + )); let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .token_mint_with_external_signer( - id, - contract_id, - token_position, - recipient, - amount, - public_note_str, - group_info, - signer, - ) - .await - }); - match result { - Ok(_) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("token_mint failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .token_mint_with_external_signer( + id, + contract_id, + token_position, + recipient, + amount, + public_note_str, + group_info, + signer, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/pause.rs b/packages/rs-platform-wallet-ffi/src/tokens/pause.rs index 24012bc689b..5818c7780a6 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/pause.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/pause.rs @@ -1,35 +1,19 @@ //! FFI binding for `IdentityWallet::token_pause_with_external_signer`. -//! -//! Pause is an emergency action and is group-capable; same group-info -//! shape as Freeze / Unfreeze. There is no `amount` and no target -//! identity — the action targets the token itself, halting all -//! operations until a matching Resume lands. use std::ffi::CStr; use std::os::raw::c_char; use rs_sdk_ffi::{SignerHandle, VTableSigner}; -use super::group_info::{decode_group_info, GroupInfoDecode}; +use super::group_info::decode_group_info; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::read_identifier; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; -/// Pause all operations for the token at `token_position` on -/// `token_contract_id`. -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle registry. -/// - `identity_id`, `token_contract_id` must each point at exactly 32 -/// readable bytes. -/// - `public_note` may be NULL; when non-NULL it must be a -/// NUL-terminated UTF-8 C string. -/// - `group_info_action_id` must point at 32 bytes when -/// `group_info_kind == 2`; ignored otherwise (may be NULL). -/// - `signer_handle` must be a valid, non-destroyed handle from -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. -/// - `out_error` may be NULL. +/// Pause all operations for the token at `token_position` on `token_contract_id`. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_token_pause( @@ -44,110 +28,51 @@ pub unsafe extern "C" fn platform_wallet_token_pause( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); - let id = match read_identifier(identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let contract_id = match read_identifier(token_contract_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid token_contract_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let id = unwrap_result_or_return!(read_identifier(identity_id)); + let contract_id = unwrap_result_or_return!(read_identifier(token_contract_id)); let public_note_str = if public_note.is_null() { None } else { - match CStr::from_ptr(public_note).to_str() { - Ok("") => None, - Ok(s) => Some(s.to_owned()), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("public_note is not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + { + let s = unwrap_result_or_return!(CStr::from_ptr(public_note).to_str()); + if s.is_empty() { + None + } else { + Some(s.to_owned()) } } }; - let group_info = match decode_group_info( + let group_info = unwrap_result_or_return!(decode_group_info( group_info_kind, group_info_position, group_info_action_id, group_info_action_is_proposer, - out_error, - ) { - GroupInfoDecode::Ok(value) => value, - GroupInfoDecode::Err(code) => return code, - }; + )); let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .token_pause_with_external_signer( - id, - contract_id, - token_position, - public_note_str, - group_info, - signer, - ) - .await - }); - match result { - Ok(_) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("token_pause failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .token_pause_with_external_signer( + id, + contract_id, + token_position, + public_note_str, + group_info, + signer, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/purchase.rs b/packages/rs-platform-wallet-ffi/src/tokens/purchase.rs index 4754b6ceba8..1bc7e7a9dcb 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/purchase.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/purchase.rs @@ -1,32 +1,15 @@ //! FFI binding for `IdentityWallet::token_purchase_with_external_signer`. -//! -//! Direct Purchase is not group-gated and the underlying builder -//! (`TokenDirectPurchaseTransitionBuilder`) takes no `public_note`, -//! so neither group-info nor public-note parameters are surfaced here. -//! -//! `expected_total_cost` is the credits the buyer agrees to pay in -//! total for `amount` tokens. Platform rejects the transition if the -//! on-chain pricing schedule disagrees with this figure, protecting -//! the buyer from price-change races between fetch and submit. use rs_sdk_ffi::{SignerHandle, VTableSigner}; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::read_identifier; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; -/// Purchase `amount` of the token at `token_position` on -/// `token_contract_id`, debiting `expected_total_cost` credits from -/// the buyer's identity. -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle registry. -/// - `identity_id`, `token_contract_id` must each point at exactly 32 -/// readable bytes. -/// - `signer_handle` must be a valid, non-destroyed handle from -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. -/// - `out_error` may be NULL. +/// Purchase `amount` of the token at `token_position` on `token_contract_id`. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_token_purchase( @@ -38,81 +21,31 @@ pub unsafe extern "C" fn platform_wallet_token_purchase( expected_total_cost: u64, _signing_key_id: u32, signer_handle: *mut SignerHandle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); - let id = match read_identifier(identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let contract_id = match read_identifier(token_contract_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid token_contract_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let id = unwrap_result_or_return!(read_identifier(identity_id)); + let contract_id = unwrap_result_or_return!(read_identifier(token_contract_id)); let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .token_purchase_with_external_signer( - id, - contract_id, - token_position, - amount, - expected_total_cost, - signer, - ) - .await - }); - match result { - Ok(_) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("token_purchase failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .token_purchase_with_external_signer( + id, + contract_id, + token_position, + amount, + expected_total_cost, + signer, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/resume.rs b/packages/rs-platform-wallet-ffi/src/tokens/resume.rs index 30ab20124c3..7df0eed35b5 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/resume.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/resume.rs @@ -1,35 +1,19 @@ //! FFI binding for `IdentityWallet::token_resume_with_external_signer`. -//! -//! Resume is an emergency action and is group-capable; same shape as -//! Pause. There is no `amount` and no target identity — the action -//! targets the token itself, re-enabling all operations after a -//! preceding Pause. use std::ffi::CStr; use std::os::raw::c_char; use rs_sdk_ffi::{SignerHandle, VTableSigner}; -use super::group_info::{decode_group_info, GroupInfoDecode}; +use super::group_info::decode_group_info; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::read_identifier; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; -/// Resume all operations for the token at `token_position` on -/// `token_contract_id`. -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle registry. -/// - `identity_id`, `token_contract_id` must each point at exactly 32 -/// readable bytes. -/// - `public_note` may be NULL; when non-NULL it must be a -/// NUL-terminated UTF-8 C string. -/// - `group_info_action_id` must point at 32 bytes when -/// `group_info_kind == 2`; ignored otherwise (may be NULL). -/// - `signer_handle` must be a valid, non-destroyed handle from -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. -/// - `out_error` may be NULL. +/// Resume all operations for the token at `token_position` on `token_contract_id`. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_token_resume( @@ -44,110 +28,51 @@ pub unsafe extern "C" fn platform_wallet_token_resume( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); - let id = match read_identifier(identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let contract_id = match read_identifier(token_contract_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid token_contract_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let id = unwrap_result_or_return!(read_identifier(identity_id)); + let contract_id = unwrap_result_or_return!(read_identifier(token_contract_id)); let public_note_str = if public_note.is_null() { None } else { - match CStr::from_ptr(public_note).to_str() { - Ok("") => None, - Ok(s) => Some(s.to_owned()), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("public_note is not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + { + let s = unwrap_result_or_return!(CStr::from_ptr(public_note).to_str()); + if s.is_empty() { + None + } else { + Some(s.to_owned()) } } }; - let group_info = match decode_group_info( + let group_info = unwrap_result_or_return!(decode_group_info( group_info_kind, group_info_position, group_info_action_id, group_info_action_is_proposer, - out_error, - ) { - GroupInfoDecode::Ok(value) => value, - GroupInfoDecode::Err(code) => return code, - }; + )); let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .token_resume_with_external_signer( - id, - contract_id, - token_position, - public_note_str, - group_info, - signer, - ) - .await - }); - match result { - Ok(_) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("token_resume failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .token_resume_with_external_signer( + id, + contract_id, + token_position, + public_note_str, + group_info, + signer, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/set_price.rs b/packages/rs-platform-wallet-ffi/src/tokens/set_price.rs index cf7daf17074..11d12d54a71 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/set_price.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/set_price.rs @@ -1,45 +1,20 @@ //! FFI binding for `IdentityWallet::token_set_price_with_external_signer`. -//! -//! Set Price configures the direct-purchase pricing schedule for a -//! token. This entry point exposes only the flat-price form: a -//! `price_per_token == 0` clears the schedule (disabling direct -//! purchase); any non-zero value is lifted into -//! `TokenPricingSchedule::SinglePrice(price)` on the Rust side. -//! -//! Set Price is group-capable; callers pass the same flat -//! `(group_info_kind, position, action_id, action_is_proposer)` tuple -//! used by Burn / Freeze / etc. -//! -//! The richer tiered-price schedule (`SetPrices`) is intentionally not -//! surfaced here — a future FFI / Swift wrapper can add it without -//! touching this entry point. use std::ffi::CStr; use std::os::raw::c_char; use rs_sdk_ffi::{SignerHandle, VTableSigner}; -use super::group_info::{decode_group_info, GroupInfoDecode}; +use super::group_info::decode_group_info; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::read_identifier; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; -/// Set the direct-purchase price for the token at `token_position` on -/// `token_contract_id`. `price_per_token == 0` disables direct -/// purchase by clearing the schedule. -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle registry. -/// - `identity_id`, `token_contract_id` must each point at exactly 32 -/// readable bytes. -/// - `public_note` may be NULL; when non-NULL it must be a -/// NUL-terminated UTF-8 C string. -/// - `group_info_action_id` must point at 32 bytes when -/// `group_info_kind == 2`; ignored otherwise (may be NULL). -/// - `signer_handle` must be a valid, non-destroyed handle from -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. -/// - `out_error` may be NULL. +/// Set the direct-purchase price for a token. `price_per_token == 0` clears +/// the schedule (disabling direct purchase). #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_token_set_price( @@ -55,111 +30,52 @@ pub unsafe extern "C" fn platform_wallet_token_set_price( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); - let id = match read_identifier(identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let contract_id = match read_identifier(token_contract_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid token_contract_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let id = unwrap_result_or_return!(read_identifier(identity_id)); + let contract_id = unwrap_result_or_return!(read_identifier(token_contract_id)); let public_note_str = if public_note.is_null() { None } else { - match CStr::from_ptr(public_note).to_str() { - Ok("") => None, - Ok(s) => Some(s.to_owned()), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("public_note is not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + { + let s = unwrap_result_or_return!(CStr::from_ptr(public_note).to_str()); + if s.is_empty() { + None + } else { + Some(s.to_owned()) } } }; - let group_info = match decode_group_info( + let group_info = unwrap_result_or_return!(decode_group_info( group_info_kind, group_info_position, group_info_action_id, group_info_action_is_proposer, - out_error, - ) { - GroupInfoDecode::Ok(value) => value, - GroupInfoDecode::Err(code) => return code, - }; + )); let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .token_set_price_with_external_signer( - id, - contract_id, - token_position, - price_per_token, - public_note_str, - group_info, - signer, - ) - .await - }); - match result { - Ok(_) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("token_set_price failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .token_set_price_with_external_signer( + id, + contract_id, + token_position, + price_per_token, + public_note_str, + group_info, + signer, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/transfer.rs b/packages/rs-platform-wallet-ffi/src/tokens/transfer.rs index de4677cfe48..a734e2435a3 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/transfer.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/transfer.rs @@ -1,39 +1,18 @@ //! FFI binding for `IdentityWallet::token_transfer_with_external_signer`. -//! -//! Transfers are never group-gated, so there's no `group_info_*` -//! payload here. use std::ffi::CStr; use std::os::raw::c_char; use rs_sdk_ffi::{SignerHandle, VTableSigner}; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::read_identifier; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; /// Transfer tokens from `identity_id` to `recipient_id`. -/// -/// The data contract is fetched server-side; the caller only ships -/// the contract id + token slot. The signing key is resolved by -/// `platform-wallet` via the standard "first AUTHENTICATION / -/// MASTER-or-HIGH / ECDSA_SECP256K1" rule on the identity. -/// -/// `signing_key_id` is currently advisory — the Rust side picks the -/// canonical authentication key for the identity. Wave 1 reserves the -/// argument for a future "explicit key id" mode without breaking the -/// ABI when that lands. -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle registry. -/// - `identity_id`, `token_contract_id`, `recipient_id` must each -/// point at exactly 32 readable bytes. -/// - `public_note` may be NULL; when non-NULL it must be a -/// NUL-terminated UTF-8 C string. -/// - `signer_handle` must be a valid, non-destroyed handle from -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. -/// - `out_error` may be NULL. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_token_transfer( @@ -46,112 +25,46 @@ pub unsafe extern "C" fn platform_wallet_token_transfer( public_note: *const c_char, _signing_key_id: u32, signer_handle: *mut SignerHandle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); - let from_id = match read_identifier(identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let contract_id = match read_identifier(token_contract_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid token_contract_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let to_id = match read_identifier(recipient_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid recipient_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let from_id = unwrap_result_or_return!(read_identifier(identity_id)); + let contract_id = unwrap_result_or_return!(read_identifier(token_contract_id)); + let to_id = unwrap_result_or_return!(read_identifier(recipient_id)); let public_note_str = if public_note.is_null() { None } else { - match CStr::from_ptr(public_note).to_str() { - Ok("") => None, - Ok(s) => Some(s.to_owned()), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("public_note is not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + { + let s = unwrap_result_or_return!(CStr::from_ptr(public_note).to_str()); + if s.is_empty() { + None + } else { + Some(s.to_owned()) } } }; let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .token_transfer_with_external_signer( - from_id, - contract_id, - token_position, - to_id, - amount, - public_note_str, - signer, - ) - .await - }); - match result { - Ok(_) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("token_transfer failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .token_transfer_with_external_signer( + from_id, + contract_id, + token_position, + to_id, + amount, + public_note_str, + signer, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/unfreeze.rs b/packages/rs-platform-wallet-ffi/src/tokens/unfreeze.rs index d9fd8a80a20..b5efeba4023 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/unfreeze.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/unfreeze.rs @@ -1,34 +1,19 @@ //! FFI binding for `IdentityWallet::token_unfreeze_with_external_signer`. -//! -//! Unfreeze is group-capable; same shape as Freeze. There is no -//! `amount` — the action unfreezes the full frozen balance of -//! `frozen_identity_id`. use std::ffi::CStr; use std::os::raw::c_char; use rs_sdk_ffi::{SignerHandle, VTableSigner}; -use super::group_info::{decode_group_info, GroupInfoDecode}; +use super::group_info::decode_group_info; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::read_identifier; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; -/// Unfreeze the entire frozen balance of `frozen_identity_id` for the -/// token at `token_position` on `token_contract_id`. -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle registry. -/// - `identity_id`, `token_contract_id`, `frozen_identity_id` must each -/// point at exactly 32 readable bytes. -/// - `public_note` may be NULL; when non-NULL it must be a -/// NUL-terminated UTF-8 C string. -/// - `group_info_action_id` must point at 32 bytes when -/// `group_info_kind == 2`; ignored otherwise (may be NULL). -/// - `signer_handle` must be a valid, non-destroyed handle from -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. -/// - `out_error` may be NULL. +/// Unfreeze the entire frozen balance of `frozen_identity_id`. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_token_unfreeze( @@ -44,123 +29,53 @@ pub unsafe extern "C" fn platform_wallet_token_unfreeze( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); - let id = match read_identifier(identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let contract_id = match read_identifier(token_contract_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid token_contract_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let frozen_id = match read_identifier(frozen_identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid frozen_identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let id = unwrap_result_or_return!(read_identifier(identity_id)); + let contract_id = unwrap_result_or_return!(read_identifier(token_contract_id)); + let frozen_id = unwrap_result_or_return!(read_identifier(frozen_identity_id)); let public_note_str = if public_note.is_null() { None } else { - match CStr::from_ptr(public_note).to_str() { - Ok("") => None, - Ok(s) => Some(s.to_owned()), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("public_note is not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + { + let s = unwrap_result_or_return!(CStr::from_ptr(public_note).to_str()); + if s.is_empty() { + None + } else { + Some(s.to_owned()) } } }; - let group_info = match decode_group_info( + let group_info = unwrap_result_or_return!(decode_group_info( group_info_kind, group_info_position, group_info_action_id, group_info_action_is_proposer, - out_error, - ) { - GroupInfoDecode::Ok(value) => value, - GroupInfoDecode::Err(code) => return code, - }; + )); let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .token_unfreeze_with_external_signer( - id, - contract_id, - token_position, - frozen_id, - public_note_str, - group_info, - signer, - ) - .await - }); - match result { - Ok(_) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("token_unfreeze failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .token_unfreeze_with_external_signer( + id, + contract_id, + token_position, + frozen_id, + public_note_str, + group_info, + signer, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/update_config.rs b/packages/rs-platform-wallet-ffi/src/tokens/update_config.rs index 7c3a02a82db..7999e0ea702 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/update_config.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/update_config.rs @@ -1,21 +1,4 @@ //! FFI binding for `IdentityWallet::token_update_config_with_external_signer`. -//! -//! `TokenConfigurationChangeItem` has 32 variants. Surfacing each one -//! through its own FFI parameter list would be both noisy and a -//! moving target — every new variant added to the enum would force a -//! new entry point. Instead, the FFI takes a `(tag, payload_json)` -//! pair and dispatches Rust-side. Wave 7 only implements one tag -//! (`0 = MaxSupply`); other tags are rejected with -//! `ErrorInvalidParameter` and a clear message so the caller sees -//! that the entry point exists but the variant is not yet wired. -//! -//! Tag 0 — MaxSupply payload shape: -//! ```json -//! { "newMaxSupply": "1000000" } // string-encoded u64; null = remove cap -//! ``` -//! The amount is a JSON string rather than a number because Platform's -//! `TokenAmount` is a u64 and JSON integers above 2^53 can't be -//! represented exactly by JS-style parsers. use std::ffi::CStr; use std::os::raw::c_char; @@ -24,145 +7,73 @@ use dpp::data_contract::associated_token::token_configuration_item::TokenConfigu use rs_sdk_ffi::{SignerHandle, VTableSigner}; use serde_json::Value; -use super::group_info::{decode_group_info, GroupInfoDecode}; +use super::group_info::decode_group_info; +use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::runtime::block_on_worker; use crate::types::read_identifier; +use crate::{unwrap_option_or_return, unwrap_result_or_return}; -/// Tag values accepted by `change_item_tag`. Mirrors the -/// `TokenConfigurationChangeItem` variant indices but the FFI keeps -/// its own table so the wire format doesn't drift if `u8_item_index` -/// is ever renumbered. const TAG_MAX_SUPPLY: u8 = 0; -/// Decode the `(tag, payload_json)` pair into a -/// `TokenConfigurationChangeItem`. Returns the variant or stamps -/// `out_error` and returns the error code to bubble back to the -/// caller. Future tags are added here without changing the surface. unsafe fn decode_change_item( tag: u8, payload_json: *const c_char, - out_error: *mut PlatformWalletFFIError, -) -> Result { +) -> Result { match tag { - TAG_MAX_SUPPLY => decode_max_supply_payload(payload_json, out_error), - other => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!( - "change_item_tag {} not yet supported by FFI (only MaxSupply = 0 is wired in this release)", - other - ), - ); - } - Err(PlatformWalletFFIResult::ErrorInvalidParameter) - } + TAG_MAX_SUPPLY => decode_max_supply_payload(payload_json), + other => Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + format!( + "change_item_tag {other} not yet supported by FFI (only MaxSupply = 0 is wired in this release)" + ), + )), } } unsafe fn decode_max_supply_payload( payload_json: *const c_char, - out_error: *mut PlatformWalletFFIError, -) -> Result { +) -> Result { if payload_json.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "change_item_payload_json is null (expected JSON object for MaxSupply)", - ); - } - return Err(PlatformWalletFFIResult::ErrorNullPointer); + return Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorNullPointer, + "change_item_payload_json is null (expected JSON object for MaxSupply)", + )); } - let payload_str = match CStr::from_ptr(payload_json).to_str() { - Ok(s) => s, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("change_item_payload_json is not valid UTF-8: {e}"), - ); - } - return Err(PlatformWalletFFIResult::ErrorUtf8Conversion); - } - }; - let parsed: Value = match serde_json::from_str(payload_str) { - Ok(v) => v, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorDeserialization, - format!("change_item_payload_json is not valid JSON: {e}"), - ); - } - return Err(PlatformWalletFFIResult::ErrorDeserialization); - } - }; + let payload_str = CStr::from_ptr(payload_json).to_str()?; + let parsed: Value = serde_json::from_str(payload_str)?; let new_max_supply_field = match parsed.get("newMaxSupply") { Some(v) => v, None => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - "MaxSupply payload missing required field 'newMaxSupply'", - ); - } - return Err(PlatformWalletFFIResult::ErrorInvalidParameter); + return Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + "MaxSupply payload missing required field 'newMaxSupply'", + )); } }; - let new_max_supply: Option = if new_max_supply_field.is_null() { - None - } else if let Some(s) = new_max_supply_field.as_str() { - match s.parse::() { - Ok(v) => Some(v), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("'newMaxSupply' is not a valid u64: {e}"), - ); - } - return Err(PlatformWalletFFIResult::ErrorInvalidParameter); - } - } - } else { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, + let new_max_supply: Option = match new_max_supply_field { + Value::Null => None, + Value::String(s) => Some(s.parse::().map_err(|e| { + PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + format!("'newMaxSupply' is not a valid u64: {e}"), + ) + })?), + _ => { + return Err(PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, "'newMaxSupply' must be a string-encoded u64 or null", - ); + )); } - return Err(PlatformWalletFFIResult::ErrorInvalidParameter); }; Ok(TokenConfigurationChangeItem::MaxSupply(new_max_supply)) } -/// Update the configuration of the token at `token_position` on -/// `token_contract_id`. -/// -/// The change is described by the `(change_item_tag, -/// change_item_payload_json)` pair so the entry point can grow new -/// variants without changing its parameter list. See the module -/// docstring for the per-tag payload shapes. -/// -/// # Safety -/// - `wallet_handle` must come from the platform-wallet handle registry. -/// - `identity_id`, `token_contract_id` must each point at exactly 32 -/// readable bytes. -/// - `change_item_payload_json` must be a NUL-terminated UTF-8 C string -/// when the tag's payload shape requires one (every currently -/// supported tag does). -/// - `public_note` may be NULL; when non-NULL it must be a -/// NUL-terminated UTF-8 C string. -/// - `group_info_action_id` must point at 32 bytes when -/// `group_info_kind == 2`; ignored otherwise (may be NULL). -/// - `signer_handle` must be a valid, non-destroyed handle from -/// `dash_sdk_signer_create_with_ctx`. Caller retains ownership. -/// - `out_error` may be NULL. +/// Update the configuration of the token at `token_position` on `token_contract_id`. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn platform_wallet_token_update_config( @@ -179,119 +90,59 @@ pub unsafe extern "C" fn platform_wallet_token_update_config( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if signer_handle.is_null() { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "signer_handle is null", - ); - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(signer_handle); - let id = match read_identifier(identity_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identity_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; - let contract_id = match read_identifier(token_contract_id) { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid token_contract_id: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let id = unwrap_result_or_return!(read_identifier(identity_id)); + let contract_id = unwrap_result_or_return!(read_identifier(token_contract_id)); - let change_item = match decode_change_item(change_item_tag, change_item_payload_json, out_error) - { - Ok(item) => item, - Err(code) => return code, - }; + let change_item = unwrap_result_or_return!(decode_change_item( + change_item_tag, + change_item_payload_json + )); let public_note_str = if public_note.is_null() { None } else { - match CStr::from_ptr(public_note).to_str() { - Ok("") => None, - Ok(s) => Some(s.to_owned()), - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - format!("public_note is not valid UTF-8: {e}"), - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; + { + let s = unwrap_result_or_return!(CStr::from_ptr(public_note).to_str()); + if s.is_empty() { + None + } else { + Some(s.to_owned()) } } }; - let group_info = match decode_group_info( + let group_info = unwrap_result_or_return!(decode_group_info( group_info_kind, group_info_position, group_info_action_id, group_info_action_is_proposer, - out_error, - ) { - GroupInfoDecode::Ok(value) => value, - GroupInfoDecode::Err(code) => return code, - }; + )); let signer_addr = signer_handle as usize; - PLATFORM_WALLET_STORAGE - .with_item(wallet_handle, |wallet| { - let identity_wallet = wallet.identity().clone(); - let result = block_on_worker(async move { - let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); - identity_wallet - .token_update_config_with_external_signer( - id, - contract_id, - token_position, - change_item, - public_note_str, - group_info, - signer, - ) - .await - }); - match result { - Ok(_) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - format!("token_update_config failed: {e}"), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or_else(|| { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidHandle, - "Invalid platform-wallet handle", - ); - } - PlatformWalletFFIResult::ErrorInvalidHandle + let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { + let identity_wallet = wallet.identity().clone(); + block_on_worker(async move { + let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); + identity_wallet + .token_update_config_with_external_signer( + id, + contract_id, + token_position, + change_item, + public_note_str, + group_info, + signer, + ) + .await }) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } #[cfg(test)] @@ -306,11 +157,10 @@ mod tests { fn decode_max_supply_some() { unsafe { let payload = cstr(r#"{"newMaxSupply":"1000000"}"#); - let item = - decode_change_item(TAG_MAX_SUPPLY, payload.as_ptr(), std::ptr::null_mut()).unwrap(); + let item = decode_change_item(TAG_MAX_SUPPLY, payload.as_ptr()).unwrap(); match item { TokenConfigurationChangeItem::MaxSupply(Some(v)) => assert_eq!(v, 1_000_000), - other => panic!("expected MaxSupply(Some), got {:?}", other), + other => panic!("expected MaxSupply(Some), got {other:?}"), } } } @@ -319,11 +169,10 @@ mod tests { fn decode_max_supply_none_removes_cap() { unsafe { let payload = cstr(r#"{"newMaxSupply":null}"#); - let item = - decode_change_item(TAG_MAX_SUPPLY, payload.as_ptr(), std::ptr::null_mut()).unwrap(); + let item = decode_change_item(TAG_MAX_SUPPLY, payload.as_ptr()).unwrap(); match item { TokenConfigurationChangeItem::MaxSupply(None) => {} - other => panic!("expected MaxSupply(None), got {:?}", other), + other => panic!("expected MaxSupply(None), got {other:?}"), } } } @@ -332,24 +181,10 @@ mod tests { fn decode_max_supply_missing_field() { unsafe { let payload = cstr(r#"{}"#); - let mut err = PlatformWalletFFIError::success(); - let result = decode_change_item(TAG_MAX_SUPPLY, payload.as_ptr(), &mut err); - match result { - Err(PlatformWalletFFIResult::ErrorInvalidParameter) => {} - other => panic!("expected ErrorInvalidParameter, got {:?}", other), - } - } - } - - #[test] - fn decode_max_supply_non_string() { - unsafe { - let payload = cstr(r#"{"newMaxSupply":123}"#); - let mut err = PlatformWalletFFIError::success(); - let result = decode_change_item(TAG_MAX_SUPPLY, payload.as_ptr(), &mut err); + let result = decode_change_item(TAG_MAX_SUPPLY, payload.as_ptr()); match result { - Err(PlatformWalletFFIResult::ErrorInvalidParameter) => {} - other => panic!("expected ErrorInvalidParameter, got {:?}", other), + Err(r) if r.code == PlatformWalletFfiResultCode::ErrorInvalidParameter => {} + _ => panic!("expected ErrorInvalidParameter"), } } } @@ -358,14 +193,10 @@ mod tests { fn decode_unsupported_tag_rejected() { unsafe { let payload = cstr(r#"{}"#); - let mut err = PlatformWalletFFIError::success(); - let result = decode_change_item(1, payload.as_ptr(), &mut err); + let result = decode_change_item(1, payload.as_ptr()); match result { - Err(PlatformWalletFFIResult::ErrorInvalidParameter) => {} - other => panic!( - "expected ErrorInvalidParameter for unsupported tag, got {:?}", - other - ), + Err(r) if r.code == PlatformWalletFfiResultCode::ErrorInvalidParameter => {} + _ => panic!("expected ErrorInvalidParameter for unsupported tag"), } } } @@ -374,11 +205,10 @@ mod tests { fn decode_invalid_json() { unsafe { let payload = cstr("not json"); - let mut err = PlatformWalletFFIError::success(); - let result = decode_change_item(TAG_MAX_SUPPLY, payload.as_ptr(), &mut err); + let result = decode_change_item(TAG_MAX_SUPPLY, payload.as_ptr()); match result { - Err(PlatformWalletFFIResult::ErrorDeserialization) => {} - other => panic!("expected ErrorDeserialization, got {:?}", other), + Err(r) if r.code == PlatformWalletFfiResultCode::ErrorDeserialization => {} + _ => panic!("expected ErrorDeserialization"), } } } diff --git a/packages/rs-platform-wallet-ffi/src/utils.rs b/packages/rs-platform-wallet-ffi/src/utils.rs index ba8f8261277..7f6b96bb1c5 100644 --- a/packages/rs-platform-wallet-ffi/src/utils.rs +++ b/packages/rs-platform-wallet-ffi/src/utils.rs @@ -1,4 +1,5 @@ use crate::error::*; +use crate::{check_ptr, unwrap_result_or_return}; use std::os::raw::{c_char, c_uchar}; /// Serialize any object to JSON bytes @@ -7,34 +8,13 @@ pub unsafe extern "C" fn platform_wallet_serialize_to_json_bytes( json_string: *const c_char, out_bytes: *mut *mut c_uchar, out_len: *mut usize, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if json_string.is_null() || out_bytes.is_null() || out_len.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(json_string); + check_ptr!(out_bytes); + check_ptr!(out_len); - let json_str = unsafe { - match std::ffi::CStr::from_ptr(json_string).to_str() { - Ok(s) => s, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "Invalid UTF-8 in JSON string", - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - } - }; + let json_str = + unwrap_result_or_return!(unsafe { std::ffi::CStr::from_ptr(json_string).to_str() }); let bytes = json_str.as_bytes().to_vec(); let len = bytes.len(); @@ -46,7 +26,7 @@ pub unsafe extern "C" fn platform_wallet_serialize_to_json_bytes( *out_len = len; } - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } /// Deserialize JSON bytes to string @@ -55,52 +35,15 @@ pub unsafe extern "C" fn platform_wallet_deserialize_from_json_bytes( bytes: *const c_uchar, len: usize, out_json_string: *mut *mut c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if bytes.is_null() || out_json_string.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(bytes); + check_ptr!(out_json_string); let data = unsafe { std::slice::from_raw_parts(bytes, len) }; - - match std::str::from_utf8(data) { - Ok(s) => match std::ffi::CString::new(s) { - Ok(c_str) => { - unsafe { *out_json_string = c_str.into_raw() }; - PlatformWalletFFIResult::Success - } - Err(_) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorDeserialization, - "Failed to convert to C string", - ); - } - } - PlatformWalletFFIResult::ErrorDeserialization - } - }, - Err(_) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "Invalid UTF-8 in bytes", - ); - } - } - PlatformWalletFFIResult::ErrorUtf8Conversion - } - } + let s = unwrap_result_or_return!(std::str::from_utf8(data)); + let c_str = unwrap_result_or_return!(std::ffi::CString::new(s)); + unsafe { *out_json_string = c_str.into_raw() }; + PlatformWalletFfiResult::ok() } /// Free bytes allocated by FFI functions @@ -117,24 +60,11 @@ pub unsafe extern "C" fn platform_wallet_bytes_free(bytes: *mut c_uchar, len: us #[no_mangle] pub unsafe extern "C" fn platform_wallet_generate_random_identifier( out_id: *mut u8, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_id.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } - +) -> PlatformWalletFfiResult { + check_ptr!(out_id); let id = dpp::prelude::Identifier::random(); unsafe { crate::types::write_identifier(out_id, &id) }; - - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } /// Convert identifier (32 bytes pointed to by `id`) to base58 hex @@ -143,53 +73,16 @@ pub unsafe extern "C" fn platform_wallet_generate_random_identifier( pub unsafe extern "C" fn platform_wallet_identifier_to_hex( id: *const u8, out_hex: *mut *mut c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_hex.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(id); + check_ptr!(out_hex); - let identifier = match unsafe { crate::types::read_identifier(id) } { - Ok(i) => i, - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Invalid identifier: {}", e), - ); - } - } - return PlatformWalletFFIResult::ErrorInvalidIdentifier; - } - }; + let identifier = unwrap_result_or_return!(unsafe { crate::types::read_identifier(id) }); let hex = identifier.to_string(dpp::platform_value::string_encoding::Encoding::Base58); - match std::ffi::CString::new(hex) { - Ok(c_str) => { - unsafe { *out_hex = c_str.into_raw() }; - PlatformWalletFFIResult::Success - } - Err(_) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorSerialization, - "Failed to convert hex to C string", - ); - } - } - PlatformWalletFFIResult::ErrorSerialization - } - } + let c_str = unwrap_result_or_return!(std::ffi::CString::new(hex)); + unsafe { *out_hex = c_str.into_raw() }; + PlatformWalletFfiResult::ok() } /// Convert base58 hex string to identifier (writes 32 bytes into @@ -198,55 +91,18 @@ pub unsafe extern "C" fn platform_wallet_identifier_to_hex( pub unsafe extern "C" fn platform_wallet_identifier_from_hex( hex: *const c_char, out_id: *mut u8, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if hex.is_null() || out_id.is_null() { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorNullPointer, - "Null pointer provided", - ); - } - } - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(hex); + check_ptr!(out_id); - let hex_str = unsafe { - match std::ffi::CStr::from_ptr(hex).to_str() { - Ok(s) => s, - Err(_) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorUtf8Conversion, - "Invalid UTF-8 in hex string", - ); - } - return PlatformWalletFFIResult::ErrorUtf8Conversion; - } - } - }; + let hex_str = unwrap_result_or_return!(unsafe { std::ffi::CStr::from_ptr(hex).to_str() }); - match dpp::prelude::Identifier::from_string( + let identifier = unwrap_result_or_return!(dpp::prelude::Identifier::from_string( hex_str, dpp::platform_value::string_encoding::Encoding::Base58, - ) { - Ok(identifier) => { - unsafe { crate::types::write_identifier(out_id, &identifier) }; - PlatformWalletFFIResult::Success - } - Err(e) => { - if !out_error.is_null() { - unsafe { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidIdentifier, - format!("Failed to parse identifier: {}", e), - ); - } - } - PlatformWalletFFIResult::ErrorInvalidIdentifier - } - } + )); + unsafe { crate::types::write_identifier(out_id, &identifier) }; + PlatformWalletFfiResult::ok() } /// Compute hash160 (RIPEMD160(SHA256(data))) of the input bytes. @@ -295,11 +151,6 @@ mod tests { #[test] fn test_hash160_matches_known_vector() { - // Test vector: hash160 of the all-zero 33-byte compressed - // pubkey. RIPEMD160(SHA256(0x00 * 33)) = known constant. The - // exact value is captured by re-deriving via dashcore — this - // guards against accidental changes to either the FFI shape - // or the underlying hasher. use dashcore::hashes::Hash; let input = [0u8; 33]; let mut out = [0u8; 20]; @@ -339,30 +190,21 @@ mod tests { let json = std::ffi::CString::new(r#"{"test":"value"}"#).unwrap(); let mut bytes: *mut c_uchar = std::ptr::null_mut(); let mut len: usize = 0; - let mut error = PlatformWalletFFIError::success(); - - // Serialize - let result = platform_wallet_serialize_to_json_bytes( - json.as_ptr(), - &mut bytes, - &mut len, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + + let result = + platform_wallet_serialize_to_json_bytes(json.as_ptr(), &mut bytes, &mut len); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert!(!bytes.is_null()); assert!(len > 0); - // Deserialize let mut json_out: *mut c_char = std::ptr::null_mut(); - let result = - platform_wallet_deserialize_from_json_bytes(bytes, len, &mut json_out, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = platform_wallet_deserialize_from_json_bytes(bytes, len, &mut json_out); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert!(!json_out.is_null()); let json_str = std::ffi::CStr::from_ptr(json_out).to_str().unwrap(); assert_eq!(json_str, r#"{"test":"value"}"#); - // Cleanup platform_wallet_bytes_free(bytes, len); crate::platform_wallet_string_free(json_out); } @@ -372,12 +214,8 @@ mod tests { fn test_generate_random_identifier() { unsafe { let mut id = [0u8; 32]; - let mut error = PlatformWalletFFIError::success(); - - let result = platform_wallet_generate_random_identifier(id.as_mut_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); - - // Check that it's not all zeros + let result = platform_wallet_generate_random_identifier(id.as_mut_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_ne!(id, [0u8; 32]); } } @@ -386,26 +224,19 @@ mod tests { fn test_identifier_to_from_hex() { unsafe { let mut id = [0u8; 32]; - let mut error = PlatformWalletFFIError::success(); - - // Generate random ID - platform_wallet_generate_random_identifier(id.as_mut_ptr(), &mut error); + platform_wallet_generate_random_identifier(id.as_mut_ptr()); - // Convert to hex let mut hex: *mut c_char = std::ptr::null_mut(); - let result = platform_wallet_identifier_to_hex(id.as_ptr(), &mut hex, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = platform_wallet_identifier_to_hex(id.as_ptr(), &mut hex); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert!(!hex.is_null()); - // Convert back from hex let mut id2 = [0u8; 32]; - let result = platform_wallet_identifier_from_hex(hex, id2.as_mut_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = platform_wallet_identifier_from_hex(hex, id2.as_mut_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); - // Should match assert_eq!(id, id2); - // Cleanup crate::platform_wallet_string_free(hex); } } diff --git a/packages/rs-platform-wallet-ffi/src/wallet.rs b/packages/rs-platform-wallet-ffi/src/wallet.rs index 086f18ac665..01b23730af6 100644 --- a/packages/rs-platform-wallet-ffi/src/wallet.rs +++ b/packages/rs-platform-wallet-ffi/src/wallet.rs @@ -3,24 +3,19 @@ use crate::error::*; use crate::handle::*; use crate::runtime::runtime; +use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; /// Get the wallet ID (32 bytes). #[no_mangle] pub unsafe extern "C" fn platform_wallet_get_id( handle: Handle, out_wallet_id: *mut [u8; 32], - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_wallet_id.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_wallet_id); - PLATFORM_WALLET_STORAGE - .with_item(handle, |wallet| { - *out_wallet_id = wallet.wallet_id(); - PlatformWalletFFIResult::Success - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = PLATFORM_WALLET_STORAGE.with_item(handle, |wallet| wallet.wallet_id()); + *out_wallet_id = unwrap_option_or_return!(option); + PlatformWalletFfiResult::ok() } /// Get lock-free balance (spendable, unconfirmed, immature, locked). @@ -33,26 +28,25 @@ pub unsafe extern "C" fn platform_wallet_get_balance( out_unconfirmed: *mut u64, out_immature: *mut u64, out_locked: *mut u64, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_WALLET_STORAGE - .with_item(handle, |wallet| { - let balance = wallet.balance(); - if !out_confirmed.is_null() { - *out_confirmed = balance.confirmed(); - } - if !out_unconfirmed.is_null() { - *out_unconfirmed = balance.unconfirmed(); - } - if !out_immature.is_null() { - *out_immature = balance.immature(); - } - if !out_locked.is_null() { - *out_locked = balance.locked(); - } - PlatformWalletFFIResult::Success - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) +) -> PlatformWalletFfiResult { + let option = PLATFORM_WALLET_STORAGE.with_item(handle, |wallet| { + let b = wallet.balance(); + (b.confirmed(), b.unconfirmed(), b.immature(), b.locked()) + }); + let (confirmed, unconfirmed, immature, locked) = unwrap_option_or_return!(option); + if !out_confirmed.is_null() { + *out_confirmed = confirmed; + } + if !out_unconfirmed.is_null() { + *out_unconfirmed = unconfirmed; + } + if !out_immature.is_null() { + *out_immature = immature; + } + if !out_locked.is_null() { + *out_locked = locked; + } + PlatformWalletFfiResult::ok() } /// Get a PlatformAddressWallet handle from a PlatformWallet. @@ -62,20 +56,13 @@ pub unsafe extern "C" fn platform_wallet_get_balance( pub unsafe extern "C" fn platform_wallet_get_platform( handle: Handle, out_platform_handle: *mut Handle, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_platform_handle.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_platform_handle); - PLATFORM_WALLET_STORAGE - .with_item(handle, |wallet| { - let platform_wallet = wallet.platform().clone(); - let platform_handle = PLATFORM_ADDRESS_WALLET_STORAGE.insert(platform_wallet); - *out_platform_handle = platform_handle; - PlatformWalletFFIResult::Success - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = PLATFORM_WALLET_STORAGE.with_item(handle, |wallet| wallet.platform().clone()); + let platform_wallet = unwrap_option_or_return!(option); + *out_platform_handle = PLATFORM_ADDRESS_WALLET_STORAGE.insert(platform_wallet); + PlatformWalletFfiResult::ok() } /// Get an AssetLockManager handle from a PlatformWallet. @@ -83,20 +70,14 @@ pub unsafe extern "C" fn platform_wallet_get_platform( pub unsafe extern "C" fn platform_wallet_get_asset_locks( handle: Handle, out_asset_lock_handle: *mut Handle, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_asset_lock_handle.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } - - PLATFORM_WALLET_STORAGE - .with_item(handle, |wallet| { - let asset_locks = std::sync::Arc::clone(wallet.asset_locks()); - let asset_lock_handle = ASSET_LOCK_MANAGER_STORAGE.insert(asset_locks); - *out_asset_lock_handle = asset_lock_handle; - PlatformWalletFFIResult::Success - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) +) -> PlatformWalletFfiResult { + check_ptr!(out_asset_lock_handle); + + let option = PLATFORM_WALLET_STORAGE + .with_item(handle, |wallet| std::sync::Arc::clone(wallet.asset_locks())); + let asset_locks = unwrap_option_or_return!(option); + *out_asset_lock_handle = ASSET_LOCK_MANAGER_STORAGE.insert(asset_locks); + PlatformWalletFfiResult::ok() } /// Get a CoreWallet handle from a PlatformWallet. @@ -106,66 +87,35 @@ pub unsafe extern "C" fn platform_wallet_get_asset_locks( pub unsafe extern "C" fn platform_wallet_get_core( handle: Handle, out_core_handle: *mut Handle, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if out_core_handle.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(out_core_handle); - PLATFORM_WALLET_STORAGE - .with_item(handle, |wallet| { - let core_wallet = wallet.core().clone(); - let core_handle = CORE_WALLET_STORAGE.insert(core_wallet); - *out_core_handle = core_handle; - PlatformWalletFFIResult::Success - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) + let option = PLATFORM_WALLET_STORAGE.with_item(handle, |wallet| wallet.core().clone()); + let core_wallet = unwrap_option_or_return!(option); + *out_core_handle = CORE_WALLET_STORAGE.insert(core_wallet); + PlatformWalletFfiResult::ok() } /// Flush all queued changesets to the storage backend. #[no_mangle] -pub unsafe extern "C" fn platform_wallet_flush_persist( - handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_WALLET_STORAGE - .with_item(handle, |wallet| match wallet.flush_persist() { - Ok(()) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) +pub unsafe extern "C" fn platform_wallet_flush_persist(handle: Handle) -> PlatformWalletFfiResult { + let option = PLATFORM_WALLET_STORAGE.with_item(handle, |wallet| wallet.flush_persist()); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } /// Load persisted state and apply it to the in-memory wallet. #[no_mangle] pub unsafe extern "C" fn platform_wallet_load_and_apply_persisted( handle: Handle, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - PLATFORM_WALLET_STORAGE - .with_item(handle, |wallet| { - match runtime().block_on(wallet.load_and_apply_persisted()) { - Ok(()) => PlatformWalletFFIResult::Success, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorWalletOperation, - e.to_string(), - ); - } - PlatformWalletFFIResult::ErrorWalletOperation - } - } - }) - .unwrap_or(PlatformWalletFFIResult::ErrorInvalidHandle) +) -> PlatformWalletFfiResult { + let option = PLATFORM_WALLET_STORAGE.with_item(handle, |wallet| { + runtime().block_on(wallet.load_and_apply_persisted()) + }); + let result = unwrap_option_or_return!(option); + unwrap_result_or_return!(result); + PlatformWalletFfiResult::ok() } /// Query per-account balances from the in-memory `WalletManager`. @@ -187,52 +137,49 @@ pub unsafe extern "C" fn platform_wallet_manager_get_account_balances( wallet_id: *const u8, out_entries: *mut *const crate::core_wallet_types::AccountBalanceEntryFFI, out_count: *mut usize, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if wallet_id.is_null() || out_entries.is_null() || out_count.is_null() { - return PlatformWalletFFIResult::ErrorNullPointer; - } +) -> PlatformWalletFfiResult { + check_ptr!(wallet_id); + check_ptr!(out_entries); + check_ptr!(out_count); let wid: [u8; 32] = std::ptr::read(wallet_id as *const [u8; 32]); - PLATFORM_WALLET_MANAGER_STORAGE - .with_item(manager_handle, |manager| { - let balances = manager.account_balances_blocking(&wid); - let entries: Vec = balances - .into_iter() - .map(|(account_type, balance)| { - let tags = crate::core_wallet_types::account_type_to_tags(&account_type); - crate::core_wallet_types::AccountBalanceEntryFFI { - type_tag: tags.type_tag, - standard_tag: tags.standard_tag, - index: tags.index, - registration_index: tags.registration_index, - key_class: tags.key_class, - user_identity_id: tags.user_identity_id, - friend_identity_id: tags.friend_identity_id, - confirmed: balance.confirmed(), - unconfirmed: balance.unconfirmed(), - immature: balance.immature(), - locked: balance.locked(), - } - }) - .collect(); - let count = entries.len(); - if count == 0 { - *out_entries = std::ptr::null(); - *out_count = 0; - return PlatformWalletFFIResult::Success; + let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(manager_handle, |manager| { + manager.account_balances_blocking(&wid) + }); + let balances = unwrap_option_or_return!(option); + + let entries: Vec = balances + .into_iter() + .map(|(account_type, balance)| { + let tags = crate::core_wallet_types::account_type_to_tags(&account_type); + crate::core_wallet_types::AccountBalanceEntryFFI { + type_tag: tags.type_tag, + standard_tag: tags.standard_tag, + index: tags.index, + registration_index: tags.registration_index, + key_class: tags.key_class, + user_identity_id: tags.user_identity_id, + friend_identity_id: tags.friend_identity_id, + confirmed: balance.confirmed(), + unconfirmed: balance.unconfirmed(), + immature: balance.immature(), + locked: balance.locked(), } - let boxed = entries.into_boxed_slice(); - *out_entries = Box::into_raw(boxed) as *const _; - *out_count = count; - PlatformWalletFFIResult::Success - }) - .unwrap_or_else(|| { - *out_entries = std::ptr::null(); - *out_count = 0; - PlatformWalletFFIResult::ErrorInvalidHandle }) + .collect(); + let count = entries.len(); + + if count == 0 { + *out_entries = std::ptr::null(); + *out_count = 0; + return PlatformWalletFfiResult::ok(); + } + + let boxed = entries.into_boxed_slice(); + *out_entries = Box::into_raw(boxed) as *const _; + *out_count = count; + PlatformWalletFfiResult::ok() } /// Free an array returned by [`platform_wallet_manager_get_account_balances`]. @@ -242,16 +189,13 @@ pub unsafe extern "C" fn platform_wallet_manager_free_account_balances( count: usize, ) { if !entries.is_null() && count > 0 { - let _ = Box::from_raw(std::slice::from_raw_parts_mut(entries, count)); + let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(entries, count)); } } /// Destroy a PlatformWallet handle. #[no_mangle] -pub unsafe extern "C" fn platform_wallet_destroy( - handle: Handle, - _out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { +pub unsafe extern "C" fn platform_wallet_destroy(handle: Handle) -> PlatformWalletFfiResult { PLATFORM_WALLET_STORAGE.remove(handle); - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/xpub_render.rs b/packages/rs-platform-wallet-ffi/src/xpub_render.rs index 71267619585..e7d6e79d8f5 100644 --- a/packages/rs-platform-wallet-ffi/src/xpub_render.rs +++ b/packages/rs-platform-wallet-ffi/src/xpub_render.rs @@ -8,7 +8,8 @@ use std::ffi::CString; use std::os::raw::c_char; use std::ptr; -use crate::error::{PlatformWalletFFIError, PlatformWalletFFIResult}; +use crate::error::{PlatformWalletFfiResult, PlatformWalletFfiResultCode}; +use crate::{check_ptr, unwrap_result_or_return}; /// Decode a bincode-encoded `ExtendedPubKey` (as emitted by /// `on_persist_account_registrations_fn`) and render it as a BIP32 base58check @@ -23,43 +24,25 @@ pub unsafe extern "C" fn platform_wallet_account_xpub_to_string( bytes: *const u8, bytes_len: usize, out_string: *mut *mut c_char, - out_error: *mut PlatformWalletFFIError, -) -> PlatformWalletFFIResult { - if bytes.is_null() || out_string.is_null() || bytes_len == 0 { - return PlatformWalletFFIResult::ErrorNullPointer; +) -> PlatformWalletFfiResult { + check_ptr!(bytes); + check_ptr!(out_string); + if bytes_len == 0 { + return PlatformWalletFfiResult::err( + PlatformWalletFfiResultCode::ErrorInvalidParameter, + "bytes_len is zero", + ); } *out_string = ptr::null_mut(); let slice = std::slice::from_raw_parts(bytes, bytes_len); - let decoded: Result<(ExtendedPubKey, usize), _> = - bincode::decode_from_slice(slice, config::standard()); - let xpub = match decoded { - Ok((v, _)) => v, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("Failed to decode account xpub: {}", e), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; + let (xpub, _): (ExtendedPubKey, usize) = unwrap_result_or_return!( + bincode::decode_from_slice::(slice, config::standard()) + ); - let cstring = match CString::new(xpub.to_string()) { - Ok(cs) => cs, - Err(e) => { - if !out_error.is_null() { - *out_error = PlatformWalletFFIError::new( - PlatformWalletFFIResult::ErrorInvalidParameter, - format!("account xpub string contained a NUL byte: {}", e), - ); - } - return PlatformWalletFFIResult::ErrorInvalidParameter; - } - }; + let cstring = unwrap_result_or_return!(CString::new(xpub.to_string())); *out_string = cstring.into_raw(); - PlatformWalletFFIResult::Success + PlatformWalletFfiResult::ok() } /// Free a C string returned by [`platform_wallet_account_xpub_to_string`]. diff --git a/packages/rs-platform-wallet-ffi/tests/comprehensive_tests.rs b/packages/rs-platform-wallet-ffi/tests/comprehensive_tests.rs index a0ec1682ecd..b844affb998 100644 --- a/packages/rs-platform-wallet-ffi/tests/comprehensive_tests.rs +++ b/packages/rs-platform-wallet-ffi/tests/comprehensive_tests.rs @@ -6,6 +6,13 @@ //! `IdentifierBytes` slot is a `*const u8` / `*mut u8` to a 32-byte //! buffer; every former by-value free is `&mut`). See the EXC_BAD_ACCESS //! sweep for the rationale. +//! +//! Also updated to the unified-result FFI ABI: every entry point returns +//! a `PlatformWalletFfiResult` (with `.code` + `.message`) instead of +//! taking a separate `&mut PlatformWalletFFIError` out-parameter. Storage +//! misses surface as `NotFound` via `unwrap_option_or_return!`; only the +//! handful of destroy entry points still return `ErrorInvalidHandle` +//! directly. mod test_data; @@ -25,73 +32,59 @@ fn test_contact_request_field_access() { let bob = identities::bob(); let bob_id_bytes: [u8; 32] = bob.identity.id().to_buffer(); - let mut error = PlatformWalletFFIError::success(); - // Get the contact request for Bob let mut request_handle: Handle = NULL_HANDLE; let result = managed_identity_get_sent_contact_request( alice_handle, bob_id_bytes.as_ptr(), &mut request_handle, - &mut error, ); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_ne!(request_handle, NULL_HANDLE); // Verify sender ID let mut sender_id = [0u8; 32]; - let result = - contact_request_get_sender_id(request_handle, sender_id.as_mut_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = contact_request_get_sender_id(request_handle, sender_id.as_mut_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(sender_id, [1u8; 32]); // Alice's ID // Verify recipient ID let mut recipient_id = [0u8; 32]; - let result = - contact_request_get_recipient_id(request_handle, recipient_id.as_mut_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = contact_request_get_recipient_id(request_handle, recipient_id.as_mut_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(recipient_id, [2u8; 32]); // Bob's ID // Verify sender key index let mut sender_key_idx = 999u32; - let result = - contact_request_get_sender_key_index(request_handle, &mut sender_key_idx, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = contact_request_get_sender_key_index(request_handle, &mut sender_key_idx); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(sender_key_idx, 0); // Verify recipient key index let mut recipient_key_idx = 999u32; - let result = contact_request_get_recipient_key_index( - request_handle, - &mut recipient_key_idx, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = + contact_request_get_recipient_key_index(request_handle, &mut recipient_key_idx); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(recipient_key_idx, 1); // Verify account reference let mut account_ref = 999u32; - let result = - contact_request_get_account_reference(request_handle, &mut account_ref, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = contact_request_get_account_reference(request_handle, &mut account_ref); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(account_ref, 0); // Verify timestamp let mut created_at = 0u64; - let result = contact_request_get_created_at(request_handle, &mut created_at, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = contact_request_get_created_at(request_handle, &mut created_at); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(created_at, 1_700_000_000); // Verify encrypted public key let mut bytes_ptr: *mut u8 = std::ptr::null_mut(); let mut len: usize = 0; - let result = contact_request_get_encrypted_public_key( - request_handle, - &mut bytes_ptr, - &mut len, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = + contact_request_get_encrypted_public_key(request_handle, &mut bytes_ptr, &mut len); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert!(!bytes_ptr.is_null()); assert_eq!(len, 96); @@ -112,30 +105,25 @@ fn test_incoming_contact_request_retrieval() { let bob = identities::bob(); let bob_id_bytes: [u8; 32] = bob.identity.id().to_buffer(); - let mut error = PlatformWalletFFIError::success(); - // Get the incoming contact request from Bob let mut request_handle: Handle = NULL_HANDLE; let result = managed_identity_get_incoming_contact_request( alice_handle, bob_id_bytes.as_ptr(), &mut request_handle, - &mut error, ); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_ne!(request_handle, NULL_HANDLE); // Verify it's from Bob to Alice let mut sender_id = [0u8; 32]; - let result = - contact_request_get_sender_id(request_handle, sender_id.as_mut_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = contact_request_get_sender_id(request_handle, sender_id.as_mut_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(sender_id, [2u8; 32]); // Bob's ID let mut recipient_id = [0u8; 32]; - let result = - contact_request_get_recipient_id(request_handle, recipient_id.as_mut_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = contact_request_get_recipient_id(request_handle, recipient_id.as_mut_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(recipient_id, [1u8; 32]); // Alice's ID // Cleanup @@ -151,16 +139,13 @@ fn test_multiple_contact_requests() { let (alice, _requests) = scenarios::alice_with_pending_sent_requests(); let alice_handle = MANAGED_IDENTITY_STORAGE.insert(alice); - let mut error = PlatformWalletFFIError::success(); - // Get all sent contact request IDs let mut array = IdentifierArray { items: std::ptr::null_mut(), count: 0, }; - let result = - managed_identity_get_sent_contact_request_ids(alice_handle, &mut array, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = managed_identity_get_sent_contact_request_ids(alice_handle, &mut array); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(array.count, 3); // Verify we can retrieve each request @@ -173,9 +158,8 @@ fn test_multiple_contact_requests() { alice_handle, row_ptr, &mut request_handle, - &mut error, ); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_ne!(request_handle, NULL_HANDLE); contact_request_destroy(request_handle); @@ -194,16 +178,13 @@ fn test_established_contacts() { let (alice, _contacts) = scenarios::alice_with_established_contacts(); let alice_handle = MANAGED_IDENTITY_STORAGE.insert(alice); - let mut error = PlatformWalletFFIError::success(); - // Get all established contact IDs let mut array = IdentifierArray { items: std::ptr::null_mut(), count: 0, }; - let result = - managed_identity_get_established_contact_ids(alice_handle, &mut array, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = managed_identity_get_established_contact_ids(alice_handle, &mut array); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(array.count, 2); // Bob and Carol // Check if Bob is established @@ -213,9 +194,8 @@ fn test_established_contacts() { alice_handle, bob_id_bytes.as_ptr(), &mut is_established, - &mut error, ); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert!(is_established); // Check if Dave is NOT established @@ -225,9 +205,8 @@ fn test_established_contacts() { alice_handle, dave_id_bytes.as_ptr(), &mut is_established, - &mut error, ); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert!(!is_established); // Cleanup @@ -243,18 +222,12 @@ fn test_mixed_contact_scenario() { let alice = scenarios::alice_with_mixed_contacts(); let alice_handle = MANAGED_IDENTITY_STORAGE.insert(alice); - let mut error = PlatformWalletFFIError::success(); - // Verify established contacts count let mut established_array = IdentifierArray { items: std::ptr::null_mut(), count: 0, }; - managed_identity_get_established_contact_ids( - alice_handle, - &mut established_array, - &mut error, - ); + managed_identity_get_established_contact_ids(alice_handle, &mut established_array); assert_eq!(established_array.count, 1); // Only Bob // Verify sent requests count @@ -262,7 +235,7 @@ fn test_mixed_contact_scenario() { items: std::ptr::null_mut(), count: 0, }; - managed_identity_get_sent_contact_request_ids(alice_handle, &mut sent_array, &mut error); + managed_identity_get_sent_contact_request_ids(alice_handle, &mut sent_array); assert_eq!(sent_array.count, 1); // Only Carol // Verify incoming requests count @@ -270,11 +243,7 @@ fn test_mixed_contact_scenario() { items: std::ptr::null_mut(), count: 0, }; - managed_identity_get_incoming_contact_request_ids( - alice_handle, - &mut incoming_array, - &mut error, - ); + managed_identity_get_incoming_contact_request_ids(alice_handle, &mut incoming_array); assert_eq!(incoming_array.count, 2); // Dave and Eve // Cleanup @@ -290,12 +259,10 @@ fn test_identity_manager_with_multiple_identities() { unsafe { use dpp::identity::accessors::IdentityGettersV0; - let mut error = PlatformWalletFFIError::success(); - // Create identity manager let mut manager_handle: Handle = NULL_HANDLE; - let result = identity_manager_create(&mut manager_handle, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = identity_manager_create(&mut manager_handle); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Add Alice, Bob, and Carol let alice = identities::alice(); @@ -310,14 +277,14 @@ fn test_identity_manager_with_multiple_identities() { let bob_handle = MANAGED_IDENTITY_STORAGE.insert(bob); let carol_handle = MANAGED_IDENTITY_STORAGE.insert(carol); - identity_manager_add_identity(manager_handle, alice_handle, &mut error); - identity_manager_add_identity(manager_handle, bob_handle, &mut error); - identity_manager_add_identity(manager_handle, carol_handle, &mut error); + identity_manager_add_identity(manager_handle, alice_handle); + identity_manager_add_identity(manager_handle, bob_handle); + identity_manager_add_identity(manager_handle, carol_handle); // Verify count let mut count: usize = 0; - let result = identity_manager_get_identity_count(manager_handle, &mut count, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = identity_manager_get_identity_count(manager_handle, &mut count); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(count, 3); // Get all identity IDs @@ -325,8 +292,8 @@ fn test_identity_manager_with_multiple_identities() { items: std::ptr::null_mut(), count: 0, }; - let result = identity_manager_get_all_identity_ids(manager_handle, &mut array, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = identity_manager_get_all_identity_ids(manager_handle, &mut array); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(array.count, 3); // Primary-identity FFI was dropped along with the field; @@ -350,17 +317,15 @@ fn test_managed_identity_label_operations() { let alice = identities::alice(); let alice_handle = MANAGED_IDENTITY_STORAGE.insert(alice); - let mut error = PlatformWalletFFIError::success(); - let mut label_ptr: *mut std::os::raw::c_char = std::ptr::null_mut(); - let result = managed_identity_get_label(alice_handle, &mut label_ptr, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = managed_identity_get_label(alice_handle, &mut label_ptr); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Stub returns null — labels live on `PersistentIdentity.alias`. assert!(label_ptr.is_null()); let new_label = CString::new("Alice the Great").unwrap(); - let result = managed_identity_set_label(alice_handle, new_label.as_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = managed_identity_set_label(alice_handle, new_label.as_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Cleanup managed_identity_destroy(alice_handle); @@ -376,12 +341,10 @@ fn test_managed_identity_balance_and_block_time() { let expected_balance = alice.identity.balance(); let alice_handle = MANAGED_IDENTITY_STORAGE.insert(alice); - let mut error = PlatformWalletFFIError::success(); - // Get balance let mut balance: u64 = 0; - let result = managed_identity_get_balance(alice_handle, &mut balance, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = managed_identity_get_balance(alice_handle, &mut balance); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(balance, expected_balance); // Set balance block time @@ -390,12 +353,9 @@ fn test_managed_identity_balance_and_block_time() { core_height: 987_654, timestamp: 1_700_000_000, }; - let result = managed_identity_set_last_updated_balance_block_time( - alice_handle, - &block_time, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = + managed_identity_set_last_updated_balance_block_time(alice_handle, &block_time); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Get balance block time let mut retrieved_bt = BlockTime { @@ -403,12 +363,9 @@ fn test_managed_identity_balance_and_block_time() { core_height: 0, timestamp: 0, }; - let result = managed_identity_get_last_updated_balance_block_time( - alice_handle, - &mut retrieved_bt, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = + managed_identity_get_last_updated_balance_block_time(alice_handle, &mut retrieved_bt); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(retrieved_bt.height, 123_456); assert_eq!(retrieved_bt.core_height, 987_654); assert_eq!(retrieved_bt.timestamp, 1_700_000_000); @@ -421,26 +378,23 @@ fn test_managed_identity_balance_and_block_time() { #[test] fn test_error_handling_invalid_handles() { unsafe { - let mut error = PlatformWalletFFIError::success(); let invalid_handle = 99999; - // Try to get identity with invalid handle + // Storage misses now route through `unwrap_option_or_return!` + // and surface as `NotFound` (with a diagnostic message on the + // result). The diagnostic remains accessible via `result.message`. let mut id_bytes = [0u8; 32]; - let result = managed_identity_get_id(invalid_handle, id_bytes.as_mut_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::ErrorInvalidHandle); - assert!(!error.message.is_null()); - platform_wallet_ffi_error_free(&mut error); + let result = managed_identity_get_id(invalid_handle, id_bytes.as_mut_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::NotFound); + assert!(!result.message.is_null()); - // Try to get contact request with invalid handle - error = PlatformWalletFFIError::success(); - let result = - contact_request_get_sender_id(invalid_handle, id_bytes.as_mut_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::ErrorInvalidHandle); - platform_wallet_ffi_error_free(&mut error); + let result = contact_request_get_sender_id(invalid_handle, id_bytes.as_mut_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::NotFound); - // Try to destroy invalid handle + // `managed_identity_destroy` retains its bespoke error mapping + // because it doesn't use the option macro. let result = managed_identity_destroy(invalid_handle); - assert_eq!(result, PlatformWalletFFIResult::ErrorInvalidHandle); + assert_eq!(result.code, PlatformWalletFfiResultCode::ErrorInvalidHandle); } } @@ -451,22 +405,17 @@ fn test_error_handling_null_pointers() { let alice_handle = MANAGED_IDENTITY_STORAGE.insert(alice); // Try to get ID with null output pointer - let result = - managed_identity_get_id(alice_handle, std::ptr::null_mut(), std::ptr::null_mut()); - assert_eq!(result, PlatformWalletFFIResult::ErrorNullPointer); + let result = managed_identity_get_id(alice_handle, std::ptr::null_mut()); + assert_eq!(result.code, PlatformWalletFfiResultCode::ErrorNullPointer); // Try to get balance with null output pointer - let result = - managed_identity_get_balance(alice_handle, std::ptr::null_mut(), std::ptr::null_mut()); - assert_eq!(result, PlatformWalletFFIResult::ErrorNullPointer); + let result = managed_identity_get_balance(alice_handle, std::ptr::null_mut()); + assert_eq!(result.code, PlatformWalletFfiResultCode::ErrorNullPointer); // Try to get sent requests with null output pointer - let result = managed_identity_get_sent_contact_request_ids( - alice_handle, - std::ptr::null_mut(), - std::ptr::null_mut(), - ); - assert_eq!(result, PlatformWalletFFIResult::ErrorNullPointer); + let result = + managed_identity_get_sent_contact_request_ids(alice_handle, std::ptr::null_mut()); + assert_eq!(result.code, PlatformWalletFfiResultCode::ErrorNullPointer); // Cleanup managed_identity_destroy(alice_handle); @@ -479,22 +428,21 @@ fn test_contact_request_not_found() { let alice = identities::alice(); // Has no contacts by default let alice_handle = MANAGED_IDENTITY_STORAGE.insert(alice); - let mut error = PlatformWalletFFIError::success(); let eve_id_bytes: [u8; 32] = identities::eve().identity.id().to_buffer(); - // Try to get non-existent sent request + // Try to get non-existent sent request — option lookup misses + // surface as `NotFound` (the helper drives off + // `unwrap_option_or_return!`). let mut request_handle: Handle = NULL_HANDLE; let result = managed_identity_get_sent_contact_request( alice_handle, eve_id_bytes.as_ptr(), &mut request_handle, - &mut error, ); - assert_eq!(result, PlatformWalletFFIResult::ErrorContactNotFound); - assert!(!error.message.is_null()); + assert_eq!(result.code, PlatformWalletFfiResultCode::NotFound); + assert!(!result.message.is_null()); // Cleanup - platform_wallet_ffi_error_free(&mut error); managed_identity_destroy(alice_handle); } } @@ -502,19 +450,17 @@ fn test_contact_request_not_found() { #[test] fn test_identifier_operations() { unsafe { - let mut error = PlatformWalletFFIError::success(); - // Generate random identifier let mut id = [0u8; 32]; - let result = platform_wallet_generate_random_identifier(id.as_mut_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = platform_wallet_generate_random_identifier(id.as_mut_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Should not be all zeros assert_ne!(id, [0u8; 32]); // Convert to string (actually Base58, despite function name) let mut id_string: *mut std::os::raw::c_char = std::ptr::null_mut(); - let result = platform_wallet_identifier_to_hex(id.as_ptr(), &mut id_string, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = platform_wallet_identifier_to_hex(id.as_ptr(), &mut id_string); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert!(!id_string.is_null()); let id_str = std::ffi::CStr::from_ptr(id_string).to_str().unwrap(); @@ -527,8 +473,8 @@ fn test_identifier_operations() { // Convert back from string let mut id2 = [0u8; 32]; - let result = platform_wallet_identifier_from_hex(id_string, id2.as_mut_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = platform_wallet_identifier_from_hex(id_string, id2.as_mut_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Should match original assert_eq!(id, id2); @@ -553,53 +499,51 @@ fn test_memory_lifecycle() { let carol_handle = MANAGED_IDENTITY_STORAGE.insert(carol); // Verify they exist - let mut error = PlatformWalletFFIError::success(); let mut id = [0u8; 32]; assert_eq!( - managed_identity_get_id(alice_handle, id.as_mut_ptr(), &mut error), - PlatformWalletFFIResult::Success + managed_identity_get_id(alice_handle, id.as_mut_ptr()).code, + PlatformWalletFfiResultCode::Success ); assert_eq!( - managed_identity_get_id(bob_handle, id.as_mut_ptr(), &mut error), - PlatformWalletFFIResult::Success + managed_identity_get_id(bob_handle, id.as_mut_ptr()).code, + PlatformWalletFfiResultCode::Success ); assert_eq!( - managed_identity_get_id(carol_handle, id.as_mut_ptr(), &mut error), - PlatformWalletFFIResult::Success + managed_identity_get_id(carol_handle, id.as_mut_ptr()).code, + PlatformWalletFfiResultCode::Success ); // Destroy Alice assert_eq!( - managed_identity_destroy(alice_handle), - PlatformWalletFFIResult::Success + managed_identity_destroy(alice_handle).code, + PlatformWalletFfiResultCode::Success ); - // Alice should be gone, but Bob and Carol should still exist + // Alice should be gone — get_id surfaces the storage miss as + // `NotFound`. assert_eq!( - managed_identity_get_id(alice_handle, id.as_mut_ptr(), &mut error), - PlatformWalletFFIResult::ErrorInvalidHandle + managed_identity_get_id(alice_handle, id.as_mut_ptr()).code, + PlatformWalletFfiResultCode::NotFound ); - platform_wallet_ffi_error_free(&mut error); - error = PlatformWalletFFIError::success(); assert_eq!( - managed_identity_get_id(bob_handle, id.as_mut_ptr(), &mut error), - PlatformWalletFFIResult::Success + managed_identity_get_id(bob_handle, id.as_mut_ptr()).code, + PlatformWalletFfiResultCode::Success ); assert_eq!( - managed_identity_get_id(carol_handle, id.as_mut_ptr(), &mut error), - PlatformWalletFFIResult::Success + managed_identity_get_id(carol_handle, id.as_mut_ptr()).code, + PlatformWalletFfiResultCode::Success ); // Cleanup remaining managed_identity_destroy(bob_handle); managed_identity_destroy(carol_handle); - // Double destroy should fail + // Double destroy still hits the bespoke ErrorInvalidHandle path. assert_eq!( - managed_identity_destroy(bob_handle), - PlatformWalletFFIResult::ErrorInvalidHandle + managed_identity_destroy(bob_handle).code, + PlatformWalletFfiResultCode::ErrorInvalidHandle ); } } @@ -617,14 +561,12 @@ fn test_concurrent_identity_operations() { let bob_handle = MANAGED_IDENTITY_STORAGE.insert(bob); let carol_handle = MANAGED_IDENTITY_STORAGE.insert(carol); - let mut error = PlatformWalletFFIError::success(); - // Verify Alice has 3 sent requests let mut alice_array = IdentifierArray { items: std::ptr::null_mut(), count: 0, }; - managed_identity_get_sent_contact_request_ids(alice_handle, &mut alice_array, &mut error); + managed_identity_get_sent_contact_request_ids(alice_handle, &mut alice_array); assert_eq!(alice_array.count, 3); // Verify Bob has 3 incoming requests (we reused the scenario) @@ -632,7 +574,7 @@ fn test_concurrent_identity_operations() { items: std::ptr::null_mut(), count: 0, }; - managed_identity_get_incoming_contact_request_ids(bob_handle, &mut bob_array, &mut error); + managed_identity_get_incoming_contact_request_ids(bob_handle, &mut bob_array); assert_eq!(bob_array.count, 3); // Verify Carol has mixed contacts @@ -640,19 +582,19 @@ fn test_concurrent_identity_operations() { items: std::ptr::null_mut(), count: 0, }; - managed_identity_get_sent_contact_request_ids(carol_handle, &mut carol_sent, &mut error); + managed_identity_get_sent_contact_request_ids(carol_handle, &mut carol_sent); assert_eq!(carol_sent.count, 1); // Operations on Alice shouldn't affect Bob or Carol let new_label = CString::new("Alice Updated").unwrap(); - managed_identity_set_label(alice_handle, new_label.as_ptr(), &mut error); + managed_identity_set_label(alice_handle, new_label.as_ptr()); // Bob's incoming requests should still be 3 let mut bob_array2 = IdentifierArray { items: std::ptr::null_mut(), count: 0, }; - managed_identity_get_incoming_contact_request_ids(bob_handle, &mut bob_array2, &mut error); + managed_identity_get_incoming_contact_request_ids(bob_handle, &mut bob_array2); assert_eq!(bob_array2.count, 3); // Cleanup @@ -683,27 +625,21 @@ fn test_get_established_contact_and_fields() { let bob_id_bytes: [u8; 32] = bob_id.to_buffer(); let mut contact_handle: Handle = NULL_HANDLE; - let mut error = PlatformWalletFFIError::success(); // Get established contact let result = managed_identity_get_established_contact( alice_handle, bob_id_bytes.as_ptr(), &mut contact_handle, - &mut error, ); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_ne!(contact_handle, NULL_HANDLE); // Get contact ID let mut retrieved_id = [0u8; 32]; - let result = established_contact_get_contact_id( - contact_handle, - retrieved_id.as_mut_ptr(), - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = established_contact_get_contact_id(contact_handle, retrieved_id.as_mut_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(retrieved_id, bob_id_bytes); // Cleanup @@ -724,33 +660,23 @@ fn test_established_contact_outgoing_and_incoming_requests() { let bob_id_bytes: [u8; 32] = bob_id.to_buffer(); let mut contact_handle: Handle = NULL_HANDLE; - let mut error = PlatformWalletFFIError::success(); managed_identity_get_established_contact( alice_handle, bob_id_bytes.as_ptr(), &mut contact_handle, - &mut error, ); // Get outgoing request let mut outgoing_handle: Handle = NULL_HANDLE; - let result = established_contact_get_outgoing_request( - contact_handle, - &mut outgoing_handle, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = established_contact_get_outgoing_request(contact_handle, &mut outgoing_handle); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_ne!(outgoing_handle, NULL_HANDLE); // Get incoming request let mut incoming_handle: Handle = NULL_HANDLE; - let result = established_contact_get_incoming_request( - contact_handle, - &mut incoming_handle, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = established_contact_get_incoming_request(contact_handle, &mut incoming_handle); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_ne!(incoming_handle, NULL_HANDLE); // Verify the requests have correct sender/recipient @@ -760,14 +686,14 @@ fn test_established_contact_outgoing_and_incoming_requests() { let mut recipient_id = [0u8; 32]; // Outgoing: from alice to bob - contact_request_get_sender_id(outgoing_handle, sender_id.as_mut_ptr(), &mut error); - contact_request_get_recipient_id(outgoing_handle, recipient_id.as_mut_ptr(), &mut error); + contact_request_get_sender_id(outgoing_handle, sender_id.as_mut_ptr()); + contact_request_get_recipient_id(outgoing_handle, recipient_id.as_mut_ptr()); assert_eq!(sender_id, alice_id_bytes); assert_eq!(recipient_id, bob_id_bytes); // Incoming: from bob to alice - contact_request_get_sender_id(incoming_handle, sender_id.as_mut_ptr(), &mut error); - contact_request_get_recipient_id(incoming_handle, recipient_id.as_mut_ptr(), &mut error); + contact_request_get_sender_id(incoming_handle, sender_id.as_mut_ptr()); + contact_request_get_recipient_id(incoming_handle, recipient_id.as_mut_ptr()); assert_eq!(sender_id, bob_id_bytes); assert_eq!(recipient_id, alice_id_bytes); @@ -791,32 +717,26 @@ fn test_established_contact_request_fields() { let bob_id_bytes: [u8; 32] = bob_id.to_buffer(); let mut contact_handle: Handle = NULL_HANDLE; - let mut error = PlatformWalletFFIError::success(); managed_identity_get_established_contact( alice_handle, bob_id_bytes.as_ptr(), &mut contact_handle, - &mut error, ); // Get outgoing request and verify all fields let mut outgoing_handle: Handle = NULL_HANDLE; - established_contact_get_outgoing_request(contact_handle, &mut outgoing_handle, &mut error); + established_contact_get_outgoing_request(contact_handle, &mut outgoing_handle); let mut sender_key_idx: u32 = 0; let mut recipient_key_idx: u32 = 0; let mut account_ref: u32 = 0; let mut created_at: u64 = 0; - contact_request_get_sender_key_index(outgoing_handle, &mut sender_key_idx, &mut error); - contact_request_get_recipient_key_index( - outgoing_handle, - &mut recipient_key_idx, - &mut error, - ); - contact_request_get_account_reference(outgoing_handle, &mut account_ref, &mut error); - contact_request_get_created_at(outgoing_handle, &mut created_at, &mut error); + contact_request_get_sender_key_index(outgoing_handle, &mut sender_key_idx); + contact_request_get_recipient_key_index(outgoing_handle, &mut recipient_key_idx); + contact_request_get_account_reference(outgoing_handle, &mut account_ref); + contact_request_get_created_at(outgoing_handle, &mut created_at); // The test data should have specific values assert_eq!(sender_key_idx, 0); @@ -827,13 +747,9 @@ fn test_established_contact_request_fields() { // Get encrypted public key let mut bytes_ptr: *mut std::os::raw::c_uchar = std::ptr::null_mut(); let mut len: usize = 0; - let result = contact_request_get_encrypted_public_key( - outgoing_handle, - &mut bytes_ptr, - &mut len, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = + contact_request_get_encrypted_public_key(outgoing_handle, &mut bytes_ptr, &mut len); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(len, 96); // Standard encrypted key length assert!(!bytes_ptr.is_null()); @@ -854,16 +770,16 @@ fn test_get_nonexistent_established_contact() { let nonexistent_id: [u8; 32] = [99u8; 32]; let mut contact_handle: Handle = NULL_HANDLE; - let mut error = PlatformWalletFFIError::success(); let result = managed_identity_get_established_contact( alice_handle, nonexistent_id.as_ptr(), &mut contact_handle, - &mut error, ); - assert_eq!(result, PlatformWalletFFIResult::ErrorContactNotFound); + // The contact lookup unwraps an `Option` via the macro, so the + // miss surfaces as `NotFound`. + assert_eq!(result.code, PlatformWalletFfiResultCode::NotFound); assert_eq!(contact_handle, NULL_HANDLE); // Cleanup @@ -874,8 +790,10 @@ fn test_get_nonexistent_established_contact() { #[test] fn test_established_contact_destroy_invalid_handle() { unsafe { + // `established_contact_destroy` runs `unwrap_option_or_return!` + // on the storage remove, so a bogus handle yields `NotFound`. let result = established_contact_destroy(9999); - assert_eq!(result, PlatformWalletFFIResult::ErrorInvalidHandle); + assert_eq!(result.code, PlatformWalletFfiResultCode::NotFound); } } @@ -892,17 +810,14 @@ fn test_multiple_established_contacts() { let bob_id_bytes: [u8; 32] = bob_id.to_buffer(); let carol_id_bytes: [u8; 32] = carol_id.to_buffer(); - let mut error = PlatformWalletFFIError::success(); - // Get Bob contact let mut bob_contact_handle: Handle = NULL_HANDLE; let result = managed_identity_get_established_contact( alice_handle, bob_id_bytes.as_ptr(), &mut bob_contact_handle, - &mut error, ); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Get Carol contact let mut carol_contact_handle: Handle = NULL_HANDLE; @@ -910,26 +825,17 @@ fn test_multiple_established_contacts() { alice_handle, carol_id_bytes.as_ptr(), &mut carol_contact_handle, - &mut error, ); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Verify Bob's contact ID let mut retrieved_bob_id = [0u8; 32]; - established_contact_get_contact_id( - bob_contact_handle, - retrieved_bob_id.as_mut_ptr(), - &mut error, - ); + established_contact_get_contact_id(bob_contact_handle, retrieved_bob_id.as_mut_ptr()); assert_eq!(retrieved_bob_id, bob_id_bytes); // Verify Carol's contact ID let mut retrieved_carol_id = [0u8; 32]; - established_contact_get_contact_id( - carol_contact_handle, - retrieved_carol_id.as_mut_ptr(), - &mut error, - ); + established_contact_get_contact_id(carol_contact_handle, retrieved_carol_id.as_mut_ptr()); assert_eq!(retrieved_carol_id, carol_id_bytes); // Cleanup diff --git a/packages/rs-platform-wallet-ffi/tests/integration_tests.rs b/packages/rs-platform-wallet-ffi/tests/integration_tests.rs index 72f4f645a96..80fea57c029 100644 --- a/packages/rs-platform-wallet-ffi/tests/integration_tests.rs +++ b/packages/rs-platform-wallet-ffi/tests/integration_tests.rs @@ -18,25 +18,23 @@ fn test_wallet_creation_and_destruction() { unsafe { let seed = [0u8; 64]; let mut handle: Handle = NULL_HANDLE; - let mut error = PlatformWalletFFIError::success(); let result = platform_wallet_info_create_from_seed( 1, // Testnet seed.as_ptr(), seed.len(), &mut handle, - &mut error, ); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_ne!(handle, NULL_HANDLE); let result = platform_wallet_info_destroy(handle); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Double destroy should fail let result = platform_wallet_info_destroy(handle); - assert_eq!(result, PlatformWalletFFIResult::ErrorInvalidHandle); + assert_eq!(result.code, PlatformWalletFfiResultCode::ErrorInvalidHandle); } } @@ -48,17 +46,15 @@ fn test_wallet_from_mnemonic() { ).unwrap(); let mut handle: Handle = NULL_HANDLE; - let mut error = PlatformWalletFFIError::success(); let result = platform_wallet_info_create_from_mnemonic( 1, // Testnet mnemonic.as_ptr(), std::ptr::null(), &mut handle, - &mut error, ); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_ne!(handle, NULL_HANDLE); platform_wallet_info_destroy(handle); @@ -71,15 +67,14 @@ fn test_identity_manager_workflow() { unsafe { // Create identity manager let mut manager_handle: Handle = NULL_HANDLE; - let mut error = PlatformWalletFFIError::success(); - let result = identity_manager_create(&mut manager_handle, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = identity_manager_create(&mut manager_handle); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Check initial count let mut count: usize = 0; - let result = identity_manager_get_identity_count(manager_handle, &mut count, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = identity_manager_get_identity_count(manager_handle, &mut count); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(count, 0); // Create a mock identity for testing @@ -88,12 +83,12 @@ fn test_identity_manager_workflow() { let managed = platform_wallet::ManagedIdentity::new(identity, 0); let identity_handle = MANAGED_IDENTITY_STORAGE.insert(managed); - let result = identity_manager_add_identity(manager_handle, identity_handle, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = identity_manager_add_identity(manager_handle, identity_handle); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Check count increased - let result = identity_manager_get_identity_count(manager_handle, &mut count, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = identity_manager_get_identity_count(manager_handle, &mut count); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(count, 1); // Primary-identity FFI was dropped along with the field — @@ -106,8 +101,8 @@ fn test_identity_manager_workflow() { items: std::ptr::null_mut(), count: 0, }; - let result = identity_manager_get_all_identity_ids(manager_handle, &mut array, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = identity_manager_get_all_identity_ids(manager_handle, &mut array); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(array.count, 1); platform_wallet_identifier_array_free(&mut array); @@ -125,26 +120,24 @@ fn test_managed_identity_operations() { let managed = platform_wallet::ManagedIdentity::new(identity, 0); let handle = MANAGED_IDENTITY_STORAGE.insert(managed); - let mut error = PlatformWalletFFIError::success(); - // Get ID let mut id_bytes = [0u8; 32]; - let result = managed_identity_get_id(handle, id_bytes.as_mut_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = managed_identity_get_id(handle, id_bytes.as_mut_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Get balance let mut balance: u64 = 0; - let result = managed_identity_get_balance(handle, &mut balance, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = managed_identity_get_balance(handle, &mut balance); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Set and get label let label = CString::new("Test Identity").unwrap(); - let result = managed_identity_set_label(handle, label.as_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = managed_identity_set_label(handle, label.as_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); let mut label_ptr: *mut std::os::raw::c_char = std::ptr::null_mut(); - let result = managed_identity_get_label(handle, &mut label_ptr, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = managed_identity_get_label(handle, &mut label_ptr); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert!(!label_ptr.is_null()); let retrieved_label = std::ffi::CStr::from_ptr(label_ptr).to_str().unwrap(); @@ -159,21 +152,17 @@ fn test_managed_identity_operations() { timestamp: 1234567890, }; - let result = - managed_identity_set_last_updated_balance_block_time(handle, &block_time, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = managed_identity_set_last_updated_balance_block_time(handle, &block_time); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); let mut retrieved_bt = BlockTime { height: 0, core_height: 0, timestamp: 0, }; - let result = managed_identity_get_last_updated_balance_block_time( - handle, - &mut retrieved_bt, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = + managed_identity_get_last_updated_balance_block_time(handle, &mut retrieved_bt); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_eq!(retrieved_bt.height, 100); assert_eq!(retrieved_bt.core_height, 200); @@ -188,20 +177,18 @@ fn test_serialization() { unsafe { let seed = [0u8; 64]; let mut handle: Handle = NULL_HANDLE; - let mut error = PlatformWalletFFIError::success(); platform_wallet_info_create_from_seed( 1, // Testnet seed.as_ptr(), seed.len(), &mut handle, - &mut error, ); // Serialize to JSON - function not yet implemented // let mut json_ptr: *mut std::os::raw::c_char = std::ptr::null_mut(); - // let result = platform_wallet_info_to_json(handle, &mut json_ptr, &mut error); - // assert_eq!(result, PlatformWalletFFIResult::Success); + // let result = platform_wallet_info_to_json(handle, &mut json_ptr); + // assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // assert!(!json_ptr.is_null()); // let json_str = unsafe { std::ffi::CStr::from_ptr(json_ptr).to_str().unwrap() }; @@ -216,23 +203,21 @@ fn test_serialization() { #[test] fn test_utils_identifier_operations() { unsafe { - let mut error = PlatformWalletFFIError::success(); - // Generate random identifier let mut id1 = [0u8; 32]; - let result = platform_wallet_generate_random_identifier(id1.as_mut_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = platform_wallet_generate_random_identifier(id1.as_mut_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Convert to hex let mut hex: *mut std::os::raw::c_char = std::ptr::null_mut(); - let result = platform_wallet_identifier_to_hex(id1.as_ptr(), &mut hex, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = platform_wallet_identifier_to_hex(id1.as_ptr(), &mut hex); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert!(!hex.is_null()); // Convert back from hex let mut id2 = [0u8; 32]; - let result = platform_wallet_identifier_from_hex(hex, id2.as_mut_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = platform_wallet_identifier_from_hex(hex, id2.as_mut_ptr()); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Should match assert_eq!(id1, id2); @@ -244,17 +229,15 @@ fn test_utils_identifier_operations() { #[test] fn test_error_handling() { unsafe { - let mut error = PlatformWalletFFIError::success(); - // Try to get identity from invalid handle let invalid_handle = 9999; let mut id_bytes = [0u8; 32]; - let result = managed_identity_get_id(invalid_handle, id_bytes.as_mut_ptr(), &mut error); - assert_eq!(result, PlatformWalletFFIResult::ErrorInvalidHandle); + let result = managed_identity_get_id(invalid_handle, id_bytes.as_mut_ptr()); + // The macro routes a missing handle through Option::None → NotFound. + assert_eq!(result.code, PlatformWalletFfiResultCode::NotFound); - // Error should have message - assert!(!error.message.is_null()); - platform_wallet_ffi_error_free(&mut error); + // Result carries a diagnostic message on the error path. + assert!(!result.message.is_null()); // Try to create wallet with null pointer let result = platform_wallet_info_create_from_seed( @@ -262,9 +245,8 @@ fn test_error_handling() { std::ptr::null(), 0, std::ptr::null_mut(), - std::ptr::null_mut(), ); - assert_eq!(result, PlatformWalletFFIResult::ErrorNullPointer); + assert_eq!(result.code, PlatformWalletFfiResultCode::ErrorNullPointer); } } @@ -275,8 +257,6 @@ fn test_full_workflow() { // Initialize platform_wallet_ffi_init(); - let mut error = PlatformWalletFFIError::success(); - // Create wallet from mnemonic let mnemonic = CString::new( "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" @@ -288,14 +268,13 @@ fn test_full_workflow() { mnemonic.as_ptr(), std::ptr::null(), &mut wallet_handle, - &mut error, ); - assert_eq!(result, PlatformWalletFFIResult::Success); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Create identity manager let mut manager_handle: Handle = NULL_HANDLE; - let result = identity_manager_create(&mut manager_handle, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = identity_manager_create(&mut manager_handle); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Create identity let identity = dpp::tests::fixtures::get_identity_fixture(0).unwrap(); @@ -307,28 +286,24 @@ fn test_full_workflow() { // its label field) — kept here only to verify the call still // links and returns Success. let label = CString::new("My Primary Identity").unwrap(); - managed_identity_set_label(identity_handle, label.as_ptr(), &mut error); + managed_identity_set_label(identity_handle, label.as_ptr()); // Add identity to manager - identity_manager_add_identity(manager_handle, identity_handle, &mut error); + identity_manager_add_identity(manager_handle, identity_handle); // Primary-identity FFI was dropped along with the field. let id_bytes: [u8; 32] = identity_id.to_buffer(); let _ = id_bytes; // Set identity manager on wallet - let result = - platform_wallet_info_set_identity_manager(wallet_handle, manager_handle, &mut error); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = platform_wallet_info_set_identity_manager(wallet_handle, manager_handle); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); // Get identity manager back let mut retrieved_manager_handle: Handle = NULL_HANDLE; - let result = platform_wallet_info_get_identity_manager( - wallet_handle, - &mut retrieved_manager_handle, - &mut error, - ); - assert_eq!(result, PlatformWalletFFIResult::Success); + let result = + platform_wallet_info_get_identity_manager(wallet_handle, &mut retrieved_manager_handle); + assert_eq!(result.code, PlatformWalletFfiResultCode::Success); assert_ne!(retrieved_manager_handle, NULL_HANDLE); // Cleanup diff --git a/packages/rs-platform-wallet/src/wallet/apply.rs b/packages/rs-platform-wallet/src/wallet/apply.rs index 6fb35bc5461..1c0ea40654b 100644 --- a/packages/rs-platform-wallet/src/wallet/apply.rs +++ b/packages/rs-platform-wallet/src/wallet/apply.rs @@ -364,7 +364,7 @@ mod tests { fn empty_info(wallet: &Wallet) -> PlatformWalletInfo { PlatformWalletInfo { - core_wallet: ManagedWalletInfo::from_wallet(wallet), + core_wallet: ManagedWalletInfo::from_wallet(wallet, 0), balance: std::sync::Arc::new(WalletBalance::new()), identity_manager: IdentityManager::new(), tracked_asset_locks: BTreeMap::new(), diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/AssetLock/ManagedAssetLockManager.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/AssetLock/ManagedAssetLockManager.swift index 7a14861a138..6083eadef16 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/AssetLock/ManagedAssetLockManager.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/AssetLock/ManagedAssetLockManager.swift @@ -1,4 +1,5 @@ import Foundation +import DashSDKFFI /// Asset lock lifecycle manager for building, broadcasting, and tracking asset locks. /// @@ -11,8 +12,7 @@ public class ManagedAssetLockManager { } deinit { - var error = PlatformWalletFFIError() - _ = asset_lock_manager_destroy(handle, &error) + asset_lock_manager_destroy(handle).discard() } // MARK: - Types @@ -79,12 +79,7 @@ public class ManagedAssetLockManager { public func listTrackedLocks() throws -> [TrackedAssetLock] { var locksPtr: UnsafeMutablePointer? = nil var count: UInt = 0 - var error = PlatformWalletFFIError() - - let result = asset_lock_manager_list_tracked_locks(handle, &locksPtr, &count, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try asset_lock_manager_list_tracked_locks(handle, &locksPtr, &count).check() defer { asset_lock_manager_free_tracked_locks(locksPtr, count) } guard let locks = locksPtr, count > 0 else { return [] } @@ -116,20 +111,14 @@ public class ManagedAssetLockManager { ) throws -> BuildResult { var txBytesPtr: UnsafeMutablePointer? = nil var txLen: UInt = 0 - var privateKey: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) = + var privateKey: FFIByteTuple32 = // gitleaks:allow (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) - var error = PlatformWalletFFIError() - let result = asset_lock_manager_build_transaction( + try asset_lock_manager_build_transaction( handle, amountDuffs, accountIndex, fundingType.rawValue, identityIndex, - &txBytesPtr, &txLen, &privateKey, &error - ) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + &txBytesPtr, &txLen, &privateKey + ).check() + guard let txPtr = txBytesPtr, txLen > 0 else { throw PlatformWalletError.unknown("FFI returned success but transaction buffer was empty") } @@ -149,25 +138,16 @@ public class ManagedAssetLockManager { ) throws -> FundedProofResult { var proofBytesPtr: UnsafeMutablePointer? = nil var proofLen: UInt = 0 - var privateKey: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) = + var privateKey: FFIByteTuple32 = // gitleaks:allow (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) - var txid: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) = + var txid: FFIByteTuple32 = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) - var error = PlatformWalletFFIError() - let result = asset_lock_manager_create_funded_proof( + try asset_lock_manager_create_funded_proof( handle, amountDuffs, accountIndex, fundingType.rawValue, identityIndex, - &proofBytesPtr, &proofLen, &privateKey, &txid, &error - ) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + &proofBytesPtr, &proofLen, &privateKey, &txid + ).check() + guard let proofPtr = proofBytesPtr, proofLen > 0 else { throw PlatformWalletError.unknown("FFI returned success but proof buffer was empty") } @@ -189,22 +169,17 @@ public class ManagedAssetLockManager { timeoutSeconds: UInt64 = 300 ) throws -> ResumeResult { guard txid.count == 32 else { - throw PlatformWalletError.invalidParameter + throw PlatformWalletError.invalidParameter( + "txid must be 32 bytes, got \(txid.count)" + ) } var proofBytesPtr: UnsafeMutablePointer? = nil var proofLen: UInt = 0 - var privateKey: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) = + var privateKey: FFIByteTuple32 = // gitleaks:allow (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) - var error = PlatformWalletFFIError() - var txidTuple: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) = + var txidTuple: FFIByteTuple32 = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) txid.withUnsafeBytes { buf in withUnsafeMutableBytes(of: &txidTuple) { dst in @@ -212,13 +187,11 @@ public class ManagedAssetLockManager { } } - let result = asset_lock_manager_resume( + try asset_lock_manager_resume( handle, &txidTuple, vout, timeoutSeconds, - &proofBytesPtr, &proofLen, &privateKey, &error - ) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + &proofBytesPtr, &proofLen, &privateKey + ).check() + guard let proofPtr = proofBytesPtr, proofLen > 0 else { throw PlatformWalletError.unknown("FFI returned success but proof buffer was empty") } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ContactRequest.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ContactRequest.swift index b34eb2efb69..78d38fd6a08 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ContactRequest.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ContactRequest.swift @@ -15,7 +15,7 @@ public final class ContactRequest: @unchecked Sendable { } deinit { - _ = contact_request_destroy(handle) + contact_request_destroy(handle).discard() } /// Create a new contact request @@ -30,14 +30,13 @@ public final class ContactRequest: @unchecked Sendable { createdAt: UInt64 ) throws -> ContactRequest { var handle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() // Nest the two `withFFIBytes` closures + `withUnsafeBytes` // so all three buffers stay live for the FFI call window. - let result = senderId.withFFIBytes { senderPtr -> PlatformWalletFFIResult in - recipientId.withFFIBytes { recipientPtr -> PlatformWalletFFIResult in - encryptedPublicKey.withUnsafeBytes { keyPtr -> PlatformWalletFFIResult in - contact_request_create( + try senderId.withFFIBytes { senderPtr in + try recipientId.withFFIBytes { recipientPtr in + try encryptedPublicKey.withUnsafeBytes { keyPtr in + try contact_request_create( senderPtr, recipientPtr, senderKeyIndex, @@ -47,86 +46,51 @@ public final class ContactRequest: @unchecked Sendable { UInt(encryptedPublicKey.count), coreHeightCreatedAt, createdAt, - &handle, - &error - ) + &handle + ).check() } } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - return ContactRequest(handle: handle) } /// Get the sender identity ID public func getSenderId() throws -> Identifier { var buf = [UInt8](repeating: 0, count: 32) - var error = PlatformWalletFFIError() - - let result = buf.withUnsafeMutableBufferPointer { bp -> PlatformWalletFFIResult in - contact_request_get_sender_id(handle, bp.baseAddress!, &error) + try buf.withUnsafeMutableBufferPointer { bp in + try contact_request_get_sender_id(handle, bp.baseAddress!).check() } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - return Data(buf) } /// Get the recipient identity ID public func getRecipientId() throws -> Identifier { var buf = [UInt8](repeating: 0, count: 32) - var error = PlatformWalletFFIError() - - let result = buf.withUnsafeMutableBufferPointer { bp -> PlatformWalletFFIResult in - contact_request_get_recipient_id(handle, bp.baseAddress!, &error) + try buf.withUnsafeMutableBufferPointer { bp in + try contact_request_get_recipient_id(handle, bp.baseAddress!).check() } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - return Data(buf) } /// Get the sender key index public func getSenderKeyIndex() throws -> UInt32 { var keyIndex: UInt32 = 0 - var error = PlatformWalletFFIError() - - let result = contact_request_get_sender_key_index(handle, &keyIndex, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - + try contact_request_get_sender_key_index(handle, &keyIndex).check() return keyIndex } /// Get the recipient key index public func getRecipientKeyIndex() throws -> UInt32 { var keyIndex: UInt32 = 0 - var error = PlatformWalletFFIError() - - let result = contact_request_get_recipient_key_index(handle, &keyIndex, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - + try contact_request_get_recipient_key_index(handle, &keyIndex).check() return keyIndex } /// Get the account reference public func getAccountReference() throws -> UInt32 { var accountRef: UInt32 = 0 - var error = PlatformWalletFFIError() - - let result = contact_request_get_account_reference(handle, &accountRef, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - + try contact_request_get_account_reference(handle, &accountRef).check() return accountRef } @@ -134,12 +98,7 @@ public final class ContactRequest: @unchecked Sendable { public func getEncryptedPublicKey() throws -> Data { var bytesPtr: UnsafeMutablePointer? = nil var length: UInt = 0 - var error = PlatformWalletFFIError() - - let result = contact_request_get_encrypted_public_key(handle, &bytesPtr, &length, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try contact_request_get_encrypted_public_key(handle, &bytesPtr, &length).check() defer { if let ptr = bytesPtr { @@ -148,7 +107,9 @@ public final class ContactRequest: @unchecked Sendable { } guard let ptr = bytesPtr else { - throw PlatformWalletError.nullPointer + throw PlatformWalletError.nullPointer( + "contact_request_get_encrypted_public_key returned a NULL bytes pointer" + ) } return Data(bytes: ptr, count: Int(length)) @@ -157,13 +118,7 @@ public final class ContactRequest: @unchecked Sendable { /// Get the creation timestamp public func getCreatedAt() throws -> UInt64 { var createdAt: UInt64 = 0 - var error = PlatformWalletFFIError() - - let result = contact_request_get_created_at(handle, &createdAt, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - + try contact_request_get_created_at(handle, &createdAt).check() return createdAt } } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/ManagedCoreWallet.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/ManagedCoreWallet.swift index 010014e85e0..8e2b5aa8cf5 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/ManagedCoreWallet.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/CoreWallet/ManagedCoreWallet.swift @@ -1,4 +1,5 @@ import Foundation +import DashSDKFFI /// Core wallet for UTXO management, address derivation, and transaction broadcasting. /// @@ -11,8 +12,7 @@ public class ManagedCoreWallet { } deinit { - var error = PlatformWalletFFIError() - _ = core_wallet_destroy(handle, &error) + core_wallet_destroy(handle).discard() } // MARK: - Balance @@ -39,14 +39,9 @@ public class ManagedCoreWallet { var unconfirmed: UInt64 = 0 var immature: UInt64 = 0 var locked: UInt64 = 0 - var error = PlatformWalletFFIError() - - let result = core_wallet_get_balance( - handle, &confirmed, &unconfirmed, &immature, &locked, &error - ) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try core_wallet_get_balance( + handle, &confirmed, &unconfirmed, &immature, &locked + ).check() return CoreBalance( confirmed: confirmed, @@ -59,13 +54,7 @@ public class ManagedCoreWallet { /// Get the network this wallet operates on. public func network() throws -> PlatformNetwork { var networkValue: UInt32 = 0 - var error = PlatformWalletFFIError() - - let result = core_wallet_get_network(handle, &networkValue, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - + try core_wallet_get_network(handle, &networkValue).check() return PlatformNetwork(rawValue: networkValue) ?? .testnet } @@ -74,28 +63,26 @@ public class ManagedCoreWallet { /// Get the next unused receive address for a specific BIP-44 account. public func nextReceiveAddress(accountIndex: UInt32 = 0) throws -> String { var addressPtr: UnsafeMutablePointer? = nil - var error = PlatformWalletFFIError() - - let result = core_wallet_next_receive_address(handle, accountIndex, &addressPtr, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS, let ptr = addressPtr else { - throw PlatformWalletError(result: result, error: error) + try core_wallet_next_receive_address(handle, accountIndex, &addressPtr).check() + guard let ptr = addressPtr else { + throw PlatformWalletError.nullPointer( + "core_wallet_next_receive_address returned a NULL address pointer" + ) } defer { core_wallet_free_address(ptr) } - return String(cString: ptr) } /// Get the next unused change address for a specific BIP-44 account. public func nextChangeAddress(accountIndex: UInt32 = 0) throws -> String { var addressPtr: UnsafeMutablePointer? = nil - var error = PlatformWalletFFIError() - - let result = core_wallet_next_change_address(handle, accountIndex, &addressPtr, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS, let ptr = addressPtr else { - throw PlatformWalletError(result: result, error: error) + try core_wallet_next_change_address(handle, accountIndex, &addressPtr).check() + guard let ptr = addressPtr else { + throw PlatformWalletError.nullPointer( + "core_wallet_next_change_address returned a NULL address pointer" + ) } defer { core_wallet_free_address(ptr) } - return String(cString: ptr) } @@ -117,15 +104,14 @@ public class ManagedCoreWallet { ) throws -> Data { var txBytesPtr: UnsafeMutablePointer? = nil var txLen: UInt = 0 - var error = PlatformWalletFFIError() // Build C string array let cStrings = recipients.map { ($0.address as NSString).utf8String } let amounts = recipients.map { $0.amountDuffs } - let result = cStrings.withUnsafeBufferPointer { addrBuf in - amounts.withUnsafeBufferPointer { amountBuf in - core_wallet_send_to_addresses( + try cStrings.withUnsafeBufferPointer { addrBuf in + try amounts.withUnsafeBufferPointer { amountBuf in + try core_wallet_send_to_addresses( handle, accountType.rawValue, accountIndex, @@ -133,14 +119,13 @@ public class ManagedCoreWallet { amountBuf.baseAddress, UInt(recipients.count), &txBytesPtr, - &txLen, - &error - ) + &txLen + ).check() } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS, let ptr = txBytesPtr, txLen > 0 else { - throw PlatformWalletError(result: result, error: error) + 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) } @@ -152,20 +137,19 @@ public class ManagedCoreWallet { /// Returns the transaction ID as a hex string. public func broadcastTransaction(_ txData: Data) throws -> String { var txidPtr: UnsafeMutablePointer? = nil - var error = PlatformWalletFFIError() - - let result = txData.withUnsafeBytes { txBuf in - core_wallet_broadcast_transaction( + try txData.withUnsafeBytes { txBuf in + try core_wallet_broadcast_transaction( handle, txBuf.baseAddress?.assumingMemoryBound(to: UInt8.self), UInt(txData.count), - &txidPtr, - &error - ) + &txidPtr + ).check() } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS, let ptr = txidPtr else { - throw PlatformWalletError(result: result, error: error) + guard let ptr = txidPtr else { + throw PlatformWalletError.nullPointer( + "core_wallet_broadcast_transaction returned a NULL txid pointer" + ) } defer { core_wallet_free_address(ptr) } // same free for C strings diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/DashPayService.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/DashPayService.swift index 6be547d1426..4184d4cfe18 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/DashPayService.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/DashPayService.swift @@ -138,7 +138,9 @@ public final class DashPayService: Sendable { /// - Throws: Error if acceptance fails public func acceptContactRequest(identity: ManagedIdentity, from senderId: Identifier) throws { guard let request = try identity.getIncomingContactRequest(senderId: senderId) else { - throw PlatformWalletError.contactNotFound + throw PlatformWalletError.contactNotFound( + "no incoming contact request from sender id" + ) } try identity.acceptContactRequest(request) } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/EstablishedContact.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/EstablishedContact.swift index f3937ff331e..1bfd3fb2300 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/EstablishedContact.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/EstablishedContact.swift @@ -13,38 +13,33 @@ public final class EstablishedContact: @unchecked Sendable { } deinit { - _ = established_contact_destroy(handle) + established_contact_destroy(handle).discard() } /// Get the contact's identity ID public func getContactIdentityId() throws -> Identifier { var buf = [UInt8](repeating: 0, count: 32) - var error = PlatformWalletFFIError() - - let result = buf.withUnsafeMutableBufferPointer { bp -> PlatformWalletFFIResult in - established_contact_get_contact_identity_id(handle, bp.baseAddress!, &error) - } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) + try buf.withUnsafeMutableBufferPointer { bp in + try established_contact_get_contact_identity_id(handle, bp.baseAddress!).check() } - return Data(buf) } - /// Get the contact's alias + /// Get the contact's alias. + /// + /// Returns `nil` when the contact has no alias set; the Rust side + /// surfaces that as `NotFound` (the inner `Option` is + /// `None`), which is distinct from a hard error. public func getAlias() throws -> String? { var aliasPtr: UnsafeMutablePointer? = nil - var error = PlatformWalletFFIError() + let result = PlatformWalletResult( + established_contact_get_alias(handle, &aliasPtr) + ) - let result = established_contact_get_alias(handle, &aliasPtr, &error) - - if result == PLATFORM_WALLET_FFI_RESULT_ERROR_CONTACT_NOT_FOUND { + if result.code == .notFound { return nil } - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.throwIfError() defer { if let ptr = aliasPtr { @@ -61,39 +56,27 @@ public final class EstablishedContact: @unchecked Sendable { /// Set the contact's alias public func setAlias(_ alias: String) throws { - var error = PlatformWalletFFIError() let aliasCStr = (alias as NSString).utf8String - - let result = established_contact_set_alias(handle, aliasCStr, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try established_contact_set_alias(handle, aliasCStr).check() } /// Clear the contact's alias public func clearAlias() throws { - var error = PlatformWalletFFIError() - - let result = established_contact_clear_alias(handle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try established_contact_clear_alias(handle).check() } - /// Get the contact's note + /// Get the contact's note. Returns `nil` when no note is set — + /// see `getAlias()` for the `NotFound` rationale. public func getNote() throws -> String? { var notePtr: UnsafeMutablePointer? = nil - var error = PlatformWalletFFIError() - - let result = established_contact_get_note(handle, ¬ePtr, &error) + let result = PlatformWalletResult( + established_contact_get_note(handle, ¬ePtr) + ) - if result == PLATFORM_WALLET_FFI_RESULT_ERROR_CONTACT_NOT_FOUND { + if result.code == .notFound { return nil } - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.throwIfError() defer { if let ptr = notePtr { @@ -110,55 +93,29 @@ public final class EstablishedContact: @unchecked Sendable { /// Set the contact's note public func setNote(_ note: String) throws { - var error = PlatformWalletFFIError() let noteCStr = (note as NSString).utf8String - - let result = established_contact_set_note(handle, noteCStr, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try established_contact_set_note(handle, noteCStr).check() } /// Clear the contact's note public func clearNote() throws { - var error = PlatformWalletFFIError() - - let result = established_contact_clear_note(handle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try established_contact_clear_note(handle).check() } /// Check if the contact is hidden public func isHidden() throws -> Bool { var hidden: Bool = false - var error = PlatformWalletFFIError() - - let result = established_contact_is_hidden(handle, &hidden, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - + try established_contact_is_hidden(handle, &hidden).check() return hidden } /// Hide the contact public func hide() throws { - var error = PlatformWalletFFIError() - - let result = established_contact_hide(handle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try established_contact_hide(handle).check() } /// Unhide the contact public func unhide() throws { - var error = PlatformWalletFFIError() - - let result = established_contact_unhide(handle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try established_contact_unhide(handle).check() } } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/IdentityManager.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/IdentityManager.swift index 95bd3c09012..a057c1a9003 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/IdentityManager.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/IdentityManager.swift @@ -15,67 +15,41 @@ public class IdentityManager { } deinit { - _ = identity_manager_destroy(handle) + identity_manager_destroy(handle).discard() } /// Create a new empty Identity Manager public static func create() throws -> IdentityManager { var handle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() - - let result = identity_manager_create(&handle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - + try identity_manager_create(&handle).check() return IdentityManager(handle: handle) } /// Add an identity to the manager public func addIdentity(_ identity: ManagedIdentity) throws { - var error = PlatformWalletFFIError() - - let result = identity_manager_add_identity(handle, identity.handle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try identity_manager_add_identity(handle, identity.handle).check() } /// Remove an identity from the manager public func removeIdentity(_ identityId: Identifier) throws { - var error = PlatformWalletFFIError() - let result = identityId.withFFIBytes { idPtr in - identity_manager_remove_identity(handle, idPtr, &error) - } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) + try identityId.withFFIBytes { idPtr in + try identity_manager_remove_identity(handle, idPtr).check() } } /// Get an identity by ID public func getIdentity(_ identityId: Identifier) throws -> ManagedIdentity { var identityHandle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() - - let result = identityId.withFFIBytes { idPtr in - identity_manager_get_identity(handle, idPtr, &identityHandle, &error) + try identityId.withFFIBytes { idPtr in + try identity_manager_get_identity(handle, idPtr, &identityHandle).check() } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - return ManagedIdentity(handle: identityHandle) } /// Get all identity IDs public func getAllIdentityIds() throws -> [Identifier] { var array = IdentifierArray(items: nil, count: 0) - var error = PlatformWalletFFIError() - - let result = identity_manager_get_all_identity_ids(handle, &array, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try identity_manager_get_all_identity_ids(handle, &array).check() defer { platform_wallet_identifier_array_free(&array) @@ -103,13 +77,7 @@ public class IdentityManager { /// Get the count of identities public func getIdentityCount() throws -> Int { var count: UInt = 0 - var error = PlatformWalletFFIError() - - let result = identity_manager_get_identity_count(handle, &count, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - + try identity_manager_get_identity_count(handle, &count).check() return Int(count) } } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedIdentity.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedIdentity.swift index 345233b1f29..6b4e414d157 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedIdentity.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedIdentity.swift @@ -15,55 +15,35 @@ public final class ManagedIdentity: @unchecked Sendable { } deinit { - _ = managed_identity_destroy(handle) + managed_identity_destroy(handle).discard() } /// Create a ManagedIdentity from identity bytes public static func fromIdentityBytes(_ bytes: Data) throws -> ManagedIdentity { var handle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() - - let result = bytes.withUnsafeBytes { bytesPtr in - managed_identity_create_from_identity_bytes( + try bytes.withUnsafeBytes { bytesPtr in + try managed_identity_create_from_identity_bytes( bytesPtr.baseAddress?.assumingMemoryBound(to: UInt8.self), UInt(bytes.count), - &handle, - &error - ) + &handle + ).check() } - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - return ManagedIdentity(handle: handle) } /// Get the identity ID public func getId() throws -> Identifier { var buf = [UInt8](repeating: 0, count: 32) - var error = PlatformWalletFFIError() - - let result = buf.withUnsafeMutableBufferPointer { bp -> PlatformWalletFFIResult in - managed_identity_get_id(handle, bp.baseAddress!, &error) - } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) + try buf.withUnsafeMutableBufferPointer { bp in + try managed_identity_get_id(handle, bp.baseAddress!).check() } - return Data(buf) } /// Get the identity balance public func getBalance() throws -> UInt64 { var balance: UInt64 = 0 - var error = PlatformWalletFFIError() - - let result = managed_identity_get_balance(handle, &balance, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - + try managed_identity_get_balance(handle, &balance).check() return balance } @@ -74,12 +54,7 @@ public final class ManagedIdentity: @unchecked Sendable { /// identity was just created and has not been mutated. public func getRevision() throws -> UInt64 { var revision: UInt64 = 0 - var error = PlatformWalletFFIError() - - let result = managed_identity_get_revision(handle, &revision, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try managed_identity_get_revision(handle, &revision).check() return revision } @@ -96,12 +71,7 @@ public final class ManagedIdentity: @unchecked Sendable { public func getIdentityIndex() throws -> UInt32? { var hasIndex: Bool = false var index: UInt32 = 0 - var error = PlatformWalletFFIError() - - let result = managed_identity_get_identity_index(handle, &hasIndex, &index, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try managed_identity_get_identity_index(handle, &hasIndex, &index).check() return hasIndex ? index : nil } @@ -109,12 +79,7 @@ public final class ManagedIdentity: @unchecked Sendable { /// side. Maps to `IdentityStatusFFI` on the Rust side. public func getStatus() throws -> IdentityStatus { var raw: UInt8 = 0 - var error = PlatformWalletFFIError() - - let result = managed_identity_get_status(handle, &raw, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try managed_identity_get_status(handle, &raw).check() return IdentityStatus(rawValue: raw) ?? .unknown } @@ -143,17 +108,7 @@ public final class ManagedIdentity: @unchecked Sendable { public func getPublicKeys() throws -> [IdentityPublicKeyInfo] { var outPtr: UnsafeMutablePointer? = nil var outCount: UInt = 0 - var error = PlatformWalletFFIError() - - let result = managed_identity_get_public_keys( - handle, - &outPtr, - &outCount, - &error - ) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try managed_identity_get_public_keys(handle, &outPtr, &outCount).check() // Empty-identity branch. Rust sets ptr=null / count=0. guard let ptr = outPtr, outCount > 0 else { @@ -206,20 +161,21 @@ public final class ManagedIdentity: @unchecked Sendable { return keys } - /// Get the identity label + /// Get the identity label. + /// + /// Returns `nil` when no label is set; `NotFound` is the canonical + /// "no value" code from `unwrap_option_or_return!` on the Rust + /// side, which subsumes the legacy `ErrorIdentityNotFound` case. public func getLabel() throws -> String? { var labelPtr: UnsafeMutablePointer? = nil - var error = PlatformWalletFFIError() - - let result = managed_identity_get_label(handle, &labelPtr, &error) + let result = PlatformWalletResult( + managed_identity_get_label(handle, &labelPtr) + ) - if result == PLATFORM_WALLET_FFI_RESULT_ERROR_IDENTITY_NOT_FOUND { + if result.code == .notFound { return nil } - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.throwIfError() defer { if let ptr = labelPtr { @@ -236,60 +192,44 @@ public final class ManagedIdentity: @unchecked Sendable { /// Set the identity label public func setLabel(_ label: String) throws { - var error = PlatformWalletFFIError() let labelCStr = (label as NSString).utf8String - - let result = managed_identity_set_label(handle, labelCStr, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try managed_identity_set_label(handle, labelCStr).check() } - /// Get the last updated balance block time + /// Get the last updated balance block time. Returns `nil` when no + /// block-time has been recorded yet. public func getLastUpdatedBalanceBlockTime() throws -> BlockTime? { var blockTime = BlockTime() - var error = PlatformWalletFFIError() - - let result = managed_identity_get_last_updated_balance_block_time(handle, &blockTime, &error) + let result = PlatformWalletResult( + managed_identity_get_last_updated_balance_block_time(handle, &blockTime) + ) - if result == PLATFORM_WALLET_FFI_RESULT_ERROR_IDENTITY_NOT_FOUND { + if result.code == .notFound { return nil } - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.throwIfError() return blockTime } /// Set the last updated balance block time public func setLastUpdatedBalanceBlockTime(_ blockTime: BlockTime) throws { - var error = PlatformWalletFFIError() var bt = blockTime - - let result = managed_identity_set_last_updated_balance_block_time( - handle, &bt, &error - ) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try managed_identity_set_last_updated_balance_block_time(handle, &bt).check() } - /// Get the last synced keys block time + /// Get the last synced keys block time. Returns `nil` when no + /// sync has been recorded yet. public func getLastSyncedKeysBlockTime() throws -> BlockTime? { var blockTime = BlockTime() - var error = PlatformWalletFFIError() - - let result = managed_identity_get_last_synced_keys_block_time(handle, &blockTime, &error) + let result = PlatformWalletResult( + managed_identity_get_last_synced_keys_block_time(handle, &blockTime) + ) - if result == PLATFORM_WALLET_FFI_RESULT_ERROR_IDENTITY_NOT_FOUND { + if result.code == .notFound { return nil } - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.throwIfError() return blockTime } @@ -299,12 +239,7 @@ public final class ManagedIdentity: @unchecked Sendable { /// Get all sent contact request IDs public func getSentContactRequestIds() throws -> [Identifier] { var array = IdentifierArray() - var error = PlatformWalletFFIError() - - let result = managed_identity_get_sent_contact_request_ids(handle, &array, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try managed_identity_get_sent_contact_request_ids(handle, &array).check() defer { platform_wallet_identifier_array_free(&array) @@ -326,12 +261,7 @@ public final class ManagedIdentity: @unchecked Sendable { /// Get all incoming contact request IDs public func getIncomingContactRequestIds() throws -> [Identifier] { var array = IdentifierArray() - var error = PlatformWalletFFIError() - - let result = managed_identity_get_incoming_contact_request_ids(handle, &array, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try managed_identity_get_incoming_contact_request_ids(handle, &array).check() defer { platform_wallet_identifier_array_free(&array) @@ -353,12 +283,7 @@ public final class ManagedIdentity: @unchecked Sendable { /// Get all established contact IDs public func getEstablishedContactIds() throws -> [Identifier] { var array = IdentifierArray() - var error = PlatformWalletFFIError() - - let result = managed_identity_get_established_contact_ids(handle, &array, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try managed_identity_get_established_contact_ids(handle, &array).check() defer { platform_wallet_identifier_array_free(&array) @@ -377,62 +302,56 @@ public final class ManagedIdentity: @unchecked Sendable { return identifiers } - /// Get a sent contact request by recipient ID + /// Get a sent contact request by recipient ID. Returns `nil` when + /// no such request exists. public func getSentContactRequest(recipientId: Identifier) throws -> ContactRequest? { var requestHandle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() - let result = recipientId.withFFIBytes { idPtr in - managed_identity_get_sent_contact_request(handle, idPtr, &requestHandle, &error) + PlatformWalletResult( + managed_identity_get_sent_contact_request(handle, idPtr, &requestHandle) + ) } - if result == PLATFORM_WALLET_FFI_RESULT_ERROR_CONTACT_NOT_FOUND { + if result.code == .notFound { return nil } - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.throwIfError() return ContactRequest(handle: requestHandle) } - /// Get an incoming contact request by sender ID + /// Get an incoming contact request by sender ID. Returns `nil` + /// when no such request exists. public func getIncomingContactRequest(senderId: Identifier) throws -> ContactRequest? { var requestHandle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() - let result = senderId.withFFIBytes { idPtr in - managed_identity_get_incoming_contact_request(handle, idPtr, &requestHandle, &error) + PlatformWalletResult( + managed_identity_get_incoming_contact_request(handle, idPtr, &requestHandle) + ) } - if result == PLATFORM_WALLET_FFI_RESULT_ERROR_CONTACT_NOT_FOUND { + if result.code == .notFound { return nil } - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.throwIfError() return ContactRequest(handle: requestHandle) } - /// Get an established contact by contact ID + /// Get an established contact by contact ID. Returns `nil` when + /// no such contact exists. public func getEstablishedContact(contactId: Identifier) throws -> EstablishedContact? { var contactHandle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() - let result = contactId.withFFIBytes { idPtr in - managed_identity_get_established_contact(handle, idPtr, &contactHandle, &error) + PlatformWalletResult( + managed_identity_get_established_contact(handle, idPtr, &contactHandle) + ) } - if result == PLATFORM_WALLET_FFI_RESULT_ERROR_CONTACT_NOT_FOUND { + if result.code == .notFound { return nil } - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.throwIfError() return EstablishedContact(handle: contactHandle) } @@ -440,48 +359,28 @@ public final class ManagedIdentity: @unchecked Sendable { /// Check if a contact is established public func isContactEstablished(contactId: Identifier) throws -> Bool { var isEstablished: Bool = false - var error = PlatformWalletFFIError() - - let result = contactId.withFFIBytes { idPtr in - managed_identity_is_contact_established(handle, idPtr, &isEstablished, &error) + try contactId.withFFIBytes { idPtr in + try managed_identity_is_contact_established(handle, idPtr, &isEstablished).check() } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - return isEstablished } /// Send a contact request by attaching a pre-built request handle. /// Build the request via `ContactRequest.create(...)` first. public func sendContactRequest(_ request: ContactRequest) throws { - var error = PlatformWalletFFIError() - - let result = managed_identity_send_contact_request(handle, request.handle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try managed_identity_send_contact_request(handle, request.handle).check() } /// Accept an incoming contact request. /// The request handle typically comes from `getIncomingContactRequest(senderId:)`. public func acceptContactRequest(_ request: ContactRequest) throws { - var error = PlatformWalletFFIError() - - let result = managed_identity_accept_contact_request(handle, request.handle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try managed_identity_accept_contact_request(handle, request.handle).check() } /// Reject a contact request from another identity public func rejectContactRequest(senderId: Identifier) throws { - var error = PlatformWalletFFIError() - let result = senderId.withFFIBytes { idPtr in - managed_identity_reject_contact_request(handle, idPtr, &error) - } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) + try senderId.withFFIBytes { idPtr in + try managed_identity_reject_contact_request(handle, idPtr).check() } } @@ -498,7 +397,7 @@ public final class ManagedIdentity: @unchecked Sendable { /// `DpnsNameInfo` is a future wrapper if the UI grows an /// acquisition-date column. public func getDpnsNames() throws -> [String] { - try readDpnsNameArray { managed_identity_get_dpns_names(handle, &$0, &$1) } + try readDpnsNameArray { managed_identity_get_dpns_names(handle, &$0) } } /// Read the cached contested DPNS labels for this identity — @@ -506,7 +405,7 @@ public final class ManagedIdentity: @unchecked Sendable { /// hasn't resolved yet. Empty until the wallet has run /// `syncContestedDpnsNames(identityId:)` at least once. public func getContestedDpnsNames() throws -> [String] { - try readDpnsNameArray { managed_identity_get_contested_dpns_names(handle, &$0, &$1) } + try readDpnsNameArray { managed_identity_get_contested_dpns_names(handle, &$0) } } /// Shared body for the two DPNS-name readers. Both share the @@ -515,14 +414,10 @@ public final class ManagedIdentity: @unchecked Sendable { /// Keeping the plumbing in one place avoids duplicating the /// error handling + defer + pointer iteration block twice. private func readDpnsNameArray( - _ fetch: (inout DpnsNameArray, inout PlatformWalletFFIError) -> PlatformWalletFFIResult + _ fetch: (inout DpnsNameArray) -> PlatformWalletFfiResult ) throws -> [String] { var array = DpnsNameArray() - var error = PlatformWalletFFIError() - let result = fetch(&array, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try fetch(&array).check() defer { dpns_name_array_free(&array) } guard let labels = array.labels, array.count > 0 else { return [] @@ -549,17 +444,7 @@ public final class ManagedIdentity: @unchecked Sendable { public func getDashPayProfile() throws -> DashPayProfile? { var ffiProfile = DashPayProfileFFI() var hasProfile: Bool = false - var error = PlatformWalletFFIError() - - let result = managed_identity_get_dashpay_profile( - handle, - &ffiProfile, - &hasProfile, - &error - ) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try managed_identity_get_dashpay_profile(handle, &ffiProfile, &hasProfile).check() // `dashpay_profile_ffi_free` walks the three nullable string // pointers — calling it on an empty / no-profile struct is diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformAddressWallet.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformAddressWallet.swift index 42329908a01..8343e2dba9a 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformAddressWallet.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformAddressWallet.swift @@ -1,4 +1,5 @@ import Foundation +import DashSDKFFI /// Platform address wallet for balance queries, transfers, and withdrawals. /// @@ -17,8 +18,7 @@ public final class ManagedPlatformAddressWallet: @unchecked Sendable { } deinit { - var error = PlatformWalletFFIError() - _ = platform_address_wallet_destroy(handle, &error) + platform_address_wallet_destroy(handle).discard() } // MARK: - Balance queries @@ -36,14 +36,7 @@ public final class ManagedPlatformAddressWallet: @unchecked Sendable { /// Get total platform credits across all addresses. public func totalCredits() throws -> UInt64 { var credits: UInt64 = 0 - var error = PlatformWalletFFIError() - - let result = platform_address_wallet_total_credits(handle, &credits, &error) - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - + try platform_address_wallet_total_credits(handle, &credits).check() return credits } @@ -51,15 +44,7 @@ public final class ManagedPlatformAddressWallet: @unchecked Sendable { public func addressesWithBalances() throws -> [AddressBalance] { var entriesPtr: UnsafeMutablePointer? var count: UInt = 0 - var error = PlatformWalletFFIError() - - let result = platform_address_wallet_addresses_with_balances( - handle, &entriesPtr, &count, &error - ) - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try platform_address_wallet_addresses_with_balances(handle, &entriesPtr, &count).check() defer { platform_address_wallet_free_address_balances(entriesPtr, count) diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift index cac091d0a64..212da133e00 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift @@ -1,4 +1,5 @@ import Foundation +import DashSDKFFI /// A managed wallet created by `PlatformWalletManager`. /// @@ -23,8 +24,7 @@ public final class ManagedPlatformWallet: @unchecked Sendable { } deinit { - var error = PlatformWalletFFIError() - _ = platform_wallet_destroy(handle, &error) + _ = platform_wallet_destroy(handle) } // MARK: - Balance (lock-free) @@ -47,20 +47,16 @@ public final class ManagedPlatformWallet: @unchecked Sendable { var unconfirmed: UInt64 = 0 var immature: UInt64 = 0 var locked: UInt64 = 0 - var error = PlatformWalletFFIError() let result = platform_wallet_get_balance( handle, &spendable, &unconfirmed, &immature, - &locked, - &error + &locked ) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() return WalletBalance( spendable: spendable, @@ -78,13 +74,10 @@ public final class ManagedPlatformWallet: @unchecked Sendable { /// The caller is responsible for the returned object's lifetime. public func platformAddressWallet() throws -> ManagedPlatformAddressWallet { var platformHandle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() - let result = platform_wallet_get_platform(handle, &platformHandle, &error) + let result = platform_wallet_get_platform(handle, &platformHandle) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() return ManagedPlatformAddressWallet(handle: platformHandle) } @@ -92,12 +85,9 @@ public final class ManagedPlatformWallet: @unchecked Sendable { /// Get the core wallet for UTXO management, addresses, and transactions. public func coreWallet() throws -> ManagedCoreWallet { var coreHandle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() - let result = platform_wallet_get_core(handle, &coreHandle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + let result = platform_wallet_get_core(handle, &coreHandle) + try result.check() return ManagedCoreWallet(handle: coreHandle) } @@ -105,12 +95,9 @@ public final class ManagedPlatformWallet: @unchecked Sendable { /// Get the asset lock manager for building and tracking asset locks. public func assetLockManager() throws -> ManagedAssetLockManager { var assetLockHandle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() - let result = platform_wallet_get_asset_locks(handle, &assetLockHandle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + let result = platform_wallet_get_asset_locks(handle, &assetLockHandle) + try result.check() return ManagedAssetLockManager(handle: assetLockHandle) } @@ -119,22 +106,16 @@ public final class ManagedPlatformWallet: @unchecked Sendable { /// Flush all queued changesets to the storage backend. public func flushPersist() throws { - var error = PlatformWalletFFIError() - let result = platform_wallet_flush_persist(handle, &error) + let result = platform_wallet_flush_persist(handle) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() } /// Load persisted state and apply it to the in-memory wallet. public func loadAndApplyPersisted() throws { - var error = PlatformWalletFFIError() - let result = platform_wallet_load_and_apply_persisted(handle, &error) + let result = platform_wallet_load_and_apply_persisted(handle) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() } // MARK: - Identity registration (address-funded) @@ -289,10 +270,10 @@ public final class ManagedPlatformWallet: @unchecked Sendable { addressSigner: KeychainSigner ) async throws -> CreatedIdentity { guard !inputs.isEmpty else { - throw PlatformWalletError.invalidParameter + throw PlatformWalletError.invalidParameter("inputs is empty") } guard !identityPubkeys.isEmpty else { - throw PlatformWalletError.invalidParameter + throw PlatformWalletError.invalidParameter("identityPubkeys is empty") } // Reject malformed address hashes up front for the same // reason `topUpFromAddresses` does — `.prefix(20)` below @@ -300,11 +281,15 @@ public final class ManagedPlatformWallet: @unchecked Sendable { // a different address. for input in inputs { guard input.hash.count == 20 else { - throw PlatformWalletError.invalidParameter + throw PlatformWalletError.invalidParameter( + "input hash must be 20 bytes, got \(input.hash.count)" + ) } } if let output, output.hash.count != 20 { - throw PlatformWalletError.invalidParameter + throw PlatformWalletError.invalidParameter( + "output hash must be 20 bytes, got \(output.hash.count)" + ) } let handle = self.handle @@ -375,7 +360,6 @@ public final class ManagedPlatformWallet: @unchecked Sendable { 0, 0, 0, 0, 0, 0, 0, 0 ) var outIdentityHandle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() // Stage each pubkey into a contiguous, owning buffer so // the `pubkey_bytes` pointers we hand to Rust stay valid @@ -389,7 +373,7 @@ public final class ManagedPlatformWallet: @unchecked Sendable { let result = ffiInputs.withUnsafeBufferPointer { inputsBuf in withUnsafePointer(to: &outputFFI) { outputPtr in - pubkeyBuffers.withUnsafeBufferPointer { _ -> PlatformWalletFFIResult in + pubkeyBuffers.withUnsafeBufferPointer { _ -> PlatformWalletFfiResult in // For each row, withUnsafeBytes pins ONE Data // at a time, so we have to assemble the FFI // row array under nested pinning. We use a @@ -411,17 +395,14 @@ public final class ManagedPlatformWallet: @unchecked Sendable { UInt(inputsBuf.count), outputPtr, &outIdentityId, - &outIdentityHandle, - &error + &outIdentityHandle ) } } } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() guard outIdentityHandle != NULL_HANDLE else { throw PlatformWalletError.walletOperation("identity handle not returned") } @@ -489,10 +470,12 @@ public final class ManagedPlatformWallet: @unchecked Sendable { addressSigner: KeychainSigner ) async throws -> UInt64 { guard identityId.count == 32 else { - throw PlatformWalletError.invalidParameter + throw PlatformWalletError.invalidParameter( + "identityId must be 32 bytes, got \(identityId.count)" + ) } guard !inputs.isEmpty else { - throw PlatformWalletError.invalidParameter + throw PlatformWalletError.invalidParameter("inputs is empty") } // Reject malformed address hashes up front. Earlier // revisions used `.prefix(20)` on the FFI build below, @@ -502,7 +485,9 @@ public final class ManagedPlatformWallet: @unchecked Sendable { // here surfaces the failure as a recoverable error. for input in inputs { guard input.hash.count == 20 else { - throw PlatformWalletError.invalidParameter + throw PlatformWalletError.invalidParameter( + "input hash must be 20 bytes, got \(input.hash.count)" + ) } } @@ -548,7 +533,6 @@ public final class ManagedPlatformWallet: @unchecked Sendable { } var newBalance: UInt64 = 0 - var error = PlatformWalletFFIError() let result = ffiInputs.withUnsafeBufferPointer { inputsBuf in withUnsafePointer(to: &idTuple) { idPtr in @@ -558,15 +542,12 @@ public final class ManagedPlatformWallet: @unchecked Sendable { inputsBuf.baseAddress, UInt(inputsBuf.count), addressSignerHandle, - &newBalance, - &error + &newBalance ) } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() return newBalance }.value } @@ -784,21 +765,17 @@ extension ManagedPlatformWallet { } var out = IdentityKeyPreviewsFFI() - var error = PlatformWalletFFIError() let result = platform_wallet_preview_identity_registration_keys( handle, startIndex, countOrNeg1, - &out, - &error + &out ) // Free the Rust-owned array whether we succeeded or bailed // out — the free function is a no-op on the zero struct. defer { platform_wallet_preview_identity_registration_keys_free(&out) } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() guard let base = out.items, out.count > 0 else { return [] @@ -899,14 +876,15 @@ extension ManagedPlatformWallet { // length guard for the (unexpected but possible) case where // the instance was constructed with a malformed wallet id. guard self.walletId.count == 32 else { - throw PlatformWalletError.invalidParameter + throw PlatformWalletError.invalidParameter( + "walletId must be 32 bytes, got \(self.walletId.count)" + ) } let resolver = MnemonicResolver(storage: storage) var row = IdentityKeyPreviewFFI() - var error = PlatformWalletFFIError() - let result = self.walletId.withUnsafeBytes { walletBytes -> PlatformWalletFFIResult in + let result = self.walletId.withUnsafeBytes { walletBytes -> PlatformWalletFfiResult in let walletPtr = walletBytes.bindMemory(to: UInt8.self).baseAddress! return dash_sdk_derive_identity_key_at_slot_with_resolver( network, @@ -914,15 +892,12 @@ extension ManagedPlatformWallet { resolver.handle, identityIndex, keyId, - &row, - &error + &row ) } defer { dash_sdk_derive_identity_key_at_slot_free(&row) } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() let path: String = row.derivation_path.map { String(cString: $0) } ?? "" let wif: String = row.private_key_wif.map { String(cString: $0) } ?? "" @@ -1035,12 +1010,11 @@ extension ManagedPlatformWallet { let persister = IdentityKeyPersister(keychain: keychain) var out = IdentityRegistrationKeyDerivationsFFI() - var error = PlatformWalletFFIError() // `walletId` is `Data`; bind into a 32-byte UInt8 pointer // so Rust receives a stable address for the duration of // the FFI call. - let result = self.walletId.withUnsafeBytes { walletBytes -> PlatformWalletFFIResult in + let result = self.walletId.withUnsafeBytes { walletBytes -> PlatformWalletFfiResult in let walletPtr = walletBytes.bindMemory(to: UInt8.self).baseAddress return dash_sdk_derive_and_persist_identity_keys( network, @@ -1049,15 +1023,12 @@ extension ManagedPlatformWallet { keyCount, resolver.handle, persister.handle, - &out, - &error + &out ) } defer { dash_sdk_derive_identity_keys_from_mnemonic_free(&out) } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() guard let base = out.items, out.count > 0 else { return [] } @@ -1177,18 +1148,14 @@ extension ManagedPlatformWallet { return try await Task.detached(priority: .userInitiated) { () -> [Identifier] in var found = DiscoveredIdentityIdsFFI() - var error = PlatformWalletFFIError() let result = platform_wallet_discover_identities( handle, startArg, gapArg, - &found, - &error + &found ) defer { platform_wallet_discover_identities_free(&found) } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() guard let base = found.ids, found.count > 0 else { return [] } @@ -1264,7 +1231,6 @@ extension ManagedPlatformWallet { return try await Task.detached(priority: .userInitiated) { () -> String in _ = signer var outPtr: UnsafeMutablePointer? = nil - var error = PlatformWalletFFIError() let result = idBytes.withUnsafeBufferPointer { idBp in name.withCString { namePtr in platform_wallet_register_dpns_name_with_signer( @@ -1272,14 +1238,11 @@ extension ManagedPlatformWallet { idBp.baseAddress!, namePtr, signerHandle, - &outPtr, - &error + &outPtr ) } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() defer { if let p = outPtr { platform_wallet_string_free(p) } } guard let p = outPtr else { throw PlatformWalletError.walletOperation( @@ -1297,21 +1260,17 @@ extension ManagedPlatformWallet { return try await Task.detached(priority: .userInitiated) { () -> Identifier? in var buf = [UInt8](repeating: 0, count: 32) var found = false - var error = PlatformWalletFFIError() - let result = buf.withUnsafeMutableBufferPointer { bp -> PlatformWalletFFIResult in + let result = buf.withUnsafeMutableBufferPointer { bp -> PlatformWalletFfiResult in name.withCString { namePtr in platform_wallet_resolve_dpns_name( handle, namePtr, bp.baseAddress!, - &found, - &error + &found ) } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() guard found else { return nil } return Data(buf) }.value @@ -1328,20 +1287,16 @@ extension ManagedPlatformWallet { return try await Task.detached(priority: .userInitiated) { () -> [DpnsSearchResult] in var outPtr: UnsafeMutablePointer? = nil var outCount: UInt = 0 - var error = PlatformWalletFFIError() let result = prefix.withCString { prefixPtr in platform_wallet_search_dpns_names( handle, prefixPtr, limit, &outPtr, - &outCount, - &error + &outCount ) } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() guard let ptr = outPtr, outCount > 0 else { return [] } @@ -1381,13 +1336,10 @@ extension ManagedPlatformWallet { } return try await Task.detached(priority: .userInitiated) { () -> UInt32 in var added: UInt32 = 0 - var error = PlatformWalletFFIError() let result = idBytes.withUnsafeBufferPointer { bp in - platform_wallet_sync_dpns_names(handle, bp.baseAddress!, &added, &error) - } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) + platform_wallet_sync_dpns_names(handle, bp.baseAddress!, &added) } + try result.check() return added }.value } @@ -1415,23 +1367,19 @@ extension ManagedPlatformWallet { () -> ContestVoteState? in var state = ContestVoteStateFFI() var found = false - var error = PlatformWalletFFIError() - let result = idBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFFIResult in + let result = idBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFfiResult in label.withCString { labelPtr in platform_wallet_fetch_contest_vote_state( handle, idBp.baseAddress!, labelPtr, &state, - &found, - &error + &found ) } } defer { contest_vote_state_ffi_free(&state) } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() guard found else { return nil } return ContestVoteState(ffi: state) }.value @@ -1456,18 +1404,14 @@ extension ManagedPlatformWallet { } return try await Task.detached(priority: .userInitiated) { () -> UInt32 in var count: UInt32 = 0 - var error = PlatformWalletFFIError() let result = idBytes.withUnsafeBufferPointer { bp in platform_wallet_sync_contested_dpns_names( handle, bp.baseAddress!, - &count, - &error + &count ) } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() return count }.value } @@ -1486,18 +1430,14 @@ extension ManagedPlatformWallet { /// this identity. public func managedIdentity(identityId: Identifier) throws -> ManagedIdentity { var outHandle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() let result = identityId.withFFIBytes { idPtr in platform_wallet_get_managed_identity( handle, idPtr, - &outHandle, - &error + &outHandle ) } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() return ManagedIdentity(handle: outHandle) } @@ -1510,11 +1450,8 @@ extension ManagedPlatformWallet { let handle = self.handle return try await Task.detached(priority: .userInitiated) { () -> [ContactRequest] in var array = ContactRequestHandleArray() - var error = PlatformWalletFFIError() - let result = platform_wallet_sync_contact_requests(handle, &array, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + let result = platform_wallet_sync_contact_requests(handle, &array) + try result.check() defer { platform_wallet_contact_request_handle_array_free(&array) } guard let handles = array.handles, array.count > 0 else { return [] @@ -1563,11 +1500,10 @@ extension ManagedPlatformWallet { () -> Handle in _ = signer var outHandle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() - let result: PlatformWalletFFIResult = senderBytes.withUnsafeBufferPointer { - senderBp -> PlatformWalletFFIResult in - recipientBytes.withUnsafeBufferPointer { recipientBp -> PlatformWalletFFIResult in - let callWithLabel: (UnsafePointer?) -> PlatformWalletFFIResult = { + let result: PlatformWalletFfiResult = senderBytes.withUnsafeBufferPointer { + senderBp -> PlatformWalletFfiResult in + recipientBytes.withUnsafeBufferPointer { recipientBp -> PlatformWalletFfiResult in + let callWithLabel: (UnsafePointer?) -> PlatformWalletFfiResult = { labelPtr in if let autoAcceptProof, !autoAcceptProof.isEmpty { return autoAcceptProof.withUnsafeBytes { rawBuf in @@ -1581,8 +1517,7 @@ extension ManagedPlatformWallet { bytesPtr, UInt(autoAcceptProof.count), signerHandle, - &outHandle, - &error + &outHandle ) } } else { @@ -1594,8 +1529,7 @@ extension ManagedPlatformWallet { nil, 0, signerHandle, - &outHandle, - &error + &outHandle ) } } @@ -1606,9 +1540,7 @@ extension ManagedPlatformWallet { } } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() return outHandle }.value @@ -1630,17 +1562,13 @@ extension ManagedPlatformWallet { ) { () -> Handle in _ = signer var outHandle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() let result = platform_wallet_accept_contact_request_with_signer( walletHandle, requestHandle, signerHandle, - &outHandle, - &error + &outHandle ) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() return outHandle }.value return EstablishedContact(handle: establishedHandle) @@ -1663,21 +1591,17 @@ extension ManagedPlatformWallet { Array(UnsafeBufferPointer(start: ptr, count: 32)) } try await Task.detached(priority: .userInitiated) { - var error = PlatformWalletFFIError() let result = ourBytes.withUnsafeBufferPointer { - ourBp -> PlatformWalletFFIResult in + ourBp -> PlatformWalletFfiResult in contactBytes.withUnsafeBufferPointer { contactBp in platform_wallet_reject_contact_request( handle, ourBp.baseAddress!, - contactBp.baseAddress!, - &error + contactBp.baseAddress! ) } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() }.value } @@ -1691,18 +1615,14 @@ extension ManagedPlatformWallet { } return try await Task.detached(priority: .userInitiated) { () -> [ContactRequest] in var array = ContactRequestHandleArray(handles: nil, count: 0) - var error = PlatformWalletFFIError() let result = idBytes.withUnsafeBufferPointer { idBp in platform_wallet_fetch_sent_contact_requests( handle, idBp.baseAddress!, - &array, - &error + &array ) } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() defer { platform_wallet_contact_request_handle_array_free(&array) } guard let handles = array.handles, array.count > 0 else { return [] @@ -1749,19 +1669,17 @@ extension ManagedPlatformWallet { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ) - var error = PlatformWalletFFIError() - let result: PlatformWalletFFIResult = fromBytes.withUnsafeBufferPointer { - fromBp -> PlatformWalletFFIResult in - toBytes.withUnsafeBufferPointer { toBp -> PlatformWalletFFIResult in - let call: (UnsafePointer?) -> PlatformWalletFFIResult = { memoPtr in + let result: PlatformWalletFfiResult = fromBytes.withUnsafeBufferPointer { + fromBp -> PlatformWalletFfiResult in + toBytes.withUnsafeBufferPointer { toBp -> PlatformWalletFfiResult in + let call: (UnsafePointer?) -> PlatformWalletFfiResult = { memoPtr in platform_wallet_send_dashpay_payment( handle, fromBp.baseAddress!, toBp.baseAddress!, amountDuffs, memoPtr, - &txidTuple, - &error + &txidTuple ) } if let memoCopy { @@ -1771,9 +1689,7 @@ extension ManagedPlatformWallet { } } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() return Swift.withUnsafeBytes(of: &txidTuple) { Data($0) } }.value } @@ -1795,22 +1711,18 @@ extension ManagedPlatformWallet { public func getDashPayProfile(identityId: Identifier) throws -> DashPayProfile? { var ffiProfile = DashPayProfileFFI() var hasProfile: Bool = false - var error = PlatformWalletFFIError() let result = identityId.withFFIBytes { idPtr in platform_wallet_get_dashpay_profile( handle, idPtr, &ffiProfile, - &hasProfile, - &error + &hasProfile ) } defer { dashpay_profile_ffi_free(&ffiProfile) } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() guard hasProfile else { return nil } return DashPayProfile(ffi: ffiProfile) } @@ -1830,15 +1742,11 @@ extension ManagedPlatformWallet { let handle = self.handle return try await Task.detached(priority: .userInitiated) { () -> UInt32 in var syncedCount: UInt32 = 0 - var error = PlatformWalletFFIError() let result = platform_wallet_sync_dashpay_profiles( handle, - &syncedCount, - &error + &syncedCount ) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() return syncedCount }.value } @@ -1900,19 +1808,18 @@ extension ManagedPlatformWallet { return try await Task.detached(priority: .userInitiated) { () -> DashPayProfile in _ = signer var outProfile = DashPayProfileFFI() - var error = PlatformWalletFFIError() - let result: PlatformWalletFFIResult = idBytes.withUnsafeBufferPointer { - idBp -> PlatformWalletFFIResult in + let result: PlatformWalletFfiResult = idBytes.withUnsafeBufferPointer { + idBp -> PlatformWalletFfiResult in let idPtr = idBp.baseAddress! return invokeWithOptionalCStrings( displayName, publicMessage, avatarUrl - ) { namePtr, msgPtr, urlPtr -> PlatformWalletFFIResult in + ) { namePtr, msgPtr, urlPtr -> PlatformWalletFfiResult in let bytes = avatarBytes ?? Data() if let avatarBytes, !avatarBytes.isEmpty { - return avatarBytes.withUnsafeBytes { rawBuf -> PlatformWalletFFIResult in + return avatarBytes.withUnsafeBytes { rawBuf -> PlatformWalletFfiResult in let bytesPtr = rawBuf.baseAddress?.assumingMemoryBound(to: UInt8.self) return platform_wallet_create_or_update_dashpay_profile_with_signer( handle, @@ -1924,8 +1831,7 @@ extension ManagedPlatformWallet { UInt(avatarBytes.count), doCreate, signerHandle, - &outProfile, - &error + &outProfile ) } } else { @@ -1940,8 +1846,7 @@ extension ManagedPlatformWallet { 0, doCreate, signerHandle, - &outProfile, - &error + &outProfile ) } } @@ -1949,9 +1854,7 @@ extension ManagedPlatformWallet { defer { dashpay_profile_ffi_free(&outProfile) } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() return DashPayProfile(ffi: outProfile) }.value } @@ -2011,8 +1914,8 @@ extension ManagedPlatformWallet { /// SwiftData's `PersistentIdentity` query when the wallet hasn't /// rehydrated all of its persisted identities into memory. public func inMemoryIdentityIds() throws -> [Identifier] { - try readIdentifierArray { array, error in - platform_wallet_list_in_memory_identity_ids(handle, &array, &error) + try readIdentifierArray { array in + platform_wallet_list_in_memory_identity_ids(handle, &array) } } @@ -2020,8 +1923,8 @@ extension ManagedPlatformWallet { /// the wallet currently knows about. Reads /// `info.identity_manager.watched_identities` directly. public func inMemoryWatchedIdentityIds() throws -> [Identifier] { - try readIdentifierArray { array, error in - platform_wallet_list_in_memory_watched_identity_ids(handle, &array, &error) + try readIdentifierArray { array in + platform_wallet_list_in_memory_watched_identity_ids(handle, &array) } } @@ -2030,12 +1933,9 @@ extension ManagedPlatformWallet { /// at the top of the per-wallet detail screen. public func inMemorySummary() throws -> InMemoryWalletSummary { var ffi = PlatformWalletMemorySummaryFFI() - var error = PlatformWalletFFIError() - let result = platform_wallet_get_in_memory_summary(handle, &ffi, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + let result = platform_wallet_get_in_memory_summary(handle, &ffi) + try result.check() return InMemoryWalletSummary( identitiesCount: Int(ffi.identities_count), @@ -2053,14 +1953,10 @@ extension ManagedPlatformWallet { /// free helper, so the per-method variance is just the FFI call /// itself. private func readIdentifierArray( - _ fetch: (inout IdentifierArray, inout PlatformWalletFFIError) -> PlatformWalletFFIResult + _ fetch: (inout IdentifierArray) -> PlatformWalletFfiResult ) throws -> [Identifier] { var array = IdentifierArray() - var error = PlatformWalletFFIError() - let result = fetch(&array, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try fetch(&array).check() defer { platform_wallet_identifier_array_free(&array) } guard array.items != nil, array.count > 0 else { return [] @@ -2156,22 +2052,18 @@ extension ManagedPlatformWallet { } try await Task.detached(priority: .userInitiated) { _ = signer - var error = PlatformWalletFFIError() - let result = fromBytes.withUnsafeBufferPointer { fromBp -> PlatformWalletFFIResult in + let result = fromBytes.withUnsafeBufferPointer { fromBp -> PlatformWalletFfiResult in toBytes.withUnsafeBufferPointer { toBp in platform_wallet_transfer_credits_with_signer( handle, fromBp.baseAddress!, toBp.baseAddress!, amount, - signerHandle, - &error + signerHandle ) } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() }.value } @@ -2186,7 +2078,7 @@ extension ManagedPlatformWallet { signer: KeychainSigner ) async throws -> UInt64 { guard !recipients.isEmpty else { - throw PlatformWalletError.invalidParameter + throw PlatformWalletError.invalidParameter("recipients is empty") } let handle = self.handle let signerHandle = signer.handle @@ -2225,9 +2117,8 @@ extension ManagedPlatformWallet { return try await Task.detached(priority: .userInitiated) { () -> UInt64 in _ = signer var newBalance: UInt64 = 0 - var error = PlatformWalletFFIError() let result = fromBytes.withUnsafeBufferPointer { - fromBp -> PlatformWalletFFIResult in + fromBp -> PlatformWalletFfiResult in rows.withUnsafeBufferPointer { rowsBp in platform_wallet_transfer_credits_to_addresses_with_signer( handle, @@ -2235,14 +2126,11 @@ extension ManagedPlatformWallet { rowsBp.baseAddress, UInt(rowsBp.count), signerHandle, - &newBalance, - &error + &newBalance ) } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() return newBalance }.value } @@ -2263,23 +2151,19 @@ extension ManagedPlatformWallet { } try await Task.detached(priority: .userInitiated) { _ = signer - var error = PlatformWalletFFIError() let result = idBytes.withUnsafeBufferPointer { - idBp -> PlatformWalletFFIResult in + idBp -> PlatformWalletFfiResult in toAddress.withCString { addrPtr in platform_wallet_withdraw_credits_with_signer( handle, idBp.baseAddress!, amount, addrPtr, - signerHandle, - &error + signerHandle ) } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() }.value } @@ -2308,13 +2192,12 @@ extension ManagedPlatformWallet { let disableIds = disablePublicKeyIds try await Task.detached(priority: .userInitiated) { _ = signer - var error = PlatformWalletFFIError() // Mirror the registration FFI's pubkey-pinning pattern via // `withPubkeyFFIArray` so each `pubkey_bytes` pointer the // FFI sees stays valid for the call duration. let pubkeyBuffers: [Data] = addPubkeys.map { $0.pubkeyBytes } let result = idBytes.withUnsafeBufferPointer { - idBp -> PlatformWalletFFIResult in + idBp -> PlatformWalletFfiResult in disableIds.withUnsafeBufferPointer { disableBp in if addPubkeys.isEmpty { return platform_wallet_update_identity_with_signer( @@ -2324,8 +2207,7 @@ extension ManagedPlatformWallet { 0, disableIds.isEmpty ? nil : disableBp.baseAddress, UInt(disableIds.count), - signerHandle, - &error + signerHandle ) } return ManagedPlatformWallet.withPubkeyFFIArray( @@ -2339,15 +2221,12 @@ extension ManagedPlatformWallet { UInt(ffiRowsCount), disableIds.isEmpty ? nil : disableBp.baseAddress, UInt(disableIds.count), - signerHandle, - &error + signerHandle ) } } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() }.value } @@ -2412,10 +2291,9 @@ extension ManagedPlatformWallet { // `withCString` scopes here are sufficient — the // pointers don't need to outlive the call. _ = signer - var error = PlatformWalletFFIError() var contractIdBytes = [UInt8](repeating: 0, count: 32) - let result = idBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFFIResult in + let result = idBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFfiResult in documentSchemasJSON.withCString { docsPtr in Self.withOptionalCString(tokenSchemasJSON) { tokensPtr in Self.withOptionalCString(groupsJSON) { groupsPtr in @@ -2433,8 +2311,7 @@ extension ManagedPlatformWallet { descriptionPtr, configPtr, signerHandle, - outBp.baseAddress!, - &error + outBp.baseAddress! ) } } @@ -2445,9 +2322,7 @@ extension ManagedPlatformWallet { } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() return Data(contractIdBytes) }.value } @@ -2485,7 +2360,7 @@ extension ManagedPlatformWallet { signer: KeychainSigner ) async throws -> (Identifier, ManagedIdentity) { guard !identityPubkeys.isEmpty else { - throw PlatformWalletError.invalidParameter + throw PlatformWalletError.invalidParameter("identityPubkeys is empty") } let handle = self.handle let signerHandle = signer.handle @@ -2503,7 +2378,6 @@ extension ManagedPlatformWallet { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ) var outManagedHandle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() // Pin each pubkey buffer simultaneously via the existing // helper, then hand the assembled row array to the FFI. let pubkeyBuffers: [Data] = pubkeys.map { $0.pubkeyBytes } @@ -2519,13 +2393,10 @@ extension ManagedPlatformWallet { UInt(ffiRowsCount), signerHandle, &idTuple, - &outManagedHandle, - &error + &outManagedHandle ) } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try result.check() // Copy the 32-byte tuple into a Data via withUnsafeBytes. let identityId = Swift.withUnsafeBytes(of: idTuple) { Data($0) } return (identityId, ManagedIdentity(handle: outManagedHandle)) diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWallet.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWallet.swift index 0623e5419e3..20b7b7c564d 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWallet.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWallet.swift @@ -11,32 +11,26 @@ public class PlatformWallet { } deinit { - _ = platform_wallet_info_destroy(handle) + platform_wallet_info_destroy(handle).discard() } /// Create a new Platform Wallet from a 64-byte seed public static func fromSeed(_ seed: Data, network: PlatformNetwork = .testnet) throws -> PlatformWallet { guard seed.count == 64 else { - throw PlatformWalletError.invalidParameter + throw PlatformWalletError.invalidParameter( + "seed must be 64 bytes, got \(seed.count)" + ) } var handle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() - - let result = seed.withUnsafeBytes { seedPtr in - platform_wallet_info_create_from_seed( + try seed.withUnsafeBytes { seedPtr in + try platform_wallet_info_create_from_seed( network.ffiValue, seedPtr.baseAddress?.assumingMemoryBound(to: UInt8.self), UInt(seed.count), - &handle, - &error - ) - } - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) + &handle + ).check() } - return PlatformWallet(handle: handle) } @@ -47,22 +41,16 @@ public class PlatformWallet { network: PlatformNetwork = .testnet ) throws -> PlatformWallet { var handle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() let mnemonicCStr = (mnemonic as NSString).utf8String let passphraseCStr = passphrase != nil ? (passphrase! as NSString).utf8String : nil - let result = platform_wallet_info_create_from_mnemonic( + try platform_wallet_info_create_from_mnemonic( network.ffiValue, mnemonicCStr, passphraseCStr, - &handle, - &error - ) - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + &handle + ).check() return PlatformWallet(handle: handle) } @@ -75,17 +63,7 @@ public class PlatformWallet { } var managerHandle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() - - let result = platform_wallet_info_get_identity_manager( - handle, - &managerHandle, - &error - ) - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try platform_wallet_info_get_identity_manager(handle, &managerHandle).check() let manager = IdentityManager(handle: managerHandle) identityManagers[network] = manager @@ -94,18 +72,7 @@ public class PlatformWallet { /// Set the identity manager for a specific network public func setIdentityManager(_ manager: IdentityManager, for network: PlatformNetwork) throws { - var error = PlatformWalletFFIError() - - let result = platform_wallet_info_set_identity_manager( - handle, - manager.handle, - &error - ) - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } - + try platform_wallet_info_set_identity_manager(handle, manager.handle).check() identityManagers[network] = manager } } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManager.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManager.swift index 84d01eb5e80..ce763c8ec7f 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManager.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManager.swift @@ -1,6 +1,7 @@ import Foundation import SwiftData import Combine +import DashSDKFFI /// The one thing SwiftUI needs for all wallet operations. /// @@ -74,10 +75,8 @@ public class PlatformWalletManager: ObservableObject { deinit { progressPollTask?.cancel() if handle != NULL_HANDLE { - var stopError = PlatformWalletFFIError() - _ = platform_wallet_manager_platform_address_sync_stop(handle, &stopError) - var error = PlatformWalletFFIError() - _ = platform_wallet_manager_destroy(handle, &error) + platform_wallet_manager_platform_address_sync_stop(handle).discard() + platform_wallet_manager_destroy(handle).discard() } } @@ -91,10 +90,12 @@ public class PlatformWalletManager: ObservableObject { public func configure(sdk: SDK, modelContainer: ModelContainer? = nil) throws { precondition(!isConfigured, "PlatformWalletManager already configured") guard let sdkHandle = sdk.handle else { - throw PlatformWalletError.invalidParameter + throw PlatformWalletError.invalidParameter("SDK has no handle") } guard let innerSdkPtr = dash_sdk_get_inner_sdk_ptr(sdkHandle) else { - throw PlatformWalletError.invalidParameter + throw PlatformWalletError.invalidParameter( + "dash_sdk_get_inner_sdk_ptr returned NULL for the supplied SDK" + ) } try configure(sdkPointer: UnsafeRawPointer(innerSdkPtr), modelContainer: modelContainer) } @@ -102,7 +103,6 @@ public class PlatformWalletManager: ObservableObject { /// Configure with a raw Sdk pointer (advanced usage). public func configure(sdkPointer: UnsafeRawPointer, modelContainer: ModelContainer? = nil) throws { var handle: Handle = NULL_HANDLE - var error = PlatformWalletFFIError() let handler: PlatformWalletPersistenceHandler? var persistence: PersistenceCallbacks @@ -118,17 +118,12 @@ public class PlatformWalletManager: ObservableObject { let eventHandler = PlatformWalletEventHandler(manager: self) var eventHandlerCallbacks = eventHandler.makeCallbacks() - let result = platform_wallet_manager_create( + try platform_wallet_manager_create( sdkPointer, &persistence, &eventHandlerCallbacks, - &handle, - &error - ) - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + &handle + ).check() self.handle = handle self.persistenceHandler = handler @@ -160,29 +155,20 @@ public class PlatformWalletManager: ObservableObject { ) throws -> ManagedPlatformWallet { try ensureConfigured() var walletHandle: Handle = NULL_HANDLE - var walletId: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) = + var walletId: FFIByteTuple32 = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) - var error = PlatformWalletFFIError() let accountOptions: UInt32 = createDefaultAccounts ? 1 : 0 - let result = mnemonic.withCString { mnemonicPtr in - platform_wallet_manager_create_wallet_from_mnemonic( + try mnemonic.withCString { mnemonicPtr in + try platform_wallet_manager_create_wallet_from_mnemonic( handle, mnemonicPtr, network.rawValue, accountOptions, &walletHandle, - &walletId, - &error - ) - } - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) + &walletId + ).check() } let idData = withUnsafeBytes(of: &walletId) { Data($0) } @@ -204,34 +190,27 @@ public class PlatformWalletManager: ObservableObject { ) throws -> ManagedPlatformWallet { try ensureConfigured() guard seed.count == 64 else { - throw PlatformWalletError.invalidParameter + throw PlatformWalletError.invalidParameter( + "seed must be 64 bytes, got \(seed.count)" + ) } var walletHandle: Handle = NULL_HANDLE - var walletId: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, - UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) = + var walletId: FFIByteTuple32 = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) - var error = PlatformWalletFFIError() let accountOptions: UInt32 = createDefaultAccounts ? 1 : 0 - let result = seed.withUnsafeBytes { seedPtr in - platform_wallet_manager_create_wallet_from_seed( + try seed.withUnsafeBytes { seedPtr in + try platform_wallet_manager_create_wallet_from_seed( handle, network.rawValue, seedPtr.baseAddress?.assumingMemoryBound(to: UInt8.self), UInt(seed.count), accountOptions, &walletHandle, - &walletId, - &error - ) - } - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) + &walletId + ).check() } let idData = withUnsafeBytes(of: &walletId) { Data($0) } @@ -266,11 +245,7 @@ public class PlatformWalletManager: ObservableObject { public func loadFromPersistor() throws -> [ManagedPlatformWallet] { try ensureConfigured() - var error = PlatformWalletFFIError() - let loadResult = platform_wallet_manager_load_from_persistor(handle, &error) - guard loadResult == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: loadResult, error: error) - } + try platform_wallet_manager_load_from_persistor(handle).check() // Ask SwiftData for the list of wallet ids we just told Rust // to load. We reuse the same container rather than shipping a @@ -286,30 +261,30 @@ public class PlatformWalletManager: ObservableObject { for walletId in walletIds { guard walletId.count == 32 else { continue } var walletHandle: Handle = NULL_HANDLE - var fetchError = PlatformWalletFFIError() - let fetchResult = walletId.withUnsafeBytes { idPtr -> PlatformWalletFFIResult in - // C signature is `const uint8_t (*wallet_id)[32]`, which Swift - // imports as `UnsafePointer?`. Rebind the raw - // 32-byte buffer to that 32-tuple shape so the call type-checks. - guard let base = idPtr.baseAddress?.assumingMemoryBound(to: FFIByteTuple32.self) else { - return PLATFORM_WALLET_FFI_RESULT_ERROR_NULL_POINTER + do { + try walletId.withUnsafeBytes { idPtr in + // C signature is `const uint8_t (*wallet_id)[32]`, which Swift + // imports as `UnsafePointer?`. Rebind the raw + // 32-byte buffer to that 32-tuple shape so the call type-checks. + guard let base = idPtr.baseAddress?.assumingMemoryBound(to: FFIByteTuple32.self) else { + throw PlatformWalletError.nullPointer( + "wallet_id buffer base address was nil" + ) + } + try platform_wallet_manager_get_wallet( + handle, + base, + &walletHandle + ).check() } - return platform_wallet_manager_get_wallet( - handle, - base, - &walletHandle, - &fetchError - ) - } - if fetchResult == PLATFORM_WALLET_FFI_RESULT_SUCCESS { let managedWallet = ManagedPlatformWallet(handle: walletHandle, walletId: walletId) restored.append(managedWallet) self.wallets[walletId] = managedWallet - } else { + } catch { // Log and skip — one wallet failing doesn't fail the // whole restore. Usually means wallet_id / xpub // disagreement (SwiftData drift vs. Rust recompute). - self.lastError = PlatformWalletError(result: fetchResult, error: fetchError) + self.lastError = error } } @@ -350,19 +325,23 @@ public class PlatformWalletManager: ObservableObject { public static func accountExtendedPubKeyString(bytes: Data) -> String? { guard !bytes.isEmpty else { return nil } var outPtr: UnsafeMutablePointer? = nil - var error = PlatformWalletFFIError() - let result: PlatformWalletFFIResult = bytes.withUnsafeBytes { raw in - guard let base = raw.baseAddress?.assumingMemoryBound(to: UInt8.self) else { - return PLATFORM_WALLET_FFI_RESULT_ERROR_NULL_POINTER + do { + try bytes.withUnsafeBytes { raw in + guard let base = raw.baseAddress?.assumingMemoryBound(to: UInt8.self) else { + throw PlatformWalletError.nullPointer( + "xpub bytes buffer base address was nil" + ) + } + try platform_wallet_account_xpub_to_string( + base, + UInt(bytes.count), + &outPtr + ).check() } - return platform_wallet_account_xpub_to_string( - base, - UInt(bytes.count), - &outPtr, - &error - ) + } catch { + return nil } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS, let cStr = outPtr else { + guard let cStr = outPtr else { return nil } let str = String(cString: cStr) @@ -398,23 +377,22 @@ public class PlatformWalletManager: ObservableObject { var outEntries: UnsafePointer? var outCount: UInt = 0 - var error = PlatformWalletFFIError() - let result = walletId.withUnsafeBytes { raw -> PlatformWalletFFIResult in - guard let base = raw.baseAddress?.assumingMemoryBound(to: UInt8.self) else { - return PLATFORM_WALLET_FFI_RESULT_ERROR_NULL_POINTER - } + let ffiResult = walletId.withUnsafeBytes { (raw: UnsafeRawBufferPointer) -> PlatformWalletFfiResult in + let base = raw.baseAddress?.assumingMemoryBound(to: UInt8.self) return platform_wallet_manager_get_account_balances( handle, base, &outEntries, - &outCount, - &error + &outCount ) } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - self.lastError = PlatformWalletError(result: result, error: error) + let result = PlatformWalletResult(ffiResult) + + + guard result.isSuccess else { + self.lastError = PlatformWalletError(result: result) return [] } @@ -453,7 +431,9 @@ public class PlatformWalletManager: ObservableObject { private func ensureConfigured() throws { if !isConfigured || handle == NULL_HANDLE { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle( + "PlatformWalletManager not configured" + ) } } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerAddressSync.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerAddressSync.swift index ee8e46b53b3..783fa110fd7 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerAddressSync.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerAddressSync.swift @@ -1,4 +1,5 @@ import Foundation +import DashSDKFFI public struct PlatformAddressWalletSyncResult: Sendable { public let walletId: Data @@ -105,7 +106,9 @@ extension PlatformWalletManager { config: AddressSyncConfig? = nil ) throws { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle( + "PlatformWalletManager not configured" + ) } if let intervalSeconds { @@ -115,102 +118,75 @@ extension PlatformWalletManager { try setPlatformAddressSyncConfig(config) } - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_platform_address_sync_start(handle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try platform_wallet_manager_platform_address_sync_start(handle).check() } public func stopPlatformAddressSync() throws { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle( + "PlatformWalletManager not configured" + ) } - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_platform_address_sync_stop(handle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try platform_wallet_manager_platform_address_sync_stop(handle).check() } public func isPlatformAddressSyncRunning() throws -> Bool { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle( + "PlatformWalletManager not configured" + ) } var running = false - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_platform_address_sync_is_running( - handle, - &running, - &error - ) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try platform_wallet_manager_platform_address_sync_is_running(handle, &running).check() return running } public func isPlatformAddressSyncing() throws -> Bool { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle( + "PlatformWalletManager not configured" + ) } var syncing = false - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_platform_address_sync_is_syncing( - handle, - &syncing, - &error - ) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try platform_wallet_manager_platform_address_sync_is_syncing(handle, &syncing).check() return syncing } public func lastPlatformAddressSyncUnixSeconds() throws -> UInt64 { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle( + "PlatformWalletManager not configured" + ) } var lastSyncUnixSeconds: UInt64 = 0 - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_platform_address_sync_last_sync_unix_seconds( + try platform_wallet_manager_platform_address_sync_last_sync_unix_seconds( handle, - &lastSyncUnixSeconds, - &error - ) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + &lastSyncUnixSeconds + ).check() return lastSyncUnixSeconds } public func setPlatformAddressSyncInterval(seconds: UInt64) throws { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle( + "PlatformWalletManager not configured" + ) } - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_platform_address_sync_set_interval( - handle, - seconds, - &error - ) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try platform_wallet_manager_platform_address_sync_set_interval(handle, seconds).check() } public func setPlatformAddressSyncConfig(_ config: AddressSyncConfig?) throws { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle( + "PlatformWalletManager not configured" + ) } - var error = PlatformWalletFFIError() - let result: PlatformWalletFFIResult if let config { var ffiConfig = AddressSyncConfigFFI( min_privacy_count: config.minPrivacyCount, @@ -218,30 +194,26 @@ extension PlatformWalletManager { max_iterations: config.maxIterations, full_rescan_after_time_s: config.fullRescanAfterTimeSeconds ) - result = withUnsafePointer(to: &ffiConfig) { configPtr in - platform_wallet_manager_platform_address_sync_set_config(handle, configPtr, &error) + try withUnsafePointer(to: &ffiConfig) { configPtr in + try platform_wallet_manager_platform_address_sync_set_config( + handle, configPtr + ).check() } } else { - result = platform_wallet_manager_platform_address_sync_set_config(handle, nil, &error) - } - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) + try platform_wallet_manager_platform_address_sync_set_config(handle, nil).check() } } public func syncPlatformAddressNow() async throws { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle( + "PlatformWalletManager not configured" + ) } let handle = self.handle try await Task.detached(priority: .userInitiated) { - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_platform_address_sync_sync_now(handle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try platform_wallet_manager_platform_address_sync_sync_now(handle).check() }.value } } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerIdentitySync.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerIdentitySync.swift index 72ce4c8b3de..89d50685a52 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerIdentitySync.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerIdentitySync.swift @@ -1,4 +1,5 @@ import Foundation +import DashSDKFFI /// One row of the per-(identity, token) sync cache held by the /// Rust-side `IdentitySyncManager`. Mirrors the FFI @@ -39,52 +40,36 @@ extension PlatformWalletManager { /// Start the identity-token sync background loop. public func startIdentityTokenSync() throws { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle - } - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_identity_sync_start(handle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) + throw PlatformWalletError.invalidHandle("PlatformWalletManager not configured") } + try platform_wallet_manager_identity_sync_start(handle).check() } /// Stop the identity-token sync background loop. public func stopIdentityTokenSync() throws { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle - } - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_identity_sync_stop(handle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) + throw PlatformWalletError.invalidHandle("PlatformWalletManager not configured") } + try platform_wallet_manager_identity_sync_stop(handle).check() } /// Whether the identity-token sync background loop is running. public func isIdentityTokenSyncRunning() throws -> Bool { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle("PlatformWalletManager not configured") } var running = false - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_identity_sync_is_running(handle, &running, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try platform_wallet_manager_identity_sync_is_running(handle, &running).check() return running } /// Whether an identity-token sync pass is currently in flight. public func isIdentityTokenSyncing() throws -> Bool { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle("PlatformWalletManager not configured") } var syncing = false - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_identity_sync_is_syncing(handle, &syncing, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try platform_wallet_manager_identity_sync_is_syncing(handle, &syncing).check() return syncing } @@ -92,23 +77,20 @@ extension PlatformWalletManager { /// the given identity, or 0 if it has never been synced. public func lastIdentityTokenSyncUnixSeconds(for identityId: Identifier) throws -> UInt64 { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle("PlatformWalletManager not configured") } guard identityId.count == 32 else { - throw PlatformWalletError.invalidIdentifier + throw PlatformWalletError.invalidIdentifier( + "identityId must be 32 bytes, got \(identityId.count)" + ) } var lastSync: UInt64 = 0 - var error = PlatformWalletFFIError() - let result = identityId.withUnsafeBytes { idPtr -> PlatformWalletFFIResult in - platform_wallet_manager_identity_sync_last_sync_unix_seconds( + try identityId.withUnsafeBytes { idPtr in + try platform_wallet_manager_identity_sync_last_sync_unix_seconds( handle, idPtr.bindMemory(to: UInt8.self).baseAddress, - &lastSync, - &error - ) - } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) + &lastSync + ).check() } return lastSync } @@ -116,13 +98,9 @@ extension PlatformWalletManager { /// Set the polling interval (clamped to >= 1 second on the Rust side). public func setIdentityTokenSyncInterval(seconds: UInt64) throws { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle - } - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_identity_sync_set_interval(handle, seconds, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) + throw PlatformWalletError.invalidHandle("PlatformWalletManager not configured") } + try platform_wallet_manager_identity_sync_set_interval(handle, seconds).check() } /// Run one identity-token sync pass across every registered @@ -131,15 +109,11 @@ extension PlatformWalletManager { /// without doing extra work. public func syncIdentityTokensNow() async throws { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle("PlatformWalletManager not configured") } let handle = self.handle try await Task.detached(priority: .userInitiated) { - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_identity_sync_sync_now(handle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try platform_wallet_manager_identity_sync_sync_now(handle).check() }.value } @@ -153,55 +127,51 @@ extension PlatformWalletManager { tokenIds: [Identifier] ) throws { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle("PlatformWalletManager not configured") } guard identityId.count == 32 else { - throw PlatformWalletError.invalidIdentifier + throw PlatformWalletError.invalidIdentifier( + "identityId must be 32 bytes, got \(identityId.count)" + ) } - var error = PlatformWalletFFIError() // Flatten token ids into one contiguous 32*N buffer so the // FFI can read them as back-to-back chunks. var flat = Data(capacity: 32 * tokenIds.count) for tid in tokenIds { guard tid.count == 32 else { - throw PlatformWalletError.invalidIdentifier + throw PlatformWalletError.invalidIdentifier( + "token id must be 32 bytes, got \(tid.count)" + ) } flat.append(tid) } - let result = identityId.withUnsafeBytes { idPtr -> PlatformWalletFFIResult in - flat.withUnsafeBytes { tokensPtr -> PlatformWalletFFIResult in - platform_wallet_manager_identity_sync_register_identity( + try identityId.withUnsafeBytes { idPtr in + try flat.withUnsafeBytes { tokensPtr in + try platform_wallet_manager_identity_sync_register_identity( handle, idPtr.bindMemory(to: UInt8.self).baseAddress, tokensPtr.bindMemory(to: UInt8.self).baseAddress, - UInt(tokenIds.count), - &error - ) + UInt(tokenIds.count) + ).check() } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } } /// Remove `identityId` from the sync registry. Idempotent. public func unregisterIdentityForTokenSync(identityId: Identifier) throws { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle("PlatformWalletManager not configured") } guard identityId.count == 32 else { - throw PlatformWalletError.invalidIdentifier - } - var error = PlatformWalletFFIError() - let result = identityId.withUnsafeBytes { idPtr -> PlatformWalletFFIResult in - platform_wallet_manager_identity_sync_unregister_identity( - handle, - idPtr.bindMemory(to: UInt8.self).baseAddress, - &error + throw PlatformWalletError.invalidIdentifier( + "identityId must be 32 bytes, got \(identityId.count)" ) } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) + try identityId.withUnsafeBytes { idPtr in + try platform_wallet_manager_identity_sync_unregister_identity( + handle, + idPtr.bindMemory(to: UInt8.self).baseAddress + ).check() } } @@ -213,33 +183,32 @@ extension PlatformWalletManager { tokenIds: [Identifier] ) throws { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle("PlatformWalletManager not configured") } guard identityId.count == 32 else { - throw PlatformWalletError.invalidIdentifier + throw PlatformWalletError.invalidIdentifier( + "identityId must be 32 bytes, got \(identityId.count)" + ) } - var error = PlatformWalletFFIError() var flat = Data(capacity: 32 * tokenIds.count) for tid in tokenIds { guard tid.count == 32 else { - throw PlatformWalletError.invalidIdentifier + throw PlatformWalletError.invalidIdentifier( + "token id must be 32 bytes, got \(tid.count)" + ) } flat.append(tid) } - let result = identityId.withUnsafeBytes { idPtr -> PlatformWalletFFIResult in - flat.withUnsafeBytes { tokensPtr -> PlatformWalletFFIResult in - platform_wallet_manager_identity_sync_update_watched_tokens( + try identityId.withUnsafeBytes { idPtr in + try flat.withUnsafeBytes { tokensPtr in + try platform_wallet_manager_identity_sync_update_watched_tokens( handle, idPtr.bindMemory(to: UInt8.self).baseAddress, tokensPtr.bindMemory(to: UInt8.self).baseAddress, - UInt(tokenIds.count), - &error - ) + UInt(tokenIds.count) + ).check() } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } } /// Snapshot the per-identity token sync state for one identity. @@ -248,28 +217,25 @@ extension PlatformWalletManager { for identityId: Identifier ) throws -> IdentityTokenSyncSnapshot? { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle("PlatformWalletManager not configured") } guard identityId.count == 32 else { - throw PlatformWalletError.invalidIdentifier + throw PlatformWalletError.invalidIdentifier( + "identityId must be 32 bytes, got \(identityId.count)" + ) } var rowsPtr: UnsafeMutablePointer? = nil var rowsCount: UInt = 0 var lastSync: UInt64 = 0 - var error = PlatformWalletFFIError() - let result = identityId.withUnsafeBytes { idPtr -> PlatformWalletFFIResult in - platform_wallet_manager_identity_sync_state_for_identity( + try identityId.withUnsafeBytes { idPtr in + try platform_wallet_manager_identity_sync_state_for_identity( handle, idPtr.bindMemory(to: UInt8.self).baseAddress, &rowsPtr, &rowsCount, - &lastSync, - &error - ) - } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) + &lastSync + ).check() } defer { @@ -290,20 +256,15 @@ extension PlatformWalletManager { /// identity in one flat array. public func allIdentityTokenSyncRows() throws -> [IdentityTokenSyncRow] { guard isConfigured, handle != NULL_HANDLE else { - throw PlatformWalletError.invalidHandle + throw PlatformWalletError.invalidHandle("PlatformWalletManager not configured") } var rowsPtr: UnsafeMutablePointer? = nil var rowsCount: UInt = 0 - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_identity_sync_state_all( + try platform_wallet_manager_identity_sync_state_all( handle, &rowsPtr, - &rowsCount, - &error - ) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + &rowsCount + ).check() defer { if let rowsPtr { diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerSPV.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerSPV.swift index 69fee0109db..0e3107b8b17 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerSPV.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerSPV.swift @@ -1,4 +1,5 @@ import Foundation +import DashSDKFFI /// SPV sync state mirroring Rust's `dash_spv::sync::SyncState`. public enum PlatformSpvSyncState: UInt32, Sendable { @@ -147,22 +148,14 @@ extension PlatformWalletManager { masternodes_current: 0, masternodes_target: 0, masternodes_percentage: 0 ) - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_sync_progress(handle, &ffi, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try platform_wallet_manager_sync_progress(handle, &ffi).check() return PlatformSpvSyncProgress(ffi) } /// Whether the SPV client is currently running. public func isSpvRunning() throws -> Bool { var running: Bool = false - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_spv_is_running(handle, &running, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try platform_wallet_manager_spv_is_running(handle, &running).check() return running } @@ -171,21 +164,19 @@ extension PlatformWalletManager { /// Spawns the sync loop on the shared tokio runtime and returns /// immediately. Use [`stopSpv`] to cancel. public func startSpv(config: PlatformSpvStartConfig) throws { - var error = PlatformWalletFFIError() - // Peer array: allocate contiguous C strings. let peerCStrings: [UnsafeMutablePointer?] = config.peers.map { strdup($0) } defer { peerCStrings.forEach { if let p = $0 { free(p) } } } - let result = config.dataDir.withCString { dataDirPtr -> PlatformWalletFFIResult in + try config.dataDir.withCString { dataDirPtr in let userAgentPtr = config.userAgent.flatMap { strdup($0) } defer { if let p = userAgentPtr { free(p) } } - return peerCStrings.withUnsafeBufferPointer { peersBuf in + try peerCStrings.withUnsafeBufferPointer { peersBuf in let peersPtr: UnsafePointer?>? = peersBuf.baseAddress.map { UnsafeRawPointer($0).assumingMemoryBound(to: UnsafePointer?.self) } - return platform_wallet_manager_spv_start( + try platform_wallet_manager_spv_start( handle, dataDirPtr, config.network.rawValue, @@ -194,34 +185,21 @@ extension PlatformWalletManager { UInt(peerCStrings.count), config.restrictToConfiguredPeers, config.startFromHeight, - config.masternodeSyncEnabled, - &error - ) + config.masternodeSyncEnabled + ).check() } } - - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } } /// Stop the SPV client (idempotent). public func stopSpv() throws { - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_spv_stop(handle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try platform_wallet_manager_spv_stop(handle).check() } /// Clear all persisted SPV storage (headers, filters, state). /// /// The SPV client must be running. public func clearSpvStorage() throws { - var error = PlatformWalletFFIError() - let result = platform_wallet_manager_spv_clear_storage(handle, &error) - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } + try platform_wallet_manager_spv_clear_storage(handle).check() } } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift new file mode 100644 index 00000000000..95acfcc0766 --- /dev/null +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift @@ -0,0 +1,181 @@ +import Foundation +import DashSDKFFI + +// MARK: - Typed result code + +/// Swift mirror of the Rust `PlatformWalletFfiResultCode` enum. +public enum PlatformWalletResultCode: Int32, Sendable { + case success = 0 + case errorInvalidHandle = 1 + case errorInvalidParameter = 2 + case errorNullPointer = 3 + case errorSerialization = 4 + case errorDeserialization = 5 + case errorWalletOperation = 6 + case errorIdentityNotFound = 7 + case errorContactNotFound = 8 + case errorInvalidNetwork = 9 + case errorInvalidIdentifier = 10 + case errorMemoryAllocation = 11 + case errorUtf8Conversion = 12 + case notFound = 98 + case errorUnknown = 99 + + init(ffi: PlatformWalletFfiResultCode) { + switch ffi { + case PLATFORM_WALLET_FFI_RESULT_CODE_SUCCESS: + self = .success + case PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_INVALID_HANDLE: + self = .errorInvalidHandle + case PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_INVALID_PARAMETER: + self = .errorInvalidParameter + case PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_NULL_POINTER: + self = .errorNullPointer + case PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_SERIALIZATION: + self = .errorSerialization + case PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_DESERIALIZATION: + self = .errorDeserialization + case PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_WALLET_OPERATION: + self = .errorWalletOperation + case PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_IDENTITY_NOT_FOUND: + self = .errorIdentityNotFound + case PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_CONTACT_NOT_FOUND: + self = .errorContactNotFound + case PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_INVALID_NETWORK: + self = .errorInvalidNetwork + case PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_INVALID_IDENTIFIER: + self = .errorInvalidIdentifier + case PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_MEMORY_ALLOCATION: + self = .errorMemoryAllocation + case PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_UTF8_CONVERSION: + self = .errorUtf8Conversion + case PLATFORM_WALLET_FFI_RESULT_CODE_NOT_FOUND: + self = .notFound + case PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_UNKNOWN: + self = .errorUnknown + default: + self = .errorUnknown + } + } +} + +// MARK: - Class wrapper + +/// Reference-counted wrapper around a `PlatformWalletFfiResult` +/// returned by Rust. Owns the heap-allocated `message` C string and +/// frees it through `platform_wallet_ffi_result_free` in `deinit`, +/// regardless of whether the caller threw, returned early, or simply +/// dropped the wrapper. +/// +/// Use the `try ffiResult.check()` extension below for the common +/// "throw on non-success" shape; reach for an explicit instance when +/// you want to inspect the `message` without throwing (e.g. logging +/// a warning on a soft-failure path). +final class PlatformWalletResult { + private var inner: PlatformWalletFfiResult + + init(_ ffi: PlatformWalletFfiResult) { + self.inner = ffi + } + + deinit { + platform_wallet_ffi_result_free(&inner) + } + + /// Typed result code; unknown raw values fall back to `.errorUnknown`. + var code: PlatformWalletResultCode { + PlatformWalletResultCode(ffi: inner.code) + } + + var message: String? { + inner.message.map { String(cString: $0) } + } + + var isSuccess: Bool { + code == .success + } + + func throwIfError() throws { + guard !isSuccess else { return } + throw PlatformWalletError(result: self) + } +} + +// MARK: - PlatformWalletError + +/// Platform Wallet error type. +/// +/// Built directly from a `PlatformWalletResult` via +/// `PlatformWalletError(result:)`. The wrapper owns both the typed +/// code and the Rust-supplied message, so taking it as a single +/// input avoids mismatched (code, message) pairs at the call site. +/// Most call sites just write `try ffi(...).check()` and let the +/// FFI extension do the construction. +public enum PlatformWalletError: LocalizedError { + case nullPointer(String) + case invalidHandle(String) + case invalidParameter(String) + case invalidIdentifier(String) + case invalidNetwork(String) + case walletOperation(String) + case identityNotFound(String) + case contactNotFound(String) + case utf8Conversion(String) + case serialization(String) + case deserialization(String) + case memoryAllocation(String) + case notFound(String) + case unknown(String) + + /// Diagnostic detail Rust attached to the originating + /// `PlatformWalletFfiResult`, or the context string a Swift-side + /// guard chose when constructing the error inline. + public var errorDescription: String? { + switch self { + case .nullPointer(let m), .invalidHandle(let m), .invalidParameter(let m), + .invalidIdentifier(let m), .invalidNetwork(let m), .walletOperation(let m), + .identityNotFound(let m), .contactNotFound(let m), .utf8Conversion(let m), + .serialization(let m), .deserialization(let m), .memoryAllocation(let m), + .notFound(let m), .unknown(let m): + return m + } + } + + init(result: PlatformWalletResult) { + let detail = result.message ?? "" + switch result.code { + case .success: + // Constructing an error from a success result is a caller bug + self = .unknown(result.message + ?? "PlatformWalletError built from a success result") + case .errorInvalidHandle: self = .invalidHandle(detail) + case .errorInvalidParameter: self = .invalidParameter(detail) + case .errorNullPointer: self = .nullPointer(detail) + case .errorSerialization: self = .serialization(detail) + case .errorDeserialization: self = .deserialization(detail) + case .errorWalletOperation: self = .walletOperation(detail) + case .errorIdentityNotFound: self = .identityNotFound(detail) + case .errorContactNotFound: self = .contactNotFound(detail) + case .errorInvalidNetwork: self = .invalidNetwork(detail) + case .errorInvalidIdentifier: self = .invalidIdentifier(detail) + case .errorMemoryAllocation: self = .memoryAllocation(detail) + case .errorUtf8Conversion: self = .utf8Conversion(detail) + case .notFound: self = .notFound(detail) + case .errorUnknown: self = .unknown(detail) + } + } +} + +// MARK: - Convenience extensions + +extension PlatformWalletFfiResult { + @inline(__always) + func check() throws { + try PlatformWalletResult(self).throwIfError() + } + + @inline(__always) + func discard() { + _ = PlatformWalletResult(self) + } +} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletTypes.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletTypes.swift index a2751691dfb..0d055eefb61 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletTypes.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletTypes.swift @@ -22,87 +22,6 @@ extension Identifier { typealias NetworkType = UInt32 -/// Platform Wallet error types -public enum PlatformWalletError: LocalizedError { - case nullPointer - case invalidHandle - case invalidParameter - case invalidIdentifier - case invalidNetwork - case walletOperation(String) - case identityNotFound - case contactNotFound - case utf8Conversion - case serialization(String) - case deserialization(String) - case memoryAllocation - case unknown(String) - - public var errorDescription: String? { - switch self { - case .nullPointer: return "Null pointer" - case .invalidHandle: return "Invalid handle" - case .invalidParameter: return "Invalid parameter" - case .invalidIdentifier: return "Invalid identifier" - case .invalidNetwork: return "Invalid network" - case .walletOperation(let msg): return "Wallet operation: \(msg)" - case .identityNotFound: return "Identity not found" - case .contactNotFound: return "Contact not found" - case .utf8Conversion: return "UTF-8 conversion error" - case .serialization(let msg): return "Serialization: \(msg)" - case .deserialization(let msg): return "Deserialization: \(msg)" - case .memoryAllocation: return "Memory allocation error" - case .unknown(let msg): return msg - } - } - - init(result: PlatformWalletFFIResult, error: PlatformWalletFFIError) { - // Prefer the Rust-side detail message when one was supplied — - // the payload-less enum cases below otherwise drop it on the - // floor, which makes alerts like "Null pointer" impossible to - // diagnose. When Rust gave us no message we fall back to the - // typed bare case (keeps existing behavior for callers that - // only compare on the case label). - let rustMessage: String? = error.message.map { String(cString: $0) } - - /// Promote a payload-less enum case to `.unknown(detail)` - /// when Rust supplied a message; otherwise keep the typed case. - func withDetail(_ bare: PlatformWalletError, prefix: String) -> PlatformWalletError { - guard let msg = rustMessage, !msg.isEmpty else { return bare } - return .unknown("\(prefix): \(msg)") - } - - switch result { - case PLATFORM_WALLET_FFI_RESULT_ERROR_INVALID_HANDLE: - self = withDetail(.invalidHandle, prefix: "Invalid handle") - case PLATFORM_WALLET_FFI_RESULT_ERROR_INVALID_PARAMETER: - self = withDetail(.invalidParameter, prefix: "Invalid parameter") - case PLATFORM_WALLET_FFI_RESULT_ERROR_NULL_POINTER: - self = withDetail(.nullPointer, prefix: "Null pointer") - case PLATFORM_WALLET_FFI_RESULT_ERROR_SERIALIZATION: - self = .serialization(rustMessage ?? "Unknown error") - case PLATFORM_WALLET_FFI_RESULT_ERROR_DESERIALIZATION: - self = .deserialization(rustMessage ?? "Unknown error") - case PLATFORM_WALLET_FFI_RESULT_ERROR_WALLET_OPERATION: - self = .walletOperation(rustMessage ?? "Unknown error") - case PLATFORM_WALLET_FFI_RESULT_ERROR_IDENTITY_NOT_FOUND: - self = withDetail(.identityNotFound, prefix: "Identity not found") - case PLATFORM_WALLET_FFI_RESULT_ERROR_CONTACT_NOT_FOUND: - self = withDetail(.contactNotFound, prefix: "Contact not found") - case PLATFORM_WALLET_FFI_RESULT_ERROR_INVALID_NETWORK: - self = withDetail(.invalidNetwork, prefix: "Invalid network") - case PLATFORM_WALLET_FFI_RESULT_ERROR_INVALID_IDENTIFIER: - self = withDetail(.invalidIdentifier, prefix: "Invalid identifier") - case PLATFORM_WALLET_FFI_RESULT_ERROR_MEMORY_ALLOCATION: - self = withDetail(.memoryAllocation, prefix: "Memory allocation") - case PLATFORM_WALLET_FFI_RESULT_ERROR_UTF8_CONVERSION: - self = withDetail(.utf8Conversion, prefix: "UTF-8 conversion") - default: - self = .unknown(rustMessage ?? "Unknown error") - } - } -} - /// Network type for Platform wallet public enum PlatformNetwork: UInt32 { case mainnet = 0 @@ -166,15 +85,9 @@ func identifierFromFFIArray(_ array: IdentifierArray, at index: Int) -> Identifi /// Generate a random identifier via the FFI. public func generateRandomIdentifier() throws -> Identifier { var buf = [UInt8](repeating: 0, count: 32) - var error = PlatformWalletFFIError() - - let result = buf.withUnsafeMutableBufferPointer { bp -> PlatformWalletFFIResult in - platform_wallet_generate_random_identifier(bp.baseAddress!, &error) - } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) + try buf.withUnsafeMutableBufferPointer { bp in + try platform_wallet_generate_random_identifier(bp.baseAddress!).check() } - return Data(buf) } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/Tokens/TokenActions.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/Tokens/TokenActions.swift index 1813d99e6de..90f75ea59b2 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/Tokens/TokenActions.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/Tokens/TokenActions.swift @@ -1,4 +1,5 @@ import Foundation +import DashSDKFFI // MARK: - Token distribution type @@ -97,12 +98,11 @@ extension ManagedPlatformWallet { try await Task.detached(priority: .userInitiated) { _ = signer - var error = PlatformWalletFFIError() - let result = identityBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFFIResult in - contractBytes.withUnsafeBufferPointer { contractBp in - recipientBytes.withUnsafeBufferPointer { recipientBp in - ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in - platform_wallet_token_transfer( + try identityBytes.withUnsafeBufferPointer { idBp in + try contractBytes.withUnsafeBufferPointer { contractBp in + try recipientBytes.withUnsafeBufferPointer { recipientBp in + try ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in + try platform_wallet_token_transfer( handle, idBp.baseAddress!, contractBp.baseAddress!, @@ -111,16 +111,12 @@ extension ManagedPlatformWallet { amount, notePtr, signingKeyId, - signerHandle, - &error - ) + signerHandle + ).check() } } } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } }.value } @@ -151,12 +147,11 @@ extension ManagedPlatformWallet { try await Task.detached(priority: .userInitiated) { _ = signer - var error = PlatformWalletFFIError() - let result = identityBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFFIResult in - contractBytes.withUnsafeBufferPointer { contractBp in - ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in - ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in - platform_wallet_token_burn( + try identityBytes.withUnsafeBufferPointer { idBp in + try contractBytes.withUnsafeBufferPointer { contractBp in + try ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in + try ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in + try platform_wallet_token_burn( handle, idBp.baseAddress!, contractBp.baseAddress!, @@ -168,16 +163,12 @@ extension ManagedPlatformWallet { actionIdPtr, groupTuple.actionIsProposer, signingKeyId, - signerHandle, - &error - ) + signerHandle + ).check() } } } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } }.value } @@ -207,13 +198,12 @@ extension ManagedPlatformWallet { try await Task.detached(priority: .userInitiated) { _ = signer - var error = PlatformWalletFFIError() - let result = identityBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFFIResult in - contractBytes.withUnsafeBufferPointer { contractBp in - ManagedPlatformWallet.tokenWithOptionalRecipient(recipientBytes) { recipientPtr in - ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in - ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in - platform_wallet_token_mint( + try identityBytes.withUnsafeBufferPointer { idBp in + try contractBytes.withUnsafeBufferPointer { contractBp in + try ManagedPlatformWallet.tokenWithOptionalRecipient(recipientBytes) { recipientPtr in + try ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in + try ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in + try platform_wallet_token_mint( handle, idBp.baseAddress!, contractBp.baseAddress!, @@ -226,17 +216,13 @@ extension ManagedPlatformWallet { actionIdPtr, groupTuple.actionIsProposer, signingKeyId, - signerHandle, - &error - ) + signerHandle + ).check() } } } } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } }.value } @@ -262,11 +248,10 @@ extension ManagedPlatformWallet { try await Task.detached(priority: .userInitiated) { _ = signer - var error = PlatformWalletFFIError() - let result = identityBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFFIResult in - contractBytes.withUnsafeBufferPointer { contractBp in - ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in - platform_wallet_token_claim( + try identityBytes.withUnsafeBufferPointer { idBp in + try contractBytes.withUnsafeBufferPointer { contractBp in + try ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in + try platform_wallet_token_claim( handle, idBp.baseAddress!, contractBp.baseAddress!, @@ -274,15 +259,11 @@ extension ManagedPlatformWallet { distRaw, notePtr, signingKeyId, - signerHandle, - &error - ) + signerHandle + ).check() } } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } }.value } @@ -311,13 +292,12 @@ extension ManagedPlatformWallet { try await Task.detached(priority: .userInitiated) { _ = signer - var error = PlatformWalletFFIError() - let result = identityBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFFIResult in - contractBytes.withUnsafeBufferPointer { contractBp in - frozenBytes.withUnsafeBufferPointer { frozenBp in - ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in - ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in - platform_wallet_token_freeze( + try identityBytes.withUnsafeBufferPointer { idBp in + try contractBytes.withUnsafeBufferPointer { contractBp in + try frozenBytes.withUnsafeBufferPointer { frozenBp in + try ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in + try ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in + try platform_wallet_token_freeze( handle, idBp.baseAddress!, contractBp.baseAddress!, @@ -329,17 +309,13 @@ extension ManagedPlatformWallet { actionIdPtr, groupTuple.actionIsProposer, signingKeyId, - signerHandle, - &error - ) + signerHandle + ).check() } } } } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } }.value } @@ -365,13 +341,12 @@ extension ManagedPlatformWallet { try await Task.detached(priority: .userInitiated) { _ = signer - var error = PlatformWalletFFIError() - let result = identityBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFFIResult in - contractBytes.withUnsafeBufferPointer { contractBp in - frozenBytes.withUnsafeBufferPointer { frozenBp in - ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in - ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in - platform_wallet_token_unfreeze( + try identityBytes.withUnsafeBufferPointer { idBp in + try contractBytes.withUnsafeBufferPointer { contractBp in + try frozenBytes.withUnsafeBufferPointer { frozenBp in + try ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in + try ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in + try platform_wallet_token_unfreeze( handle, idBp.baseAddress!, contractBp.baseAddress!, @@ -383,17 +358,13 @@ extension ManagedPlatformWallet { actionIdPtr, groupTuple.actionIsProposer, signingKeyId, - signerHandle, - &error - ) + signerHandle + ).check() } } } } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } }.value } @@ -420,13 +391,12 @@ extension ManagedPlatformWallet { try await Task.detached(priority: .userInitiated) { _ = signer - var error = PlatformWalletFFIError() - let result = identityBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFFIResult in - contractBytes.withUnsafeBufferPointer { contractBp in - frozenBytes.withUnsafeBufferPointer { frozenBp in - ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in - ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in - platform_wallet_token_destroy_frozen_funds( + try identityBytes.withUnsafeBufferPointer { idBp in + try contractBytes.withUnsafeBufferPointer { contractBp in + try frozenBytes.withUnsafeBufferPointer { frozenBp in + try ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in + try ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in + try platform_wallet_token_destroy_frozen_funds( handle, idBp.baseAddress!, contractBp.baseAddress!, @@ -438,17 +408,13 @@ extension ManagedPlatformWallet { actionIdPtr, groupTuple.actionIsProposer, signingKeyId, - signerHandle, - &error - ) + signerHandle + ).check() } } } } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } }.value } @@ -475,12 +441,11 @@ extension ManagedPlatformWallet { try await Task.detached(priority: .userInitiated) { _ = signer - var error = PlatformWalletFFIError() - let result = identityBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFFIResult in - contractBytes.withUnsafeBufferPointer { contractBp in - ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in - ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in - platform_wallet_token_pause( + try identityBytes.withUnsafeBufferPointer { idBp in + try contractBytes.withUnsafeBufferPointer { contractBp in + try ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in + try ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in + try platform_wallet_token_pause( handle, idBp.baseAddress!, contractBp.baseAddress!, @@ -491,16 +456,12 @@ extension ManagedPlatformWallet { actionIdPtr, groupTuple.actionIsProposer, signingKeyId, - signerHandle, - &error - ) + signerHandle + ).check() } } } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } }.value } @@ -527,12 +488,11 @@ extension ManagedPlatformWallet { try await Task.detached(priority: .userInitiated) { _ = signer - var error = PlatformWalletFFIError() - let result = identityBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFFIResult in - contractBytes.withUnsafeBufferPointer { contractBp in - ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in - ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in - platform_wallet_token_resume( + try identityBytes.withUnsafeBufferPointer { idBp in + try contractBytes.withUnsafeBufferPointer { contractBp in + try ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in + try ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in + try platform_wallet_token_resume( handle, idBp.baseAddress!, contractBp.baseAddress!, @@ -543,16 +503,12 @@ extension ManagedPlatformWallet { actionIdPtr, groupTuple.actionIsProposer, signingKeyId, - signerHandle, - &error - ) + signerHandle + ).check() } } } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } }.value } @@ -585,12 +541,11 @@ extension ManagedPlatformWallet { try await Task.detached(priority: .userInitiated) { _ = signer - var error = PlatformWalletFFIError() - let result = identityBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFFIResult in - contractBytes.withUnsafeBufferPointer { contractBp in - ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in - ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in - platform_wallet_token_set_price( + try identityBytes.withUnsafeBufferPointer { idBp in + try contractBytes.withUnsafeBufferPointer { contractBp in + try ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in + try ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in + try platform_wallet_token_set_price( handle, idBp.baseAddress!, contractBp.baseAddress!, @@ -602,16 +557,12 @@ extension ManagedPlatformWallet { actionIdPtr, groupTuple.actionIsProposer, signingKeyId, - signerHandle, - &error - ) + signerHandle + ).check() } } } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } }.value } @@ -644,10 +595,9 @@ extension ManagedPlatformWallet { try await Task.detached(priority: .userInitiated) { _ = signer - var error = PlatformWalletFFIError() - let result = identityBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFFIResult in - contractBytes.withUnsafeBufferPointer { contractBp in - platform_wallet_token_purchase( + try identityBytes.withUnsafeBufferPointer { idBp in + try contractBytes.withUnsafeBufferPointer { contractBp in + try platform_wallet_token_purchase( handle, idBp.baseAddress!, contractBp.baseAddress!, @@ -655,14 +605,10 @@ extension ManagedPlatformWallet { amount, expectedTotalCost, signingKeyId, - signerHandle, - &error - ) + signerHandle + ).check() } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } }.value } @@ -696,13 +642,12 @@ extension ManagedPlatformWallet { try await Task.detached(priority: .userInitiated) { _ = signer - var error = PlatformWalletFFIError() - let result = identityBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFFIResult in - contractBytes.withUnsafeBufferPointer { contractBp in - payload.withCString { payloadPtr in - ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in - ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in - platform_wallet_token_update_config( + try identityBytes.withUnsafeBufferPointer { idBp in + try contractBytes.withUnsafeBufferPointer { contractBp in + try payload.withCString { payloadPtr in + try ManagedPlatformWallet.tokenWithOptionalCString(publicNote) { notePtr in + try ManagedPlatformWallet.tokenWithOptionalActionId(groupTuple.actionId) { actionIdPtr in + try platform_wallet_token_update_config( handle, idBp.baseAddress!, contractBp.baseAddress!, @@ -715,17 +660,13 @@ extension ManagedPlatformWallet { actionIdPtr, groupTuple.actionIsProposer, signingKeyId, - signerHandle, - &error - ) + signerHandle + ).check() } } } } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } }.value } @@ -736,12 +677,12 @@ extension ManagedPlatformWallet { /// `withOptionalCString`. fileprivate static func tokenWithOptionalCString( _ value: String?, - _ body: (UnsafePointer?) -> R - ) -> R { + _ body: (UnsafePointer?) throws -> R + ) rethrows -> R { if let value = value { - return value.withCString { body($0) } + return try value.withCString { try body($0) } } - return body(nil) + return try body(nil) } /// Run `body` with a `*const u8` to the 32-byte action id, or @@ -749,13 +690,13 @@ extension ManagedPlatformWallet { /// modes). fileprivate static func tokenWithOptionalActionId( _ value: Data?, - _ body: (UnsafePointer?) -> R - ) -> R { + _ body: (UnsafePointer?) throws -> R + ) rethrows -> R { guard let value = value else { - return body(nil) + return try body(nil) } - return value.withUnsafeBytes { raw in - body(raw.bindMemory(to: UInt8.self).baseAddress) + return try value.withUnsafeBytes { raw in + try body(raw.bindMemory(to: UInt8.self).baseAddress) } } @@ -764,13 +705,13 @@ extension ManagedPlatformWallet { /// (mint-to-self). fileprivate static func tokenWithOptionalRecipient( _ value: [UInt8]?, - _ body: (UnsafePointer?) -> R - ) -> R { + _ body: (UnsafePointer?) throws -> R + ) rethrows -> R { guard let value = value else { - return body(nil) + return try body(nil) } - return value.withUnsafeBufferPointer { bp in - body(bp.baseAddress) + return try value.withUnsafeBufferPointer { bp in + try body(bp.baseAddress) } } } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/Tokens/TokenGroupActionQueries.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/Tokens/TokenGroupActionQueries.swift index c1f718ce1fa..52d00c774b8 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/Tokens/TokenGroupActionQueries.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/Tokens/TokenGroupActionQueries.swift @@ -1,4 +1,5 @@ import Foundation +import DashSDKFFI // MARK: - Public types @@ -301,25 +302,20 @@ extension ManagedPlatformWallet { let statusRaw = status.rawValue return try await Task.detached(priority: .userInitiated) { - var error = PlatformWalletFFIError() var jsonOut: UnsafeMutablePointer? = nil - let result = contractBytes.withUnsafeBufferPointer { contractBp -> PlatformWalletFFIResult in - ManagedPlatformWallet.tokenWithOptionalActionIdBytes(startBytes) { startPtr in - platform_wallet_token_pending_group_actions( + try contractBytes.withUnsafeBufferPointer { contractBp in + try ManagedPlatformWallet.tokenWithOptionalActionIdBytes(startBytes) { startPtr in + try platform_wallet_token_pending_group_actions( handle, contractBp.baseAddress!, groupContractPosition, statusRaw, startPtr, limit, - &jsonOut, - &error - ) + &jsonOut + ).check() } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } return try ManagedPlatformWallet.consumeJSONArray(jsonOut) }.value } @@ -338,24 +334,19 @@ extension ManagedPlatformWallet { let statusRaw = status.rawValue return try await Task.detached(priority: .userInitiated) { - var error = PlatformWalletFFIError() var jsonOut: UnsafeMutablePointer? = nil - let result = contractBytes.withUnsafeBufferPointer { contractBp -> PlatformWalletFFIResult in - actionBytes.withUnsafeBufferPointer { actionBp in - platform_wallet_token_group_action_signers( + try contractBytes.withUnsafeBufferPointer { contractBp in + try actionBytes.withUnsafeBufferPointer { actionBp in + try platform_wallet_token_group_action_signers( handle, contractBp.baseAddress!, groupContractPosition, statusRaw, actionBp.baseAddress!, - &jsonOut, - &error - ) + &jsonOut + ).check() } } - guard result == PLATFORM_WALLET_FFI_RESULT_SUCCESS else { - throw PlatformWalletError(result: result, error: error) - } return try ManagedPlatformWallet.consumeJSONArray(jsonOut) }.value } @@ -366,13 +357,13 @@ extension ManagedPlatformWallet { /// when no action id is present (initial page of pending actions). fileprivate static func tokenWithOptionalActionIdBytes( _ value: [UInt8]?, - _ body: (UnsafePointer?) -> R - ) -> R { + _ body: (UnsafePointer?) throws -> R + ) rethrows -> R { guard let value = value else { - return body(nil) + return try body(nil) } - return value.withUnsafeBufferPointer { bp in - body(bp.baseAddress) + return try value.withUnsafeBufferPointer { bp in + try body(bp.baseAddress) } } From d245dde7452f58fc3217c8a9d6ee926b13dd6260 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 1 May 2026 10:49:34 +0800 Subject: [PATCH 2/2] style: keep FFI in caps for PlatformWalletFFIResult types Rename PlatformWalletFfiResult/PlatformWalletFfiResultCode back to PlatformWalletFFIResult/PlatformWalletFFIResultCode to maintain the existing FFI-uppercase convention used elsewhere in the workspace. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/asset_lock/build.rs | 8 +- .../src/asset_lock/manager.rs | 8 +- .../src/asset_lock/sync.rs | 8 +- .../rs-platform-wallet-ffi/src/contact.rs | 40 ++--- .../src/contact_request.rs | 62 ++++---- .../src/core_wallet/addresses.rs | 8 +- .../src/core_wallet/broadcast.rs | 12 +- .../src/core_wallet/wallet.rs | 12 +- .../rs-platform-wallet-ffi/src/dashpay.rs | 28 ++-- .../src/dashpay_profile.rs | 24 +-- .../src/data_contract.rs | 6 +- .../rs-platform-wallet-ffi/src/derivation.rs | 8 +- .../src/derive_identity_key_at_slot.rs | 28 ++-- packages/rs-platform-wallet-ffi/src/dpns.rs | 40 ++--- packages/rs-platform-wallet-ffi/src/error.rs | 138 +++++++++--------- .../src/established_contact.rs | 58 ++++---- .../src/identity_derive_and_persist.rs | 48 +++--- .../src/identity_discovery.rs | 6 +- .../src/identity_key_preview.rs | 12 +- .../src/identity_keys_from_mnemonic.rs | 30 ++-- .../src/identity_manager.rs | 44 +++--- ...dentity_registration_funded_with_signer.rs | 12 +- .../src/identity_registration_with_signer.rs | 70 ++++----- .../src/identity_sync.rs | 58 ++++---- .../src/identity_top_up.rs | 12 +- .../src/identity_transfer.rs | 16 +- .../src/identity_update.rs | 8 +- .../src/identity_withdrawal.rs | 8 +- .../src/managed_identity.rs | 50 +++---- .../rs-platform-wallet-ffi/src/manager.rs | 40 ++--- .../src/memory_explorer.rs | 20 +-- .../src/platform_address_sync.rs | 32 ++-- .../fund_from_asset_lock.rs | 4 +- .../src/platform_addresses/mod.rs | 12 +- .../src/platform_addresses/sync.rs | 4 +- .../src/platform_addresses/transfer.rs | 4 +- .../src/platform_addresses/wallet.rs | 20 +-- .../src/platform_addresses/withdrawal.rs | 4 +- .../src/platform_wallet_info.rs | 42 +++--- packages/rs-platform-wallet-ffi/src/spv.rs | 24 +-- .../rs-platform-wallet-ffi/src/tokens/burn.rs | 4 +- .../src/tokens/claim.rs | 8 +- .../src/tokens/destroy_frozen_funds.rs | 4 +- .../src/tokens/freeze.rs | 4 +- .../src/tokens/group_info.rs | 14 +- .../src/tokens/group_queries.rs | 14 +- .../rs-platform-wallet-ffi/src/tokens/mint.rs | 4 +- .../src/tokens/pause.rs | 4 +- .../src/tokens/purchase.rs | 4 +- .../src/tokens/resume.rs | 4 +- .../src/tokens/set_price.rs | 4 +- .../src/tokens/transfer.rs | 4 +- .../src/tokens/unfreeze.rs | 4 +- .../src/tokens/update_config.rs | 34 ++--- packages/rs-platform-wallet-ffi/src/utils.rs | 30 ++-- packages/rs-platform-wallet-ffi/src/wallet.rs | 38 ++--- .../rs-platform-wallet-ffi/src/xpub_render.rs | 10 +- .../tests/comprehensive_tests.rs | 104 ++++++------- .../tests/integration_tests.rs | 50 +++---- .../PlatformWallet/ManagedIdentity.swift | 2 +- .../ManagedPlatformWallet.swift | 48 +++--- .../PlatformWalletManager.swift | 2 +- .../PlatformWallet/PlatformWalletResult.swift | 14 +- 63 files changed, 738 insertions(+), 738 deletions(-) diff --git a/packages/rs-platform-wallet-ffi/src/asset_lock/build.rs b/packages/rs-platform-wallet-ffi/src/asset_lock/build.rs index 3174a04cffc..1bb8c5fe286 100644 --- a/packages/rs-platform-wallet-ffi/src/asset_lock/build.rs +++ b/packages/rs-platform-wallet-ffi/src/asset_lock/build.rs @@ -22,7 +22,7 @@ pub unsafe extern "C" fn asset_lock_manager_build_transaction( out_tx_bytes: *mut *mut u8, out_tx_len: *mut usize, out_private_key: *mut [u8; 32], -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_tx_bytes); check_ptr!(out_tx_len); check_ptr!(out_private_key); @@ -49,7 +49,7 @@ pub unsafe extern "C" fn asset_lock_manager_build_transaction( *out_tx_len = len; *out_private_key = key.inner.secret_bytes(); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Build, broadcast, and wait for an asset lock proof. @@ -72,7 +72,7 @@ pub unsafe extern "C" fn asset_lock_manager_create_funded_proof( out_proof_len: *mut usize, out_private_key: *mut [u8; 32], out_txid: *mut [u8; 32], -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_proof_bytes); check_ptr!(out_proof_len); check_ptr!(out_private_key); @@ -105,7 +105,7 @@ pub unsafe extern "C" fn asset_lock_manager_create_funded_proof( txid_bytes.copy_from_slice(&out_point.txid[..]); *out_txid = txid_bytes; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Free transaction bytes. diff --git a/packages/rs-platform-wallet-ffi/src/asset_lock/manager.rs b/packages/rs-platform-wallet-ffi/src/asset_lock/manager.rs index 746f04890ab..1a031d9db46 100644 --- a/packages/rs-platform-wallet-ffi/src/asset_lock/manager.rs +++ b/packages/rs-platform-wallet-ffi/src/asset_lock/manager.rs @@ -29,9 +29,9 @@ pub struct TrackedAssetLockFFI { /// Destroy an AssetLockManager handle. #[no_mangle] -pub unsafe extern "C" fn asset_lock_manager_destroy(handle: Handle) -> PlatformWalletFfiResult { +pub unsafe extern "C" fn asset_lock_manager_destroy(handle: Handle) -> PlatformWalletFFIResult { ASSET_LOCK_MANAGER_STORAGE.remove(handle); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// List all tracked asset locks. @@ -43,7 +43,7 @@ pub unsafe extern "C" fn asset_lock_manager_list_tracked_locks( handle: Handle, out_locks: *mut *mut TrackedAssetLockFFI, out_count: *mut usize, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_locks); check_ptr!(out_count); @@ -90,7 +90,7 @@ pub unsafe extern "C" fn asset_lock_manager_list_tracked_locks( } else { *out_locks = Box::into_raw(entries.into_boxed_slice()) as *mut TrackedAssetLockFFI; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Free tracked locks array. diff --git a/packages/rs-platform-wallet-ffi/src/asset_lock/sync.rs b/packages/rs-platform-wallet-ffi/src/asset_lock/sync.rs index 2c855118868..9ecc4069ed1 100644 --- a/packages/rs-platform-wallet-ffi/src/asset_lock/sync.rs +++ b/packages/rs-platform-wallet-ffi/src/asset_lock/sync.rs @@ -31,7 +31,7 @@ pub unsafe extern "C" fn asset_lock_manager_resume( out_proof_bytes: *mut *mut u8, out_proof_len: *mut usize, out_private_key: *mut [u8; 32], -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(txid); check_ptr!(out_proof_bytes); check_ptr!(out_proof_len); @@ -54,7 +54,7 @@ pub unsafe extern "C" fn asset_lock_manager_resume( *out_proof_bytes = Box::into_raw(boxed) as *mut u8; *out_proof_len = len; *out_private_key = key.inner.secret_bytes(); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Recover a tracked asset lock from a serialized transaction. @@ -76,7 +76,7 @@ pub unsafe extern "C" fn asset_lock_manager_recover( vout: u32, proof_bytes: *const u8, proof_len: usize, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(tx_bytes); check_ptr!(txid); @@ -113,5 +113,5 @@ pub unsafe extern "C" fn asset_lock_manager_recover( ); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/contact.rs b/packages/rs-platform-wallet-ffi/src/contact.rs index c969a8973f1..d0553068fee 100644 --- a/packages/rs-platform-wallet-ffi/src/contact.rs +++ b/packages/rs-platform-wallet-ffi/src/contact.rs @@ -10,7 +10,7 @@ use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; pub unsafe extern "C" fn managed_identity_get_sent_contact_request_ids( identity_handle: Handle, out_array: *mut IdentifierArray, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_array); let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { @@ -22,7 +22,7 @@ pub unsafe extern "C" fn managed_identity_get_sent_contact_request_ids( }); let ids = unwrap_option_or_return!(option); unsafe { *out_array = IdentifierArray::new(ids) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get all incoming contact request IDs @@ -30,7 +30,7 @@ pub unsafe extern "C" fn managed_identity_get_sent_contact_request_ids( pub unsafe extern "C" fn managed_identity_get_incoming_contact_request_ids( identity_handle: Handle, out_array: *mut IdentifierArray, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_array); let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { @@ -42,7 +42,7 @@ pub unsafe extern "C" fn managed_identity_get_incoming_contact_request_ids( }); let ids = unwrap_option_or_return!(option); unsafe { *out_array = IdentifierArray::new(ids) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get all established contact IDs @@ -50,7 +50,7 @@ pub unsafe extern "C" fn managed_identity_get_incoming_contact_request_ids( pub unsafe extern "C" fn managed_identity_get_established_contact_ids( identity_handle: Handle, out_array: *mut IdentifierArray, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_array); let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { @@ -62,7 +62,7 @@ pub unsafe extern "C" fn managed_identity_get_established_contact_ids( }); let ids = unwrap_option_or_return!(option); unsafe { *out_array = IdentifierArray::new(ids) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Check if a contact is established. `contact_id` is a `*const u8` @@ -72,7 +72,7 @@ pub unsafe extern "C" fn managed_identity_is_contact_established( identity_handle: Handle, contact_id: *const u8, out_is_established: *mut bool, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_is_established); let id = unwrap_result_or_return!(unsafe { read_identifier(contact_id) }); @@ -81,7 +81,7 @@ pub unsafe extern "C" fn managed_identity_is_contact_established( identity.established_contacts.contains_key(&id) }); *out_is_established = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Send a contact request from this identity to another @@ -91,7 +91,7 @@ pub unsafe extern "C" fn managed_identity_is_contact_established( pub unsafe extern "C" fn managed_identity_send_contact_request( identity_handle: Handle, request_handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let request_result = CONTACT_REQUEST_STORAGE.with_item(request_handle, |req| req.clone()); let request = unwrap_option_or_return!(request_result); @@ -100,7 +100,7 @@ pub unsafe extern "C" fn managed_identity_send_contact_request( identity.add_sent_contact_request(request, &ffi_noop_persister()); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Accept an incoming contact request @@ -110,7 +110,7 @@ pub unsafe extern "C" fn managed_identity_send_contact_request( pub unsafe extern "C" fn managed_identity_accept_contact_request( identity_handle: Handle, request_handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let request_result = CONTACT_REQUEST_STORAGE.with_item(request_handle, |req| req.clone()); let request = unwrap_option_or_return!(request_result); @@ -119,7 +119,7 @@ pub unsafe extern "C" fn managed_identity_accept_contact_request( identity.add_incoming_contact_request(request, &ffi_noop_persister()); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Reject an incoming contact request @@ -128,7 +128,7 @@ pub unsafe extern "C" fn managed_identity_accept_contact_request( pub unsafe extern "C" fn managed_identity_reject_contact_request( identity_handle: Handle, sender_id: *const u8, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let id = unwrap_result_or_return!(unsafe { read_identifier(sender_id) }); let option = MANAGED_IDENTITY_STORAGE.with_item_mut(identity_handle, |identity| { @@ -136,10 +136,10 @@ pub unsafe extern "C" fn managed_identity_reject_contact_request( }); let removed = unwrap_option_or_return!(option); if removed { - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } else { - PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorContactNotFound, + PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorContactNotFound, "Contact request not found", ) } @@ -195,7 +195,7 @@ mod tests { }; let result = managed_identity_get_sent_contact_request_ids(handle, &mut array); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(array.count, 0); // Should be empty for new identity // Cleanup @@ -217,7 +217,7 @@ mod tests { }; let result = managed_identity_get_incoming_contact_request_ids(handle, &mut array); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(array.count, 0); // Cleanup @@ -239,7 +239,7 @@ mod tests { }; let result = managed_identity_get_established_contact_ids(handle, &mut array); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(array.count, 0); // Cleanup @@ -264,7 +264,7 @@ mod tests { id_bytes.as_ptr(), &mut is_established, ); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert!(!is_established); // Cleanup diff --git a/packages/rs-platform-wallet-ffi/src/contact_request.rs b/packages/rs-platform-wallet-ffi/src/contact_request.rs index 7dc4e3e3c7d..eb70298acc5 100644 --- a/packages/rs-platform-wallet-ffi/src/contact_request.rs +++ b/packages/rs-platform-wallet-ffi/src/contact_request.rs @@ -26,7 +26,7 @@ pub unsafe extern "C" fn contact_request_create( core_height_created_at: u32, created_at: u64, out_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(encrypted_public_key_bytes); check_ptr!(out_handle); @@ -51,7 +51,7 @@ pub unsafe extern "C" fn contact_request_create( let handle = CONTACT_REQUEST_STORAGE.insert(contact_request); unsafe { *out_handle = handle }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Create a contact request handle from a managed identity's sent request @@ -60,7 +60,7 @@ pub unsafe extern "C" fn managed_identity_get_sent_contact_request( identity_handle: Handle, recipient_id: *const u8, out_request_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_request_handle); let id = unwrap_result_or_return!(unsafe { read_identifier(recipient_id) }); @@ -72,7 +72,7 @@ pub unsafe extern "C" fn managed_identity_get_sent_contact_request( let request = unwrap_option_or_return!(option); unsafe { *out_request_handle = CONTACT_REQUEST_STORAGE.insert(request) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Create a contact request handle from a managed identity's incoming request @@ -81,7 +81,7 @@ pub unsafe extern "C" fn managed_identity_get_incoming_contact_request( identity_handle: Handle, sender_id: *const u8, out_request_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_request_handle); let id = unwrap_result_or_return!(unsafe { read_identifier(sender_id) }); @@ -92,7 +92,7 @@ pub unsafe extern "C" fn managed_identity_get_incoming_contact_request( let inner = unwrap_option_or_return!(option); let request = unwrap_option_or_return!(inner); unsafe { *out_request_handle = CONTACT_REQUEST_STORAGE.insert(request) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get sender ID from contact request into a 32-byte out-buffer. @@ -100,13 +100,13 @@ pub unsafe extern "C" fn managed_identity_get_incoming_contact_request( pub unsafe extern "C" fn contact_request_get_sender_id( request_handle: Handle, out_id: *mut u8, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_id); let option = CONTACT_REQUEST_STORAGE.with_item(request_handle, |request| request.sender_id); let id = unwrap_option_or_return!(option); unsafe { write_identifier(out_id, &id) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get recipient ID from contact request into a 32-byte out-buffer. @@ -114,13 +114,13 @@ pub unsafe extern "C" fn contact_request_get_sender_id( pub unsafe extern "C" fn contact_request_get_recipient_id( request_handle: Handle, out_id: *mut u8, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_id); let option = CONTACT_REQUEST_STORAGE.with_item(request_handle, |request| request.recipient_id); let id = unwrap_option_or_return!(option); unsafe { write_identifier(out_id, &id) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get sender key index from contact request @@ -128,13 +128,13 @@ pub unsafe extern "C" fn contact_request_get_recipient_id( pub unsafe extern "C" fn contact_request_get_sender_key_index( request_handle: Handle, out_index: *mut u32, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_index); let option = CONTACT_REQUEST_STORAGE.with_item(request_handle, |request| request.sender_key_index); *out_index = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get recipient key index from contact request @@ -142,13 +142,13 @@ pub unsafe extern "C" fn contact_request_get_sender_key_index( pub unsafe extern "C" fn contact_request_get_recipient_key_index( request_handle: Handle, out_index: *mut u32, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_index); let option = CONTACT_REQUEST_STORAGE.with_item(request_handle, |request| request.recipient_key_index); *out_index = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get account reference from contact request @@ -156,13 +156,13 @@ pub unsafe extern "C" fn contact_request_get_recipient_key_index( pub unsafe extern "C" fn contact_request_get_account_reference( request_handle: Handle, out_account_ref: *mut u32, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_account_ref); let option = CONTACT_REQUEST_STORAGE.with_item(request_handle, |request| request.account_reference); *out_account_ref = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get encrypted public key from contact request @@ -171,7 +171,7 @@ pub unsafe extern "C" fn contact_request_get_encrypted_public_key( request_handle: Handle, out_bytes: *mut *mut u8, out_len: *mut usize, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_bytes); check_ptr!(out_len); @@ -185,7 +185,7 @@ pub unsafe extern "C" fn contact_request_get_encrypted_public_key( *out_bytes = ptr; *out_len = len; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get creation timestamp from contact request @@ -193,22 +193,22 @@ pub unsafe extern "C" fn contact_request_get_encrypted_public_key( pub unsafe extern "C" fn contact_request_get_created_at( request_handle: Handle, out_timestamp: *mut u64, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_timestamp); let option = CONTACT_REQUEST_STORAGE.with_item(request_handle, |request| request.created_at); *out_timestamp = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Destroy contact request handle #[no_mangle] -pub extern "C" fn contact_request_destroy(request_handle: Handle) -> PlatformWalletFfiResult { +pub extern "C" fn contact_request_destroy(request_handle: Handle) -> PlatformWalletFFIResult { if CONTACT_REQUEST_STORAGE.remove(request_handle).is_some() { - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } else { - PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidHandle, + PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidHandle, "Invalid contact request handle", ) } @@ -242,43 +242,43 @@ mod tests { // Test sender ID let mut out_id = [0u8; 32]; let result = contact_request_get_sender_id(handle, out_id.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(out_id, [1u8; 32]); // Test recipient ID let result = contact_request_get_recipient_id(handle, out_id.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(out_id, [2u8; 32]); // Test sender key index let mut sender_key_idx = 0u32; let result = contact_request_get_sender_key_index(handle, &mut sender_key_idx); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(sender_key_idx, 0); // Test recipient key index let mut recipient_key_idx = 0u32; let result = contact_request_get_recipient_key_index(handle, &mut recipient_key_idx); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(recipient_key_idx, 1); // Test account reference let mut account_ref = 0u32; let result = contact_request_get_account_reference(handle, &mut account_ref); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(account_ref, 42); // Test created_at let mut created_at = 0u64; let result = contact_request_get_created_at(handle, &mut created_at); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(created_at, 1_700_000_000); // Test encrypted public key let mut bytes_ptr: *mut u8 = std::ptr::null_mut(); let mut len: usize = 0; let result = contact_request_get_encrypted_public_key(handle, &mut bytes_ptr, &mut len); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(len, 96); assert!(!bytes_ptr.is_null()); 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 318a6daa6eb..ccd16e52798 100644 --- a/packages/rs-platform-wallet-ffi/src/core_wallet/addresses.rs +++ b/packages/rs-platform-wallet-ffi/src/core_wallet/addresses.rs @@ -16,7 +16,7 @@ pub unsafe extern "C" fn core_wallet_next_receive_address( handle: Handle, account_index: u32, out_address: *mut *mut c_char, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_address); let option = CORE_WALLET_STORAGE.with_item(handle, |wallet| { @@ -26,7 +26,7 @@ pub unsafe extern "C" fn core_wallet_next_receive_address( let addr = unwrap_result_or_return!(result); let c_str = unwrap_result_or_return!(CString::new(addr.to_string())); *out_address = c_str.into_raw(); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get the next unused BIP-44 internal (change) address for a specific account. @@ -38,7 +38,7 @@ pub unsafe extern "C" fn core_wallet_next_change_address( handle: Handle, account_index: u32, out_address: *mut *mut c_char, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_address); let option = CORE_WALLET_STORAGE.with_item(handle, |wallet| { @@ -48,7 +48,7 @@ pub unsafe extern "C" fn core_wallet_next_change_address( let addr = unwrap_result_or_return!(result); let c_str = unwrap_result_or_return!(CString::new(addr.to_string())); *out_address = c_str.into_raw(); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Free an address string returned by `core_wallet_next_receive_address` 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 9a6af8467cf..8a2cafa5912 100644 --- a/packages/rs-platform-wallet-ffi/src/core_wallet/broadcast.rs +++ b/packages/rs-platform-wallet-ffi/src/core_wallet/broadcast.rs @@ -14,7 +14,7 @@ pub unsafe extern "C" fn core_wallet_broadcast_transaction( tx_bytes: *const u8, tx_bytes_len: usize, out_txid: *mut *mut c_char, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(tx_bytes); check_ptr!(out_txid); @@ -29,7 +29,7 @@ pub unsafe extern "C" fn core_wallet_broadcast_transaction( 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() + PlatformWalletFFIResult::ok() } /// Build, sign, and broadcast a payment to the given addresses. @@ -44,7 +44,7 @@ pub unsafe extern "C" fn core_wallet_send_to_addresses( count: usize, out_tx_bytes: *mut *mut u8, out_tx_len: *mut usize, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { if count > 0 { check_ptr!(addresses); check_ptr!(amounts); @@ -69,8 +69,8 @@ pub unsafe extern "C" fn core_wallet_send_to_addresses( 0 => StandardAccountType::BIP44Account, 1 => StandardAccountType::BIP32Account, _ => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, format!("Unknown account type: {account_type}"), ); } @@ -86,7 +86,7 @@ pub unsafe extern "C" fn core_wallet_send_to_addresses( let boxed = serialized.into_boxed_slice(); *out_tx_bytes = Box::into_raw(boxed) as *mut u8; *out_tx_len = len; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Free transaction bytes returned by `core_wallet_send_to_addresses`. diff --git a/packages/rs-platform-wallet-ffi/src/core_wallet/wallet.rs b/packages/rs-platform-wallet-ffi/src/core_wallet/wallet.rs index 8044e40229b..cf58ee4b63d 100644 --- a/packages/rs-platform-wallet-ffi/src/core_wallet/wallet.rs +++ b/packages/rs-platform-wallet-ffi/src/core_wallet/wallet.rs @@ -6,9 +6,9 @@ use crate::{check_ptr, unwrap_option_or_return}; /// Destroy a CoreWallet handle. #[no_mangle] -pub unsafe extern "C" fn core_wallet_destroy(handle: Handle) -> PlatformWalletFfiResult { +pub unsafe extern "C" fn core_wallet_destroy(handle: Handle) -> PlatformWalletFFIResult { CORE_WALLET_STORAGE.remove(handle); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get lock-free balance (spendable, unconfirmed, immature, locked). @@ -21,7 +21,7 @@ pub unsafe extern "C" fn core_wallet_get_balance( out_unconfirmed: *mut u64, out_immature: *mut u64, out_locked: *mut u64, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = CORE_WALLET_STORAGE.with_item(handle, |wallet| { let b = wallet.balance(); (b.confirmed(), b.unconfirmed(), b.immature(), b.locked()) @@ -40,7 +40,7 @@ pub unsafe extern "C" fn core_wallet_get_balance( if !out_locked.is_null() { *out_locked = locked; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get the network this wallet operates on. @@ -50,7 +50,7 @@ pub unsafe extern "C" fn core_wallet_get_balance( pub unsafe extern "C" fn core_wallet_get_network( handle: Handle, out_network: *mut u32, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_network); let option = CORE_WALLET_STORAGE.with_item(handle, |wallet| match wallet.network() { @@ -60,5 +60,5 @@ pub unsafe extern "C" fn core_wallet_get_network( key_wallet::Network::Regtest => 3, }); *out_network = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/dashpay.rs b/packages/rs-platform-wallet-ffi/src/dashpay.rs index fd6d120aa88..5ab9f0ed543 100644 --- a/packages/rs-platform-wallet-ffi/src/dashpay.rs +++ b/packages/rs-platform-wallet-ffi/src/dashpay.rs @@ -69,7 +69,7 @@ pub unsafe extern "C" fn platform_wallet_get_managed_identity( wallet_handle: Handle, identity_id: *const u8, out_managed_identity_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_managed_identity_handle); let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); @@ -81,7 +81,7 @@ pub unsafe extern "C" fn platform_wallet_get_managed_identity( let inner = unwrap_option_or_return!(option); let managed = unwrap_option_or_return!(inner); unsafe { *out_managed_identity_handle = MANAGED_IDENTITY_STORAGE.insert(managed) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } // --------------------------------------------------------------------------- @@ -170,7 +170,7 @@ pub unsafe extern "C" fn platform_wallet_contact_request_handle_array_free( pub unsafe extern "C" fn platform_wallet_sync_contact_requests( wallet_handle: Handle, out_array: *mut ContactRequestHandleArray, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_array); let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { @@ -180,7 +180,7 @@ pub unsafe extern "C" fn platform_wallet_sync_contact_requests( let result = unwrap_option_or_return!(option); let list = unwrap_result_or_return!(result); unsafe { *out_array = ContactRequestHandleArray::from_requests(list) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } // --------------------------------------------------------------------------- @@ -221,7 +221,7 @@ pub unsafe extern "C" fn platform_wallet_send_contact_request_with_signer( auto_accept_proof_len: usize, signer_handle: *mut SignerHandle, out_request_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_request_handle); check_ptr!(signer_handle); @@ -254,7 +254,7 @@ pub unsafe extern "C" fn platform_wallet_send_contact_request_with_signer( let result = unwrap_option_or_return!(option); let request = unwrap_result_or_return!(result); *out_request_handle = CONTACT_REQUEST_STORAGE.insert(request); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Accept an incoming contact request using an externally-supplied @@ -276,7 +276,7 @@ pub unsafe extern "C" fn platform_wallet_accept_contact_request_with_signer( request_handle: Handle, signer_handle: *mut SignerHandle, out_established_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_established_handle); check_ptr!(signer_handle); @@ -297,7 +297,7 @@ pub unsafe extern "C" fn platform_wallet_accept_contact_request_with_signer( let result = unwrap_option_or_return!(option); let contact = unwrap_result_or_return!(result); *out_established_handle = ESTABLISHED_CONTACT_STORAGE.insert(contact); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } // --------------------------------------------------------------------------- @@ -315,7 +315,7 @@ pub unsafe extern "C" fn platform_wallet_reject_contact_request( wallet_handle: Handle, our_identity_id: *const u8, contact_identity_id: *const u8, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let our_id = unwrap_result_or_return!(unsafe { read_identifier(our_identity_id) }); let contact_id = unwrap_result_or_return!(unsafe { read_identifier(contact_identity_id) }); @@ -325,7 +325,7 @@ pub unsafe extern "C" fn platform_wallet_reject_contact_request( }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } // --------------------------------------------------------------------------- @@ -338,7 +338,7 @@ pub unsafe extern "C" fn platform_wallet_fetch_sent_contact_requests( wallet_handle: Handle, identity_id: *const u8, out_array: *mut ContactRequestHandleArray, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_array); let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); @@ -349,7 +349,7 @@ pub unsafe extern "C" fn platform_wallet_fetch_sent_contact_requests( let result = unwrap_option_or_return!(option); let list = unwrap_result_or_return!(result); unsafe { *out_array = ContactRequestHandleArray::from_requests(list) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } // --------------------------------------------------------------------------- @@ -365,7 +365,7 @@ pub unsafe extern "C" fn platform_wallet_send_dashpay_payment( amount_duffs: u64, memo: *const c_char, out_txid: *mut [u8; 32], -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_txid); let from_id = unwrap_result_or_return!(unsafe { read_identifier(from_identity_id) }); @@ -390,5 +390,5 @@ pub unsafe extern "C" fn platform_wallet_send_dashpay_payment( unsafe { std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_txid.cast::(), 32); } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/dashpay_profile.rs b/packages/rs-platform-wallet-ffi/src/dashpay_profile.rs index c7c4facf6d6..43534e966d1 100644 --- a/packages/rs-platform-wallet-ffi/src/dashpay_profile.rs +++ b/packages/rs-platform-wallet-ffi/src/dashpay_profile.rs @@ -75,7 +75,7 @@ fn option_string_to_c(s: Option<&str>) -> *mut c_char { } } -unsafe fn decode_opt_c_str(ptr: *const c_char) -> Result, PlatformWalletFfiResult> { +unsafe fn decode_opt_c_str(ptr: *const c_char) -> Result, PlatformWalletFFIResult> { if ptr.is_null() { return Ok(None); } @@ -89,7 +89,7 @@ pub unsafe extern "C" fn managed_identity_get_dashpay_profile( identity_handle: Handle, out_profile: *mut DashPayProfileFFI, out_has_profile: *mut bool, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_profile); check_ptr!(out_has_profile); @@ -106,7 +106,7 @@ pub unsafe extern "C" fn managed_identity_get_dashpay_profile( *out_has_profile = false; }, } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Read the cached DashPay profile for a specific identity owned by a wallet. @@ -116,7 +116,7 @@ pub unsafe extern "C" fn platform_wallet_get_dashpay_profile( identity_id: *const u8, out_profile: *mut DashPayProfileFFI, out_has_profile: *mut bool, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_profile); check_ptr!(out_has_profile); @@ -139,7 +139,7 @@ pub unsafe extern "C" fn platform_wallet_get_dashpay_profile( *out_has_profile = false; }, } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Release strings owned by a [`DashPayProfileFFI`]. @@ -163,7 +163,7 @@ pub unsafe extern "C" fn dashpay_profile_ffi_free(profile: *mut DashPayProfileFF pub unsafe extern "C" fn platform_wallet_sync_dashpay_profiles( wallet_handle: Handle, out_synced_count: *mut u32, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { let identity = wallet.identity().clone(); block_on_worker(async move { identity.sync_profiles().await }) @@ -173,7 +173,7 @@ pub unsafe extern "C" fn platform_wallet_sync_dashpay_profiles( if !out_synced_count.is_null() { unsafe { *out_synced_count = count }; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Create or update a DashPay profile using an externally-supplied signer. @@ -190,7 +190,7 @@ pub unsafe extern "C" fn platform_wallet_create_or_update_dashpay_profile_with_s do_create: bool, signer_handle: *mut SignerHandle, out_profile: *mut DashPayProfileFFI, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_profile); check_ptr!(signer_handle); @@ -233,7 +233,7 @@ pub unsafe extern "C" fn platform_wallet_create_or_update_dashpay_profile_with_s let result = unwrap_option_or_return!(option); let profile = unwrap_result_or_return!(result); *out_profile = DashPayProfileFFI::from_profile(&profile); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } #[cfg(test)] @@ -263,7 +263,7 @@ mod tests { let mut has_profile = true; let result = managed_identity_get_dashpay_profile(handle, &mut out, &mut has_profile); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert!(!has_profile); assert!(out.display_name.is_null()); assert!(out.public_message.is_null()); @@ -296,7 +296,7 @@ mod tests { let mut has_profile = false; let result = managed_identity_get_dashpay_profile(handle, &mut out, &mut has_profile); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert!(has_profile); let display = std::ffi::CStr::from_ptr(out.display_name).to_str().unwrap(); @@ -325,7 +325,7 @@ mod tests { let result = managed_identity_get_dashpay_profile(9_999_999, &mut out, &mut has_profile); - assert_eq!(result.code, PlatformWalletFfiResultCode::NotFound); + assert_eq!(result.code, PlatformWalletFFIResultCode::NotFound); dashpay_profile_ffi_free(&mut out); } diff --git a/packages/rs-platform-wallet-ffi/src/data_contract.rs b/packages/rs-platform-wallet-ffi/src/data_contract.rs index 2e6b5374cc1..a4185958549 100644 --- a/packages/rs-platform-wallet-ffi/src/data_contract.rs +++ b/packages/rs-platform-wallet-ffi/src/data_contract.rs @@ -30,7 +30,7 @@ pub unsafe extern "C" fn platform_wallet_create_data_contract_with_signer( config_json: *const c_char, signer_handle: *mut SignerHandle, out_contract_id: *mut u8, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); check_ptr!(documents_schema_json); check_ptr!(out_contract_id); @@ -73,13 +73,13 @@ pub unsafe extern "C" fn platform_wallet_create_data_contract_with_signer( let bytes = contract_id.to_buffer(); let dst = slice::from_raw_parts_mut(out_contract_id, 32); dst.copy_from_slice(&bytes); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Decode an optional NUL-terminated UTF-8 C string. unsafe fn read_optional_str<'a>( ptr: *const c_char, -) -> Result, PlatformWalletFfiResult> { +) -> Result, PlatformWalletFFIResult> { if ptr.is_null() { return Ok(None); } diff --git a/packages/rs-platform-wallet-ffi/src/derivation.rs b/packages/rs-platform-wallet-ffi/src/derivation.rs index 70dba1a32a1..c830d136d2f 100644 --- a/packages/rs-platform-wallet-ffi/src/derivation.rs +++ b/packages/rs-platform-wallet-ffi/src/derivation.rs @@ -45,7 +45,7 @@ pub unsafe extern "C" fn platform_wallet_derive_ext_priv_key_from_mnemonic( out_secret_key: *mut u8, out_chain_code: *mut u8, out_public_key: *mut u8, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(mnemonic); check_ptr!(path_utf8); check_ptr!(out_secret_key); @@ -65,8 +65,8 @@ pub unsafe extern "C" fn platform_wallet_derive_ext_priv_key_from_mnemonic( 2 => Network::Devnet, 3 => Network::Regtest, _ => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, "invalid network (expected 0..=3)", ); } @@ -93,5 +93,5 @@ pub unsafe extern "C" fn platform_wallet_derive_ext_priv_key_from_mnemonic( std::ptr::copy_nonoverlapping(pubkey_bytes.as_ptr(), out_public_key, 33); } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/derive_identity_key_at_slot.rs b/packages/rs-platform-wallet-ffi/src/derive_identity_key_at_slot.rs index ea28ee1e1e7..b91c9d7b128 100644 --- a/packages/rs-platform-wallet-ffi/src/derive_identity_key_at_slot.rs +++ b/packages/rs-platform-wallet-ffi/src/derive_identity_key_at_slot.rs @@ -27,7 +27,7 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_key_at_slot( identity_index: u32, key_index: u32, out_row: *mut IdentityKeyPreviewFFI, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { use std::ffi::CStr; check_ptr!(out_row); @@ -59,7 +59,7 @@ unsafe fn derive_at_slot_inner( identity_index: u32, key_index: u32, out_row: *mut IdentityKeyPreviewFFI, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let mnemonic = unwrap_result_or_return!(parse_mnemonic_any_language(mnemonic_str)); let seed: Zeroizing<[u8; 64]> = Zeroizing::new(mnemonic.to_seed(passphrase_str)); @@ -89,8 +89,8 @@ unsafe fn derive_at_slot_inner( pub_ptr, pub_len, ))); drop(path_cstring); - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, format!("SecretKey::from_slice failed: {e}"), ); } @@ -107,8 +107,8 @@ unsafe fn derive_at_slot_inner( pub_ptr, pub_len, ))); drop(path_cstring); - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorUtf8Conversion, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorUtf8Conversion, format!("WIF string contained NUL byte: {e}"), ); } @@ -126,7 +126,7 @@ unsafe fn derive_at_slot_inner( private_key_bytes: *private_key_bytes_buf, }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Resolver-based variant of [`dash_sdk_derive_identity_key_at_slot`]. @@ -138,7 +138,7 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_key_at_slot_with_resolver( identity_index: u32, key_index: u32, out_row: *mut IdentityKeyPreviewFFI, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_row); *out_row = IdentityKeyPreviewFFI::empty(); @@ -161,20 +161,20 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_key_at_slot_with_resolver( match rc { x if x == mnemonic_resolver_result::SUCCESS => {} x if x == mnemonic_resolver_result::NOT_FOUND => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, "mnemonic resolver: no mnemonic stored for the supplied wallet_id", ); } x if x == mnemonic_resolver_result::BUFFER_TOO_SMALL => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, "mnemonic resolver: mnemonic exceeded the FFI buffer capacity", ); } _ => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, "mnemonic resolver: failed (other / Keychain access error)", ); } diff --git a/packages/rs-platform-wallet-ffi/src/dpns.rs b/packages/rs-platform-wallet-ffi/src/dpns.rs index 33ffd464d9f..fdfeb16a0d5 100644 --- a/packages/rs-platform-wallet-ffi/src/dpns.rs +++ b/packages/rs-platform-wallet-ffi/src/dpns.rs @@ -51,7 +51,7 @@ pub unsafe extern "C" fn platform_wallet_register_dpns_name_with_signer( name: *const c_char, signer_handle: *mut SignerHandle, out_full_domain_name: *mut *mut c_char, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(name); check_ptr!(out_full_domain_name); check_ptr!(signer_handle); @@ -74,7 +74,7 @@ pub unsafe extern "C" fn platform_wallet_register_dpns_name_with_signer( let full_name = unwrap_result_or_return!(result); let cstr = unwrap_result_or_return!(CString::new(full_name)); unsafe { *out_full_domain_name = cstr.into_raw() }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Resolve a DPNS name (`"alice"` or `"alice.dash"`) to an identity id. @@ -88,7 +88,7 @@ pub unsafe extern "C" fn platform_wallet_resolve_dpns_name( name: *const c_char, out_identity_id: *mut u8, out_found: *mut bool, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(name); check_ptr!(out_identity_id); check_ptr!(out_found); @@ -111,7 +111,7 @@ pub unsafe extern "C" fn platform_wallet_resolve_dpns_name( *out_found = false; }, } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Prefix search over DPNS documents on Platform. @@ -130,7 +130,7 @@ pub unsafe extern "C" fn platform_wallet_search_dpns_names( limit: u32, out_results: *mut *mut DpnsSearchResultFFI, out_count: *mut usize, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(prefix); check_ptr!(out_results); check_ptr!(out_count); @@ -152,7 +152,7 @@ pub unsafe extern "C" fn platform_wallet_search_dpns_names( *out_results = ptr::null_mut(); *out_count = 0; } - return PlatformWalletFfiResult::ok(); + return PlatformWalletFFIResult::ok(); } let mut buf: Vec = Vec::with_capacity(list.len()); for u in list { @@ -176,7 +176,7 @@ pub unsafe extern "C" fn platform_wallet_search_dpns_names( *out_results = array_ptr; *out_count = count; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Release an array previously returned by @@ -214,7 +214,7 @@ pub unsafe extern "C" fn platform_wallet_sync_dpns_names( wallet_handle: Handle, identity_id: *const u8, out_added: *mut u32, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { @@ -226,7 +226,7 @@ pub unsafe extern "C" fn platform_wallet_sync_dpns_names( if !out_added.is_null() { unsafe { *out_added = added }; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Heap-allocated array of DPNS labels returned by @@ -259,7 +259,7 @@ impl DpnsNameArray { pub unsafe extern "C" fn managed_identity_get_dpns_names( identity_handle: Handle, out_array: *mut DpnsNameArray, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_array); let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { @@ -272,7 +272,7 @@ pub unsafe extern "C" fn managed_identity_get_dpns_names( let labels = unwrap_option_or_return!(option); if labels.is_empty() { unsafe { *out_array = DpnsNameArray::empty() }; - return PlatformWalletFfiResult::ok(); + return PlatformWalletFFIResult::ok(); } let mut raw_labels: Vec<*mut c_char> = Vec::with_capacity(labels.len()); for label in labels { @@ -286,7 +286,7 @@ pub unsafe extern "C" fn managed_identity_get_dpns_names( let boxed = raw_labels.into_boxed_slice(); let ptr = Box::into_raw(boxed) as *mut *mut c_char; unsafe { *out_array = DpnsNameArray { labels: ptr, count } }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Release an array previously returned by @@ -387,7 +387,7 @@ pub unsafe extern "C" fn platform_wallet_fetch_contest_vote_state( label: *const c_char, out_state: *mut ContestVoteStateFFI, out_found: *mut bool, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(label); check_ptr!(out_state); check_ptr!(out_found); @@ -454,14 +454,14 @@ pub unsafe extern "C" fn platform_wallet_fetch_contest_vote_state( }; *out_found = true; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } None => { unsafe { *out_state = ContestVoteStateFFI::empty(); *out_found = false; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } } } @@ -504,7 +504,7 @@ pub unsafe extern "C" fn platform_wallet_sync_contested_dpns_names( wallet_handle: Handle, identity_id: *const u8, out_count: *mut u32, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { @@ -516,7 +516,7 @@ pub unsafe extern "C" fn platform_wallet_sync_contested_dpns_names( if !out_count.is_null() { unsafe { *out_count = labels.len() as u32 }; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Read the cached contested DPNS labels for a [`ManagedIdentity`] @@ -528,7 +528,7 @@ pub unsafe extern "C" fn platform_wallet_sync_contested_dpns_names( pub unsafe extern "C" fn managed_identity_get_contested_dpns_names( identity_handle: Handle, out_array: *mut DpnsNameArray, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_array); let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { @@ -537,7 +537,7 @@ pub unsafe extern "C" fn managed_identity_get_contested_dpns_names( let labels = unwrap_option_or_return!(option); if labels.is_empty() { unsafe { *out_array = DpnsNameArray::empty() }; - return PlatformWalletFfiResult::ok(); + return PlatformWalletFFIResult::ok(); } let mut raw_labels: Vec<*mut c_char> = Vec::with_capacity(labels.len()); for label in labels { @@ -551,5 +551,5 @@ pub unsafe extern "C" fn managed_identity_get_contested_dpns_names( let boxed = raw_labels.into_boxed_slice(); let ptr = Box::into_raw(boxed) as *mut *mut c_char; unsafe { *out_array = DpnsNameArray { labels: ptr, count } }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/error.rs b/packages/rs-platform-wallet-ffi/src/error.rs index c1a3d0832a3..adbe771c8c0 100644 --- a/packages/rs-platform-wallet-ffi/src/error.rs +++ b/packages/rs-platform-wallet-ffi/src/error.rs @@ -6,8 +6,8 @@ use std::os::raw::c_char; macro_rules! deref_ptr { ($ptr:expr) => {{ if $ptr.is_null() { - return $crate::error::PlatformWalletFfiResult::err( - $crate::error::PlatformWalletFfiResultCode::ErrorNullPointer, + return $crate::error::PlatformWalletFFIResult::err( + $crate::error::PlatformWalletFFIResultCode::ErrorNullPointer, format!("{} ptr is null", stringify!($ptr)), ); } @@ -19,8 +19,8 @@ macro_rules! deref_ptr { macro_rules! deref_ptr_mut { ($ptr:expr) => {{ if $ptr.is_null() { - return $crate::error::PlatformWalletFfiResult::err( - $crate::error::PlatformWalletFfiResultCode::ErrorNullPointer, + return $crate::error::PlatformWalletFFIResult::err( + $crate::error::PlatformWalletFFIResultCode::ErrorNullPointer, format!("{} ptr is null", stringify!($ptr)), ); } @@ -32,8 +32,8 @@ macro_rules! deref_ptr_mut { macro_rules! check_ptr { ($ptr:expr) => {{ if $ptr.is_null() { - return $crate::error::PlatformWalletFfiResult::err( - $crate::error::PlatformWalletFfiResultCode::ErrorNullPointer, + return $crate::error::PlatformWalletFFIResult::err( + $crate::error::PlatformWalletFFIResultCode::ErrorNullPointer, format!("{} ptr is null", stringify!($ptr)), ); } @@ -62,7 +62,7 @@ macro_rules! unwrap_option_or_return { #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PlatformWalletFfiResultCode { +pub enum PlatformWalletFFIResultCode { Success = 0, ErrorInvalidHandle = 1, ErrorInvalidParameter = 2, @@ -84,12 +84,12 @@ pub enum PlatformWalletFfiResultCode { /// Must be freed with ['platform_wallet_ffi_result_free'] #[repr(C)] #[derive(Debug)] -pub struct PlatformWalletFfiResult { - pub code: PlatformWalletFfiResultCode, +pub struct PlatformWalletFFIResult { + pub code: PlatformWalletFFIResultCode, pub message: *mut c_char, } -impl Drop for PlatformWalletFfiResult { +impl Drop for PlatformWalletFFIResult { fn drop(&mut self) { if !self.message.is_null() { unsafe { @@ -100,15 +100,15 @@ impl Drop for PlatformWalletFfiResult { } } -impl PlatformWalletFfiResult { +impl PlatformWalletFFIResult { pub const fn ok() -> Self { Self { - code: PlatformWalletFfiResultCode::Success, + code: PlatformWalletFFIResultCode::Success, message: std::ptr::null_mut(), } } - pub fn err(code: PlatformWalletFfiResultCode, message: impl Into) -> Self { + pub fn err(code: PlatformWalletFFIResultCode, message: impl Into) -> Self { let msg = message.into(); let c_msg = CString::new(msg).unwrap_or_else(|_| CString::new("").unwrap()); Self { @@ -125,10 +125,10 @@ impl PlatformWalletFfiResult { /// (message is already NULL). /// /// # Safety -/// `result` must point to a valid `PlatformWalletFfiResult` +/// `result` must point to a valid `PlatformWalletFFIResult` /// produced by this crate. Mutates the struct through the pointer. #[no_mangle] -pub unsafe extern "C" fn platform_wallet_ffi_result_free(result: *mut PlatformWalletFfiResult) { +pub unsafe extern "C" fn platform_wallet_ffi_result_free(result: *mut PlatformWalletFFIResult) { if result.is_null() { return; } @@ -142,196 +142,196 @@ pub unsafe extern "C" fn platform_wallet_ffi_result_free(result: *mut PlatformWa } } -impl From> for PlatformWalletFfiResult { +impl From> for PlatformWalletFFIResult { fn from(value: Option) -> Self { match value { Some(_) => Self::ok(), None => Self::err( - PlatformWalletFfiResultCode::NotFound, + PlatformWalletFFIResultCode::NotFound, format!("requested {} not found", std::any::type_name::()), ), } } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(error: PlatformWalletError) -> Self { - PlatformWalletFfiResult::err(PlatformWalletFfiResultCode::ErrorUnknown, error.to_string()) + PlatformWalletFFIResult::err(PlatformWalletFFIResultCode::ErrorUnknown, error.to_string()) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(error: dashcore::consensus::encode::Error) -> Self { - PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorDeserialization, + PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorDeserialization, error.to_string(), ) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(e: std::ffi::NulError) -> Self { Self::err( - PlatformWalletFfiResultCode::ErrorUtf8Conversion, + PlatformWalletFFIResultCode::ErrorUtf8Conversion, format!("string contained an interior NUL byte: {e}"), ) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(e: std::str::Utf8Error) -> Self { Self::err( - PlatformWalletFfiResultCode::ErrorUtf8Conversion, + PlatformWalletFFIResultCode::ErrorUtf8Conversion, format!("invalid UTF-8: {e}"), ) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(e: std::string::FromUtf8Error) -> Self { Self::err( - PlatformWalletFfiResultCode::ErrorUtf8Conversion, + PlatformWalletFFIResultCode::ErrorUtf8Conversion, format!("invalid UTF-8: {e}"), ) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(e: bs58::decode::Error) -> Self { Self::err( - PlatformWalletFfiResultCode::ErrorInvalidIdentifier, + PlatformWalletFFIResultCode::ErrorInvalidIdentifier, format!("base58 decode failed: {e}"), ) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(e: hex::FromHexError) -> Self { Self::err( - PlatformWalletFfiResultCode::ErrorInvalidIdentifier, + PlatformWalletFFIResultCode::ErrorInvalidIdentifier, format!("hex decode failed: {e}"), ) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(e: serde_json::Error) -> Self { let code = if e.is_data() || e.is_syntax() { - PlatformWalletFfiResultCode::ErrorDeserialization + PlatformWalletFFIResultCode::ErrorDeserialization } else { - PlatformWalletFfiResultCode::ErrorSerialization + PlatformWalletFFIResultCode::ErrorSerialization }; Self::err(code, format!("JSON error: {e}")) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(e: bincode::error::EncodeError) -> Self { Self::err( - PlatformWalletFfiResultCode::ErrorSerialization, + PlatformWalletFFIResultCode::ErrorSerialization, format!("bincode encode failed: {e}"), ) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(e: bincode::error::DecodeError) -> Self { Self::err( - PlatformWalletFfiResultCode::ErrorDeserialization, + PlatformWalletFFIResultCode::ErrorDeserialization, format!("bincode decode failed: {e}"), ) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(e: dpp::ProtocolError) -> Self { let msg = e.to_string(); let code = if msg.contains("identifier") { - PlatformWalletFfiResultCode::ErrorInvalidIdentifier + PlatformWalletFFIResultCode::ErrorInvalidIdentifier } else if msg.contains("deserialization") || msg.contains("decode") { - PlatformWalletFfiResultCode::ErrorDeserialization + PlatformWalletFFIResultCode::ErrorDeserialization } else if msg.contains("serialization") || msg.contains("encode") { - PlatformWalletFfiResultCode::ErrorSerialization + PlatformWalletFFIResultCode::ErrorSerialization } else { - PlatformWalletFfiResultCode::ErrorWalletOperation + PlatformWalletFFIResultCode::ErrorWalletOperation }; Self::err(code, format!("DPP protocol error: {msg}")) } } -impl From<&str> for PlatformWalletFfiResult { +impl From<&str> for PlatformWalletFFIResult { fn from(e: &str) -> Self { - Self::err(PlatformWalletFfiResultCode::ErrorInvalidParameter, e) + Self::err(PlatformWalletFFIResultCode::ErrorInvalidParameter, e) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(e: key_wallet::bip32::Error) -> Self { Self::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + PlatformWalletFFIResultCode::ErrorWalletOperation, format!("bip32 derivation failed: {e}"), ) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(e: key_wallet::Error) -> Self { Self::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + PlatformWalletFFIResultCode::ErrorWalletOperation, format!("key-wallet error: {e}"), ) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(e: dashcore::address::Error) -> Self { Self::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + PlatformWalletFFIResultCode::ErrorInvalidParameter, format!("address parse failed: {e}"), ) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(e: dashcore::key::Error) -> Self { Self::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + PlatformWalletFFIResultCode::ErrorInvalidParameter, format!("dashcore key error: {e}"), ) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(e: dpp::platform_value::Error) -> Self { Self::err( - PlatformWalletFfiResultCode::ErrorSerialization, + PlatformWalletFFIResultCode::ErrorSerialization, format!("platform_value error: {e}"), ) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(e: platform_wallet::changeset::PersistenceError) -> Self { Self::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + PlatformWalletFFIResultCode::ErrorWalletOperation, format!("persistence error: {e}"), ) } } -impl From> for PlatformWalletFfiResult { +impl From> for PlatformWalletFFIResult { fn from(e: Box) -> Self { Self::err( - PlatformWalletFfiResultCode::ErrorUnknown, + PlatformWalletFFIResultCode::ErrorUnknown, format!("unclassified error: {e}"), ) } } -impl From for PlatformWalletFfiResult { +impl From for PlatformWalletFFIResult { fn from(e: anyhow::Error) -> Self { Self::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + PlatformWalletFFIResultCode::ErrorInvalidParameter, e.to_string(), ) } @@ -343,16 +343,16 @@ mod tests { #[test] fn ok_has_null_message() { - let r = PlatformWalletFfiResult::ok(); - assert_eq!(r.code, PlatformWalletFfiResultCode::Success); + let r = PlatformWalletFFIResult::ok(); + assert_eq!(r.code, PlatformWalletFFIResultCode::Success); assert!(r.message.is_null()); } #[test] fn err_carries_message() { let mut r = - PlatformWalletFfiResult::err(PlatformWalletFfiResultCode::ErrorDeserialization, "boom"); - assert_ne!(r.code, PlatformWalletFfiResultCode::Success); + PlatformWalletFFIResult::err(PlatformWalletFFIResultCode::ErrorDeserialization, "boom"); + assert_ne!(r.code, PlatformWalletFFIResultCode::Success); assert!(!r.message.is_null()); unsafe { platform_wallet_ffi_result_free(&mut r) }; assert!(r.message.is_null()); @@ -360,7 +360,7 @@ mod tests { #[test] fn free_is_idempotent() { - let mut r = PlatformWalletFfiResult::err(PlatformWalletFfiResultCode::ErrorUnknown, "x"); + let mut r = PlatformWalletFFIResult::err(PlatformWalletFFIResultCode::ErrorUnknown, "x"); unsafe { platform_wallet_ffi_result_free(&mut r); platform_wallet_ffi_result_free(&mut r); @@ -370,8 +370,8 @@ mod tests { #[test] fn nul_in_message_is_replaced() { - let r = PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorUnknown, + let r = PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorUnknown, "before\0after", ); assert!(!r.message.is_null()); diff --git a/packages/rs-platform-wallet-ffi/src/established_contact.rs b/packages/rs-platform-wallet-ffi/src/established_contact.rs index 8b46cfdae31..d38e9511ec5 100644 --- a/packages/rs-platform-wallet-ffi/src/established_contact.rs +++ b/packages/rs-platform-wallet-ffi/src/established_contact.rs @@ -20,7 +20,7 @@ pub unsafe extern "C" fn managed_identity_get_established_contact( identity_handle: Handle, contact_id: *const u8, out_contact_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_contact_handle); let contact_identifier = unwrap_result_or_return!(unsafe { read_identifier(contact_id) }); @@ -34,7 +34,7 @@ pub unsafe extern "C" fn managed_identity_get_established_contact( let inner = unwrap_option_or_return!(option); let contact = unwrap_option_or_return!(inner); unsafe { *out_contact_handle = ESTABLISHED_CONTACT_STORAGE.insert(contact) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get the contact identity ID from an established contact into a @@ -43,14 +43,14 @@ pub unsafe extern "C" fn managed_identity_get_established_contact( pub unsafe extern "C" fn established_contact_get_contact_id( contact_handle: Handle, out_id: *mut u8, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_id); let option = ESTABLISHED_CONTACT_STORAGE .with_item(contact_handle, |contact| contact.contact_identity_id); let id = unwrap_option_or_return!(option); unsafe { write_identifier(out_id, &id) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get a handle to the outgoing contact request from an established contact @@ -58,7 +58,7 @@ pub unsafe extern "C" fn established_contact_get_contact_id( pub unsafe extern "C" fn established_contact_get_outgoing_request( contact_handle: Handle, out_request_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_request_handle); let option = ESTABLISHED_CONTACT_STORAGE @@ -67,7 +67,7 @@ pub unsafe extern "C" fn established_contact_get_outgoing_request( unsafe { *out_request_handle = crate::contact_request::CONTACT_REQUEST_STORAGE.insert(req); } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get a handle to the incoming contact request from an established contact @@ -75,7 +75,7 @@ pub unsafe extern "C" fn established_contact_get_outgoing_request( pub unsafe extern "C" fn established_contact_get_incoming_request( contact_handle: Handle, out_request_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_request_handle); let option = ESTABLISHED_CONTACT_STORAGE @@ -84,7 +84,7 @@ pub unsafe extern "C" fn established_contact_get_incoming_request( unsafe { *out_request_handle = crate::contact_request::CONTACT_REQUEST_STORAGE.insert(req); } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get the contact identity ID from an established contact (alias @@ -94,7 +94,7 @@ pub unsafe extern "C" fn established_contact_get_incoming_request( pub unsafe extern "C" fn established_contact_get_contact_identity_id( contact_handle: Handle, out_id: *mut u8, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { unsafe { established_contact_get_contact_id(contact_handle, out_id) } } @@ -103,7 +103,7 @@ pub unsafe extern "C" fn established_contact_get_contact_identity_id( pub unsafe extern "C" fn established_contact_get_alias( contact_handle: Handle, out_alias: *mut *mut std::os::raw::c_char, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_alias); *out_alias = std::ptr::null_mut(); @@ -113,7 +113,7 @@ pub unsafe extern "C" fn established_contact_get_alias( let alias = unwrap_option_or_return!(option); let c_str = unwrap_result_or_return!(std::ffi::CString::new(alias)); unsafe { *out_alias = c_str.into_raw() }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Set the alias for an established contact @@ -121,7 +121,7 @@ pub unsafe extern "C" fn established_contact_get_alias( pub unsafe extern "C" fn established_contact_set_alias( contact_handle: Handle, alias: *const std::os::raw::c_char, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let alias_str = if alias.is_null() { None } else { @@ -136,19 +136,19 @@ pub unsafe extern "C" fn established_contact_set_alias( } }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Clear the alias for an established contact #[no_mangle] pub unsafe extern "C" fn established_contact_clear_alias( contact_handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = ESTABLISHED_CONTACT_STORAGE.with_item_mut(contact_handle, |contact| { contact.clear_alias(); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get the note for an established contact @@ -156,7 +156,7 @@ pub unsafe extern "C" fn established_contact_clear_alias( pub unsafe extern "C" fn established_contact_get_note( contact_handle: Handle, out_note: *mut *mut std::os::raw::c_char, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_note); let option = @@ -165,7 +165,7 @@ pub unsafe extern "C" fn established_contact_get_note( let note = unwrap_option_or_return!(option); let c_str = unwrap_result_or_return!(std::ffi::CString::new(note)); unsafe { *out_note = c_str.into_raw() }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Set the note for an established contact @@ -173,7 +173,7 @@ pub unsafe extern "C" fn established_contact_get_note( pub unsafe extern "C" fn established_contact_set_note( contact_handle: Handle, note: *const std::os::raw::c_char, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let note_str = if note.is_null() { None } else { @@ -188,19 +188,19 @@ pub unsafe extern "C" fn established_contact_set_note( } }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Clear the note for an established contact #[no_mangle] pub unsafe extern "C" fn established_contact_clear_note( contact_handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = ESTABLISHED_CONTACT_STORAGE.with_item_mut(contact_handle, |contact| { contact.clear_note(); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Check if an established contact is hidden @@ -208,46 +208,46 @@ pub unsafe extern "C" fn established_contact_clear_note( pub unsafe extern "C" fn established_contact_is_hidden( contact_handle: Handle, out_is_hidden: *mut bool, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_is_hidden); let option = ESTABLISHED_CONTACT_STORAGE.with_item(contact_handle, |contact| contact.is_hidden); *out_is_hidden = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Hide an established contact from the contact list #[no_mangle] pub unsafe extern "C" fn established_contact_hide( contact_handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = ESTABLISHED_CONTACT_STORAGE.with_item_mut(contact_handle, |contact| { contact.hide(); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Unhide an established contact #[no_mangle] pub unsafe extern "C" fn established_contact_unhide( contact_handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = ESTABLISHED_CONTACT_STORAGE.with_item_mut(contact_handle, |contact| { contact.unhide(); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Destroy an established contact handle and free resources #[no_mangle] pub unsafe extern "C" fn established_contact_destroy( contact_handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = ESTABLISHED_CONTACT_STORAGE.remove(contact_handle); let _ = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } // Tests for this module are in tests/comprehensive_tests.rs diff --git a/packages/rs-platform-wallet-ffi/src/identity_derive_and_persist.rs b/packages/rs-platform-wallet-ffi/src/identity_derive_and_persist.rs index 6c4539430fd..5f6211ec0a7 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_derive_and_persist.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_derive_and_persist.rs @@ -163,7 +163,7 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( mnemonic_resolver_handle: *mut MnemonicResolverHandle, persister_handle: *mut IdentityKeyPersisterHandle, out_pubkeys: *mut IdentityRegistrationKeyDerivationsFFI, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_pubkeys); // Pre-zero so a failed call leaves the caller staring at a known // empty struct, never uninitialized memory. @@ -177,7 +177,7 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( check_ptr!(persister_handle); if key_count == 0 { - return PlatformWalletFfiResult::ok(); + return PlatformWalletFFIResult::ok(); } // ---- Resolve mnemonic ---------------------------------------------------- @@ -198,27 +198,27 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( match rc { x if x == mnemonic_resolver_result::SUCCESS => {} x if x == mnemonic_resolver_result::NOT_FOUND => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, "mnemonic resolver: no mnemonic stored for the supplied wallet_id", ); } x if x == mnemonic_resolver_result::BUFFER_TOO_SMALL => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, "mnemonic resolver: mnemonic exceeded the FFI buffer capacity", ); } _ => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, "mnemonic resolver: failed (other / Keychain access error)", ); } } if mnemonic_len == 0 || mnemonic_len > MNEMONIC_RESOLVER_BUFFER_CAPACITY { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, "mnemonic resolver: returned invalid length", ); } @@ -267,8 +267,8 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( Ok(p) => p, Err(detail) => { cleanup(rows); - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, format!( "derive_and_persist: path build failed at \ (identity={identity_index}, key={key_index}): {detail}" @@ -281,8 +281,8 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( Ok(d) => d, Err(e) => { cleanup(rows); - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, format!( "derive_and_persist: derive_priv failed at \ (identity={identity_index}, key={key_index}): {e}" @@ -305,8 +305,8 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( Err(e) => { priv_scalar.zeroize(); cleanup(rows); - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorUtf8Conversion, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorUtf8Conversion, format!("derivation path contained NUL byte: {e}"), ); } @@ -354,8 +354,8 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( // it falls out of scope here. drop(path_cstring); cleanup(rows); - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, format!( "persister callback returned false at \ (identity={identity_index}, key={key_index})" @@ -395,7 +395,7 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( count: items_count, }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } #[cfg(test)] @@ -561,7 +561,7 @@ mod tests { &mut out, ) }; - assert_eq!(rc.code, PlatformWalletFfiResultCode::Success); + assert_eq!(rc.code, PlatformWalletFFIResultCode::Success); assert_eq!(out.count, 3); // SAFETY: `capture` is alive (we're about to free it @@ -625,7 +625,7 @@ mod tests { &mut out, ) }; - assert_eq!(rc.code, PlatformWalletFfiResultCode::ErrorWalletOperation); + assert_eq!(rc.code, PlatformWalletFFIResultCode::ErrorWalletOperation); assert_eq!(out.count, 0); assert!(out.items.is_null()); unsafe { @@ -650,7 +650,7 @@ mod tests { std::ptr::null_mut(), ) }; - assert_eq!(rc.code, PlatformWalletFfiResultCode::ErrorNullPointer); + assert_eq!(rc.code, PlatformWalletFFIResultCode::ErrorNullPointer); // Null wallet id. let mut out = IdentityRegistrationKeyDerivationsFFI { @@ -668,7 +668,7 @@ mod tests { &mut out, ) }; - assert_eq!(rc.code, PlatformWalletFfiResultCode::ErrorNullPointer); + assert_eq!(rc.code, PlatformWalletFFIResultCode::ErrorNullPointer); unsafe { dash_sdk_mnemonic_resolver_destroy(resolver); dash_sdk_identity_key_persister_destroy(persister); @@ -695,7 +695,7 @@ mod tests { &mut out, ) }; - assert_eq!(rc.code, PlatformWalletFfiResultCode::Success); + assert_eq!(rc.code, PlatformWalletFFIResultCode::Success); assert_eq!(out.count, 0); assert_eq!(unsafe { (*capture).rows.lock().unwrap().len() }, 0); unsafe { diff --git a/packages/rs-platform-wallet-ffi/src/identity_discovery.rs b/packages/rs-platform-wallet-ffi/src/identity_discovery.rs index a1be35c82ad..2d8c38bc2a3 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_discovery.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_discovery.rs @@ -78,7 +78,7 @@ pub unsafe extern "C" fn platform_wallet_discover_identities( start_index_or_neg1: i64, gap_limit: u32, out_found: *mut DiscoveredIdentityIdsFFI, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_found); // Pre-clear the out-array so partial failures don't leave the @@ -105,7 +105,7 @@ pub unsafe extern "C" fn platform_wallet_discover_identities( let result = unwrap_option_or_return!(option); let found = unwrap_result_or_return!(result); if found.is_empty() { - return PlatformWalletFfiResult::ok(); + return PlatformWalletFFIResult::ok(); } use dpp::identity::accessors::IdentityGettersV0; @@ -117,7 +117,7 @@ pub unsafe extern "C" fn platform_wallet_discover_identities( unsafe { *out_found = DiscoveredIdentityIdsFFI { ids: ptr, count }; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Release a [`DiscoveredIdentityIdsFFI`] previously populated by diff --git a/packages/rs-platform-wallet-ffi/src/identity_key_preview.rs b/packages/rs-platform-wallet-ffi/src/identity_key_preview.rs index 0f7ab414369..95f2ba5c540 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_key_preview.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_key_preview.rs @@ -143,7 +143,7 @@ pub unsafe extern "C" fn platform_wallet_preview_identity_registration_keys( start_index: u32, count_or_neg1: i32, out_previews: *mut IdentityKeyPreviewsFFI, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_previews); // Pre-clear so partial failures don't leave the caller staring @@ -159,7 +159,7 @@ pub unsafe extern "C" fn platform_wallet_preview_identity_registration_keys( if count == 0 { // Empty preview is a valid result — early-out before // touching the wallet manager. - return PlatformWalletFfiResult::ok(); + return PlatformWalletFFIResult::ok(); } let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { @@ -167,8 +167,8 @@ pub unsafe extern "C" fn platform_wallet_preview_identity_registration_keys( // in on non-tokio threads. let wm = wallet.wallet_manager().blocking_read(); let key_wallet = wm.get_wallet(&wallet.wallet_id()).ok_or_else(|| { - PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidHandle, + PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidHandle, "Wallet not found in wallet manager", ) })?; @@ -178,7 +178,7 @@ pub unsafe extern "C" fn platform_wallet_preview_identity_registration_keys( // pointer detachment (`into_raw`, `mem::forget`) happens at // the very end so an early `?` cleans up via Drop. let build_row = - |identity_index: u32| -> Result { + |identity_index: u32| -> Result { let (path, ext_priv, public_key) = derive_identity_auth_keypair( key_wallet, network, @@ -249,7 +249,7 @@ pub unsafe extern "C" fn platform_wallet_preview_identity_registration_keys( count: items_count, }; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Release an [`IdentityKeyPreviewsFFI`] previously populated by diff --git a/packages/rs-platform-wallet-ffi/src/identity_keys_from_mnemonic.rs b/packages/rs-platform-wallet-ffi/src/identity_keys_from_mnemonic.rs index bf2eee79e66..fe09a16c2d2 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_keys_from_mnemonic.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_keys_from_mnemonic.rs @@ -84,7 +84,7 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( identity_index: u32, key_count: u32, out_rows: *mut IdentityRegistrationKeyDerivationsFFI, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { use std::ffi::CStr; check_ptr!(out_rows); @@ -96,7 +96,7 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( check_ptr!(mnemonic_cstr); if key_count == 0 { - return PlatformWalletFfiResult::ok(); + return PlatformWalletFFIResult::ok(); } let mnemonic_str = unwrap_result_or_return!(CStr::from_ptr(mnemonic_cstr).to_str()); @@ -134,8 +134,8 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( Ok(p) => p, Err(detail) => { cleanup(rows); - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, format!( "derive_identity_keys_from_mnemonic: path build failed at \ (identity={identity_index}, key={key_index}): {detail}" @@ -148,8 +148,8 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( Ok(d) => d, Err(e) => { cleanup(rows); - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, format!( "derive_identity_keys_from_mnemonic: derive_priv failed at \ (identity={identity_index}, key={key_index}): {e}" @@ -164,8 +164,8 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( Ok(s) => s, Err(e) => { cleanup(rows); - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorUtf8Conversion, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorUtf8Conversion, format!("derivation path contained NUL byte: {e}"), ); } @@ -188,8 +188,8 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( drop(Vec::from_raw_parts(pub_ptr, pub_len, pub_len)); drop(path_cstring); cleanup(rows); - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorUtf8Conversion, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorUtf8Conversion, format!("WIF string contained NUL byte: {e}"), ); } @@ -214,7 +214,7 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( items: items_ptr, count: items_count, }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Release a [`IdentityRegistrationKeyDerivationsFFI`] previously @@ -278,7 +278,7 @@ mod tests { &mut out, ) }; - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(out.count, 3); assert!(!out.items.is_null()); @@ -329,7 +329,7 @@ mod tests { &mut out, ) }; - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(out.count, 1); let row = unsafe { &*out.items }; @@ -358,7 +358,7 @@ mod tests { &mut out, ) }; - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(out.count, 0); assert!(out.items.is_null()); unsafe { dash_sdk_derive_identity_keys_from_mnemonic_free(&mut out) }; @@ -383,7 +383,7 @@ mod tests { }; assert_eq!( result.code, - PlatformWalletFfiResultCode::ErrorInvalidParameter + PlatformWalletFFIResultCode::ErrorInvalidParameter ); assert!(out.items.is_null()); assert_eq!(out.count, 0); diff --git a/packages/rs-platform-wallet-ffi/src/identity_manager.rs b/packages/rs-platform-wallet-ffi/src/identity_manager.rs index 44c1833cb3d..71149bf6b9d 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_manager.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_manager.rs @@ -15,14 +15,14 @@ pub(crate) fn ffi_noop_persister() -> WalletPersister { #[no_mangle] pub unsafe extern "C" fn identity_manager_create( out_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_handle); let manager = IdentityManager::default(); let handle = IDENTITY_MANAGER_STORAGE.insert(manager); unsafe { *out_handle = handle }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Add a managed identity to the manager. @@ -35,7 +35,7 @@ pub unsafe extern "C" fn identity_manager_create( pub unsafe extern "C" fn identity_manager_add_identity( manager_handle: Handle, identity_handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let identity_option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| identity.clone()); let identity = unwrap_option_or_return!(identity_option); @@ -45,7 +45,7 @@ pub unsafe extern "C" fn identity_manager_add_identity( }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Remove an identity from the manager. @@ -58,7 +58,7 @@ pub unsafe extern "C" fn identity_manager_add_identity( pub unsafe extern "C" fn identity_manager_remove_identity( manager_handle: Handle, identity_id: *const u8, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); let option = IDENTITY_MANAGER_STORAGE.with_item_mut(manager_handle, |manager| { @@ -66,12 +66,12 @@ pub unsafe extern "C" fn identity_manager_remove_identity( }); let result = unwrap_option_or_return!(option); if result.is_err() { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorIdentityNotFound, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorIdentityNotFound, "Identity not found", ); } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get an identity by ID. `identity_id` is a `*const u8` to a @@ -82,7 +82,7 @@ pub unsafe extern "C" fn identity_manager_get_identity( manager_handle: Handle, identity_id: *const u8, out_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_handle); let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); @@ -93,7 +93,7 @@ pub unsafe extern "C" fn identity_manager_get_identity( let inner = unwrap_option_or_return!(option); let identity = unwrap_option_or_return!(inner); unsafe { *out_handle = MANAGED_IDENTITY_STORAGE.insert(identity) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get all identity IDs across both buckets. @@ -101,7 +101,7 @@ pub unsafe extern "C" fn identity_manager_get_identity( pub unsafe extern "C" fn identity_manager_get_all_identity_ids( manager_handle: Handle, out_array: *mut IdentifierArray, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { use dpp::identity::accessors::IdentityGettersV0; check_ptr!(out_array); @@ -115,7 +115,7 @@ pub unsafe extern "C" fn identity_manager_get_all_identity_ids( }); let ids = unwrap_option_or_return!(option); unsafe { *out_array = IdentifierArray::new(ids) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get the count of identities across both buckets. @@ -123,25 +123,25 @@ pub unsafe extern "C" fn identity_manager_get_all_identity_ids( pub unsafe extern "C" fn identity_manager_get_identity_count( manager_handle: Handle, out_count: *mut usize, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_count); let option = IDENTITY_MANAGER_STORAGE.with_item(manager_handle, |manager| manager.identity_count()); *out_count = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Destroy IdentityManager and free resources #[no_mangle] pub unsafe extern "C" fn identity_manager_destroy( manager_handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { if IDENTITY_MANAGER_STORAGE.remove(manager_handle).is_some() { - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } else { - PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidHandle, + PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidHandle, "Invalid manager handle", ) } @@ -192,7 +192,7 @@ mod tests { let result = identity_manager_create(&mut handle); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_ne!(handle, NULL_HANDLE); identity_manager_destroy(handle); @@ -209,7 +209,7 @@ mod tests { let mut count: usize = 0; let result = identity_manager_get_identity_count(handle, &mut count); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(count, 0); identity_manager_destroy(handle); @@ -239,7 +239,7 @@ mod tests { let mut got: Handle = NULL_HANDLE; let result = identity_manager_get_identity(manager_handle, id_bytes.as_ptr(), &mut got); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_ne!(got, NULL_HANDLE); identity_manager_destroy(manager_handle); @@ -250,7 +250,7 @@ mod tests { fn test_destroy_invalid_handle() { unsafe { let result = identity_manager_destroy(9999); - assert_eq!(result.code, PlatformWalletFfiResultCode::ErrorInvalidHandle); + assert_eq!(result.code, PlatformWalletFFIResultCode::ErrorInvalidHandle); } } } diff --git a/packages/rs-platform-wallet-ffi/src/identity_registration_funded_with_signer.rs b/packages/rs-platform-wallet-ffi/src/identity_registration_funded_with_signer.rs index 54d4182659a..c7a12adbd0a 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_registration_funded_with_signer.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_registration_funded_with_signer.rs @@ -31,14 +31,14 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_funding_signer( signer_handle: *mut SignerHandle, out_identity_id: *mut [u8; 32], out_identity_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); check_ptr!(identity_pubkeys); check_ptr!(out_identity_id); check_ptr!(out_identity_handle); if identity_pubkeys_count == 0 { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, "identity_pubkeys_count must be >= 1", ); } @@ -51,8 +51,8 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_funding_signer( let purpose = unwrap_result_or_return!(Purpose::try_from(row.purpose)); let security_level = unwrap_result_or_return!(SecurityLevel::try_from(row.security_level)); if row.pubkey_bytes.is_null() || row.pubkey_len == 0 { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorNullPointer, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorNullPointer, format!("identity_pubkeys[{i}].pubkey_bytes is null or empty"), ); } @@ -97,5 +97,5 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_funding_signer( let managed = platform_wallet::ManagedIdentity::new(identity, identity_index); let handle = MANAGED_IDENTITY_STORAGE.insert(managed); *out_identity_handle = handle; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/identity_registration_with_signer.rs b/packages/rs-platform-wallet-ffi/src/identity_registration_with_signer.rs index 04e689c6885..d517be26dd6 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_registration_with_signer.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_registration_with_signer.rs @@ -149,7 +149,7 @@ pub struct IdentityPubkeyFFI { /// different prefixes — `add_public_keys[i]` for update, /// `identity_pubkeys[i]` for registration). /// -/// Returns `Err(PlatformWalletFfiResult)` carrying the FFI error the +/// Returns `Err(PlatformWalletFFIResult)` carrying the FFI error the /// caller should bubble up (the result already holds the message); /// caller does `unwrap_result_or_return!(decode_contract_bounds(...))`. pub(crate) unsafe fn decode_contract_bounds( @@ -157,12 +157,12 @@ pub(crate) unsafe fn decode_contract_bounds( purpose: Purpose, row_index: usize, field_label: &str, -) -> Result, PlatformWalletFfiResult> { +) -> Result, PlatformWalletFFIResult> { match row.contract_bounds_kind { 0 => { if matches!(purpose, Purpose::ENCRYPTION | Purpose::DECRYPTION) { - return Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, format!( "{field_label}[{row_index}].contract_bounds_kind = 0 (no bounds) but \ purpose = {purpose:?} requires bounds — Drive scopes Encryption / \ @@ -174,8 +174,8 @@ pub(crate) unsafe fn decode_contract_bounds( } 1 => { if row.contract_bounds_id.is_null() { - return Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorNullPointer, + return Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorNullPointer, format!( "{field_label}[{row_index}].contract_bounds_id is null but kind == 1 \ (SingleContract)" @@ -193,8 +193,8 @@ pub(crate) unsafe fn decode_contract_bounds( } 2 => { if row.contract_bounds_id.is_null() || row.contract_bounds_document_type.is_null() { - return Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorNullPointer, + return Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorNullPointer, format!( "{field_label}[{row_index}].contract_bounds_id or \ .contract_bounds_document_type is null but kind == 2 \ @@ -210,8 +210,8 @@ pub(crate) unsafe fn decode_contract_bounds( let doc_type = match CStr::from_ptr(row.contract_bounds_document_type).to_str() { Ok(s) => s.to_string(), Err(e) => { - return Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorUtf8Conversion, + return Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorUtf8Conversion, format!( "{field_label}[{row_index}].contract_bounds_document_type is not \ valid UTF-8: {e}" @@ -224,8 +224,8 @@ pub(crate) unsafe fn decode_contract_bounds( document_type_name: doc_type, })) } - other => Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + other => Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, format!( "{field_label}[{row_index}].contract_bounds_kind = {other} is not a valid \ discriminant (0=none, 1=SingleContract, 2=SingleContractDocumentType)" @@ -283,7 +283,7 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_signer( output: *const IdentityFundingOutputFFI, out_identity_id: *mut [u8; 32], out_identity_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(inputs); check_ptr!(identity_pubkeys); check_ptr!(signer_identity_handle); @@ -291,14 +291,14 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_signer( check_ptr!(out_identity_id); check_ptr!(out_identity_handle); if inputs_count == 0 { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, "`inputs_count` is zero", ); } if identity_pubkeys_count == 0 { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, "`identity_pubkeys_count` must be >= 1", ); } @@ -310,8 +310,8 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_signer( 0 => PlatformAddress::P2pkh(entry.hash), 1 => PlatformAddress::P2sh(entry.hash), _ => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, "invalid address_type (expected 0 or 1)", ); } @@ -331,8 +331,8 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_signer( 0 => PlatformAddress::P2pkh(output_ref.hash), 1 => PlatformAddress::P2sh(output_ref.hash), _ => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, "invalid output address_type (expected 0 or 1)", ); } @@ -354,8 +354,8 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_signer( let purpose = unwrap_result_or_return!(Purpose::try_from(row.purpose)); let security_level = unwrap_result_or_return!(SecurityLevel::try_from(row.security_level)); if row.pubkey_bytes.is_null() || row.pubkey_len == 0 { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorNullPointer, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorNullPointer, format!("identity_pubkeys[{i}].pubkey_bytes is null or empty"), ); } @@ -414,7 +414,7 @@ pub unsafe extern "C" fn platform_wallet_register_identity_with_signer( let managed = platform_wallet::ManagedIdentity::new(identity, identity_index); let handle = MANAGED_IDENTITY_STORAGE.insert(managed); *out_identity_handle = handle; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } // --------------------------------------------------------------------------- @@ -480,11 +480,11 @@ pub unsafe extern "C" fn platform_wallet_derive_identity_keys_for_index( identity_index: u32, key_count: u32, out_rows: *mut IdentityRegistrationKeyDerivationsFFI, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_rows); *out_rows = IdentityRegistrationKeyDerivationsFFI::empty(); if key_count == 0 { - return PlatformWalletFfiResult::ok(); + return PlatformWalletFFIResult::ok(); } let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { @@ -493,8 +493,8 @@ pub unsafe extern "C" fn platform_wallet_derive_identity_keys_for_index( let key_wallet = match wm.get_wallet(&wallet_id) { Some(w) => w, None => { - return Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidHandle, + return Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidHandle, "Wallet not found in wallet manager", )); } @@ -525,8 +525,8 @@ pub unsafe extern "C" fn platform_wallet_derive_identity_keys_for_index( Ok(t) => t, Err(e) => { cleanup(rows); - return Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorWalletOperation, + return Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorWalletOperation, format!( "derive_identity_keys_for_index: derivation failed at \ (identity={identity_index}, key={key_id}): {e}" @@ -539,8 +539,8 @@ pub unsafe extern "C" fn platform_wallet_derive_identity_keys_for_index( Ok(s) => s, Err(e) => { cleanup(rows); - return Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorUtf8Conversion, + return Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorUtf8Conversion, format!("derivation path contained NUL byte: {e}"), )); } @@ -565,8 +565,8 @@ pub unsafe extern "C" fn platform_wallet_derive_identity_keys_for_index( } drop(path_cstring); cleanup(rows); - return Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorUtf8Conversion, + return Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorUtf8Conversion, format!("WIF string contained NUL byte: {e}"), )); } @@ -595,7 +595,7 @@ pub unsafe extern "C" fn platform_wallet_derive_identity_keys_for_index( items: items_ptr, count: items_count, }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Release a [`IdentityRegistrationKeyDerivationsFFI`] previously diff --git a/packages/rs-platform-wallet-ffi/src/identity_sync.rs b/packages/rs-platform-wallet-ffi/src/identity_sync.rs index 1cfd7b0be96..36bbdfb7115 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_sync.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_sync.rs @@ -8,7 +8,7 @@ //! identity or whole-store) with a paired free helper. //! //! Error handling follows the same shape as the rest of this crate: -//! every entry point returns a `PlatformWalletFfiResult` carrying the +//! every entry point returns a `PlatformWalletFFIResult` carrying the //! typed code + Rust-supplied detail; null / invalid inputs surface //! through the `check_ptr!` and `unwrap_option_or_return!` macros. @@ -64,25 +64,25 @@ impl IdentityTokenSyncInfoFFI { #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_identity_sync_start( handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { let _entered = runtime().enter(); manager.identity_sync_arc().start(); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Stop the identity-token sync manager if it is running. #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_identity_sync_stop( handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { manager.identity_sync().stop(); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Whether the identity-token sync background loop is running. @@ -90,14 +90,14 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_stop( pub unsafe extern "C" fn platform_wallet_manager_identity_sync_is_running( handle: Handle, out_running: *mut bool, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_running); let option = PLATFORM_WALLET_MANAGER_STORAGE .with_item(handle, |manager| manager.identity_sync().is_running()); let running = unwrap_option_or_return!(option); *out_running = running; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Whether an identity-token sync pass is currently in flight. @@ -105,14 +105,14 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_is_running( pub unsafe extern "C" fn platform_wallet_manager_identity_sync_is_syncing( handle: Handle, out_syncing: *mut bool, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_syncing); let option = PLATFORM_WALLET_MANAGER_STORAGE .with_item(handle, |manager| manager.identity_sync().is_syncing()); let syncing = unwrap_option_or_return!(option); *out_syncing = syncing; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Unix seconds of the last completed identity-token sync pass for @@ -126,7 +126,7 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_last_sync_unix_se handle: Handle, identity_id_ptr: *const u8, out_last_sync_unix: *mut u64, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(identity_id_ptr); check_ptr!(out_last_sync_unix); @@ -140,7 +140,7 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_last_sync_unix_se }); let value = unwrap_option_or_return!(option); *out_last_sync_unix = value.unwrap_or(0); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Set the background identity-token sync interval in seconds. @@ -148,14 +148,14 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_last_sync_unix_se pub unsafe extern "C" fn platform_wallet_manager_identity_sync_set_interval( handle: Handle, interval_seconds: u64, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { manager .identity_sync() .set_interval(Duration::from_secs(interval_seconds)); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Run one identity-token sync pass across all registered wallets. @@ -168,12 +168,12 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_set_interval( #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_identity_sync_sync_now( handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { runtime().block_on(manager.identity_sync().sync_now()); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Snapshot the identity-token sync state for one identity. @@ -197,7 +197,7 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_state_for_identit out_rows: *mut *mut IdentityTokenSyncInfoFFI, out_rows_count: *mut usize, out_last_sync_unix: *mut u64, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(identity_id_ptr); check_ptr!(out_rows); check_ptr!(out_rows_count); @@ -231,7 +231,7 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_state_for_identit *out_last_sync_unix = 0; } } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Snapshot the identity-token sync state for every cached identity @@ -252,7 +252,7 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_state_all( handle: Handle, out_rows: *mut *mut IdentityTokenSyncInfoFFI, out_rows_count: *mut usize, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_rows); check_ptr!(out_rows_count); @@ -276,7 +276,7 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_state_all( *out_rows = Box::into_raw(boxed) as *mut IdentityTokenSyncInfoFFI; *out_rows_count = len; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Free a heap-owned `IdentityTokenSyncInfoFFI` array returned by one @@ -327,15 +327,15 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_register_identity identity_id_ptr: *const u8, token_ids_ptr: *const u8, token_ids_count: usize, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(identity_id_ptr); let mut id_bytes = [0u8; 32]; std::ptr::copy_nonoverlapping(identity_id_ptr, id_bytes.as_mut_ptr(), 32); let identity_id = dpp::prelude::Identifier::from(id_bytes); let Some(token_ids) = read_token_ids(token_ids_ptr, token_ids_count) else { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorNullPointer, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorNullPointer, "token_ids_ptr is null with non-zero count", ); }; @@ -345,7 +345,7 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_register_identity runtime().block_on(async move { mgr.register_identity(identity_id, token_ids).await }); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Unregister an identity from the token-sync registry. @@ -356,7 +356,7 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_register_identity pub unsafe extern "C" fn platform_wallet_manager_identity_sync_unregister_identity( handle: Handle, identity_id_ptr: *const u8, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(identity_id_ptr); let mut id_bytes = [0u8; 32]; std::ptr::copy_nonoverlapping(identity_id_ptr, id_bytes.as_mut_ptr(), 32); @@ -367,7 +367,7 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_unregister_identi runtime().block_on(async move { mgr.unregister_identity(&identity_id).await }); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Replace the watched-token list for an already-registered identity. @@ -385,15 +385,15 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_update_watched_to identity_id_ptr: *const u8, token_ids_ptr: *const u8, token_ids_count: usize, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(identity_id_ptr); let mut id_bytes = [0u8; 32]; std::ptr::copy_nonoverlapping(identity_id_ptr, id_bytes.as_mut_ptr(), 32); let identity_id = dpp::prelude::Identifier::from(id_bytes); let Some(token_ids) = read_token_ids(token_ids_ptr, token_ids_count) else { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorNullPointer, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorNullPointer, "token_ids_ptr is null with non-zero count", ); }; @@ -403,5 +403,5 @@ pub unsafe extern "C" fn platform_wallet_manager_identity_sync_update_watched_to runtime().block_on(async move { mgr.update_watched_tokens(identity_id, token_ids).await }); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/identity_top_up.rs b/packages/rs-platform-wallet-ffi/src/identity_top_up.rs index 02d8dea1888..44d57d7ecd2 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_top_up.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_top_up.rs @@ -68,14 +68,14 @@ pub unsafe extern "C" fn platform_wallet_top_up_from_addresses_with_signer( inputs_count: usize, signer_address_handle: *mut SignerHandle, out_new_balance: *mut u64, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(identity_id); check_ptr!(inputs); check_ptr!(signer_address_handle); check_ptr!(out_new_balance); if inputs_count == 0 { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, "`inputs_count` is zero", ); } @@ -90,8 +90,8 @@ pub unsafe extern "C" fn platform_wallet_top_up_from_addresses_with_signer( 0 => PlatformAddress::P2pkh(entry.hash), 1 => PlatformAddress::P2sh(entry.hash), _ => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, "invalid address_type (expected 0 or 1)", ); } @@ -128,5 +128,5 @@ pub unsafe extern "C" fn platform_wallet_top_up_from_addresses_with_signer( let result = unwrap_option_or_return!(option); let new_balance = unwrap_result_or_return!(result); *out_new_balance = new_balance; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/identity_transfer.rs b/packages/rs-platform-wallet-ffi/src/identity_transfer.rs index 6788a4bca26..654a7420ee4 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_transfer.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_transfer.rs @@ -66,7 +66,7 @@ pub unsafe extern "C" fn platform_wallet_transfer_credits_with_signer( to_identity_id: *const u8, amount: u64, signer_handle: *mut SignerHandle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); let from_id = unwrap_result_or_return!(read_identifier(from_identity_id)); @@ -90,7 +90,7 @@ pub unsafe extern "C" fn platform_wallet_transfer_credits_with_signer( }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Transfer credits from `from_identity_id` to a set of @@ -118,12 +118,12 @@ pub unsafe extern "C" fn platform_wallet_transfer_credits_to_addresses_with_sign outputs_count: usize, signer_handle: *mut SignerHandle, out_new_balance: *mut u64, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); check_ptr!(outputs); if outputs_count == 0 { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, "`outputs_count` is zero", ); } @@ -137,8 +137,8 @@ pub unsafe extern "C" fn platform_wallet_transfer_credits_to_addresses_with_sign 0 => PlatformAddress::P2pkh(entry.hash), 1 => PlatformAddress::P2sh(entry.hash), _ => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, "invalid address_type (expected 0 or 1)", ); } @@ -164,5 +164,5 @@ pub unsafe extern "C" fn platform_wallet_transfer_credits_to_addresses_with_sign if !out_new_balance.is_null() { *out_new_balance = new_balance; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/identity_update.rs b/packages/rs-platform-wallet-ffi/src/identity_update.rs index 38fdcd20855..326062c9c85 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_update.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_update.rs @@ -33,7 +33,7 @@ pub unsafe extern "C" fn platform_wallet_update_identity_with_signer( disable_public_key_ids: *const u32, disable_public_key_ids_count: usize, signer_handle: *mut SignerHandle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); let id = unwrap_result_or_return!(read_identifier(identity_id)); @@ -51,8 +51,8 @@ pub unsafe extern "C" fn platform_wallet_update_identity_with_signer( let security_level = unwrap_result_or_return!(SecurityLevel::try_from(row.security_level)); if row.pubkey_bytes.is_null() || row.pubkey_len == 0 { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorNullPointer, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorNullPointer, format!("add_public_keys[{i}].pubkey_bytes is null or empty"), ); } @@ -100,5 +100,5 @@ pub unsafe extern "C" fn platform_wallet_update_identity_with_signer( }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/identity_withdrawal.rs b/packages/rs-platform-wallet-ffi/src/identity_withdrawal.rs index f3a5930bfc3..8b330d62aae 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_withdrawal.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_withdrawal.rs @@ -23,7 +23,7 @@ pub unsafe extern "C" fn platform_wallet_withdraw_credits_with_signer( amount: u64, to_address: *const c_char, signer_handle: *mut SignerHandle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); check_ptr!(to_address); @@ -40,13 +40,13 @@ pub unsafe extern "C" fn platform_wallet_withdraw_credits_with_signer( wallet_handle, |wallet| -> Result< Result<(), platform_wallet::PlatformWalletError>, - PlatformWalletFfiResult, + PlatformWalletFFIResult, > { let wallet_network = wallet.platform().network(); let to_address_parsed = to_address_unchecked .clone() .require_network(wallet_network) - .map_err(PlatformWalletFfiResult::from)?; + .map_err(PlatformWalletFFIResult::from)?; let identity_wallet = wallet.identity().clone(); Ok(block_on_worker(async move { let signer: &VTableSigner = &*(signer_addr as *const VTableSigner); @@ -65,5 +65,5 @@ pub unsafe extern "C" fn platform_wallet_withdraw_credits_with_signer( let inner = unwrap_option_or_return!(option); let result = unwrap_result_or_return!(inner); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/managed_identity.rs b/packages/rs-platform-wallet-ffi/src/managed_identity.rs index e8c38573c32..11a62760b8d 100644 --- a/packages/rs-platform-wallet-ffi/src/managed_identity.rs +++ b/packages/rs-platform-wallet-ffi/src/managed_identity.rs @@ -14,7 +14,7 @@ pub unsafe extern "C" fn managed_identity_create_from_identity_bytes( identity_bytes: *const std::os::raw::c_uchar, identity_len: usize, out_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(identity_bytes); check_ptr!(out_handle); @@ -28,7 +28,7 @@ pub unsafe extern "C" fn managed_identity_create_from_identity_bytes( let handle = MANAGED_IDENTITY_STORAGE.insert(managed_identity); unsafe { *out_handle = handle }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get the identity ID into a 32-byte out-buffer. @@ -36,14 +36,14 @@ pub unsafe extern "C" fn managed_identity_create_from_identity_bytes( pub unsafe extern "C" fn managed_identity_get_id( identity_handle: Handle, out_id: *mut u8, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_id); let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| identity.identity.id()); let id = unwrap_option_or_return!(option); unsafe { write_identifier(out_id, &id) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get the identity balance @@ -51,13 +51,13 @@ pub unsafe extern "C" fn managed_identity_get_id( pub unsafe extern "C" fn managed_identity_get_balance( identity_handle: Handle, out_balance: *mut u64, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_balance); let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| identity.identity.balance()); *out_balance = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get the label. @@ -68,13 +68,13 @@ pub unsafe extern "C" fn managed_identity_get_balance( pub unsafe extern "C" fn managed_identity_get_label( identity_handle: Handle, out_label: *mut *mut c_char, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_label); let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |_identity| ()); unwrap_option_or_return!(option); unsafe { *out_label = std::ptr::null_mut() }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Set the label — no-op stub. @@ -82,10 +82,10 @@ pub unsafe extern "C" fn managed_identity_get_label( pub unsafe extern "C" fn managed_identity_set_label( identity_handle: Handle, _label: *const c_char, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = MANAGED_IDENTITY_STORAGE.with_item_mut(identity_handle, |_identity| ()); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get last updated balance block time @@ -93,7 +93,7 @@ pub unsafe extern "C" fn managed_identity_set_label( pub unsafe extern "C" fn managed_identity_get_last_updated_balance_block_time( identity_handle: Handle, out_block_time: *mut BlockTime, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_block_time); let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { @@ -110,7 +110,7 @@ pub unsafe extern "C" fn managed_identity_get_last_updated_balance_block_time( }, }; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Set last updated balance block time. @@ -118,7 +118,7 @@ pub unsafe extern "C" fn managed_identity_get_last_updated_balance_block_time( pub unsafe extern "C" fn managed_identity_set_last_updated_balance_block_time( identity_handle: Handle, block_time: *const BlockTime, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let bt = deref_ptr!(block_time); let owned: platform_wallet::BlockTime = platform_wallet::BlockTime { height: bt.height, @@ -129,7 +129,7 @@ pub unsafe extern "C" fn managed_identity_set_last_updated_balance_block_time( identity.last_updated_balance_block_time = Some(owned); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get last synced keys block time @@ -137,7 +137,7 @@ pub unsafe extern "C" fn managed_identity_set_last_updated_balance_block_time( pub unsafe extern "C" fn managed_identity_get_last_synced_keys_block_time( identity_handle: Handle, out_block_time: *mut BlockTime, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_block_time); let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { @@ -154,7 +154,7 @@ pub unsafe extern "C" fn managed_identity_get_last_synced_keys_block_time( }, }; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get the identity revision. @@ -162,13 +162,13 @@ pub unsafe extern "C" fn managed_identity_get_last_synced_keys_block_time( pub unsafe extern "C" fn managed_identity_get_revision( identity_handle: Handle, out_revision: *mut u64, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_revision); let option = MANAGED_IDENTITY_STORAGE .with_item(identity_handle, |identity| identity.identity.revision()); *out_revision = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Flat C representation of an `IdentityPublicKey` for the Swift @@ -193,7 +193,7 @@ pub unsafe extern "C" fn managed_identity_get_public_keys( identity_handle: Handle, out_keys: *mut *mut IdentityPublicKeyFFI, out_count: *mut usize, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_keys); check_ptr!(out_count); @@ -232,7 +232,7 @@ pub unsafe extern "C" fn managed_identity_get_public_keys( *out_keys = std::ptr::null_mut(); *out_count = 0; } - return PlatformWalletFfiResult::ok(); + return PlatformWalletFFIResult::ok(); } let count = buf.len(); let boxed = buf.into_boxed_slice(); @@ -241,7 +241,7 @@ pub unsafe extern "C" fn managed_identity_get_public_keys( *out_keys = array_ptr; *out_count = count; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Release an array previously returned by @@ -271,12 +271,12 @@ pub unsafe extern "C" fn managed_identity_free_public_keys( #[no_mangle] pub unsafe extern "C" fn managed_identity_destroy( identity_handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { if MANAGED_IDENTITY_STORAGE.remove(identity_handle).is_some() { - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } else { - PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidHandle, + PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidHandle, "Invalid identity handle", ) } diff --git a/packages/rs-platform-wallet-ffi/src/manager.rs b/packages/rs-platform-wallet-ffi/src/manager.rs index dfd249ca249..8afd97ba7f9 100644 --- a/packages/rs-platform-wallet-ffi/src/manager.rs +++ b/packages/rs-platform-wallet-ffi/src/manager.rs @@ -37,7 +37,7 @@ pub unsafe extern "C" fn platform_wallet_manager_create( persistence: *const PersistenceCallbacks, event_handler: *const EventHandlerCallbacks, out_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(sdk_ptr); check_ptr!(persistence); check_ptr!(event_handler); @@ -62,7 +62,7 @@ pub unsafe extern "C" fn platform_wallet_manager_create( let handle = PLATFORM_WALLET_MANAGER_STORAGE.insert(manager); *out_handle = handle; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Create a wallet from raw seed bytes (64 bytes). @@ -78,13 +78,13 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_seed( account_options: u32, out_wallet_handle: *mut Handle, out_wallet_id: *mut [u8; 32], -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(seed_bytes); check_ptr!(out_wallet_handle); check_ptr!(out_wallet_id); if seed_len != 64 { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, format!("Seed must be 64 bytes, got {seed_len}"), ); } @@ -95,8 +95,8 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_seed( 2 => Network::Devnet, 3 => Network::Regtest, _ => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidNetwork, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidNetwork, format!("Unknown network: {network}"), ); } @@ -120,7 +120,7 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_seed( let wallet_handle = PLATFORM_WALLET_STORAGE.insert(wallet); *out_wallet_handle = wallet_handle; *out_wallet_id = wallet_id; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Create a wallet from a BIP39 mnemonic phrase (English). @@ -135,7 +135,7 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_mnemonic( account_options: u32, out_wallet_handle: *mut Handle, out_wallet_id: *mut [u8; 32], -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(mnemonic); check_ptr!(out_wallet_handle); check_ptr!(out_wallet_id); @@ -148,8 +148,8 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_mnemonic( 2 => Network::Devnet, 3 => Network::Regtest, _ => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidNetwork, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidNetwork, format!("Unknown network: {network}"), ); } @@ -169,7 +169,7 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_mnemonic( let wallet_handle = PLATFORM_WALLET_STORAGE.insert(wallet); *out_wallet_handle = wallet_handle; *out_wallet_id = wallet_id; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Hydrate the manager from its persister. @@ -184,13 +184,13 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_mnemonic( #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_load_from_persistor( manager_handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(manager_handle, |manager| { runtime().block_on(manager.load_from_persistor()) }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get a `PlatformWallet` handle for a wallet registered in the @@ -201,7 +201,7 @@ pub unsafe extern "C" fn platform_wallet_manager_get_wallet( manager_handle: Handle, wallet_id: *const [u8; 32], out_wallet_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(wallet_id); check_ptr!(out_wallet_handle); let wallet_id_value = *wallet_id; @@ -214,10 +214,10 @@ pub unsafe extern "C" fn platform_wallet_manager_get_wallet( Some(wallet) => { let handle = PLATFORM_WALLET_STORAGE.insert(wallet); *out_wallet_handle = handle; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } - None => PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::NotFound, + None => PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::NotFound, format!( "Wallet {} not found in manager", hex::encode(wallet_id_value) @@ -230,9 +230,9 @@ pub unsafe extern "C" fn platform_wallet_manager_get_wallet( #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_destroy( handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { if let Some(manager) = PLATFORM_WALLET_MANAGER_STORAGE.remove(handle) { manager.platform_address_sync().stop(); } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/memory_explorer.rs b/packages/rs-platform-wallet-ffi/src/memory_explorer.rs index 1a760315242..7e20f7daed3 100644 --- a/packages/rs-platform-wallet-ffi/src/memory_explorer.rs +++ b/packages/rs-platform-wallet-ffi/src/memory_explorer.rs @@ -65,7 +65,7 @@ impl From for IdentityStatusFFI { pub unsafe extern "C" fn platform_wallet_list_in_memory_identity_ids( wallet_handle: Handle, out_array: *mut IdentifierArray, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_array); let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { @@ -82,7 +82,7 @@ pub unsafe extern "C" fn platform_wallet_list_in_memory_identity_ids( let inner = unwrap_option_or_return!(option); let ids = unwrap_option_or_return!(inner); unsafe { *out_array = IdentifierArray::new(ids) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// List the ids of every out-of-wallet / observed identity. @@ -90,7 +90,7 @@ pub unsafe extern "C" fn platform_wallet_list_in_memory_identity_ids( pub unsafe extern "C" fn platform_wallet_list_in_memory_watched_identity_ids( wallet_handle: Handle, out_array: *mut IdentifierArray, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_array); let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { @@ -107,7 +107,7 @@ pub unsafe extern "C" fn platform_wallet_list_in_memory_watched_identity_ids( let inner = unwrap_option_or_return!(option); let ids = unwrap_option_or_return!(inner); unsafe { *out_array = IdentifierArray::new(ids) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Populate `out` with a snapshot of the wallet's in-memory state. @@ -115,7 +115,7 @@ pub unsafe extern "C" fn platform_wallet_list_in_memory_watched_identity_ids( pub unsafe extern "C" fn platform_wallet_get_in_memory_summary( wallet_handle: Handle, out: *mut PlatformWalletMemorySummaryFFI, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out); let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { @@ -149,7 +149,7 @@ pub unsafe extern "C" fn platform_wallet_get_in_memory_summary( let inner = unwrap_option_or_return!(option); let summary = unwrap_option_or_return!(inner); unsafe { *out = summary }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Read the BIP-9 identity index recorded on a managed identity. @@ -158,7 +158,7 @@ pub unsafe extern "C" fn managed_identity_get_identity_index( identity_handle: Handle, out_has_index: *mut bool, out_index: *mut u32, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_has_index); check_ptr!(out_index); @@ -175,7 +175,7 @@ pub unsafe extern "C" fn managed_identity_get_identity_index( *out_index = 0; }, } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Read the lifecycle status of a managed identity. @@ -183,12 +183,12 @@ pub unsafe extern "C" fn managed_identity_get_identity_index( pub unsafe extern "C" fn managed_identity_get_status( identity_handle: Handle, out_status: *mut u8, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_status); let option = MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| { IdentityStatusFFI::from(identity.status) as u8 }); *out_status = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/platform_address_sync.rs b/packages/rs-platform-wallet-ffi/src/platform_address_sync.rs index c0be333c11b..1a97cce8b5b 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_address_sync.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_address_sync.rs @@ -80,25 +80,25 @@ impl Default for PlatformAddressSyncWalletResultFFI { #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_start( handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { let _entered = runtime().enter(); manager.platform_address_sync_arc().start(); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Stop the platform-address sync manager if it is running. #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_stop( handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { manager.platform_address_sync().stop(); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Whether the platform-address sync manager background loop is running. @@ -106,14 +106,14 @@ pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_stop( pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_is_running( handle: Handle, out_running: *mut bool, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_running); let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { manager.platform_address_sync().is_running() }); *out_running = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Whether a platform-address sync pass is currently in flight. @@ -121,14 +121,14 @@ pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_is_runnin pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_is_syncing( handle: Handle, out_syncing: *mut bool, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_syncing); let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { manager.platform_address_sync().is_syncing() }); *out_syncing = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Unix seconds of the last completed platform-address sync pass, or 0 if none ran. @@ -136,7 +136,7 @@ pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_is_syncin pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_last_sync_unix_seconds( handle: Handle, out_last_sync_unix: *mut u64, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_last_sync_unix); let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { @@ -146,7 +146,7 @@ pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_last_sync .unwrap_or(0) }); *out_last_sync_unix = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Set the background platform-address sync interval in seconds. @@ -154,14 +154,14 @@ pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_last_sync pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_set_interval( handle: Handle, interval_seconds: u64, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { manager .platform_address_sync() .set_interval(Duration::from_secs(interval_seconds)); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Replace the shared platform-address sync config used on each pass. @@ -171,7 +171,7 @@ pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_set_inter pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_set_config( handle: Handle, config: *const AddressSyncConfigFFI, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let cfg = if config.is_null() { None } else { @@ -181,17 +181,17 @@ pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_set_confi manager.platform_address_sync().set_config(cfg); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Run one platform-address sync pass across all registered wallets. #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_platform_address_sync_sync_now( handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { runtime().block_on(manager.platform_address_sync().sync_now()); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/platform_addresses/fund_from_asset_lock.rs b/packages/rs-platform-wallet-ffi/src/platform_addresses/fund_from_asset_lock.rs index cdc7c8859b8..97025b7d87f 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_addresses/fund_from_asset_lock.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_addresses/fund_from_asset_lock.rs @@ -25,7 +25,7 @@ pub unsafe extern "C" fn platform_address_wallet_fund_from_asset_lock( fee_strategy_count: usize, signer_address_handle: *mut SignerHandle, out_changeset: *mut PlatformAddressChangeSetFFI, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_changeset); check_ptr!(addresses); check_ptr!(asset_lock_proof_bytes); @@ -71,5 +71,5 @@ pub unsafe extern "C" fn platform_address_wallet_fund_from_asset_lock( let result = unwrap_option_or_return!(option); let changeset = unwrap_result_or_return!(result); *out_changeset = PlatformAddressChangeSetFFI::from(&changeset); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/platform_addresses/mod.rs b/packages/rs-platform-wallet-ffi/src/platform_addresses/mod.rs index 9d58e17ece4..d0a3cd724fd 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_addresses/mod.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_addresses/mod.rs @@ -23,7 +23,7 @@ use crate::runtime::runtime; /// Parse an `InputSelectionType` + raw arrays into a Rust `InputSelection`. /// -/// On error, returns the populated `PlatformWalletFfiResult` so the caller +/// On error, returns the populated `PlatformWalletFFIResult` so the caller /// can early-return it directly: /// `let sel = unwrap_result_or_return!(parse_input_selection(...));` /// @@ -35,14 +35,14 @@ pub(crate) unsafe fn parse_input_selection( explicit_inputs_count: usize, nonce_inputs: *const ExplicitInputWithNonceFFI, nonce_inputs_count: usize, -) -> Result { +) -> Result { match input_type { InputSelectionType::Auto => Ok(InputSelection::Auto), InputSelectionType::Explicit => { match parse_explicit_inputs(explicit_inputs, explicit_inputs_count) { Ok(m) => Ok(InputSelection::Explicit(m)), - Err(e) => Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + Err(e) => Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, e, )), } @@ -50,8 +50,8 @@ pub(crate) unsafe fn parse_input_selection( InputSelectionType::ExplicitWithNonces => { match parse_explicit_inputs_with_nonces(nonce_inputs, nonce_inputs_count) { Ok(m) => Ok(InputSelection::ExplicitWithNonces(m)), - Err(e) => Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + Err(e) => Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, e, )), } diff --git a/packages/rs-platform-wallet-ffi/src/platform_addresses/sync.rs b/packages/rs-platform-wallet-ffi/src/platform_addresses/sync.rs index d086fd7a88b..27fe3c47b7d 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_addresses/sync.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_addresses/sync.rs @@ -21,7 +21,7 @@ pub unsafe extern "C" fn platform_address_wallet_sync_balances( has_config: bool, config: *const AddressSyncConfigFFI, out_result: *mut AddressSyncResultFFI, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_result); let config_opt = if has_config && !config.is_null() { @@ -38,5 +38,5 @@ pub unsafe extern "C" fn platform_address_wallet_sync_balances( let result = unwrap_option_or_return!(option); let sync = unwrap_result_or_return!(result); *out_result = AddressSyncResultFFI::from(&sync); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/platform_addresses/transfer.rs b/packages/rs-platform-wallet-ffi/src/platform_addresses/transfer.rs index 294df2e4ac3..a8660d37227 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_addresses/transfer.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_addresses/transfer.rs @@ -34,7 +34,7 @@ pub unsafe extern "C" fn platform_address_wallet_transfer( fee_strategy_count: usize, signer_address_handle: *mut SignerHandle, out_changeset: *mut PlatformAddressChangeSetFFI, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_changeset); check_ptr!(signer_address_handle); @@ -67,5 +67,5 @@ pub unsafe extern "C" fn platform_address_wallet_transfer( let result = unwrap_option_or_return!(option); let changeset = unwrap_result_or_return!(result); *out_changeset = PlatformAddressChangeSetFFI::from(&changeset); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/platform_addresses/wallet.rs b/packages/rs-platform-wallet-ffi/src/platform_addresses/wallet.rs index 6784f56158d..89df3884151 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_addresses/wallet.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_addresses/wallet.rs @@ -16,9 +16,9 @@ use super::runtime; #[no_mangle] pub unsafe extern "C" fn platform_address_wallet_destroy( handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { PLATFORM_ADDRESS_WALLET_STORAGE.remove(handle); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Add a provider for a new account index. @@ -26,13 +26,13 @@ pub unsafe extern "C" fn platform_address_wallet_destroy( pub unsafe extern "C" fn platform_address_wallet_add_provider( handle: Handle, account_index: u32, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = PLATFORM_ADDRESS_WALLET_STORAGE.with_item(handle, |wallet| { runtime().block_on(wallet.add_provider(account_index)) }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Restore sync state from persisted values. @@ -46,7 +46,7 @@ pub unsafe extern "C" fn platform_address_wallet_restore_sync_state( sync_height: u64, sync_timestamp: u64, last_known_recent_block: u64, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = PLATFORM_ADDRESS_WALLET_STORAGE.with_item(handle, |wallet| { runtime().block_on(wallet.restore_sync_state( sync_height, @@ -55,7 +55,7 @@ pub unsafe extern "C" fn platform_address_wallet_restore_sync_state( )); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } // --------------------------------------------------------------------------- @@ -67,13 +67,13 @@ pub unsafe extern "C" fn platform_address_wallet_restore_sync_state( pub unsafe extern "C" fn platform_address_wallet_total_credits( handle: Handle, out_credits: *mut u64, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_credits); let option = PLATFORM_ADDRESS_WALLET_STORAGE .with_item(handle, |wallet| runtime().block_on(wallet.total_credits())); *out_credits = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get all platform addresses with their cached balances. @@ -85,7 +85,7 @@ pub unsafe extern "C" fn platform_address_wallet_addresses_with_balances( handle: Handle, out_entries: *mut *mut AddressBalanceEntryFFI, out_count: *mut usize, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_entries); check_ptr!(out_count); @@ -109,7 +109,7 @@ pub unsafe extern "C" fn platform_address_wallet_addresses_with_balances( } else { *out_entries = Box::into_raw(entries.into_boxed_slice()) as *mut AddressBalanceEntryFFI; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } // --------------------------------------------------------------------------- diff --git a/packages/rs-platform-wallet-ffi/src/platform_addresses/withdrawal.rs b/packages/rs-platform-wallet-ffi/src/platform_addresses/withdrawal.rs index c3c30755aeb..1a5fa193b4e 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_addresses/withdrawal.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_addresses/withdrawal.rs @@ -28,7 +28,7 @@ pub unsafe extern "C" fn platform_address_wallet_withdraw( fee_strategy_count: usize, signer_address_handle: *mut SignerHandle, out_changeset: *mut PlatformAddressChangeSetFFI, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_changeset); check_ptr!(output_script); check_ptr!(signer_address_handle); @@ -62,5 +62,5 @@ pub unsafe extern "C" fn platform_address_wallet_withdraw( let result = unwrap_option_or_return!(option); let changeset = unwrap_result_or_return!(result); *out_changeset = PlatformAddressChangeSetFFI::from(&changeset); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/platform_wallet_info.rs b/packages/rs-platform-wallet-ffi/src/platform_wallet_info.rs index f6988be33f5..fe000456247 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_wallet_info.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_wallet_info.rs @@ -17,7 +17,7 @@ pub unsafe extern "C" fn platform_wallet_info_create_from_seed( seed_bytes: *const c_uchar, seed_len: usize, out_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(seed_bytes); check_ptr!(out_handle); @@ -27,8 +27,8 @@ pub unsafe extern "C" fn platform_wallet_info_create_from_seed( 2 => Network::Devnet, 3 => Network::Regtest, _ => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidNetwork, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidNetwork, format!("Unknown network: {network}"), ); } @@ -36,8 +36,8 @@ pub unsafe extern "C" fn platform_wallet_info_create_from_seed( // Validate seed length (should be 64 bytes for BIP39) if seed_len != 64 { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, format!("Invalid seed length: expected 64 bytes, got {seed_len}"), ); } @@ -60,7 +60,7 @@ pub unsafe extern "C" fn platform_wallet_info_create_from_seed( let handle = WALLET_INFO_STORAGE.insert(platform_wallet); unsafe { *out_handle = handle }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Create a new PlatformWalletInfo from mnemonic. @@ -70,7 +70,7 @@ pub unsafe extern "C" fn platform_wallet_info_create_from_mnemonic( mnemonic: *const c_char, passphrase: *const c_char, out_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(mnemonic); check_ptr!(out_handle); @@ -80,8 +80,8 @@ pub unsafe extern "C" fn platform_wallet_info_create_from_mnemonic( 2 => Network::Devnet, 3 => Network::Regtest, _ => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidNetwork, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidNetwork, format!("Unknown network: {network}"), ); } @@ -123,7 +123,7 @@ pub unsafe extern "C" fn platform_wallet_info_create_from_mnemonic( let handle = WALLET_INFO_STORAGE.insert(platform_wallet); unsafe { *out_handle = handle }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get the identity manager @@ -131,7 +131,7 @@ pub unsafe extern "C" fn platform_wallet_info_create_from_mnemonic( pub unsafe extern "C" fn platform_wallet_info_get_identity_manager( wallet_handle: Handle, out_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_handle); let option = WALLET_INFO_STORAGE.with_item(wallet_handle, |wallet_info| { @@ -140,7 +140,7 @@ pub unsafe extern "C" fn platform_wallet_info_get_identity_manager( let manager = unwrap_option_or_return!(option); let handle = IDENTITY_MANAGER_STORAGE.insert(manager); unsafe { *out_handle = handle }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Set the identity manager @@ -148,7 +148,7 @@ pub unsafe extern "C" fn platform_wallet_info_get_identity_manager( pub unsafe extern "C" fn platform_wallet_info_set_identity_manager( wallet_handle: Handle, manager_handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let manager_option = IDENTITY_MANAGER_STORAGE.with_item(manager_handle, |manager| manager.clone()); let manager = unwrap_option_or_return!(manager_option); @@ -157,19 +157,19 @@ pub unsafe extern "C" fn platform_wallet_info_set_identity_manager( wallet_info.identity_manager = manager; }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Destroy PlatformWalletInfo and free resources #[no_mangle] pub unsafe extern "C" fn platform_wallet_info_destroy( wallet_handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { if WALLET_INFO_STORAGE.remove(wallet_handle).is_some() { - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } else { - PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidHandle, + PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidHandle, "Invalid wallet handle", ) } @@ -192,7 +192,7 @@ mod tests { &mut handle, ); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_ne!(handle, NULL_HANDLE); platform_wallet_info_destroy(handle); @@ -215,7 +215,7 @@ mod tests { &mut handle, ); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_ne!(handle, NULL_HANDLE); platform_wallet_info_destroy(handle); @@ -226,7 +226,7 @@ mod tests { fn test_destroy_invalid_handle() { unsafe { let result = platform_wallet_info_destroy(9999); - assert_eq!(result.code, PlatformWalletFfiResultCode::ErrorInvalidHandle); + assert_eq!(result.code, PlatformWalletFFIResultCode::ErrorInvalidHandle); } } } diff --git a/packages/rs-platform-wallet-ffi/src/spv.rs b/packages/rs-platform-wallet-ffi/src/spv.rs index a9949fdc417..fc1efb250e7 100644 --- a/packages/rs-platform-wallet-ffi/src/spv.rs +++ b/packages/rs-platform-wallet-ffi/src/spv.rs @@ -134,7 +134,7 @@ fn progress_to_ffi(p: &SyncProgress) -> FFISpvSyncProgress { pub unsafe extern "C" fn platform_wallet_manager_sync_progress( handle: Handle, out_progress: *mut FFISpvSyncProgress, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_progress); let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { @@ -145,7 +145,7 @@ pub unsafe extern "C" fn platform_wallet_manager_sync_progress( Some(p) => progress_to_ffi(&p), None => FFISpvSyncProgress::default(), }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Whether the SPV client is currently running. @@ -153,12 +153,12 @@ pub unsafe extern "C" fn platform_wallet_manager_sync_progress( pub unsafe extern "C" fn platform_wallet_manager_spv_is_running( handle: Handle, out_running: *mut bool, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_running); let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| manager.spv().is_started()); *out_running = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Start SPV sync in the background. @@ -174,7 +174,7 @@ pub unsafe extern "C" fn platform_wallet_manager_spv_start( restrict_to_configured_peers: bool, start_from_height: u32, masternode_sync_enabled: bool, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(data_dir); let data_dir_str = unwrap_result_or_return!(CStr::from_ptr(data_dir).to_str()).to_string(); let user_agent_str = if user_agent.is_null() { @@ -189,8 +189,8 @@ pub unsafe extern "C" fn platform_wallet_manager_spv_start( 2 => dashcore::Network::Devnet, 3 => dashcore::Network::Regtest, _ => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidNetwork, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidNetwork, format!("Unknown network: {network}"), ); } @@ -231,32 +231,32 @@ pub unsafe extern "C" fn platform_wallet_manager_spv_start( manager.spv_arc().spawn_in_background(config); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Stop the SPV client. #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_spv_stop( handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { runtime().block_on(async { let _ = manager.spv().stop().await; }); }); unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Clear all persisted SPV storage (headers, filters, state). #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_spv_clear_storage( handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { runtime().block_on(manager.spv().clear_storage()) }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/burn.rs b/packages/rs-platform-wallet-ffi/src/tokens/burn.rs index 4ed96981fe9..843d5bfecc5 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/burn.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/burn.rs @@ -30,7 +30,7 @@ pub unsafe extern "C" fn platform_wallet_token_burn( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); let id = unwrap_result_or_return!(read_identifier(identity_id)); @@ -77,5 +77,5 @@ pub unsafe extern "C" fn platform_wallet_token_burn( }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/claim.rs b/packages/rs-platform-wallet-ffi/src/tokens/claim.rs index cf43ce328a1..b4d750ecf7f 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/claim.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/claim.rs @@ -36,7 +36,7 @@ pub unsafe extern "C" fn platform_wallet_token_claim( public_note: *const c_char, _signing_key_id: u32, signer_handle: *mut SignerHandle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); let id = unwrap_result_or_return!(read_identifier(identity_id)); @@ -46,8 +46,8 @@ pub unsafe extern "C" fn platform_wallet_token_claim( 0 => TokenDistributionType::PreProgrammed, 1 => TokenDistributionType::Perpetual, other => { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, format!("Invalid distribution_type: {other} (expected 0 or 1)"), ); } @@ -86,5 +86,5 @@ pub unsafe extern "C" fn platform_wallet_token_claim( }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/destroy_frozen_funds.rs b/packages/rs-platform-wallet-ffi/src/tokens/destroy_frozen_funds.rs index 3ae20ae32e8..34456c68615 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/destroy_frozen_funds.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/destroy_frozen_funds.rs @@ -29,7 +29,7 @@ pub unsafe extern "C" fn platform_wallet_token_destroy_frozen_funds( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); let id = unwrap_result_or_return!(read_identifier(identity_id)); @@ -77,5 +77,5 @@ pub unsafe extern "C" fn platform_wallet_token_destroy_frozen_funds( }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/freeze.rs b/packages/rs-platform-wallet-ffi/src/tokens/freeze.rs index 0866dad6e77..fe9509ba6a9 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/freeze.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/freeze.rs @@ -30,7 +30,7 @@ pub unsafe extern "C" fn platform_wallet_token_freeze( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); let id = unwrap_result_or_return!(read_identifier(identity_id)); @@ -78,5 +78,5 @@ pub unsafe extern "C" fn platform_wallet_token_freeze( }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/group_info.rs b/packages/rs-platform-wallet-ffi/src/tokens/group_info.rs index 74989b01fe8..78595b5050c 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/group_info.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/group_info.rs @@ -14,13 +14,13 @@ use dpp::group::{GroupStateTransitionInfo, GroupStateTransitionInfoStatus}; -use crate::error::{PlatformWalletFfiResult, PlatformWalletFfiResultCode}; +use crate::error::{PlatformWalletFFIResult, PlatformWalletFFIResultCode}; use crate::types::read_identifier; /// Decode a flat `(kind, position, action_id, action_is_proposer)` /// tuple from an FFI caller into an `Option`. /// -/// On error returns `Err(PlatformWalletFfiResult)` carrying the FFI +/// On error returns `Err(PlatformWalletFFIResult)` carrying the FFI /// error the caller should bubble up. /// /// # Safety @@ -31,7 +31,7 @@ pub(crate) unsafe fn decode_group_info( position: u16, action_id: *const u8, action_is_proposer: bool, -) -> Result, PlatformWalletFfiResult> { +) -> Result, PlatformWalletFFIResult> { match kind { 0 => Ok(None), 1 => Ok(Some( @@ -39,8 +39,8 @@ pub(crate) unsafe fn decode_group_info( )), 2 => { if action_id.is_null() { - return Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorNullPointer, + return Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorNullPointer, "group_info_action_id is null but kind == 2 (other-signer)", )); } @@ -55,8 +55,8 @@ pub(crate) unsafe fn decode_group_info( ), )) } - other => Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + other => Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, format!("Invalid group_info_kind: {other} (expected 0, 1, or 2)"), )), } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/group_queries.rs b/packages/rs-platform-wallet-ffi/src/tokens/group_queries.rs index 4d21c09d2ca..ee73e415d30 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/group_queries.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/group_queries.rs @@ -33,12 +33,12 @@ use crate::types::read_identifier; use crate::{unwrap_option_or_return, unwrap_result_or_return}; /// Decode a raw `u8` status from the FFI caller into the rs-dpp enum. -fn decode_status(status: u8) -> Result { +fn decode_status(status: u8) -> Result { match status { 0 => Ok(GroupActionStatus::ActionActive), 1 => Ok(GroupActionStatus::ActionClosed), - other => Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + other => Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, format!("Invalid group action status: {other} (expected 0 or 1)"), )), } @@ -164,11 +164,11 @@ fn render_signer_entry(entry: &GroupActionSignerEntry) -> Value { /// Allocate a JSON `Value` as a NUL-terminated C string and write it /// to `out_json`. Caller frees with `platform_wallet_free_string`. -unsafe fn emit_json(value: Value, out_json: *mut *mut c_char) -> PlatformWalletFfiResult { +unsafe fn emit_json(value: Value, out_json: *mut *mut c_char) -> PlatformWalletFFIResult { let serialized = unwrap_result_or_return!(serde_json::to_string(&value)); let cstring = unwrap_result_or_return!(std::ffi::CString::new(serialized)); *out_json = cstring.into_raw(); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Fetch group-action proposals on `(token_contract_id, group_contract_position)` @@ -214,7 +214,7 @@ pub unsafe extern "C" fn platform_wallet_token_pending_group_actions( start_at_action_id: *const u8, limit: u16, out_json: *mut *mut c_char, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_json); *out_json = std::ptr::null_mut(); @@ -264,7 +264,7 @@ pub unsafe extern "C" fn platform_wallet_token_group_action_signers( status: u8, action_id: *const u8, out_json: *mut *mut c_char, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_json); *out_json = std::ptr::null_mut(); diff --git a/packages/rs-platform-wallet-ffi/src/tokens/mint.rs b/packages/rs-platform-wallet-ffi/src/tokens/mint.rs index 63d92d72566..50339aaf137 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/mint.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/mint.rs @@ -30,7 +30,7 @@ pub unsafe extern "C" fn platform_wallet_token_mint( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); let id = unwrap_result_or_return!(read_identifier(identity_id)); @@ -86,5 +86,5 @@ pub unsafe extern "C" fn platform_wallet_token_mint( }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/pause.rs b/packages/rs-platform-wallet-ffi/src/tokens/pause.rs index 5818c7780a6..5fb63e5caa1 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/pause.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/pause.rs @@ -28,7 +28,7 @@ pub unsafe extern "C" fn platform_wallet_token_pause( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); let id = unwrap_result_or_return!(read_identifier(identity_id)); @@ -74,5 +74,5 @@ pub unsafe extern "C" fn platform_wallet_token_pause( }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/purchase.rs b/packages/rs-platform-wallet-ffi/src/tokens/purchase.rs index 1bc7e7a9dcb..e06696c9029 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/purchase.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/purchase.rs @@ -21,7 +21,7 @@ pub unsafe extern "C" fn platform_wallet_token_purchase( expected_total_cost: u64, _signing_key_id: u32, signer_handle: *mut SignerHandle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); let id = unwrap_result_or_return!(read_identifier(identity_id)); @@ -47,5 +47,5 @@ pub unsafe extern "C" fn platform_wallet_token_purchase( }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/resume.rs b/packages/rs-platform-wallet-ffi/src/tokens/resume.rs index 7df0eed35b5..a64e50ce7ea 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/resume.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/resume.rs @@ -28,7 +28,7 @@ pub unsafe extern "C" fn platform_wallet_token_resume( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); let id = unwrap_result_or_return!(read_identifier(identity_id)); @@ -74,5 +74,5 @@ pub unsafe extern "C" fn platform_wallet_token_resume( }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/set_price.rs b/packages/rs-platform-wallet-ffi/src/tokens/set_price.rs index 11d12d54a71..9e5b7965a95 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/set_price.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/set_price.rs @@ -30,7 +30,7 @@ pub unsafe extern "C" fn platform_wallet_token_set_price( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); let id = unwrap_result_or_return!(read_identifier(identity_id)); @@ -77,5 +77,5 @@ pub unsafe extern "C" fn platform_wallet_token_set_price( }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/transfer.rs b/packages/rs-platform-wallet-ffi/src/tokens/transfer.rs index a734e2435a3..24833272118 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/transfer.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/transfer.rs @@ -25,7 +25,7 @@ pub unsafe extern "C" fn platform_wallet_token_transfer( public_note: *const c_char, _signing_key_id: u32, signer_handle: *mut SignerHandle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); let from_id = unwrap_result_or_return!(read_identifier(identity_id)); @@ -66,5 +66,5 @@ pub unsafe extern "C" fn platform_wallet_token_transfer( }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/unfreeze.rs b/packages/rs-platform-wallet-ffi/src/tokens/unfreeze.rs index b5efeba4023..c32223aec4c 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/unfreeze.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/unfreeze.rs @@ -29,7 +29,7 @@ pub unsafe extern "C" fn platform_wallet_token_unfreeze( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); let id = unwrap_result_or_return!(read_identifier(identity_id)); @@ -77,5 +77,5 @@ pub unsafe extern "C" fn platform_wallet_token_unfreeze( }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/tokens/update_config.rs b/packages/rs-platform-wallet-ffi/src/tokens/update_config.rs index 7999e0ea702..de6fb18ff47 100644 --- a/packages/rs-platform-wallet-ffi/src/tokens/update_config.rs +++ b/packages/rs-platform-wallet-ffi/src/tokens/update_config.rs @@ -20,11 +20,11 @@ const TAG_MAX_SUPPLY: u8 = 0; unsafe fn decode_change_item( tag: u8, payload_json: *const c_char, -) -> Result { +) -> Result { match tag { TAG_MAX_SUPPLY => decode_max_supply_payload(payload_json), - other => Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + other => Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, format!( "change_item_tag {other} not yet supported by FFI (only MaxSupply = 0 is wired in this release)" ), @@ -34,10 +34,10 @@ unsafe fn decode_change_item( unsafe fn decode_max_supply_payload( payload_json: *const c_char, -) -> Result { +) -> Result { if payload_json.is_null() { - return Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorNullPointer, + return Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorNullPointer, "change_item_payload_json is null (expected JSON object for MaxSupply)", )); } @@ -47,8 +47,8 @@ unsafe fn decode_max_supply_payload( let new_max_supply_field = match parsed.get("newMaxSupply") { Some(v) => v, None => { - return Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, "MaxSupply payload missing required field 'newMaxSupply'", )); } @@ -57,14 +57,14 @@ unsafe fn decode_max_supply_payload( let new_max_supply: Option = match new_max_supply_field { Value::Null => None, Value::String(s) => Some(s.parse::().map_err(|e| { - PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, format!("'newMaxSupply' is not a valid u64: {e}"), ) })?), _ => { - return Err(PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return Err(PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, "'newMaxSupply' must be a string-encoded u64 or null", )); } @@ -90,7 +90,7 @@ pub unsafe extern "C" fn platform_wallet_token_update_config( group_info_action_is_proposer: bool, _signing_key_id: u32, signer_handle: *mut SignerHandle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(signer_handle); let id = unwrap_result_or_return!(read_identifier(identity_id)); @@ -142,7 +142,7 @@ pub unsafe extern "C" fn platform_wallet_token_update_config( }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } #[cfg(test)] @@ -183,7 +183,7 @@ mod tests { let payload = cstr(r#"{}"#); let result = decode_change_item(TAG_MAX_SUPPLY, payload.as_ptr()); match result { - Err(r) if r.code == PlatformWalletFfiResultCode::ErrorInvalidParameter => {} + Err(r) if r.code == PlatformWalletFFIResultCode::ErrorInvalidParameter => {} _ => panic!("expected ErrorInvalidParameter"), } } @@ -195,7 +195,7 @@ mod tests { let payload = cstr(r#"{}"#); let result = decode_change_item(1, payload.as_ptr()); match result { - Err(r) if r.code == PlatformWalletFfiResultCode::ErrorInvalidParameter => {} + Err(r) if r.code == PlatformWalletFFIResultCode::ErrorInvalidParameter => {} _ => panic!("expected ErrorInvalidParameter for unsupported tag"), } } @@ -207,7 +207,7 @@ mod tests { let payload = cstr("not json"); let result = decode_change_item(TAG_MAX_SUPPLY, payload.as_ptr()); match result { - Err(r) if r.code == PlatformWalletFfiResultCode::ErrorDeserialization => {} + Err(r) if r.code == PlatformWalletFFIResultCode::ErrorDeserialization => {} _ => panic!("expected ErrorDeserialization"), } } diff --git a/packages/rs-platform-wallet-ffi/src/utils.rs b/packages/rs-platform-wallet-ffi/src/utils.rs index 7f6b96bb1c5..b9e9a999bbf 100644 --- a/packages/rs-platform-wallet-ffi/src/utils.rs +++ b/packages/rs-platform-wallet-ffi/src/utils.rs @@ -8,7 +8,7 @@ pub unsafe extern "C" fn platform_wallet_serialize_to_json_bytes( json_string: *const c_char, out_bytes: *mut *mut c_uchar, out_len: *mut usize, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(json_string); check_ptr!(out_bytes); check_ptr!(out_len); @@ -26,7 +26,7 @@ pub unsafe extern "C" fn platform_wallet_serialize_to_json_bytes( *out_len = len; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Deserialize JSON bytes to string @@ -35,7 +35,7 @@ pub unsafe extern "C" fn platform_wallet_deserialize_from_json_bytes( bytes: *const c_uchar, len: usize, out_json_string: *mut *mut c_char, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(bytes); check_ptr!(out_json_string); @@ -43,7 +43,7 @@ pub unsafe extern "C" fn platform_wallet_deserialize_from_json_bytes( let s = unwrap_result_or_return!(std::str::from_utf8(data)); let c_str = unwrap_result_or_return!(std::ffi::CString::new(s)); unsafe { *out_json_string = c_str.into_raw() }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Free bytes allocated by FFI functions @@ -60,11 +60,11 @@ pub unsafe extern "C" fn platform_wallet_bytes_free(bytes: *mut c_uchar, len: us #[no_mangle] pub unsafe extern "C" fn platform_wallet_generate_random_identifier( out_id: *mut u8, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_id); let id = dpp::prelude::Identifier::random(); unsafe { crate::types::write_identifier(out_id, &id) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Convert identifier (32 bytes pointed to by `id`) to base58 hex @@ -73,7 +73,7 @@ pub unsafe extern "C" fn platform_wallet_generate_random_identifier( pub unsafe extern "C" fn platform_wallet_identifier_to_hex( id: *const u8, out_hex: *mut *mut c_char, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(id); check_ptr!(out_hex); @@ -82,7 +82,7 @@ pub unsafe extern "C" fn platform_wallet_identifier_to_hex( let hex = identifier.to_string(dpp::platform_value::string_encoding::Encoding::Base58); let c_str = unwrap_result_or_return!(std::ffi::CString::new(hex)); unsafe { *out_hex = c_str.into_raw() }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Convert base58 hex string to identifier (writes 32 bytes into @@ -91,7 +91,7 @@ pub unsafe extern "C" fn platform_wallet_identifier_to_hex( pub unsafe extern "C" fn platform_wallet_identifier_from_hex( hex: *const c_char, out_id: *mut u8, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(hex); check_ptr!(out_id); @@ -102,7 +102,7 @@ pub unsafe extern "C" fn platform_wallet_identifier_from_hex( dpp::platform_value::string_encoding::Encoding::Base58, )); unsafe { crate::types::write_identifier(out_id, &identifier) }; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Compute hash160 (RIPEMD160(SHA256(data))) of the input bytes. @@ -193,13 +193,13 @@ mod tests { let result = platform_wallet_serialize_to_json_bytes(json.as_ptr(), &mut bytes, &mut len); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert!(!bytes.is_null()); assert!(len > 0); let mut json_out: *mut c_char = std::ptr::null_mut(); let result = platform_wallet_deserialize_from_json_bytes(bytes, len, &mut json_out); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert!(!json_out.is_null()); let json_str = std::ffi::CStr::from_ptr(json_out).to_str().unwrap(); @@ -215,7 +215,7 @@ mod tests { unsafe { let mut id = [0u8; 32]; let result = platform_wallet_generate_random_identifier(id.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_ne!(id, [0u8; 32]); } } @@ -228,12 +228,12 @@ mod tests { let mut hex: *mut c_char = std::ptr::null_mut(); let result = platform_wallet_identifier_to_hex(id.as_ptr(), &mut hex); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert!(!hex.is_null()); let mut id2 = [0u8; 32]; let result = platform_wallet_identifier_from_hex(hex, id2.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(id, id2); diff --git a/packages/rs-platform-wallet-ffi/src/wallet.rs b/packages/rs-platform-wallet-ffi/src/wallet.rs index 01b23730af6..b4a8d3e5180 100644 --- a/packages/rs-platform-wallet-ffi/src/wallet.rs +++ b/packages/rs-platform-wallet-ffi/src/wallet.rs @@ -10,12 +10,12 @@ use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; pub unsafe extern "C" fn platform_wallet_get_id( handle: Handle, out_wallet_id: *mut [u8; 32], -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_wallet_id); let option = PLATFORM_WALLET_STORAGE.with_item(handle, |wallet| wallet.wallet_id()); *out_wallet_id = unwrap_option_or_return!(option); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get lock-free balance (spendable, unconfirmed, immature, locked). @@ -28,7 +28,7 @@ pub unsafe extern "C" fn platform_wallet_get_balance( out_unconfirmed: *mut u64, out_immature: *mut u64, out_locked: *mut u64, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = PLATFORM_WALLET_STORAGE.with_item(handle, |wallet| { let b = wallet.balance(); (b.confirmed(), b.unconfirmed(), b.immature(), b.locked()) @@ -46,7 +46,7 @@ pub unsafe extern "C" fn platform_wallet_get_balance( if !out_locked.is_null() { *out_locked = locked; } - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get a PlatformAddressWallet handle from a PlatformWallet. @@ -56,13 +56,13 @@ pub unsafe extern "C" fn platform_wallet_get_balance( pub unsafe extern "C" fn platform_wallet_get_platform( handle: Handle, out_platform_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_platform_handle); let option = PLATFORM_WALLET_STORAGE.with_item(handle, |wallet| wallet.platform().clone()); let platform_wallet = unwrap_option_or_return!(option); *out_platform_handle = PLATFORM_ADDRESS_WALLET_STORAGE.insert(platform_wallet); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get an AssetLockManager handle from a PlatformWallet. @@ -70,14 +70,14 @@ pub unsafe extern "C" fn platform_wallet_get_platform( pub unsafe extern "C" fn platform_wallet_get_asset_locks( handle: Handle, out_asset_lock_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_asset_lock_handle); let option = PLATFORM_WALLET_STORAGE .with_item(handle, |wallet| std::sync::Arc::clone(wallet.asset_locks())); let asset_locks = unwrap_option_or_return!(option); *out_asset_lock_handle = ASSET_LOCK_MANAGER_STORAGE.insert(asset_locks); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Get a CoreWallet handle from a PlatformWallet. @@ -87,35 +87,35 @@ pub unsafe extern "C" fn platform_wallet_get_asset_locks( pub unsafe extern "C" fn platform_wallet_get_core( handle: Handle, out_core_handle: *mut Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(out_core_handle); let option = PLATFORM_WALLET_STORAGE.with_item(handle, |wallet| wallet.core().clone()); let core_wallet = unwrap_option_or_return!(option); *out_core_handle = CORE_WALLET_STORAGE.insert(core_wallet); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Flush all queued changesets to the storage backend. #[no_mangle] -pub unsafe extern "C" fn platform_wallet_flush_persist(handle: Handle) -> PlatformWalletFfiResult { +pub unsafe extern "C" fn platform_wallet_flush_persist(handle: Handle) -> PlatformWalletFFIResult { let option = PLATFORM_WALLET_STORAGE.with_item(handle, |wallet| wallet.flush_persist()); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Load persisted state and apply it to the in-memory wallet. #[no_mangle] pub unsafe extern "C" fn platform_wallet_load_and_apply_persisted( handle: Handle, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { let option = PLATFORM_WALLET_STORAGE.with_item(handle, |wallet| { runtime().block_on(wallet.load_and_apply_persisted()) }); let result = unwrap_option_or_return!(option); unwrap_result_or_return!(result); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Query per-account balances from the in-memory `WalletManager`. @@ -137,7 +137,7 @@ pub unsafe extern "C" fn platform_wallet_manager_get_account_balances( wallet_id: *const u8, out_entries: *mut *const crate::core_wallet_types::AccountBalanceEntryFFI, out_count: *mut usize, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(wallet_id); check_ptr!(out_entries); check_ptr!(out_count); @@ -173,13 +173,13 @@ pub unsafe extern "C" fn platform_wallet_manager_get_account_balances( if count == 0 { *out_entries = std::ptr::null(); *out_count = 0; - return PlatformWalletFfiResult::ok(); + return PlatformWalletFFIResult::ok(); } let boxed = entries.into_boxed_slice(); *out_entries = Box::into_raw(boxed) as *const _; *out_count = count; - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Free an array returned by [`platform_wallet_manager_get_account_balances`]. @@ -195,7 +195,7 @@ pub unsafe extern "C" fn platform_wallet_manager_free_account_balances( /// Destroy a PlatformWallet handle. #[no_mangle] -pub unsafe extern "C" fn platform_wallet_destroy(handle: Handle) -> PlatformWalletFfiResult { +pub unsafe extern "C" fn platform_wallet_destroy(handle: Handle) -> PlatformWalletFFIResult { PLATFORM_WALLET_STORAGE.remove(handle); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/xpub_render.rs b/packages/rs-platform-wallet-ffi/src/xpub_render.rs index e7d6e79d8f5..a92f8ce17ed 100644 --- a/packages/rs-platform-wallet-ffi/src/xpub_render.rs +++ b/packages/rs-platform-wallet-ffi/src/xpub_render.rs @@ -8,7 +8,7 @@ use std::ffi::CString; use std::os::raw::c_char; use std::ptr; -use crate::error::{PlatformWalletFfiResult, PlatformWalletFfiResultCode}; +use crate::error::{PlatformWalletFFIResult, PlatformWalletFFIResultCode}; use crate::{check_ptr, unwrap_result_or_return}; /// Decode a bincode-encoded `ExtendedPubKey` (as emitted by @@ -24,12 +24,12 @@ pub unsafe extern "C" fn platform_wallet_account_xpub_to_string( bytes: *const u8, bytes_len: usize, out_string: *mut *mut c_char, -) -> PlatformWalletFfiResult { +) -> PlatformWalletFFIResult { check_ptr!(bytes); check_ptr!(out_string); if bytes_len == 0 { - return PlatformWalletFfiResult::err( - PlatformWalletFfiResultCode::ErrorInvalidParameter, + return PlatformWalletFFIResult::err( + PlatformWalletFFIResultCode::ErrorInvalidParameter, "bytes_len is zero", ); } @@ -42,7 +42,7 @@ pub unsafe extern "C" fn platform_wallet_account_xpub_to_string( let cstring = unwrap_result_or_return!(CString::new(xpub.to_string())); *out_string = cstring.into_raw(); - PlatformWalletFfiResult::ok() + PlatformWalletFFIResult::ok() } /// Free a C string returned by [`platform_wallet_account_xpub_to_string`]. diff --git a/packages/rs-platform-wallet-ffi/tests/comprehensive_tests.rs b/packages/rs-platform-wallet-ffi/tests/comprehensive_tests.rs index b844affb998..3936adf9a5d 100644 --- a/packages/rs-platform-wallet-ffi/tests/comprehensive_tests.rs +++ b/packages/rs-platform-wallet-ffi/tests/comprehensive_tests.rs @@ -8,7 +8,7 @@ //! sweep for the rationale. //! //! Also updated to the unified-result FFI ABI: every entry point returns -//! a `PlatformWalletFfiResult` (with `.code` + `.message`) instead of +//! a `PlatformWalletFFIResult` (with `.code` + `.message`) instead of //! taking a separate `&mut PlatformWalletFFIError` out-parameter. Storage //! misses surface as `NotFound` via `unwrap_option_or_return!`; only the //! handful of destroy entry points still return `ErrorInvalidHandle` @@ -39,44 +39,44 @@ fn test_contact_request_field_access() { bob_id_bytes.as_ptr(), &mut request_handle, ); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_ne!(request_handle, NULL_HANDLE); // Verify sender ID let mut sender_id = [0u8; 32]; let result = contact_request_get_sender_id(request_handle, sender_id.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(sender_id, [1u8; 32]); // Alice's ID // Verify recipient ID let mut recipient_id = [0u8; 32]; let result = contact_request_get_recipient_id(request_handle, recipient_id.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(recipient_id, [2u8; 32]); // Bob's ID // Verify sender key index let mut sender_key_idx = 999u32; let result = contact_request_get_sender_key_index(request_handle, &mut sender_key_idx); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(sender_key_idx, 0); // Verify recipient key index let mut recipient_key_idx = 999u32; let result = contact_request_get_recipient_key_index(request_handle, &mut recipient_key_idx); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(recipient_key_idx, 1); // Verify account reference let mut account_ref = 999u32; let result = contact_request_get_account_reference(request_handle, &mut account_ref); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(account_ref, 0); // Verify timestamp let mut created_at = 0u64; let result = contact_request_get_created_at(request_handle, &mut created_at); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(created_at, 1_700_000_000); // Verify encrypted public key @@ -84,7 +84,7 @@ fn test_contact_request_field_access() { let mut len: usize = 0; let result = contact_request_get_encrypted_public_key(request_handle, &mut bytes_ptr, &mut len); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert!(!bytes_ptr.is_null()); assert_eq!(len, 96); @@ -112,18 +112,18 @@ fn test_incoming_contact_request_retrieval() { bob_id_bytes.as_ptr(), &mut request_handle, ); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_ne!(request_handle, NULL_HANDLE); // Verify it's from Bob to Alice let mut sender_id = [0u8; 32]; let result = contact_request_get_sender_id(request_handle, sender_id.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(sender_id, [2u8; 32]); // Bob's ID let mut recipient_id = [0u8; 32]; let result = contact_request_get_recipient_id(request_handle, recipient_id.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(recipient_id, [1u8; 32]); // Alice's ID // Cleanup @@ -145,7 +145,7 @@ fn test_multiple_contact_requests() { count: 0, }; let result = managed_identity_get_sent_contact_request_ids(alice_handle, &mut array); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(array.count, 3); // Verify we can retrieve each request @@ -159,7 +159,7 @@ fn test_multiple_contact_requests() { row_ptr, &mut request_handle, ); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_ne!(request_handle, NULL_HANDLE); contact_request_destroy(request_handle); @@ -184,7 +184,7 @@ fn test_established_contacts() { count: 0, }; let result = managed_identity_get_established_contact_ids(alice_handle, &mut array); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(array.count, 2); // Bob and Carol // Check if Bob is established @@ -195,7 +195,7 @@ fn test_established_contacts() { bob_id_bytes.as_ptr(), &mut is_established, ); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert!(is_established); // Check if Dave is NOT established @@ -206,7 +206,7 @@ fn test_established_contacts() { dave_id_bytes.as_ptr(), &mut is_established, ); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert!(!is_established); // Cleanup @@ -262,7 +262,7 @@ fn test_identity_manager_with_multiple_identities() { // Create identity manager let mut manager_handle: Handle = NULL_HANDLE; let result = identity_manager_create(&mut manager_handle); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Add Alice, Bob, and Carol let alice = identities::alice(); @@ -284,7 +284,7 @@ fn test_identity_manager_with_multiple_identities() { // Verify count let mut count: usize = 0; let result = identity_manager_get_identity_count(manager_handle, &mut count); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(count, 3); // Get all identity IDs @@ -293,7 +293,7 @@ fn test_identity_manager_with_multiple_identities() { count: 0, }; let result = identity_manager_get_all_identity_ids(manager_handle, &mut array); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(array.count, 3); // Primary-identity FFI was dropped along with the field; @@ -319,13 +319,13 @@ fn test_managed_identity_label_operations() { let mut label_ptr: *mut std::os::raw::c_char = std::ptr::null_mut(); let result = managed_identity_get_label(alice_handle, &mut label_ptr); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Stub returns null — labels live on `PersistentIdentity.alias`. assert!(label_ptr.is_null()); let new_label = CString::new("Alice the Great").unwrap(); let result = managed_identity_set_label(alice_handle, new_label.as_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Cleanup managed_identity_destroy(alice_handle); @@ -344,7 +344,7 @@ fn test_managed_identity_balance_and_block_time() { // Get balance let mut balance: u64 = 0; let result = managed_identity_get_balance(alice_handle, &mut balance); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(balance, expected_balance); // Set balance block time @@ -355,7 +355,7 @@ fn test_managed_identity_balance_and_block_time() { }; let result = managed_identity_set_last_updated_balance_block_time(alice_handle, &block_time); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Get balance block time let mut retrieved_bt = BlockTime { @@ -365,7 +365,7 @@ fn test_managed_identity_balance_and_block_time() { }; let result = managed_identity_get_last_updated_balance_block_time(alice_handle, &mut retrieved_bt); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(retrieved_bt.height, 123_456); assert_eq!(retrieved_bt.core_height, 987_654); assert_eq!(retrieved_bt.timestamp, 1_700_000_000); @@ -385,16 +385,16 @@ fn test_error_handling_invalid_handles() { // result). The diagnostic remains accessible via `result.message`. let mut id_bytes = [0u8; 32]; let result = managed_identity_get_id(invalid_handle, id_bytes.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::NotFound); + assert_eq!(result.code, PlatformWalletFFIResultCode::NotFound); assert!(!result.message.is_null()); let result = contact_request_get_sender_id(invalid_handle, id_bytes.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::NotFound); + assert_eq!(result.code, PlatformWalletFFIResultCode::NotFound); // `managed_identity_destroy` retains its bespoke error mapping // because it doesn't use the option macro. let result = managed_identity_destroy(invalid_handle); - assert_eq!(result.code, PlatformWalletFfiResultCode::ErrorInvalidHandle); + assert_eq!(result.code, PlatformWalletFFIResultCode::ErrorInvalidHandle); } } @@ -406,16 +406,16 @@ fn test_error_handling_null_pointers() { // Try to get ID with null output pointer let result = managed_identity_get_id(alice_handle, std::ptr::null_mut()); - assert_eq!(result.code, PlatformWalletFfiResultCode::ErrorNullPointer); + assert_eq!(result.code, PlatformWalletFFIResultCode::ErrorNullPointer); // Try to get balance with null output pointer let result = managed_identity_get_balance(alice_handle, std::ptr::null_mut()); - assert_eq!(result.code, PlatformWalletFfiResultCode::ErrorNullPointer); + assert_eq!(result.code, PlatformWalletFFIResultCode::ErrorNullPointer); // Try to get sent requests with null output pointer let result = managed_identity_get_sent_contact_request_ids(alice_handle, std::ptr::null_mut()); - assert_eq!(result.code, PlatformWalletFfiResultCode::ErrorNullPointer); + assert_eq!(result.code, PlatformWalletFFIResultCode::ErrorNullPointer); // Cleanup managed_identity_destroy(alice_handle); @@ -439,7 +439,7 @@ fn test_contact_request_not_found() { eve_id_bytes.as_ptr(), &mut request_handle, ); - assert_eq!(result.code, PlatformWalletFfiResultCode::NotFound); + assert_eq!(result.code, PlatformWalletFFIResultCode::NotFound); assert!(!result.message.is_null()); // Cleanup @@ -453,14 +453,14 @@ fn test_identifier_operations() { // Generate random identifier let mut id = [0u8; 32]; let result = platform_wallet_generate_random_identifier(id.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Should not be all zeros assert_ne!(id, [0u8; 32]); // Convert to string (actually Base58, despite function name) let mut id_string: *mut std::os::raw::c_char = std::ptr::null_mut(); let result = platform_wallet_identifier_to_hex(id.as_ptr(), &mut id_string); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert!(!id_string.is_null()); let id_str = std::ffi::CStr::from_ptr(id_string).to_str().unwrap(); @@ -474,7 +474,7 @@ fn test_identifier_operations() { // Convert back from string let mut id2 = [0u8; 32]; let result = platform_wallet_identifier_from_hex(id_string, id2.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Should match original assert_eq!(id, id2); @@ -503,37 +503,37 @@ fn test_memory_lifecycle() { assert_eq!( managed_identity_get_id(alice_handle, id.as_mut_ptr()).code, - PlatformWalletFfiResultCode::Success + PlatformWalletFFIResultCode::Success ); assert_eq!( managed_identity_get_id(bob_handle, id.as_mut_ptr()).code, - PlatformWalletFfiResultCode::Success + PlatformWalletFFIResultCode::Success ); assert_eq!( managed_identity_get_id(carol_handle, id.as_mut_ptr()).code, - PlatformWalletFfiResultCode::Success + PlatformWalletFFIResultCode::Success ); // Destroy Alice assert_eq!( managed_identity_destroy(alice_handle).code, - PlatformWalletFfiResultCode::Success + PlatformWalletFFIResultCode::Success ); // Alice should be gone — get_id surfaces the storage miss as // `NotFound`. assert_eq!( managed_identity_get_id(alice_handle, id.as_mut_ptr()).code, - PlatformWalletFfiResultCode::NotFound + PlatformWalletFFIResultCode::NotFound ); assert_eq!( managed_identity_get_id(bob_handle, id.as_mut_ptr()).code, - PlatformWalletFfiResultCode::Success + PlatformWalletFFIResultCode::Success ); assert_eq!( managed_identity_get_id(carol_handle, id.as_mut_ptr()).code, - PlatformWalletFfiResultCode::Success + PlatformWalletFFIResultCode::Success ); // Cleanup remaining @@ -543,7 +543,7 @@ fn test_memory_lifecycle() { // Double destroy still hits the bespoke ErrorInvalidHandle path. assert_eq!( managed_identity_destroy(bob_handle).code, - PlatformWalletFfiResultCode::ErrorInvalidHandle + PlatformWalletFFIResultCode::ErrorInvalidHandle ); } } @@ -633,13 +633,13 @@ fn test_get_established_contact_and_fields() { &mut contact_handle, ); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_ne!(contact_handle, NULL_HANDLE); // Get contact ID let mut retrieved_id = [0u8; 32]; let result = established_contact_get_contact_id(contact_handle, retrieved_id.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(retrieved_id, bob_id_bytes); // Cleanup @@ -670,13 +670,13 @@ fn test_established_contact_outgoing_and_incoming_requests() { // Get outgoing request let mut outgoing_handle: Handle = NULL_HANDLE; let result = established_contact_get_outgoing_request(contact_handle, &mut outgoing_handle); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_ne!(outgoing_handle, NULL_HANDLE); // Get incoming request let mut incoming_handle: Handle = NULL_HANDLE; let result = established_contact_get_incoming_request(contact_handle, &mut incoming_handle); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_ne!(incoming_handle, NULL_HANDLE); // Verify the requests have correct sender/recipient @@ -749,7 +749,7 @@ fn test_established_contact_request_fields() { let mut len: usize = 0; let result = contact_request_get_encrypted_public_key(outgoing_handle, &mut bytes_ptr, &mut len); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(len, 96); // Standard encrypted key length assert!(!bytes_ptr.is_null()); @@ -779,7 +779,7 @@ fn test_get_nonexistent_established_contact() { // The contact lookup unwraps an `Option` via the macro, so the // miss surfaces as `NotFound`. - assert_eq!(result.code, PlatformWalletFfiResultCode::NotFound); + assert_eq!(result.code, PlatformWalletFFIResultCode::NotFound); assert_eq!(contact_handle, NULL_HANDLE); // Cleanup @@ -793,7 +793,7 @@ fn test_established_contact_destroy_invalid_handle() { // `established_contact_destroy` runs `unwrap_option_or_return!` // on the storage remove, so a bogus handle yields `NotFound`. let result = established_contact_destroy(9999); - assert_eq!(result.code, PlatformWalletFfiResultCode::NotFound); + assert_eq!(result.code, PlatformWalletFFIResultCode::NotFound); } } @@ -817,7 +817,7 @@ fn test_multiple_established_contacts() { bob_id_bytes.as_ptr(), &mut bob_contact_handle, ); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Get Carol contact let mut carol_contact_handle: Handle = NULL_HANDLE; @@ -826,7 +826,7 @@ fn test_multiple_established_contacts() { carol_id_bytes.as_ptr(), &mut carol_contact_handle, ); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Verify Bob's contact ID let mut retrieved_bob_id = [0u8; 32]; diff --git a/packages/rs-platform-wallet-ffi/tests/integration_tests.rs b/packages/rs-platform-wallet-ffi/tests/integration_tests.rs index 80fea57c029..2fcfe3f85f7 100644 --- a/packages/rs-platform-wallet-ffi/tests/integration_tests.rs +++ b/packages/rs-platform-wallet-ffi/tests/integration_tests.rs @@ -26,15 +26,15 @@ fn test_wallet_creation_and_destruction() { &mut handle, ); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_ne!(handle, NULL_HANDLE); let result = platform_wallet_info_destroy(handle); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Double destroy should fail let result = platform_wallet_info_destroy(handle); - assert_eq!(result.code, PlatformWalletFfiResultCode::ErrorInvalidHandle); + assert_eq!(result.code, PlatformWalletFFIResultCode::ErrorInvalidHandle); } } @@ -54,7 +54,7 @@ fn test_wallet_from_mnemonic() { &mut handle, ); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_ne!(handle, NULL_HANDLE); platform_wallet_info_destroy(handle); @@ -69,12 +69,12 @@ fn test_identity_manager_workflow() { let mut manager_handle: Handle = NULL_HANDLE; let result = identity_manager_create(&mut manager_handle); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Check initial count let mut count: usize = 0; let result = identity_manager_get_identity_count(manager_handle, &mut count); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(count, 0); // Create a mock identity for testing @@ -84,11 +84,11 @@ fn test_identity_manager_workflow() { let identity_handle = MANAGED_IDENTITY_STORAGE.insert(managed); let result = identity_manager_add_identity(manager_handle, identity_handle); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Check count increased let result = identity_manager_get_identity_count(manager_handle, &mut count); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(count, 1); // Primary-identity FFI was dropped along with the field — @@ -102,7 +102,7 @@ fn test_identity_manager_workflow() { count: 0, }; let result = identity_manager_get_all_identity_ids(manager_handle, &mut array); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(array.count, 1); platform_wallet_identifier_array_free(&mut array); @@ -123,21 +123,21 @@ fn test_managed_identity_operations() { // Get ID let mut id_bytes = [0u8; 32]; let result = managed_identity_get_id(handle, id_bytes.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Get balance let mut balance: u64 = 0; let result = managed_identity_get_balance(handle, &mut balance); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Set and get label let label = CString::new("Test Identity").unwrap(); let result = managed_identity_set_label(handle, label.as_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); let mut label_ptr: *mut std::os::raw::c_char = std::ptr::null_mut(); let result = managed_identity_get_label(handle, &mut label_ptr); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert!(!label_ptr.is_null()); let retrieved_label = std::ffi::CStr::from_ptr(label_ptr).to_str().unwrap(); @@ -153,7 +153,7 @@ fn test_managed_identity_operations() { }; let result = managed_identity_set_last_updated_balance_block_time(handle, &block_time); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); let mut retrieved_bt = BlockTime { height: 0, @@ -162,7 +162,7 @@ fn test_managed_identity_operations() { }; let result = managed_identity_get_last_updated_balance_block_time(handle, &mut retrieved_bt); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_eq!(retrieved_bt.height, 100); assert_eq!(retrieved_bt.core_height, 200); @@ -188,7 +188,7 @@ fn test_serialization() { // Serialize to JSON - function not yet implemented // let mut json_ptr: *mut std::os::raw::c_char = std::ptr::null_mut(); // let result = platform_wallet_info_to_json(handle, &mut json_ptr); - // assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + // assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // assert!(!json_ptr.is_null()); // let json_str = unsafe { std::ffi::CStr::from_ptr(json_ptr).to_str().unwrap() }; @@ -206,18 +206,18 @@ fn test_utils_identifier_operations() { // Generate random identifier let mut id1 = [0u8; 32]; let result = platform_wallet_generate_random_identifier(id1.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Convert to hex let mut hex: *mut std::os::raw::c_char = std::ptr::null_mut(); let result = platform_wallet_identifier_to_hex(id1.as_ptr(), &mut hex); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert!(!hex.is_null()); // Convert back from hex let mut id2 = [0u8; 32]; let result = platform_wallet_identifier_from_hex(hex, id2.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Should match assert_eq!(id1, id2); @@ -234,7 +234,7 @@ fn test_error_handling() { let mut id_bytes = [0u8; 32]; let result = managed_identity_get_id(invalid_handle, id_bytes.as_mut_ptr()); // The macro routes a missing handle through Option::None → NotFound. - assert_eq!(result.code, PlatformWalletFfiResultCode::NotFound); + assert_eq!(result.code, PlatformWalletFFIResultCode::NotFound); // Result carries a diagnostic message on the error path. assert!(!result.message.is_null()); @@ -246,7 +246,7 @@ fn test_error_handling() { 0, std::ptr::null_mut(), ); - assert_eq!(result.code, PlatformWalletFfiResultCode::ErrorNullPointer); + assert_eq!(result.code, PlatformWalletFFIResultCode::ErrorNullPointer); } } @@ -269,12 +269,12 @@ fn test_full_workflow() { std::ptr::null(), &mut wallet_handle, ); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Create identity manager let mut manager_handle: Handle = NULL_HANDLE; let result = identity_manager_create(&mut manager_handle); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Create identity let identity = dpp::tests::fixtures::get_identity_fixture(0).unwrap(); @@ -297,13 +297,13 @@ fn test_full_workflow() { // Set identity manager on wallet let result = platform_wallet_info_set_identity_manager(wallet_handle, manager_handle); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); // Get identity manager back let mut retrieved_manager_handle: Handle = NULL_HANDLE; let result = platform_wallet_info_get_identity_manager(wallet_handle, &mut retrieved_manager_handle); - assert_eq!(result.code, PlatformWalletFfiResultCode::Success); + assert_eq!(result.code, PlatformWalletFFIResultCode::Success); assert_ne!(retrieved_manager_handle, NULL_HANDLE); // Cleanup diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedIdentity.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedIdentity.swift index 6b4e414d157..6e37c9ba826 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedIdentity.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedIdentity.swift @@ -414,7 +414,7 @@ public final class ManagedIdentity: @unchecked Sendable { /// Keeping the plumbing in one place avoids duplicating the /// error handling + defer + pointer iteration block twice. private func readDpnsNameArray( - _ fetch: (inout DpnsNameArray) -> PlatformWalletFfiResult + _ fetch: (inout DpnsNameArray) -> PlatformWalletFFIResult ) throws -> [String] { var array = DpnsNameArray() try fetch(&array).check() diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift index 212da133e00..84ab79227ea 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift @@ -373,7 +373,7 @@ public final class ManagedPlatformWallet: @unchecked Sendable { let result = ffiInputs.withUnsafeBufferPointer { inputsBuf in withUnsafePointer(to: &outputFFI) { outputPtr in - pubkeyBuffers.withUnsafeBufferPointer { _ -> PlatformWalletFfiResult in + pubkeyBuffers.withUnsafeBufferPointer { _ -> PlatformWalletFFIResult in // For each row, withUnsafeBytes pins ONE Data // at a time, so we have to assemble the FFI // row array under nested pinning. We use a @@ -884,7 +884,7 @@ extension ManagedPlatformWallet { var row = IdentityKeyPreviewFFI() - let result = self.walletId.withUnsafeBytes { walletBytes -> PlatformWalletFfiResult in + let result = self.walletId.withUnsafeBytes { walletBytes -> PlatformWalletFFIResult in let walletPtr = walletBytes.bindMemory(to: UInt8.self).baseAddress! return dash_sdk_derive_identity_key_at_slot_with_resolver( network, @@ -1014,7 +1014,7 @@ extension ManagedPlatformWallet { // `walletId` is `Data`; bind into a 32-byte UInt8 pointer // so Rust receives a stable address for the duration of // the FFI call. - let result = self.walletId.withUnsafeBytes { walletBytes -> PlatformWalletFfiResult in + let result = self.walletId.withUnsafeBytes { walletBytes -> PlatformWalletFFIResult in let walletPtr = walletBytes.bindMemory(to: UInt8.self).baseAddress return dash_sdk_derive_and_persist_identity_keys( network, @@ -1260,7 +1260,7 @@ extension ManagedPlatformWallet { return try await Task.detached(priority: .userInitiated) { () -> Identifier? in var buf = [UInt8](repeating: 0, count: 32) var found = false - let result = buf.withUnsafeMutableBufferPointer { bp -> PlatformWalletFfiResult in + let result = buf.withUnsafeMutableBufferPointer { bp -> PlatformWalletFFIResult in name.withCString { namePtr in platform_wallet_resolve_dpns_name( handle, @@ -1367,7 +1367,7 @@ extension ManagedPlatformWallet { () -> ContestVoteState? in var state = ContestVoteStateFFI() var found = false - let result = idBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFfiResult in + let result = idBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFFIResult in label.withCString { labelPtr in platform_wallet_fetch_contest_vote_state( handle, @@ -1500,10 +1500,10 @@ extension ManagedPlatformWallet { () -> Handle in _ = signer var outHandle: Handle = NULL_HANDLE - let result: PlatformWalletFfiResult = senderBytes.withUnsafeBufferPointer { - senderBp -> PlatformWalletFfiResult in - recipientBytes.withUnsafeBufferPointer { recipientBp -> PlatformWalletFfiResult in - let callWithLabel: (UnsafePointer?) -> PlatformWalletFfiResult = { + let result: PlatformWalletFFIResult = senderBytes.withUnsafeBufferPointer { + senderBp -> PlatformWalletFFIResult in + recipientBytes.withUnsafeBufferPointer { recipientBp -> PlatformWalletFFIResult in + let callWithLabel: (UnsafePointer?) -> PlatformWalletFFIResult = { labelPtr in if let autoAcceptProof, !autoAcceptProof.isEmpty { return autoAcceptProof.withUnsafeBytes { rawBuf in @@ -1592,7 +1592,7 @@ extension ManagedPlatformWallet { } try await Task.detached(priority: .userInitiated) { let result = ourBytes.withUnsafeBufferPointer { - ourBp -> PlatformWalletFfiResult in + ourBp -> PlatformWalletFFIResult in contactBytes.withUnsafeBufferPointer { contactBp in platform_wallet_reject_contact_request( handle, @@ -1669,10 +1669,10 @@ extension ManagedPlatformWallet { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ) - let result: PlatformWalletFfiResult = fromBytes.withUnsafeBufferPointer { - fromBp -> PlatformWalletFfiResult in - toBytes.withUnsafeBufferPointer { toBp -> PlatformWalletFfiResult in - let call: (UnsafePointer?) -> PlatformWalletFfiResult = { memoPtr in + let result: PlatformWalletFFIResult = fromBytes.withUnsafeBufferPointer { + fromBp -> PlatformWalletFFIResult in + toBytes.withUnsafeBufferPointer { toBp -> PlatformWalletFFIResult in + let call: (UnsafePointer?) -> PlatformWalletFFIResult = { memoPtr in platform_wallet_send_dashpay_payment( handle, fromBp.baseAddress!, @@ -1809,17 +1809,17 @@ extension ManagedPlatformWallet { _ = signer var outProfile = DashPayProfileFFI() - let result: PlatformWalletFfiResult = idBytes.withUnsafeBufferPointer { - idBp -> PlatformWalletFfiResult in + let result: PlatformWalletFFIResult = idBytes.withUnsafeBufferPointer { + idBp -> PlatformWalletFFIResult in let idPtr = idBp.baseAddress! return invokeWithOptionalCStrings( displayName, publicMessage, avatarUrl - ) { namePtr, msgPtr, urlPtr -> PlatformWalletFfiResult in + ) { namePtr, msgPtr, urlPtr -> PlatformWalletFFIResult in let bytes = avatarBytes ?? Data() if let avatarBytes, !avatarBytes.isEmpty { - return avatarBytes.withUnsafeBytes { rawBuf -> PlatformWalletFfiResult in + return avatarBytes.withUnsafeBytes { rawBuf -> PlatformWalletFFIResult in let bytesPtr = rawBuf.baseAddress?.assumingMemoryBound(to: UInt8.self) return platform_wallet_create_or_update_dashpay_profile_with_signer( handle, @@ -1953,7 +1953,7 @@ extension ManagedPlatformWallet { /// free helper, so the per-method variance is just the FFI call /// itself. private func readIdentifierArray( - _ fetch: (inout IdentifierArray) -> PlatformWalletFfiResult + _ fetch: (inout IdentifierArray) -> PlatformWalletFFIResult ) throws -> [Identifier] { var array = IdentifierArray() try fetch(&array).check() @@ -2052,7 +2052,7 @@ extension ManagedPlatformWallet { } try await Task.detached(priority: .userInitiated) { _ = signer - let result = fromBytes.withUnsafeBufferPointer { fromBp -> PlatformWalletFfiResult in + let result = fromBytes.withUnsafeBufferPointer { fromBp -> PlatformWalletFFIResult in toBytes.withUnsafeBufferPointer { toBp in platform_wallet_transfer_credits_with_signer( handle, @@ -2118,7 +2118,7 @@ extension ManagedPlatformWallet { _ = signer var newBalance: UInt64 = 0 let result = fromBytes.withUnsafeBufferPointer { - fromBp -> PlatformWalletFfiResult in + fromBp -> PlatformWalletFFIResult in rows.withUnsafeBufferPointer { rowsBp in platform_wallet_transfer_credits_to_addresses_with_signer( handle, @@ -2152,7 +2152,7 @@ extension ManagedPlatformWallet { try await Task.detached(priority: .userInitiated) { _ = signer let result = idBytes.withUnsafeBufferPointer { - idBp -> PlatformWalletFfiResult in + idBp -> PlatformWalletFFIResult in toAddress.withCString { addrPtr in platform_wallet_withdraw_credits_with_signer( handle, @@ -2197,7 +2197,7 @@ extension ManagedPlatformWallet { // FFI sees stays valid for the call duration. let pubkeyBuffers: [Data] = addPubkeys.map { $0.pubkeyBytes } let result = idBytes.withUnsafeBufferPointer { - idBp -> PlatformWalletFfiResult in + idBp -> PlatformWalletFFIResult in disableIds.withUnsafeBufferPointer { disableBp in if addPubkeys.isEmpty { return platform_wallet_update_identity_with_signer( @@ -2293,7 +2293,7 @@ extension ManagedPlatformWallet { _ = signer var contractIdBytes = [UInt8](repeating: 0, count: 32) - let result = idBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFfiResult in + let result = idBytes.withUnsafeBufferPointer { idBp -> PlatformWalletFFIResult in documentSchemasJSON.withCString { docsPtr in Self.withOptionalCString(tokenSchemasJSON) { tokensPtr in Self.withOptionalCString(groupsJSON) { groupsPtr in diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManager.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManager.swift index ce763c8ec7f..46cb6e51d9d 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManager.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManager.swift @@ -378,7 +378,7 @@ public class PlatformWalletManager: ObservableObject { var outEntries: UnsafePointer? var outCount: UInt = 0 - let ffiResult = walletId.withUnsafeBytes { (raw: UnsafeRawBufferPointer) -> PlatformWalletFfiResult in + let ffiResult = walletId.withUnsafeBytes { (raw: UnsafeRawBufferPointer) -> PlatformWalletFFIResult in let base = raw.baseAddress?.assumingMemoryBound(to: UInt8.self) return platform_wallet_manager_get_account_balances( handle, diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift index 95acfcc0766..199d07bd5e3 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift @@ -3,7 +3,7 @@ import DashSDKFFI // MARK: - Typed result code -/// Swift mirror of the Rust `PlatformWalletFfiResultCode` enum. +/// Swift mirror of the Rust `PlatformWalletFFIResultCode` enum. public enum PlatformWalletResultCode: Int32, Sendable { case success = 0 case errorInvalidHandle = 1 @@ -21,7 +21,7 @@ public enum PlatformWalletResultCode: Int32, Sendable { case notFound = 98 case errorUnknown = 99 - init(ffi: PlatformWalletFfiResultCode) { + init(ffi: PlatformWalletFFIResultCode) { switch ffi { case PLATFORM_WALLET_FFI_RESULT_CODE_SUCCESS: self = .success @@ -61,7 +61,7 @@ public enum PlatformWalletResultCode: Int32, Sendable { // MARK: - Class wrapper -/// Reference-counted wrapper around a `PlatformWalletFfiResult` +/// Reference-counted wrapper around a `PlatformWalletFFIResult` /// returned by Rust. Owns the heap-allocated `message` C string and /// frees it through `platform_wallet_ffi_result_free` in `deinit`, /// regardless of whether the caller threw, returned early, or simply @@ -72,9 +72,9 @@ public enum PlatformWalletResultCode: Int32, Sendable { /// you want to inspect the `message` without throwing (e.g. logging /// a warning on a soft-failure path). final class PlatformWalletResult { - private var inner: PlatformWalletFfiResult + private var inner: PlatformWalletFFIResult - init(_ ffi: PlatformWalletFfiResult) { + init(_ ffi: PlatformWalletFFIResult) { self.inner = ffi } @@ -128,7 +128,7 @@ public enum PlatformWalletError: LocalizedError { case unknown(String) /// Diagnostic detail Rust attached to the originating - /// `PlatformWalletFfiResult`, or the context string a Swift-side + /// `PlatformWalletFFIResult`, or the context string a Swift-side /// guard chose when constructing the error inline. public var errorDescription: String? { switch self { @@ -168,7 +168,7 @@ public enum PlatformWalletError: LocalizedError { // MARK: - Convenience extensions -extension PlatformWalletFfiResult { +extension PlatformWalletFFIResult { @inline(__always) func check() throws { try PlatformWalletResult(self).throwIfError()