diff --git a/dash-spv-ffi/src/bin/ffi_cli.rs b/dash-spv-ffi/src/bin/ffi_cli.rs index fc521242d..348452d0b 100644 --- a/dash-spv-ffi/src/bin/ffi_cli.rs +++ b/dash-spv-ffi/src/bin/ffi_cli.rs @@ -468,7 +468,7 @@ fn main() { } let mnemonic_c = CString::new(mnemonic.as_str()).unwrap(); - let mut error = FFIError::success(); + let mut error = FFIError::default(); let success = wallet_manager_add_wallet_from_mnemonic( wallet_manager as *mut _, mnemonic_c.as_ptr(), diff --git a/dash-spv-ffi/tests/dashd_sync/context.rs b/dash-spv-ffi/tests/dashd_sync/context.rs index 497ed3046..d9b4699a5 100644 --- a/dash-spv-ffi/tests/dashd_sync/context.rs +++ b/dash-spv-ffi/tests/dashd_sync/context.rs @@ -171,7 +171,7 @@ impl FFITestContext { pub(super) unsafe fn add_wallet(&self, mnemonic: &str) -> Vec { let mnemonic_c = CString::new(mnemonic).unwrap(); let passphrase = CString::new("").unwrap(); - let mut error = FFIError::success(); + let mut error = FFIError::default(); let wm = self.session.wallet_manager as *mut FFIWalletManager; let success = wallet_manager_add_wallet_from_mnemonic( @@ -208,7 +208,7 @@ impl FFITestContext { pub(super) unsafe fn get_wallet_balance(&self, wallet_id: &[u8]) -> (u64, u64) { let mut confirmed: u64 = 0; let mut unconfirmed: u64 = 0; - let mut error = FFIError::success(); + let mut error = FFIError::default(); let wm = self.session.wallet_manager as *mut FFIWalletManager; let success = wallet_manager_get_wallet_balance( @@ -279,7 +279,7 @@ impl FFITestContext { /// /// Calls FFI wallet functions through raw pointers held by the context. pub(super) unsafe fn get_receive_address(&self, wallet_id: &[u8]) -> Address { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let wm = self.session.wallet_manager as *mut FFIWalletManager; let ffi_wallet = wallet_manager_get_wallet(wm, wallet_id.as_ptr(), &mut error); diff --git a/dash-spv-ffi/tests/test_wallet_manager.rs b/dash-spv-ffi/tests/test_wallet_manager.rs index 4b1fd1fa7..1c173a680 100644 --- a/dash-spv-ffi/tests/test_wallet_manager.rs +++ b/dash-spv-ffi/tests/test_wallet_manager.rs @@ -39,7 +39,7 @@ mod tests { assert_eq!((*wallet_manager_ptr).network(), FFINetwork::Testnet); // Get wallet count (should be 0 initially) - let mut error = FFIError::success(); + let mut error = FFIError::default(); let count = wallet_manager_wallet_count( wallet_manager as *const FFIWalletManager, &mut error as *mut FFIError, @@ -88,7 +88,7 @@ mod tests { .expect("wallet serialization should succeed"); // Import the serialized wallet through the FFI pointer we retrieved from the client - let mut error = FFIError::success(); + let mut error = FFIError::default(); let mut imported_wallet_id = [0u8; 32]; let import_ok = wallet_manager_import_wallet_from_bytes( wallet_manager_ptr, @@ -118,7 +118,7 @@ mod tests { wallet_manager_free_wallet_ids(ids_ptr, id_count); // Call the describe helper through FFI to ensure the shared instance reports correctly - let mut description_error = FFIError::success(); + let mut description_error = FFIError::default(); let description_ptr = key_wallet_ffi::wallet_manager_describe( wallet_manager_ptr as *const FFIWalletManager, &mut description_error as *mut FFIError, diff --git a/key-wallet-ffi/FFI_API.md b/key-wallet-ffi/FFI_API.md index a90ff908b..fd57ee77e 100644 --- a/key-wallet-ffi/FFI_API.md +++ b/key-wallet-ffi/FFI_API.md @@ -50,7 +50,7 @@ Functions: 19 | `wallet_manager_add_wallet_from_mnemonic` | Add a wallet from mnemonic to the manager (backward compatibility) # Safety... | wallet_manager | | `wallet_manager_add_wallet_from_mnemonic_return_serialized_bytes` | No description | wallet_manager | | `wallet_manager_add_wallet_from_mnemonic_with_options` | Add a wallet from mnemonic to the manager with options # Safety -... | wallet_manager | -| `wallet_manager_create` | Create a new wallet manager | wallet_manager | +| `wallet_manager_create` | Create a new wallet manager # Safety `error` must be a valid pointer to an... | wallet_manager | | `wallet_manager_current_height` | Get current height for a network # Safety - `manager` must be a valid... | wallet_manager | | `wallet_manager_describe` | Describe the wallet manager for a given network and return a newly allocated... | wallet_manager | | `wallet_manager_free` | Free wallet manager # Safety - `manager` must be a valid pointer to an... | wallet_manager | @@ -190,7 +190,7 @@ Functions: 109 | `bls_account_get_extended_public_key_as_string` | No description | account | | `bls_account_get_is_watch_only` | No description | account | | `bls_account_get_network` | No description | account | -| `derivation_bip44_account_path` | Derive a BIP44 account path (m/44'/5'/account') | derivation | +| `derivation_bip44_account_path` | Derive a BIP44 account path (m/44'/5'/account') # Safety `path_out` must... | derivation | | `eddsa_account_derive_private_key_from_mnemonic` | No description | account_derivation | | `eddsa_account_derive_private_key_from_seed` | No description | account_derivation | | `eddsa_account_free` | No description | account | @@ -320,8 +320,8 @@ Functions: 6 | Function | Description | Module | |----------|-------------|--------| | `mnemonic_free` | Free a mnemonic string # Safety - `mnemonic` must be a valid pointer... | mnemonic | -| `mnemonic_generate` | Generate a new mnemonic with specified word count (12, 15, 18, 21, or 24) | mnemonic | -| `mnemonic_generate_with_language` | Generate a new mnemonic with specified language and word count | mnemonic | +| `mnemonic_generate` | Generate a new mnemonic with specified word count (12, 15, 18, 21, or 24) #... | mnemonic | +| `mnemonic_generate_with_language` | Generate a new mnemonic with specified language and word count # Safety ... | mnemonic | | `mnemonic_to_seed` | Convert mnemonic to seed with optional passphrase # Safety - `mnemonic`... | mnemonic | | `mnemonic_validate` | Validate a mnemonic phrase # Safety - `mnemonic` must be a valid... | mnemonic | | `mnemonic_word_count` | Get word count from mnemonic # Safety - `mnemonic` must be a valid... | mnemonic | @@ -332,11 +332,11 @@ Functions: 17 | Function | Description | Module | |----------|-------------|--------| -| `derivation_bip44_payment_path` | Derive a BIP44 payment path (m/44'/5'/account'/change/index) | derivation | -| `derivation_coinjoin_path` | Derive CoinJoin path (m/9'/5'/4'/account') | derivation | -| `derivation_identity_authentication_path` | Derive identity authentication path (m/9'/5'/5'/0'/identity_index'/key_index') | derivation | -| `derivation_identity_registration_path` | Derive identity registration path (m/9'/5'/5'/1'/index') | derivation | -| `derivation_identity_topup_path` | Derive identity top-up path (m/9'/5'/5'/2'/identity_index'/top_up_index') | derivation | +| `derivation_bip44_payment_path` | Derive a BIP44 payment path (m/44'/5'/account'/change/index) # Safety ... | derivation | +| `derivation_coinjoin_path` | Derive CoinJoin path (m/9'/5'/4'/account') # Safety `path_out` must point... | derivation | +| `derivation_identity_authentication_path` | Derive identity authentication path (m/9'/5'/5'/0'/identity_index'/key_index'... | derivation | +| `derivation_identity_registration_path` | Derive identity registration path (m/9'/5'/5'/1'/index') # Safety ... | derivation | +| `derivation_identity_topup_path` | Derive identity top-up path (m/9'/5'/5'/2'/identity_index'/top_up_index') #... | derivation | | `derivation_path_free` | Free derivation path arrays Note: This function expects the count to... | keys | | `derivation_path_parse` | Convert derivation path string to indices # Safety - `path` must be a... | keys | | `derivation_string_free` | Free derivation path string # Safety - `s` must be a valid pointer to a C... | derivation | @@ -455,10 +455,10 @@ wallet_manager_add_wallet_from_mnemonic(manager: *mut FFIWalletManager, mnemonic ``` **Description:** -Add a wallet from mnemonic to the manager (backward compatibility) # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `mnemonic` must be a valid pointer to a null-terminated C string - `passphrase` must be a valid pointer to a null-terminated C string or null - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Add a wallet from mnemonic to the manager (backward compatibility) # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `mnemonic` must be a valid pointer to a null-terminated C string - `passphrase` must be a valid pointer to a null-terminated C string or null - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `manager` must be a valid pointer to an FFIWalletManager instance - `mnemonic` must be a valid pointer to a null-terminated C string - `passphrase` must be a valid pointer to a null-terminated C string or null - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `manager` must be a valid pointer to an FFIWalletManager instance - `mnemonic` must be a valid pointer to a null-terminated C string - `passphrase` must be a valid pointer to a null-terminated C string or null - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `wallet_manager` @@ -481,10 +481,10 @@ wallet_manager_add_wallet_from_mnemonic_with_options(manager: *mut FFIWalletMana ``` **Description:** -Add a wallet from mnemonic to the manager with options # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `mnemonic` must be a valid pointer to a null-terminated C string - `passphrase` must be a valid pointer to a null-terminated C string or null - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Add a wallet from mnemonic to the manager with options # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `mnemonic` must be a valid pointer to a null-terminated C string - `passphrase` must be a valid pointer to a null-terminated C string or null - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `manager` must be a valid pointer to an FFIWalletManager instance - `mnemonic` must be a valid pointer to a null-terminated C string - `passphrase` must be a valid pointer to a null-terminated C string or null - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `manager` must be a valid pointer to an FFIWalletManager instance - `mnemonic` must be a valid pointer to a null-terminated C string - `passphrase` must be a valid pointer to a null-terminated C string or null - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `wallet_manager` @@ -497,7 +497,10 @@ wallet_manager_create(network: FFINetwork, error: *mut FFIError,) -> *mut FFIWal ``` **Description:** -Create a new wallet manager +Create a new wallet manager # Safety `error` must be a valid pointer to an `FFIError`. The returned pointer must be freed with `wallet_manager_free`. + +**Safety:** +`error` must be a valid pointer to an `FFIError`. The returned pointer must be freed with `wallet_manager_free`. **Module:** `wallet_manager` @@ -510,10 +513,10 @@ wallet_manager_current_height(manager: *const FFIWalletManager, error: *mut FFIE ``` **Description:** -Get current height for a network # Safety - `manager` must be a valid pointer to an FFIWalletManager - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Get current height for a network # Safety - `manager` must be a valid pointer to an FFIWalletManager - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `manager` must be a valid pointer to an FFIWalletManager - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `manager` must be a valid pointer to an FFIWalletManager - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `wallet_manager` @@ -616,10 +619,10 @@ wallet_manager_get_managed_wallet_info(manager: *const FFIWalletManager, wallet_ ``` **Description:** -Get managed wallet info from the manager Returns a reference to the managed wallet info if found # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call - The returned managed wallet info must be freed with managed_wallet_info_free() +Get managed wallet info from the manager Returns a reference to the managed wallet info if found # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call - The returned managed wallet info must be freed with managed_wallet_info_free() **Safety:** -- `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call - The returned managed wallet info must be freed with managed_wallet_info_free() +- `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call - The returned managed wallet info must be freed with managed_wallet_info_free() **Module:** `wallet_manager` @@ -632,10 +635,10 @@ wallet_manager_get_wallet(manager: *const FFIWalletManager, wallet_id: *const u8 ``` **Description:** -Get a wallet from the manager Returns a reference to the wallet if found # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call - The returned wallet must be freed with wallet_free_const() +Get a wallet from the manager Returns a reference to the wallet if found # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call - The returned wallet must be freed with wallet_free_const() **Safety:** -- `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call - The returned wallet must be freed with wallet_free_const() +- `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call - The returned wallet must be freed with wallet_free_const() **Module:** `wallet_manager` @@ -648,10 +651,10 @@ wallet_manager_get_wallet_balance(manager: *const FFIWalletManager, wallet_id: * ``` **Description:** -Get wallet balance Returns the confirmed and unconfirmed balance for a specific wallet # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `confirmed_out` must be a valid pointer to a u64 (maps to C uint64_t) - `unconfirmed_out` must be a valid pointer to a u64 (maps to C uint64_t) - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Get wallet balance Returns the confirmed and unconfirmed balance for a specific wallet # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `confirmed_out` must be a valid pointer to a u64 (maps to C uint64_t) - `unconfirmed_out` must be a valid pointer to a u64 (maps to C uint64_t) - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `confirmed_out` must be a valid pointer to a u64 (maps to C uint64_t) - `unconfirmed_out` must be a valid pointer to a u64 (maps to C uint64_t) - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `confirmed_out` must be a valid pointer to a u64 (maps to C uint64_t) - `unconfirmed_out` must be a valid pointer to a u64 (maps to C uint64_t) - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `wallet_manager` @@ -664,10 +667,10 @@ wallet_manager_get_wallet_ids(manager: *const FFIWalletManager, wallet_ids_out: ``` **Description:** -Get wallet IDs # Safety - `manager` must be a valid pointer to an FFIWalletManager - `wallet_ids_out` must be a valid pointer to a pointer that will receive the wallet IDs - `count_out` must be a valid pointer to receive the count - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Get wallet IDs # Safety - `manager` must be a valid pointer to an FFIWalletManager - `wallet_ids_out` must be a valid pointer to a pointer that will receive the wallet IDs - `count_out` must be a valid pointer to receive the count - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `manager` must be a valid pointer to an FFIWalletManager - `wallet_ids_out` must be a valid pointer to a pointer that will receive the wallet IDs - `count_out` must be a valid pointer to receive the count - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `manager` must be a valid pointer to an FFIWalletManager - `wallet_ids_out` must be a valid pointer to a pointer that will receive the wallet IDs - `count_out` must be a valid pointer to receive the count - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `wallet_manager` @@ -690,10 +693,10 @@ wallet_manager_network(manager: *const FFIWalletManager, error: *mut FFIError,) ``` **Description:** -Get the network for this wallet manager # Safety - `manager` must be a valid pointer to an FFIWalletManager - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Get the network for this wallet manager # Safety - `manager` must be a valid pointer to an FFIWalletManager - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `manager` must be a valid pointer to an FFIWalletManager - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `manager` must be a valid pointer to an FFIWalletManager - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `wallet_manager` @@ -706,10 +709,10 @@ wallet_manager_process_transaction(manager: *mut FFIWalletManager, tx_bytes: *co ``` **Description:** -Process a transaction through all wallets Checks a transaction against all wallets and updates their states if relevant. Returns true if the transaction was relevant to at least one wallet. # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `tx_bytes` must be a valid pointer to transaction bytes - `tx_len` must be the length of the transaction bytes - `context` must be a valid pointer to FFITransactionContext - `update_state_if_found` indicates whether to update wallet state when transaction is relevant - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Process a transaction through all wallets Checks a transaction against all wallets and updates their states if relevant. Returns true if the transaction was relevant to at least one wallet. # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `tx_bytes` must be a valid pointer to transaction bytes - `tx_len` must be the length of the transaction bytes - `context` must be a valid pointer to FFITransactionContext - `update_state_if_found` indicates whether to update wallet state when transaction is relevant - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `manager` must be a valid pointer to an FFIWalletManager instance - `tx_bytes` must be a valid pointer to transaction bytes - `tx_len` must be the length of the transaction bytes - `context` must be a valid pointer to FFITransactionContext - `update_state_if_found` indicates whether to update wallet state when transaction is relevant - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `manager` must be a valid pointer to an FFIWalletManager instance - `tx_bytes` must be a valid pointer to transaction bytes - `tx_len` must be the length of the transaction bytes - `context` must be a valid pointer to FFITransactionContext - `update_state_if_found` indicates whether to update wallet state when transaction is relevant - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `wallet_manager` @@ -722,10 +725,10 @@ wallet_manager_wallet_count(manager: *const FFIWalletManager, error: *mut FFIErr ``` **Description:** -Get wallet count # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Get wallet count # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `manager` must be a valid pointer to an FFIWalletManager instance - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `manager` must be a valid pointer to an FFIWalletManager instance - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `wallet_manager` @@ -888,10 +891,10 @@ managed_wallet_generate_addresses_to_index(managed_wallet: *mut FFIManagedWallet ``` **Description:** -Generate addresses up to a specific index in a pool This ensures that addresses up to and including the specified index exist in the pool. This is useful for wallet recovery or when specific indices are needed. # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `wallet` must be a valid pointer to an FFIWallet (for key derivation) - `error` must be a valid pointer to an FFIError or null +Generate addresses up to a specific index in a pool This ensures that addresses up to and including the specified index exist in the pool. This is useful for wallet recovery or when specific indices are needed. # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `wallet` must be a valid pointer to an FFIWallet (for key derivation) - `error` must be a valid pointer to an FFIError **Safety:** -- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `wallet` must be a valid pointer to an FFIWallet (for key derivation) - `error` must be a valid pointer to an FFIError or null +- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `wallet` must be a valid pointer to an FFIWallet (for key derivation) - `error` must be a valid pointer to an FFIError **Module:** `address_pool` @@ -920,10 +923,10 @@ managed_wallet_get_account_collection(manager: *const FFIWalletManager, wallet_i ``` **Description:** -Get managed account collection for a specific network from wallet manager # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `error` must be a valid pointer to an FFIError structure or null - The returned pointer must be freed with `managed_account_collection_free` when no longer needed +Get managed account collection for a specific network from wallet manager # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `error` must be a valid pointer to an FFIError structure - The returned pointer must be freed with `managed_account_collection_free` when no longer needed **Safety:** -- `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `error` must be a valid pointer to an FFIError structure or null - The returned pointer must be freed with `managed_account_collection_free` when no longer needed +- `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `error` must be a valid pointer to an FFIError structure - The returned pointer must be freed with `managed_account_collection_free` when no longer needed **Module:** `managed_account_collection` @@ -936,10 +939,10 @@ managed_wallet_get_account_count(manager: *const FFIWalletManager, wallet_id: *c ``` **Description:** -Get number of accounts in a managed wallet # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Get number of accounts in a managed wallet # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `managed_account` @@ -952,10 +955,10 @@ managed_wallet_get_address_pool_info(managed_wallet: *const FFIManagedWalletInfo ``` **Description:** -Get address pool information for an account # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `info_out` must be a valid pointer to store the pool info - `error` must be a valid pointer to an FFIError or null +Get address pool information for an account # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `info_out` must be a valid pointer to store the pool info - `error` must be a valid pointer to an FFIError **Safety:** -- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `info_out` must be a valid pointer to store the pool info - `error` must be a valid pointer to an FFIError or null +- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `info_out` must be a valid pointer to store the pool info - `error` must be a valid pointer to an FFIError **Module:** `address_pool` @@ -1112,10 +1115,10 @@ managed_wallet_get_utxos(managed_info: *const FFIManagedWalletInfo, utxos_out: * ``` **Description:** -Get all UTXOs from managed wallet info # Safety - `managed_info` must be a valid pointer to an FFIManagedWalletInfo instance - `utxos_out` must be a valid pointer to store the UTXO array pointer - `count_out` must be a valid pointer to store the UTXO count - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call - The returned UTXO array must be freed with `utxo_array_free` when no longer needed +Get all UTXOs from managed wallet info # Safety - `managed_info` must be a valid pointer to an FFIManagedWalletInfo instance - `utxos_out` must be a valid pointer to store the UTXO array pointer - `count_out` must be a valid pointer to store the UTXO count - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call - The returned UTXO array must be freed with `utxo_array_free` when no longer needed **Safety:** -- `managed_info` must be a valid pointer to an FFIManagedWalletInfo instance - `utxos_out` must be a valid pointer to store the UTXO array pointer - `count_out` must be a valid pointer to store the UTXO count - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call - The returned UTXO array must be freed with `utxo_array_free` when no longer needed +- `managed_info` must be a valid pointer to an FFIManagedWalletInfo instance - `utxos_out` must be a valid pointer to store the UTXO array pointer - `count_out` must be a valid pointer to store the UTXO count - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call - The returned UTXO array must be freed with `utxo_array_free` when no longer needed **Module:** `utxo` @@ -1144,10 +1147,10 @@ managed_wallet_mark_address_used(managed_wallet: *mut FFIManagedWalletInfo, addr ``` **Description:** -Mark an address as used in the pool This updates the pool's tracking of which addresses have been used, which is important for gap limit management and wallet recovery. # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `address` must be a valid C string - `error` must be a valid pointer to an FFIError or null +Mark an address as used in the pool This updates the pool's tracking of which addresses have been used, which is important for gap limit management and wallet recovery. # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `address` must be a valid C string - `error` must be a valid pointer to an FFIError **Safety:** -- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `address` must be a valid C string - `error` must be a valid pointer to an FFIError or null +- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `address` must be a valid C string - `error` must be a valid pointer to an FFIError **Module:** `address_pool` @@ -1160,10 +1163,10 @@ managed_wallet_set_gap_limit(managed_wallet: *mut FFIManagedWalletInfo, account_ ``` **Description:** -Set the gap limit for an address pool The gap limit determines how many unused addresses to maintain at the end of the pool. This is important for wallet recovery and address discovery. # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `error` must be a valid pointer to an FFIError or null +Set the gap limit for an address pool The gap limit determines how many unused addresses to maintain at the end of the pool. This is important for wallet recovery and address discovery. # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `error` must be a valid pointer to an FFIError **Safety:** -- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `error` must be a valid pointer to an FFIError or null +- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `error` must be a valid pointer to an FFIError **Module:** `address_pool` @@ -1176,10 +1179,10 @@ managed_wallet_synced_height(managed_wallet: *const FFIManagedWalletInfo, error: ``` **Description:** -Get current synced height from wallet info # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Get current synced height from wallet info # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `managed_wallet` @@ -1336,10 +1339,10 @@ wallet_create_from_mnemonic(mnemonic: *const c_char, passphrase: *const c_char, ``` **Description:** -Create a new wallet from mnemonic (backward compatibility - single network) # Safety - `mnemonic` must be a valid pointer to a null-terminated C string - `passphrase` must be a valid pointer to a null-terminated C string or null - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call - The returned pointer must be freed with `wallet_free` when no longer needed +Create a new wallet from mnemonic (backward compatibility - single network) # Safety - `mnemonic` must be a valid pointer to a null-terminated C string - `passphrase` must be a valid pointer to a null-terminated C string or null - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call - The returned pointer must be freed with `wallet_free` when no longer needed **Safety:** -- `mnemonic` must be a valid pointer to a null-terminated C string - `passphrase` must be a valid pointer to a null-terminated C string or null - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call - The returned pointer must be freed with `wallet_free` when no longer needed +- `mnemonic` must be a valid pointer to a null-terminated C string - `passphrase` must be a valid pointer to a null-terminated C string or null - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call - The returned pointer must be freed with `wallet_free` when no longer needed **Module:** `wallet` @@ -1352,10 +1355,10 @@ wallet_create_from_mnemonic_with_options(mnemonic: *const c_char, passphrase: *c ``` **Description:** -Create a new wallet from mnemonic with options # Safety - `mnemonic` must be a valid pointer to a null-terminated C string - `passphrase` must be a valid pointer to a null-terminated C string or null - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call - The returned pointer must be freed with `wallet_free` when no longer needed +Create a new wallet from mnemonic with options # Safety - `mnemonic` must be a valid pointer to a null-terminated C string - `passphrase` must be a valid pointer to a null-terminated C string or null - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call - The returned pointer must be freed with `wallet_free` when no longer needed **Safety:** -- `mnemonic` must be a valid pointer to a null-terminated C string - `passphrase` must be a valid pointer to a null-terminated C string or null - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call - The returned pointer must be freed with `wallet_free` when no longer needed +- `mnemonic` must be a valid pointer to a null-terminated C string - `passphrase` must be a valid pointer to a null-terminated C string or null - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call - The returned pointer must be freed with `wallet_free` when no longer needed **Module:** `wallet` @@ -1368,10 +1371,10 @@ wallet_create_from_seed(seed: *const u8, seed_len: usize, network: FFINetwork, e ``` **Description:** -Create a new wallet from seed (backward compatibility) # Safety - `seed` must be a valid pointer to a byte array of `seed_len` length - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Create a new wallet from seed (backward compatibility) # Safety - `seed` must be a valid pointer to a byte array of `seed_len` length - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `seed` must be a valid pointer to a byte array of `seed_len` length - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `seed` must be a valid pointer to a byte array of `seed_len` length - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `wallet` @@ -1384,10 +1387,10 @@ wallet_create_from_seed_with_options(seed: *const u8, seed_len: usize, network: ``` **Description:** -Create a new wallet from seed with options # Safety - `seed` must be a valid pointer to a byte array of `seed_len` length - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Create a new wallet from seed with options # Safety - `seed` must be a valid pointer to a byte array of `seed_len` length - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `seed` must be a valid pointer to a byte array of `seed_len` length - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `seed` must be a valid pointer to a byte array of `seed_len` length - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `wallet` @@ -1400,10 +1403,10 @@ wallet_create_managed_wallet(wallet: *const FFIWallet, error: *mut FFIError,) -> ``` **Description:** -Create a managed wallet from a regular wallet This creates a ManagedWalletInfo instance from a Wallet, which includes address pools and transaction checking capabilities. # Safety - `wallet` must be a valid pointer to an FFIWallet - `error` must be a valid pointer to an FFIError or null - The returned pointer must be freed with `managed_wallet_info_free` (or `ffi_managed_wallet_free` for compatibility) +Create a managed wallet from a regular wallet This creates a ManagedWalletInfo instance from a Wallet, which includes address pools and transaction checking capabilities. # Safety - `wallet` must be a valid pointer to an FFIWallet - `error` must be a valid pointer to an FFIError - The returned pointer must be freed with `managed_wallet_info_free` (or `ffi_managed_wallet_free` for compatibility) **Safety:** -- `wallet` must be a valid pointer to an FFIWallet - `error` must be a valid pointer to an FFIError or null - The returned pointer must be freed with `managed_wallet_info_free` (or `ffi_managed_wallet_free` for compatibility) +- `wallet` must be a valid pointer to an FFIWallet - `error` must be a valid pointer to an FFIError - The returned pointer must be freed with `managed_wallet_info_free` (or `ffi_managed_wallet_free` for compatibility) **Module:** `transaction_checking` @@ -1416,10 +1419,10 @@ wallet_create_random(network: FFINetwork, error: *mut FFIError,) -> *mut FFIWall ``` **Description:** -Create a new random wallet (backward compatibility) # Safety - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure the pointer remains valid for the duration of this call +Create a new random wallet (backward compatibility) # Safety - `error` must be a valid pointer to an FFIError structure - The caller must ensure the pointer remains valid for the duration of this call **Safety:** -- `error` must be a valid pointer to an FFIError structure or null - The caller must ensure the pointer remains valid for the duration of this call +- `error` must be a valid pointer to an FFIError structure - The caller must ensure the pointer remains valid for the duration of this call **Module:** `wallet` @@ -1432,10 +1435,10 @@ wallet_create_random_with_options(network: FFINetwork, account_options: *const F ``` **Description:** -Create a new random wallet with options # Safety - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Create a new random wallet with options # Safety - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `wallet` @@ -1592,10 +1595,10 @@ wallet_get_account_collection(wallet: *const FFIWallet, error: *mut FFIError,) - ``` **Description:** -Get account collection for a specific network from wallet # Safety - `wallet` must be a valid pointer to an FFIWallet instance - `error` must be a valid pointer to an FFIError structure or null - The returned pointer must be freed with `account_collection_free` when no longer needed +Get account collection for a specific network from wallet # Safety - `wallet` must be a valid pointer to an FFIWallet instance - `error` must be a valid pointer to an FFIError structure - The returned pointer must be freed with `account_collection_free` when no longer needed **Safety:** -- `wallet` must be a valid pointer to an FFIWallet instance - `error` must be a valid pointer to an FFIError structure or null - The returned pointer must be freed with `account_collection_free` when no longer needed +- `wallet` must be a valid pointer to an FFIWallet instance - `error` must be a valid pointer to an FFIError structure - The returned pointer must be freed with `account_collection_free` when no longer needed **Module:** `account_collection` @@ -1656,10 +1659,10 @@ wallet_get_id(wallet: *const FFIWallet, id_out: *mut u8, error: *mut FFIError,) ``` **Description:** -Get wallet ID (32-byte hash) # Safety - `wallet` must be a valid pointer to an FFIWallet - `id_out` must be a valid pointer to a 32-byte buffer - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Get wallet ID (32-byte hash) # Safety - `wallet` must be a valid pointer to an FFIWallet - `id_out` must be a valid pointer to a 32-byte buffer - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `wallet` must be a valid pointer to an FFIWallet - `id_out` must be a valid pointer to a 32-byte buffer - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `wallet` must be a valid pointer to an FFIWallet - `id_out` must be a valid pointer to a 32-byte buffer - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `wallet` @@ -1704,10 +1707,10 @@ wallet_get_xpub(wallet: *const FFIWallet, account_index: c_uint, error: *mut FFI ``` **Description:** -Get extended public key for account # Safety - `wallet` must be a valid pointer to an FFIWallet instance - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call - The returned C string must be freed by the caller when no longer needed +Get extended public key for account # Safety - `wallet` must be a valid pointer to an FFIWallet instance - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call - The returned C string must be freed by the caller when no longer needed **Safety:** -- `wallet` must be a valid pointer to an FFIWallet instance - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call - The returned C string must be freed by the caller when no longer needed +- `wallet` must be a valid pointer to an FFIWallet instance - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call - The returned C string must be freed by the caller when no longer needed **Module:** `wallet` @@ -1720,10 +1723,10 @@ wallet_has_mnemonic(wallet: *const FFIWallet, error: *mut FFIError,) -> bool ``` **Description:** -Check if wallet has mnemonic # Safety - `wallet` must be a valid pointer to an FFIWallet instance - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Check if wallet has mnemonic # Safety - `wallet` must be a valid pointer to an FFIWallet instance - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `wallet` must be a valid pointer to an FFIWallet instance - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `wallet` must be a valid pointer to an FFIWallet instance - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `wallet` @@ -1736,10 +1739,10 @@ wallet_is_watch_only(wallet: *const FFIWallet, error: *mut FFIError,) -> bool ``` **Description:** -Check if wallet is watch-only # Safety - `wallet` must be a valid pointer to an FFIWallet instance - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Check if wallet is watch-only # Safety - `wallet` must be a valid pointer to an FFIWallet instance - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `wallet` must be a valid pointer to an FFIWallet instance - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `wallet` must be a valid pointer to an FFIWallet instance - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `wallet` @@ -2186,10 +2189,10 @@ account_derive_extended_private_key_at(account: *const FFIAccount, master_xpriv: ``` **Description:** -Derive an extended private key from an account at a given index, using the provided master xpriv. Returns an opaque FFIExtendedPrivKey pointer that must be freed with `extended_private_key_free`. Notes: - This is chain-agnostic. For accounts with internal/external chains, this returns an error. - For hardened-only account types (e.g., EdDSA), a hardened index is used. # Safety - `account` and `master_xpriv` must be valid, non-null pointers allocated by this library. - `error` must be a valid pointer to an FFIError or null. - The caller must free the returned pointer with `extended_private_key_free`. +Derive an extended private key from an account at a given index, using the provided master xpriv. Returns an opaque FFIExtendedPrivKey pointer that must be freed with `extended_private_key_free`. Notes: - This is chain-agnostic. For accounts with internal/external chains, this returns an error. - For hardened-only account types (e.g., EdDSA), a hardened index is used. # Safety - `account` and `master_xpriv` must be valid, non-null pointers allocated by this library. - `error` must be a valid pointer to an FFIError. - The caller must free the returned pointer with `extended_private_key_free`. **Safety:** -- `account` and `master_xpriv` must be valid, non-null pointers allocated by this library. - `error` must be a valid pointer to an FFIError or null. - The caller must free the returned pointer with `extended_private_key_free`. +- `account` and `master_xpriv` must be valid, non-null pointers allocated by this library. - `error` must be a valid pointer to an FFIError. - The caller must free the returned pointer with `extended_private_key_free`. **Module:** `account_derivation` @@ -2202,10 +2205,10 @@ account_derive_extended_private_key_from_mnemonic(account: *const FFIAccount, mn ``` **Description:** -Derive an extended private key from a mnemonic + optional passphrase at the given index. Returns an opaque FFIExtendedPrivKey pointer that must be freed with `extended_private_key_free`. # Safety - `account` must be a valid pointer to an FFIAccount - `mnemonic` must be a valid, null-terminated C string - `passphrase` may be null; if not null, must be a valid C string - `error` must be a valid pointer to an FFIError or null +Derive an extended private key from a mnemonic + optional passphrase at the given index. Returns an opaque FFIExtendedPrivKey pointer that must be freed with `extended_private_key_free`. # Safety - `account` must be a valid pointer to an FFIAccount - `mnemonic` must be a valid, null-terminated C string - `passphrase` may be null; if not null, must be a valid C string - `error` must be a valid pointer to an FFIError **Safety:** -- `account` must be a valid pointer to an FFIAccount - `mnemonic` must be a valid, null-terminated C string - `passphrase` may be null; if not null, must be a valid C string - `error` must be a valid pointer to an FFIError or null +- `account` must be a valid pointer to an FFIAccount - `mnemonic` must be a valid, null-terminated C string - `passphrase` may be null; if not null, must be a valid C string - `error` must be a valid pointer to an FFIError **Module:** `account_derivation` @@ -2218,10 +2221,10 @@ account_derive_extended_private_key_from_seed(account: *const FFIAccount, seed: ``` **Description:** -Derive an extended private key from a raw seed buffer at the given index. Returns an opaque FFIExtendedPrivKey pointer that must be freed with `extended_private_key_free`. # Safety - `account` must be a valid pointer to an FFIAccount - `seed` must point to a valid buffer of length `seed_len` - `error` must be a valid pointer to an FFIError or null +Derive an extended private key from a raw seed buffer at the given index. Returns an opaque FFIExtendedPrivKey pointer that must be freed with `extended_private_key_free`. # Safety - `account` must be a valid pointer to an FFIAccount - `seed` must point to a valid buffer of length `seed_len` - `error` must be a valid pointer to an FFIError **Safety:** -- `account` must be a valid pointer to an FFIAccount - `seed` must point to a valid buffer of length `seed_len` - `error` must be a valid pointer to an FFIError or null +- `account` must be a valid pointer to an FFIAccount - `seed` must point to a valid buffer of length `seed_len` - `error` must be a valid pointer to an FFIError **Module:** `account_derivation` @@ -2234,10 +2237,10 @@ account_derive_private_key_as_wif_at(account: *const FFIAccount, master_xpriv: * ``` **Description:** -Derive a private key from an account at a given chain/index and return as WIF string. Caller must free the returned string with `string_free`. # Safety - `account` and `master_xpriv` must be valid pointers allocated by this library - `error` must be a valid pointer to an FFIError or null +Derive a private key from an account at a given chain/index and return as WIF string. Caller must free the returned string with `string_free`. # Safety - `account` and `master_xpriv` must be valid pointers allocated by this library - `error` must be a valid pointer to an FFIError **Safety:** -- `account` and `master_xpriv` must be valid pointers allocated by this library - `error` must be a valid pointer to an FFIError or null +- `account` and `master_xpriv` must be valid pointers allocated by this library - `error` must be a valid pointer to an FFIError **Module:** `account_derivation` @@ -2250,10 +2253,10 @@ account_derive_private_key_at(account: *const FFIAccount, master_xpriv: *const F ``` **Description:** -Derive a private key (secp256k1) from an account at a given chain/index, using the provided master xpriv. Returns an opaque FFIPrivateKey pointer that must be freed with `private_key_free`. # Safety - `account` and `master_xpriv` must be valid pointers allocated by this library - `error` must be a valid pointer to an FFIError or null +Derive a private key (secp256k1) from an account at a given chain/index, using the provided master xpriv. Returns an opaque FFIPrivateKey pointer that must be freed with `private_key_free`. # Safety - `account` and `master_xpriv` must be valid pointers allocated by this library - `error` must be a valid pointer to an FFIError **Safety:** -- `account` and `master_xpriv` must be valid pointers allocated by this library - `error` must be a valid pointer to an FFIError or null +- `account` and `master_xpriv` must be valid pointers allocated by this library - `error` must be a valid pointer to an FFIError **Module:** `account_derivation` @@ -2266,10 +2269,10 @@ account_derive_private_key_from_mnemonic(account: *const FFIAccount, mnemonic: * ``` **Description:** -Derive a private key from a mnemonic + optional passphrase at the given index. Returns an opaque FFIPrivateKey pointer that must be freed with `private_key_free`. # Safety - `account` must be a valid pointer to an FFIAccount - `mnemonic` must be a valid, null-terminated C string - `passphrase` may be null; if not null, must be a valid C string - `error` must be a valid pointer to an FFIError or null +Derive a private key from a mnemonic + optional passphrase at the given index. Returns an opaque FFIPrivateKey pointer that must be freed with `private_key_free`. # Safety - `account` must be a valid pointer to an FFIAccount - `mnemonic` must be a valid, null-terminated C string - `passphrase` may be null; if not null, must be a valid C string - `error` must be a valid pointer to an FFIError **Safety:** -- `account` must be a valid pointer to an FFIAccount - `mnemonic` must be a valid, null-terminated C string - `passphrase` may be null; if not null, must be a valid C string - `error` must be a valid pointer to an FFIError or null +- `account` must be a valid pointer to an FFIAccount - `mnemonic` must be a valid, null-terminated C string - `passphrase` may be null; if not null, must be a valid C string - `error` must be a valid pointer to an FFIError **Module:** `account_derivation` @@ -2282,10 +2285,10 @@ account_derive_private_key_from_seed(account: *const FFIAccount, seed: *const u8 ``` **Description:** -Derive a private key from a raw seed buffer at the given index. Returns an opaque FFIPrivateKey pointer that must be freed with `private_key_free`. # Safety - `account` must be a valid pointer to an FFIAccount - `seed` must point to a valid buffer of length `seed_len` - `error` must be a valid pointer to an FFIError or null +Derive a private key from a raw seed buffer at the given index. Returns an opaque FFIPrivateKey pointer that must be freed with `private_key_free`. # Safety - `account` must be a valid pointer to an FFIAccount - `seed` must point to a valid buffer of length `seed_len` - `error` must be a valid pointer to an FFIError **Safety:** -- `account` must be a valid pointer to an FFIAccount - `seed` must point to a valid buffer of length `seed_len` - `error` must be a valid pointer to an FFIError or null +- `account` must be a valid pointer to an FFIAccount - `seed` must point to a valid buffer of length `seed_len` - `error` must be a valid pointer to an FFIError **Module:** `account_derivation` @@ -2448,7 +2451,10 @@ derivation_bip44_account_path(network: FFINetwork, account_index: c_uint, path_o ``` **Description:** -Derive a BIP44 account path (m/44'/5'/account') +Derive a BIP44 account path (m/44'/5'/account') # Safety `path_out` must point to a writable buffer of at least `path_max_len` bytes and `error` must be a valid pointer to an `FFIError`. + +**Safety:** +`path_out` must point to a writable buffer of at least `path_max_len` bytes and `error` must be a valid pointer to an `FFIError`. **Module:** `derivation` @@ -3509,10 +3515,10 @@ address_pool_get_address_at_index(pool: *const FFIAddressPool, index: u32, error ``` **Description:** -Get a single address info at a specific index from the pool Returns detailed information about the address at the given index, or NULL if the index is out of bounds or not generated yet. # Safety - `pool` must be a valid pointer to an FFIAddressPool - `error` must be a valid pointer to an FFIError or null - The returned FFIAddressInfo must be freed using `address_info_free` +Get a single address info at a specific index from the pool Returns detailed information about the address at the given index, or NULL if the index is out of bounds or not generated yet. # Safety - `pool` must be a valid pointer to an FFIAddressPool - `error` must be a valid pointer to an FFIError - The returned FFIAddressInfo must be freed using `address_info_free` **Safety:** -- `pool` must be a valid pointer to an FFIAddressPool - `error` must be a valid pointer to an FFIError or null - The returned FFIAddressInfo must be freed using `address_info_free` +- `pool` must be a valid pointer to an FFIAddressPool - `error` must be a valid pointer to an FFIError - The returned FFIAddressInfo must be freed using `address_info_free` **Module:** `address_pool` @@ -3525,10 +3531,10 @@ address_pool_get_addresses_in_range(pool: *const FFIAddressPool, start_index: u3 ``` **Description:** -Get a range of addresses from the pool Returns an array of FFIAddressInfo structures for addresses in the range [start_index, end_index). The count_out parameter will be set to the actual number of addresses returned. Note: This function only reads existing addresses from the pool. It does not generate new addresses. Use managed_wallet_generate_addresses_to_index if you need to generate addresses first. # Safety - `pool` must be a valid pointer to an FFIAddressPool - `count_out` must be a valid pointer to store the count - `error` must be a valid pointer to an FFIError or null - The returned array must be freed using `address_info_array_free` +Get a range of addresses from the pool Returns an array of FFIAddressInfo structures for addresses in the range [start_index, end_index). The count_out parameter will be set to the actual number of addresses returned. Note: This function only reads existing addresses from the pool. It does not generate new addresses. Use managed_wallet_generate_addresses_to_index if you need to generate addresses first. # Safety - `pool` must be a valid pointer to an FFIAddressPool - `count_out` must be a valid pointer to store the count - `error` must be a valid pointer to an FFIError - The returned array must be freed using `address_info_array_free` **Safety:** -- `pool` must be a valid pointer to an FFIAddressPool - `count_out` must be a valid pointer to store the count - `error` must be a valid pointer to an FFIError or null - The returned array must be freed using `address_info_array_free` +- `pool` must be a valid pointer to an FFIAddressPool - `count_out` must be a valid pointer to store the count - `error` must be a valid pointer to an FFIError - The returned array must be freed using `address_info_array_free` **Module:** `address_pool` @@ -3639,10 +3645,10 @@ transaction_classify(tx_bytes: *const u8, tx_len: usize, error: *mut FFIError,) ``` **Description:** -Get the transaction classification for routing Returns a string describing the transaction type (e.g., "Standard", "CoinJoin", "AssetLock", "AssetUnlock", "ProviderRegistration", etc.) # Safety - `tx_bytes` must be a valid pointer to transaction bytes with at least `tx_len` bytes - `error` must be a valid pointer to an FFIError or null - The returned string must be freed by the caller +Get the transaction classification for routing Returns a string describing the transaction type (e.g., "Standard", "CoinJoin", "AssetLock", "AssetUnlock", "ProviderRegistration", etc.) # Safety - `tx_bytes` must be a valid pointer to transaction bytes with at least `tx_len` bytes - `error` must be a valid pointer to an FFIError - The returned string must be freed by the caller **Safety:** -- `tx_bytes` must be a valid pointer to transaction bytes with at least `tx_len` bytes - `error` must be a valid pointer to an FFIError or null - The returned string must be freed by the caller +- `tx_bytes` must be a valid pointer to transaction bytes with at least `tx_len` bytes - `error` must be a valid pointer to an FFIError - The returned string must be freed by the caller **Module:** `transaction_checking` @@ -3794,14 +3800,14 @@ Free UTXO array # Safety - `utxos` must be a valid pointer to an array of FFIU #### `bip38_decrypt_private_key` ```c -bip38_decrypt_private_key(encrypted_key: *const c_char, passphrase: *const c_char, error: *mut FFIError,) -> *mut c_char +bip38_decrypt_private_key(_encrypted_key: *const c_char, _passphrase: *const c_char, error: *mut FFIError,) -> *mut c_char ``` **Description:** -Decrypt a BIP38 encrypted private key # Safety This function is unsafe because it dereferences raw pointers: - `encrypted_key` must be a valid, null-terminated C string - `passphrase` must be a valid, null-terminated C string - `error` must be a valid pointer to an FFIError or null +Decrypt a BIP38 encrypted private key # Safety This function is unsafe because it dereferences raw pointers: - `encrypted_key` must be a valid, null-terminated C string - `passphrase` must be a valid, null-terminated C string - `error` must be a valid pointer to an FFIError **Safety:** -This function is unsafe because it dereferences raw pointers: - `encrypted_key` must be a valid, null-terminated C string - `passphrase` must be a valid, null-terminated C string - `error` must be a valid pointer to an FFIError or null +This function is unsafe because it dereferences raw pointers: - `encrypted_key` must be a valid, null-terminated C string - `passphrase` must be a valid, null-terminated C string - `error` must be a valid pointer to an FFIError **Module:** `bip38` @@ -3810,14 +3816,14 @@ This function is unsafe because it dereferences raw pointers: - `encrypted_key` #### `bip38_encrypt_private_key` ```c -bip38_encrypt_private_key(private_key: *const c_char, passphrase: *const c_char, error: *mut FFIError,) -> *mut c_char +bip38_encrypt_private_key(_private_key: *const c_char, _passphrase: *const c_char, error: *mut FFIError,) -> *mut c_char ``` **Description:** -Encrypt a private key with BIP38 # Safety This function is unsafe because it dereferences raw pointers: - `private_key` must be a valid, null-terminated C string - `passphrase` must be a valid, null-terminated C string - `error` must be a valid pointer to an FFIError or null +Encrypt a private key with BIP38 # Safety This function is unsafe because it dereferences raw pointers: - `private_key` must be a valid, null-terminated C string - `passphrase` must be a valid, null-terminated C string - `error` must be a valid pointer to an FFIError **Safety:** -This function is unsafe because it dereferences raw pointers: - `private_key` must be a valid, null-terminated C string - `passphrase` must be a valid, null-terminated C string - `error` must be a valid pointer to an FFIError or null +This function is unsafe because it dereferences raw pointers: - `private_key` must be a valid, null-terminated C string - `passphrase` must be a valid, null-terminated C string - `error` must be a valid pointer to an FFIError **Module:** `bip38` @@ -3830,10 +3836,10 @@ derivation_derive_private_key_from_seed(seed: *const u8, seed_len: usize, path: ``` **Description:** -Derive private key for a specific path from seed # Safety - `seed` must be a valid pointer to a byte array of `seed_len` length - `path` must be a valid pointer to a null-terminated C string - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +Derive private key for a specific path from seed # Safety - `seed` must be a valid pointer to a byte array of `seed_len` length - `path` must be a valid pointer to a null-terminated C string - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Safety:** -- `seed` must be a valid pointer to a byte array of `seed_len` length - `path` must be a valid pointer to a null-terminated C string - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call +- `seed` must be a valid pointer to a byte array of `seed_len` length - `path` must be a valid pointer to a null-terminated C string - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call **Module:** `derivation` @@ -3846,10 +3852,10 @@ derivation_new_master_key(seed: *const u8, seed_len: usize, network: FFINetwork, ``` **Description:** -Create a new master extended private key from seed # Safety - `seed` must be a valid pointer to a byte array of `seed_len` length - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure the seed pointer remains valid for the duration of this call +Create a new master extended private key from seed # Safety - `seed` must be a valid pointer to a byte array of `seed_len` length - `error` must be a valid pointer to an FFIError structure - The caller must ensure the seed pointer remains valid for the duration of this call **Safety:** -- `seed` must be a valid pointer to a byte array of `seed_len` length - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure the seed pointer remains valid for the duration of this call +- `seed` must be a valid pointer to a byte array of `seed_len` length - `error` must be a valid pointer to an FFIError structure - The caller must ensure the seed pointer remains valid for the duration of this call **Module:** `derivation` @@ -3890,7 +3896,7 @@ Get the private key from an extended private key Extracts the non-extended priv #### `extended_private_key_to_string` ```c -extended_private_key_to_string(key: *const FFIExtendedPrivKey, network: FFINetwork, error: *mut FFIError,) -> *mut c_char +extended_private_key_to_string(key: *const FFIExtendedPrivKey, _network: FFINetwork, error: *mut FFIError,) -> *mut c_char ``` **Description:** @@ -3938,7 +3944,7 @@ Get the public key from an extended public key Extracts the non-extended public #### `extended_public_key_to_string` ```c -extended_public_key_to_string(key: *const FFIExtendedPubKey, network: FFINetwork, error: *mut FFIError,) -> *mut c_char +extended_public_key_to_string(key: *const FFIExtendedPubKey, _network: FFINetwork, error: *mut FFIError,) -> *mut c_char ``` **Description:** @@ -4036,11 +4042,14 @@ Free a mnemonic string # Safety - `mnemonic` must be a valid pointer created b #### `mnemonic_generate` ```c -mnemonic_generate(word_count: c_uint, error: *mut FFIError) -> *mut c_char +mnemonic_generate(word_count: c_uint, error: *mut FFIError,) -> *mut c_char ``` **Description:** -Generate a new mnemonic with specified word count (12, 15, 18, 21, or 24) +Generate a new mnemonic with specified word count (12, 15, 18, 21, or 24) # Safety `error` must be a valid pointer to an `FFIError`. The returned string must be freed with `mnemonic_free`. + +**Safety:** +`error` must be a valid pointer to an `FFIError`. The returned string must be freed with `mnemonic_free`. **Module:** `mnemonic` @@ -4053,7 +4062,10 @@ mnemonic_generate_with_language(word_count: c_uint, language: FFILanguage, error ``` **Description:** -Generate a new mnemonic with specified language and word count +Generate a new mnemonic with specified language and word count # Safety `error` must be a valid pointer to an `FFIError`. The returned string must be freed with `mnemonic_free`. + +**Safety:** +`error` must be a valid pointer to an `FFIError`. The returned string must be freed with `mnemonic_free`. **Module:** `mnemonic` @@ -4116,7 +4128,10 @@ derivation_bip44_payment_path(network: FFINetwork, account_index: c_uint, is_cha ``` **Description:** -Derive a BIP44 payment path (m/44'/5'/account'/change/index) +Derive a BIP44 payment path (m/44'/5'/account'/change/index) # Safety `path_out` must point to a writable buffer of at least `path_max_len` bytes and `error` must be a valid pointer to an `FFIError`. + +**Safety:** +`path_out` must point to a writable buffer of at least `path_max_len` bytes and `error` must be a valid pointer to an `FFIError`. **Module:** `derivation` @@ -4129,7 +4144,10 @@ derivation_coinjoin_path(network: FFINetwork, account_index: c_uint, path_out: * ``` **Description:** -Derive CoinJoin path (m/9'/5'/4'/account') +Derive CoinJoin path (m/9'/5'/4'/account') # Safety `path_out` must point to a writable buffer of at least `path_max_len` bytes and `error` must be a valid pointer to an `FFIError`. + +**Safety:** +`path_out` must point to a writable buffer of at least `path_max_len` bytes and `error` must be a valid pointer to an `FFIError`. **Module:** `derivation` @@ -4142,7 +4160,10 @@ derivation_identity_authentication_path(network: FFINetwork, identity_index: c_u ``` **Description:** -Derive identity authentication path (m/9'/5'/5'/0'/identity_index'/key_index') +Derive identity authentication path (m/9'/5'/5'/0'/identity_index'/key_index') # Safety `path_out` must point to a writable buffer of at least `path_max_len` bytes and `error` must be a valid pointer to an `FFIError`. + +**Safety:** +`path_out` must point to a writable buffer of at least `path_max_len` bytes and `error` must be a valid pointer to an `FFIError`. **Module:** `derivation` @@ -4155,7 +4176,10 @@ derivation_identity_registration_path(network: FFINetwork, identity_index: c_uin ``` **Description:** -Derive identity registration path (m/9'/5'/5'/1'/index') +Derive identity registration path (m/9'/5'/5'/1'/index') # Safety `path_out` must point to a writable buffer of at least `path_max_len` bytes and `error` must be a valid pointer to an `FFIError`. + +**Safety:** +`path_out` must point to a writable buffer of at least `path_max_len` bytes and `error` must be a valid pointer to an `FFIError`. **Module:** `derivation` @@ -4168,7 +4192,10 @@ derivation_identity_topup_path(network: FFINetwork, identity_index: c_uint, topu ``` **Description:** -Derive identity top-up path (m/9'/5'/5'/2'/identity_index'/top_up_index') +Derive identity top-up path (m/9'/5'/5'/2'/identity_index'/top_up_index') # Safety `path_out` must point to a writable buffer of at least `path_max_len` bytes and `error` must be a valid pointer to an `FFIError`. + +**Safety:** +`path_out` must point to a writable buffer of at least `path_max_len` bytes and `error` must be a valid pointer to an `FFIError`. **Module:** `derivation` diff --git a/key-wallet-ffi/src/account.rs b/key-wallet-ffi/src/account.rs index 4fa847d87..0c933095b 100644 --- a/key-wallet-ffi/src/account.rs +++ b/key-wallet-ffi/src/account.rs @@ -1,5 +1,6 @@ //! Account management functions +use crate::deref_ptr; use crate::error::{FFIError, FFIErrorCode}; use crate::types::{FFIAccountResult, FFIAccountType, FFIWallet}; use dashcore::ffi::FFINetwork; @@ -554,14 +555,8 @@ pub unsafe extern "C" fn wallet_get_account_count( wallet: *const FFIWallet, error: *mut FFIError, ) -> c_uint { - if wallet.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Wallet is null".to_string()); - return 0; - } - - let wallet = &*wallet; + let wallet = deref_ptr!(wallet, error); let accounts = &wallet.inner().accounts; - FFIError::set_success(error); let count = accounts.standard_bip44_accounts.len() + accounts.standard_bip32_accounts.len() + accounts.coinjoin_accounts.len() diff --git a/key-wallet-ffi/src/account_collection.rs b/key-wallet-ffi/src/account_collection.rs index b608e2149..e37c7a133 100644 --- a/key-wallet-ffi/src/account_collection.rs +++ b/key-wallet-ffi/src/account_collection.rs @@ -8,7 +8,8 @@ use std::os::raw::{c_char, c_uint}; use std::ptr; use crate::account::FFIAccount; -use crate::error::{FFIError, FFIErrorCode}; +use crate::deref_ptr; +use crate::error::FFIError; use crate::types::FFIWallet; /// Opaque handle to an account collection @@ -78,20 +79,14 @@ pub struct FFIAccountCollectionSummary { /// # Safety /// /// - `wallet` must be a valid pointer to an FFIWallet instance -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The returned pointer must be freed with `account_collection_free` when no longer needed #[no_mangle] pub unsafe extern "C" fn wallet_get_account_collection( wallet: *const FFIWallet, error: *mut FFIError, ) -> *mut FFIAccountCollection { - if wallet.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Wallet is null".to_string()); - return ptr::null_mut(); - } - - let wallet = &*wallet; - FFIError::set_success(error); + let wallet = deref_ptr!(wallet, error); let ffi_collection = FFIAccountCollection::new(&wallet.inner().accounts); Box::into_raw(Box::new(ffi_collection)) } @@ -1071,6 +1066,7 @@ mod tests { fn test_account_collection_basic() { unsafe { let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); + let error = &mut FFIError::default(); // Create wallet with default accounts let wallet = wallet_create_from_mnemonic_with_options( @@ -1078,12 +1074,12 @@ mod tests { ptr::null(), FFINetwork::Testnet, ptr::null(), - ptr::null_mut(), + error, ); assert!(!wallet.is_null()); // Get account collection - let collection = wallet_get_account_collection(wallet, ptr::null_mut()); + let collection = wallet_get_account_collection(wallet, error); assert!(!collection.is_null()); // Check that we have some accounts @@ -1117,6 +1113,7 @@ mod tests { fn test_bls_account() { unsafe { let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); + let error = &mut FFIError::default(); // Create wallet with provider accounts let mut options = crate::types::FFIWalletAccountCreationOptions::default_options(); @@ -1132,12 +1129,12 @@ mod tests { ptr::null(), FFINetwork::Testnet, &options, - ptr::null_mut(), + error, ); assert!(!wallet.is_null()); // Get account collection - let collection = wallet_get_account_collection(wallet, ptr::null_mut()); + let collection = wallet_get_account_collection(wallet, error); assert!(!collection.is_null()); // Check for provider operator keys account (BLS) @@ -1163,6 +1160,7 @@ mod tests { fn test_eddsa_account() { unsafe { let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); + let error = &mut FFIError::default(); // Create wallet with provider accounts let mut options = crate::types::FFIWalletAccountCreationOptions::default_options(); @@ -1178,12 +1176,12 @@ mod tests { ptr::null(), FFINetwork::Testnet, &options, - ptr::null_mut(), + error, ); assert!(!wallet.is_null()); // Get account collection - let collection = wallet_get_account_collection(wallet, ptr::null_mut()); + let collection = wallet_get_account_collection(wallet, error); assert!(!collection.is_null()); // Check for provider platform keys account (EdDSA) @@ -1210,6 +1208,7 @@ mod tests { use std::ffi::CStr; let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); + let error = &mut FFIError::default(); // Create wallet with multiple account types let mut options = crate::types::FFIWalletAccountCreationOptions::default_options(); @@ -1248,12 +1247,12 @@ mod tests { ptr::null(), FFINetwork::Testnet, &options, - ptr::null_mut(), + error, ); assert!(!wallet.is_null()); // Get account collection - let collection = wallet_get_account_collection(wallet, ptr::null_mut()); + let collection = wallet_get_account_collection(wallet, error); assert!(!collection.is_null()); // Get the summary @@ -1289,6 +1288,7 @@ mod tests { use std::ffi::CStr; let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); + let error = &mut FFIError::default(); // Create wallet with no accounts using SpecificAccounts with empty lists let mut options = crate::types::FFIWalletAccountCreationOptions::default_options(); @@ -1300,12 +1300,12 @@ mod tests { ptr::null(), FFINetwork::Testnet, &options, - ptr::null_mut(), + error, ); assert!(!wallet.is_null()); // Get account collection - let collection = wallet_get_account_collection(wallet, ptr::null_mut()); + let collection = wallet_get_account_collection(wallet, error); // With SpecificAccounts and empty lists, collection might be null or empty if collection.is_null() { @@ -1346,6 +1346,7 @@ mod tests { fn test_account_collection_summary_data() { unsafe { let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); + let error = &mut FFIError::default(); // Create wallet with various account types let mut options = crate::types::FFIWalletAccountCreationOptions::default_options(); @@ -1384,12 +1385,12 @@ mod tests { ptr::null(), FFINetwork::Testnet, &options, - ptr::null_mut(), + error, ); assert!(!wallet.is_null()); // Get account collection - let collection = wallet_get_account_collection(wallet, ptr::null_mut()); + let collection = wallet_get_account_collection(wallet, error); assert!(!collection.is_null()); // Get the summary data @@ -1447,6 +1448,7 @@ mod tests { fn test_account_collection_summary_data_empty() { unsafe { let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); + let error = &mut FFIError::default(); // Create wallet with no accounts - but still create a collection on the network // Use SpecificAccounts with empty lists to get truly empty collections @@ -1470,12 +1472,12 @@ mod tests { ptr::null(), FFINetwork::Testnet, &options, - ptr::null_mut(), + error, ); assert!(!wallet.is_null()); // Get account collection - let collection = wallet_get_account_collection(wallet, ptr::null_mut()); + let collection = wallet_get_account_collection(wallet, error); // With AllAccounts but empty lists, collection should still exist if collection.is_null() { @@ -1533,6 +1535,7 @@ mod tests { fn test_account_collection_summary_memory_management() { unsafe { let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); + let error = &mut FFIError::default(); // Create wallet with default accounts (which should have at least BIP44 account 0) let wallet = wallet_create_from_mnemonic_with_options( @@ -1540,12 +1543,12 @@ mod tests { ptr::null(), FFINetwork::Testnet, ptr::null(), - ptr::null_mut(), + error, ); assert!(!wallet.is_null()); // Get account collection - let collection = wallet_get_account_collection(wallet, ptr::null_mut()); + let collection = wallet_get_account_collection(wallet, error); assert!(!collection.is_null()); // Get multiple summaries to test memory management diff --git a/key-wallet-ffi/src/account_derivation.rs b/key-wallet-ffi/src/account_derivation.rs index 7caa69044..cabc9d35a 100644 --- a/key-wallet-ffi/src/account_derivation.rs +++ b/key-wallet-ffi/src/account_derivation.rs @@ -7,6 +7,7 @@ use crate::account::FFIBLSAccount; use crate::account::FFIEdDSAAccount; use crate::error::{FFIError, FFIErrorCode}; use crate::keys::{FFIExtendedPrivKey, FFIPrivateKey}; +use crate::{check_ptr, deref_ptr, unwrap_or_return}; use key_wallet::account::derivation::AccountDerivation; use key_wallet::account::AccountTrait; use std::ffi::CString; @@ -25,7 +26,7 @@ use std::ptr; /// /// # Safety /// - `account` and `master_xpriv` must be valid, non-null pointers allocated by this library. -/// - `error` must be a valid pointer to an FFIError or null. +/// - `error` must be a valid pointer to an FFIError. /// - The caller must free the returned pointer with `extended_private_key_free`. #[no_mangle] pub unsafe extern "C" fn account_derive_extended_private_key_at( @@ -34,37 +35,22 @@ pub unsafe extern "C" fn account_derive_extended_private_key_at( index: c_uint, error: *mut FFIError, ) -> *mut FFIExtendedPrivKey { - if account.is_null() || master_xpriv.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let account = &*account; - let master_xpriv = &*master_xpriv; + let account = deref_ptr!(account, error); + let master_xpriv = deref_ptr!(master_xpriv, error); if account.inner().is_watch_only() { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - "Account is watch-only; private derivation not allowed".to_string(), + "Account is watch-only; private derivation not allowed", ); return ptr::null_mut(); } - match account.inner().derive_from_master_xpriv_extended_xpriv_at(master_xpriv.inner(), index) { - Ok(derived) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIExtendedPrivKey::from_inner(derived))) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive extended private key: {:?}", e), - ); - ptr::null_mut() - } - } + let derived = unwrap_or_return!( + account.inner().derive_from_master_xpriv_extended_xpriv_at(master_xpriv.inner(), index), + error + ); + Box::into_raw(Box::new(FFIExtendedPrivKey::from_inner(derived))) } // ========================= BLS (feature = "bls") ========================= @@ -80,7 +66,7 @@ pub unsafe extern "C" fn account_derive_extended_private_key_at( /// # Safety /// - `account` must be a valid, non-null pointer to an `FFIBLSAccount` (only when `bls` feature is enabled). /// - `seed` must point to a readable buffer of length `seed_len` (1..=64 bytes expected). -/// - `error` must be a valid pointer to an FFIError or null. +/// - `error` must be a valid pointer to an FFIError. /// - Returned string must be freed with `string_free`. #[cfg(feature = "bls")] #[no_mangle] @@ -91,48 +77,18 @@ pub unsafe extern "C" fn bls_account_derive_private_key_from_seed( index: c_uint, error: *mut FFIError, ) -> *mut c_char { - if account.is_null() || seed.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - let account = &*account; + let account = deref_ptr!(account, error); + check_ptr!(seed, error); if seed_len == 0 || seed_len > 64 { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Seed length must be between 1 and 64 bytes".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "Seed length must be between 1 and 64 bytes"); return ptr::null_mut(); } let seed_slice = std::slice::from_raw_parts(seed, seed_len); - match account.inner().derive_from_seed_private_key_at(seed_slice, index) { - Ok(sk) => { - // Return private key bytes as hex - let hex = hex::encode(sk.to_be_bytes()); - match CString::new(hex) { - Ok(s) => { - FFIError::set_success(error); - s.into_raw() - } - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Allocation failed".into(), - ); - ptr::null_mut() - } - } - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive BLS private key from seed: {:?}", e), - ); - ptr::null_mut() - } - } + let sk = unwrap_or_return!( + account.inner().derive_from_seed_private_key_at(seed_slice, index), + error + ); + unwrap_or_return!(CString::new(hex::encode(sk.to_be_bytes())), error).into_raw() } /// Derive a BLS private key from a mnemonic + optional passphrase at the given index. @@ -148,7 +104,7 @@ pub unsafe extern "C" fn bls_account_derive_private_key_from_seed( /// - `account` must be a valid, non-null pointer to an `FFIBLSAccount` (only when `bls` feature is enabled). /// - `mnemonic` must be a valid, null-terminated UTF-8 C string. /// - `passphrase` may be null; if not null, must be a valid UTF-8 C string. -/// - `error` must be a valid pointer to an FFIError or null. +/// - `error` must be a valid pointer to an FFIError. /// - Returned string must be freed with `string_free`. #[cfg(feature = "bls")] #[no_mangle] @@ -159,69 +115,24 @@ pub unsafe extern "C" fn bls_account_derive_private_key_from_mnemonic( index: c_uint, error: *mut FFIError, ) -> *mut c_char { - if account.is_null() || mnemonic.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - let account = &*account; - let mnemonic_str = match std::ffi::CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid mnemonic string".into(), - ); - return ptr::null_mut(); - } - }; + let account = deref_ptr!(account, error); + let mnemonic = deref_ptr!(mnemonic, error); + let mnemonic_str = unwrap_or_return!(std::ffi::CStr::from_ptr(mnemonic).to_str(), error); let passphrase_str = if passphrase.is_null() { None } else { - match std::ffi::CStr::from_ptr(passphrase).to_str() { - Ok(s) => Some(s), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid passphrase string".into(), - ); - return ptr::null_mut(); - } - } + Some(unwrap_or_return!(std::ffi::CStr::from_ptr(passphrase).to_str(), error)) }; - match account.inner().derive_from_mnemonic_private_key_at( - mnemonic_str, - passphrase_str, - key_wallet::mnemonic::Language::English, - index, - ) { - Ok(sk) => { - let hex = hex::encode(sk.to_be_bytes()); - match CString::new(hex) { - Ok(s) => { - FFIError::set_success(error); - s.into_raw() - } - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Allocation failed".into(), - ); - ptr::null_mut() - } - } - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive BLS private key from mnemonic: {:?}", e), - ); - ptr::null_mut() - } - } + let sk = unwrap_or_return!( + account.inner().derive_from_mnemonic_private_key_at( + mnemonic_str, + passphrase_str, + key_wallet::mnemonic::Language::English, + index, + ), + error + ); + unwrap_or_return!(CString::new(hex::encode(sk.to_be_bytes())), error).into_raw() } // ========================= EdDSA (feature = "eddsa") ========================= @@ -237,7 +148,7 @@ pub unsafe extern "C" fn bls_account_derive_private_key_from_mnemonic( /// # Safety /// - `account` must be a valid, non-null pointer to an `FFIEdDSAAccount` (only when `eddsa` feature is enabled). /// - `seed` must point to a readable buffer of length `seed_len` (1..=64 bytes expected). -/// - `error` must be a valid pointer to an FFIError or null. +/// - `error` must be a valid pointer to an FFIError. /// - Returned string must be freed with `string_free`. #[cfg(feature = "eddsa")] #[no_mangle] @@ -248,40 +159,14 @@ pub unsafe extern "C" fn eddsa_account_derive_private_key_from_seed( index: c_uint, error: *mut FFIError, ) -> *mut c_char { - if account.is_null() || seed.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - let account = &*account; + let account = deref_ptr!(account, error); + check_ptr!(seed, error); let seed_slice = std::slice::from_raw_parts(seed, seed_len); - match account.inner().derive_from_seed_private_key_at(seed_slice, index) { - Ok(sk) => { - // Return 32-byte ed25519 seed/private key as hex - let hex = hex::encode(sk.to_bytes()); - match CString::new(hex) { - Ok(s) => { - FFIError::set_success(error); - s.into_raw() - } - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Allocation failed".into(), - ); - ptr::null_mut() - } - } - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive EdDSA private key from seed: {:?}", e), - ); - ptr::null_mut() - } - } + let sk = unwrap_or_return!( + account.inner().derive_from_seed_private_key_at(seed_slice, index), + error + ); + unwrap_or_return!(CString::new(hex::encode(sk.to_bytes())), error).into_raw() } /// Derive an EdDSA (ed25519) private key from a mnemonic + optional passphrase at the given index. @@ -296,7 +181,7 @@ pub unsafe extern "C" fn eddsa_account_derive_private_key_from_seed( /// - `account` must be a valid, non-null pointer to an `FFIEdDSAAccount` (only when `eddsa` feature is enabled). /// - `mnemonic` must be a valid, null-terminated UTF-8 C string. /// - `passphrase` may be null; if not null, must be a valid UTF-8 C string. -/// - `error` must be a valid pointer to an FFIError or null. +/// - `error` must be a valid pointer to an FFIError. /// - Returned string must be freed with `string_free`. #[cfg(feature = "eddsa")] #[no_mangle] @@ -307,69 +192,24 @@ pub unsafe extern "C" fn eddsa_account_derive_private_key_from_mnemonic( index: c_uint, error: *mut FFIError, ) -> *mut c_char { - if account.is_null() || mnemonic.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - let account = &*account; - let mnemonic_str = match std::ffi::CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid mnemonic string".into(), - ); - return ptr::null_mut(); - } - }; + let account = deref_ptr!(account, error); + let mnemonic = deref_ptr!(mnemonic, error); + let mnemonic_str = unwrap_or_return!(std::ffi::CStr::from_ptr(mnemonic).to_str(), error); let passphrase_str = if passphrase.is_null() { None } else { - match std::ffi::CStr::from_ptr(passphrase).to_str() { - Ok(s) => Some(s), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid passphrase string".into(), - ); - return ptr::null_mut(); - } - } + Some(unwrap_or_return!(std::ffi::CStr::from_ptr(passphrase).to_str(), error)) }; - match account.inner().derive_from_mnemonic_private_key_at( - mnemonic_str, - passphrase_str, - key_wallet::mnemonic::Language::English, - index, - ) { - Ok(sk) => { - let hex = hex::encode(sk.to_bytes()); - match CString::new(hex) { - Ok(s) => { - FFIError::set_success(error); - s.into_raw() - } - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Allocation failed".into(), - ); - ptr::null_mut() - } - } - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive EdDSA private key from mnemonic: {:?}", e), - ); - ptr::null_mut() - } - } + let sk = unwrap_or_return!( + account.inner().derive_from_mnemonic_private_key_at( + mnemonic_str, + passphrase_str, + key_wallet::mnemonic::Language::English, + index, + ), + error + ); + unwrap_or_return!(CString::new(hex::encode(sk.to_bytes())), error).into_raw() } /// Derive a private key (secp256k1) from an account at a given chain/index, using the provided master xpriv. @@ -377,7 +217,7 @@ pub unsafe extern "C" fn eddsa_account_derive_private_key_from_mnemonic( /// /// # Safety /// - `account` and `master_xpriv` must be valid pointers allocated by this library -/// - `error` must be a valid pointer to an FFIError or null +/// - `error` must be a valid pointer to an FFIError #[no_mangle] pub unsafe extern "C" fn account_derive_private_key_at( account: *const FFIAccount, @@ -385,37 +225,22 @@ pub unsafe extern "C" fn account_derive_private_key_at( index: c_uint, error: *mut FFIError, ) -> *mut FFIPrivateKey { - if account.is_null() || master_xpriv.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let account = &*account; - let master_xpriv = &*master_xpriv; + let account = deref_ptr!(account, error); + let master_xpriv = deref_ptr!(master_xpriv, error); if account.inner().is_watch_only() { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - "Account is watch-only; private derivation not allowed".to_string(), + "Account is watch-only; private derivation not allowed", ); return ptr::null_mut(); } - match account.inner().derive_from_master_xpriv_extended_xpriv_at(master_xpriv.inner(), index) { - Ok(derived) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIPrivateKey::from_secret(derived.private_key))) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive private key: {:?}", e), - ); - ptr::null_mut() - } - } + let derived = unwrap_or_return!( + account.inner().derive_from_master_xpriv_extended_xpriv_at(master_xpriv.inner(), index), + error + ); + Box::into_raw(Box::new(FFIPrivateKey::from_secret(derived.private_key))) } /// Derive a private key from an account at a given chain/index and return as WIF string. @@ -423,7 +248,7 @@ pub unsafe extern "C" fn account_derive_private_key_at( /// /// # Safety /// - `account` and `master_xpriv` must be valid pointers allocated by this library -/// - `error` must be a valid pointer to an FFIError or null +/// - `error` must be a valid pointer to an FFIError #[no_mangle] pub unsafe extern "C" fn account_derive_private_key_as_wif_at( account: *const FFIAccount, @@ -431,55 +256,27 @@ pub unsafe extern "C" fn account_derive_private_key_as_wif_at( index: c_uint, error: *mut FFIError, ) -> *mut c_char { - if account.is_null() || master_xpriv.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let account = &*account; - let master_xpriv = &*master_xpriv; + let account = deref_ptr!(account, error); + let master_xpriv = deref_ptr!(master_xpriv, error); if account.inner().is_watch_only() { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - "Account is watch-only; private derivation not allowed".to_string(), + "Account is watch-only; private derivation not allowed", ); return ptr::null_mut(); } - match account.inner().derive_from_master_xpriv_extended_xpriv_at(master_xpriv.inner(), index) { - Ok(derived) => { - // Wrap into dashcore::PrivateKey to WIF encode - let dash_priv = dashcore::PrivateKey { - compressed: true, - network: account.inner().network(), - inner: derived.private_key, - }; - match CString::new(dash_priv.to_wif()) { - Ok(c_str) => { - FFIError::set_success(error); - c_str.into_raw() - } - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate WIF string".to_string(), - ); - ptr::null_mut() - } - } - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive private key: {:?}", e), - ); - ptr::null_mut() - } - } + let derived = unwrap_or_return!( + account.inner().derive_from_master_xpriv_extended_xpriv_at(master_xpriv.inner(), index), + error + ); + let dash_priv = dashcore::PrivateKey { + compressed: true, + network: account.inner().network(), + inner: derived.private_key, + }; + unwrap_or_return!(CString::new(dash_priv.to_wif()), error).into_raw() } /// Derive an extended private key from a raw seed buffer at the given index. @@ -488,7 +285,7 @@ pub unsafe extern "C" fn account_derive_private_key_as_wif_at( /// # Safety /// - `account` must be a valid pointer to an FFIAccount /// - `seed` must point to a valid buffer of length `seed_len` -/// - `error` must be a valid pointer to an FFIError or null +/// - `error` must be a valid pointer to an FFIError #[no_mangle] pub unsafe extern "C" fn account_derive_extended_private_key_from_seed( account: *const FFIAccount, @@ -497,28 +294,14 @@ pub unsafe extern "C" fn account_derive_extended_private_key_from_seed( index: c_uint, error: *mut FFIError, ) -> *mut FFIExtendedPrivKey { - if account.is_null() || seed.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let account = &*account; + let account = deref_ptr!(account, error); + check_ptr!(seed, error); let seed_slice = std::slice::from_raw_parts(seed, seed_len); - - match account.inner().derive_from_seed_extended_xpriv_at(seed_slice, index) { - Ok(derived) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIExtendedPrivKey::from_inner(derived))) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive extended private key from seed: {:?}", e), - ); - ptr::null_mut() - } - } + let derived = unwrap_or_return!( + account.inner().derive_from_seed_extended_xpriv_at(seed_slice, index), + error + ); + Box::into_raw(Box::new(FFIExtendedPrivKey::from_inner(derived))) } /// Derive a private key from a raw seed buffer at the given index. @@ -527,7 +310,7 @@ pub unsafe extern "C" fn account_derive_extended_private_key_from_seed( /// # Safety /// - `account` must be a valid pointer to an FFIAccount /// - `seed` must point to a valid buffer of length `seed_len` -/// - `error` must be a valid pointer to an FFIError or null +/// - `error` must be a valid pointer to an FFIError #[no_mangle] pub unsafe extern "C" fn account_derive_private_key_from_seed( account: *const FFIAccount, @@ -536,28 +319,14 @@ pub unsafe extern "C" fn account_derive_private_key_from_seed( index: c_uint, error: *mut FFIError, ) -> *mut FFIPrivateKey { - if account.is_null() || seed.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let account = &*account; + let account = deref_ptr!(account, error); + check_ptr!(seed, error); let seed_slice = std::slice::from_raw_parts(seed, seed_len); - - match account.inner().derive_from_seed_extended_xpriv_at(seed_slice, index) { - Ok(derived) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIPrivateKey::from_secret(derived.private_key))) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive private key from seed: {:?}", e), - ); - ptr::null_mut() - } - } + let derived = unwrap_or_return!( + account.inner().derive_from_seed_extended_xpriv_at(seed_slice, index), + error + ); + Box::into_raw(Box::new(FFIPrivateKey::from_secret(derived.private_key))) } /// Derive an extended private key from a mnemonic + optional passphrase at the given index. @@ -567,7 +336,7 @@ pub unsafe extern "C" fn account_derive_private_key_from_seed( /// - `account` must be a valid pointer to an FFIAccount /// - `mnemonic` must be a valid, null-terminated C string /// - `passphrase` may be null; if not null, must be a valid C string -/// - `error` must be a valid pointer to an FFIError or null +/// - `error` must be a valid pointer to an FFIError #[no_mangle] pub unsafe extern "C" fn account_derive_extended_private_key_from_mnemonic( account: *const FFIAccount, @@ -576,58 +345,24 @@ pub unsafe extern "C" fn account_derive_extended_private_key_from_mnemonic( index: c_uint, error: *mut FFIError, ) -> *mut FFIExtendedPrivKey { - if account.is_null() || mnemonic.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let account = &*account; - let mnemonic_str = match std::ffi::CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid mnemonic string".to_string(), - ); - return ptr::null_mut(); - } - }; + let account = deref_ptr!(account, error); + let mnemonic = deref_ptr!(mnemonic, error); + let mnemonic_str = unwrap_or_return!(std::ffi::CStr::from_ptr(mnemonic).to_str(), error); let passphrase_str = if passphrase.is_null() { None } else { - match std::ffi::CStr::from_ptr(passphrase).to_str() { - Ok(s) => Some(s), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid passphrase string".to_string(), - ); - return ptr::null_mut(); - } - } + Some(unwrap_or_return!(std::ffi::CStr::from_ptr(passphrase).to_str(), error)) }; - - match account.inner().derive_from_mnemonic_extended_xpriv_at( - mnemonic_str, - passphrase_str, - key_wallet::mnemonic::Language::English, - index, - ) { - Ok(derived) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIExtendedPrivKey::from_inner(derived))) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive extended private key from mnemonic: {:?}", e), - ); - ptr::null_mut() - } - } + let derived = unwrap_or_return!( + account.inner().derive_from_mnemonic_extended_xpriv_at( + mnemonic_str, + passphrase_str, + key_wallet::mnemonic::Language::English, + index, + ), + error + ); + Box::into_raw(Box::new(FFIExtendedPrivKey::from_inner(derived))) } /// Derive a private key from a mnemonic + optional passphrase at the given index. @@ -637,7 +372,7 @@ pub unsafe extern "C" fn account_derive_extended_private_key_from_mnemonic( /// - `account` must be a valid pointer to an FFIAccount /// - `mnemonic` must be a valid, null-terminated C string /// - `passphrase` may be null; if not null, must be a valid C string -/// - `error` must be a valid pointer to an FFIError or null +/// - `error` must be a valid pointer to an FFIError #[no_mangle] pub unsafe extern "C" fn account_derive_private_key_from_mnemonic( account: *const FFIAccount, @@ -646,58 +381,24 @@ pub unsafe extern "C" fn account_derive_private_key_from_mnemonic( index: c_uint, error: *mut FFIError, ) -> *mut FFIPrivateKey { - if account.is_null() || mnemonic.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let account = &*account; - let mnemonic_str = match std::ffi::CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid mnemonic string".to_string(), - ); - return ptr::null_mut(); - } - }; + let account = deref_ptr!(account, error); + let mnemonic = deref_ptr!(mnemonic, error); + let mnemonic_str = unwrap_or_return!(std::ffi::CStr::from_ptr(mnemonic).to_str(), error); let passphrase_str = if passphrase.is_null() { None } else { - match std::ffi::CStr::from_ptr(passphrase).to_str() { - Ok(s) => Some(s), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid passphrase string".to_string(), - ); - return ptr::null_mut(); - } - } + Some(unwrap_or_return!(std::ffi::CStr::from_ptr(passphrase).to_str(), error)) }; - - match account.inner().derive_from_mnemonic_extended_xpriv_at( - mnemonic_str, - passphrase_str, - key_wallet::mnemonic::Language::English, - index, - ) { - Ok(derived) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIPrivateKey::from_secret(derived.private_key))) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive private key from mnemonic: {:?}", e), - ); - ptr::null_mut() - } - } + let derived = unwrap_or_return!( + account.inner().derive_from_mnemonic_extended_xpriv_at( + mnemonic_str, + passphrase_str, + key_wallet::mnemonic::Language::English, + index, + ), + error + ); + Box::into_raw(Box::new(FFIPrivateKey::from_secret(derived.private_key))) } #[cfg(test)] diff --git a/key-wallet-ffi/src/account_derivation_tests.rs b/key-wallet-ffi/src/account_derivation_tests.rs index 95e2d1a22..3d505b8a4 100644 --- a/key-wallet-ffi/src/account_derivation_tests.rs +++ b/key-wallet-ffi/src/account_derivation_tests.rs @@ -17,7 +17,7 @@ mod tests { #[test] fn test_account_derive_private_key_at_receive_index() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let mnemonic = std::ffi::CString::new(MNEMONIC).unwrap(); let passphrase = std::ffi::CString::new("").unwrap(); @@ -63,13 +63,13 @@ mod tests { let priv_key = unsafe { account_derive_private_key_at(account, master_xpriv, 0, &mut error) }; assert!(priv_key.is_null()); - assert_eq!(error.code, FFIErrorCode::WalletError); + assert_eq!(error.code, FFIErrorCode::InvalidInput); // Derive WIF should also fail for such accounts let wif = unsafe { account_derive_private_key_as_wif_at(account, master_xpriv, 0, &mut error) }; assert!(wif.is_null()); - assert_eq!(error.code, FFIErrorCode::WalletError); + assert_eq!(error.code, FFIErrorCode::InvalidInput); // Cleanup unsafe { @@ -78,13 +78,12 @@ mod tests { extended_private_key_free(master_xpriv); account_free(account); wallet::wallet_free(wallet); - error.free_message(); } } #[test] fn test_bls_and_eddsa_from_seed_and_mnemonic_null_safety() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // BLS nulls #[cfg(feature = "bls")] @@ -113,13 +112,11 @@ mod tests { .is_null()); assert_eq!(error.code, FFIErrorCode::InvalidInput); } - - unsafe { error.free_message() }; } #[test] fn test_account_derive_extended_private_key_at_change_index() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let mnemonic = std::ffi::CString::new(MNEMONIC).unwrap(); let passphrase = std::ffi::CString::new("").unwrap(); @@ -162,20 +159,19 @@ mod tests { let xpriv = unsafe { account_derive_extended_private_key_at(account, master_xpriv, 5, &mut error) }; assert!(xpriv.is_null()); - assert_eq!(error.code, FFIErrorCode::WalletError); + assert_eq!(error.code, FFIErrorCode::InvalidInput); // Cleanup unsafe { extended_private_key_free(master_xpriv); account_free(account); wallet::wallet_free(wallet); - error.free_message(); } } #[test] fn test_account_derive_from_seed_and_mnemonic_helpers_fail_for_standard() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let mnemonic = std::ffi::CString::new(MNEMONIC).unwrap(); let passphrase = std::ffi::CString::new("").unwrap(); @@ -220,7 +216,7 @@ mod tests { ) }; assert!(xpriv_seed.is_null()); - assert_eq!(error.code, FFIErrorCode::WalletError); + assert_eq!(error.code, FFIErrorCode::InvalidInput); // account_derive_private_key_from_seed should fail let priv_seed = unsafe { @@ -233,7 +229,7 @@ mod tests { ) }; assert!(priv_seed.is_null()); - assert_eq!(error.code, FFIErrorCode::WalletError); + assert_eq!(error.code, FFIErrorCode::InvalidInput); // account_derive_extended_private_key_from_mnemonic should fail let xpriv_mn = unsafe { @@ -246,7 +242,7 @@ mod tests { ) }; assert!(xpriv_mn.is_null()); - assert_eq!(error.code, FFIErrorCode::WalletError); + assert_eq!(error.code, FFIErrorCode::InvalidInput); // account_derive_private_key_from_mnemonic should fail let priv_mn = unsafe { @@ -259,12 +255,11 @@ mod tests { ) }; assert!(priv_mn.is_null()); - assert_eq!(error.code, FFIErrorCode::WalletError); + assert_eq!(error.code, FFIErrorCode::InvalidInput); unsafe { account_free(account); wallet::wallet_free(wallet); - error.free_message(); } } } diff --git a/key-wallet-ffi/src/account_tests.rs b/key-wallet-ffi/src/account_tests.rs index eb445288c..e5ee42392 100644 --- a/key-wallet-ffi/src/account_tests.rs +++ b/key-wallet-ffi/src/account_tests.rs @@ -26,7 +26,7 @@ mod tests { #[test] fn test_wallet_get_account_existing() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create a wallet with default accounts let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); @@ -70,19 +70,17 @@ mod tests { #[test] fn test_wallet_get_account_count_null_wallet() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let count = unsafe { wallet_get_account_count(ptr::null(), &mut error) }; assert_eq!(count, 0); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_wallet_get_account_count() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create a wallet let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); @@ -106,7 +104,6 @@ mod tests { // Clean up unsafe { wallet::wallet_free(wallet); - error.free_message(); } } @@ -128,7 +125,7 @@ mod tests { #[test] fn test_account_getters() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create a wallet let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); @@ -192,34 +189,31 @@ mod tests { // Clean up unsafe { wallet::wallet_free(wallet); - error.free_message(); } } #[test] fn test_account_getters_null_safety() { - unsafe { - // Test all getter functions with null pointers - let xpub = account_get_extended_public_key_as_string(ptr::null()); - assert!(xpub.is_null()); + // Test all getter functions with null pointers + let xpub = unsafe { account_get_extended_public_key_as_string(ptr::null()) }; + assert!(xpub.is_null()); - let network = account_get_network(ptr::null()); - assert_eq!(network, FFINetwork::Mainnet); + let network = unsafe { account_get_network(ptr::null()) }; + assert_eq!(network, FFINetwork::Mainnet); - let wallet_id = account_get_parent_wallet_id(ptr::null()); - assert!(wallet_id.is_null()); + let wallet_id = unsafe { account_get_parent_wallet_id(ptr::null()) }; + assert!(wallet_id.is_null()); - let mut index = 0u32; - let account_type = account_get_account_type(ptr::null(), &mut index); - assert_eq!(account_type as u32, FFIAccountType::StandardBIP44 as u32); - assert_eq!(index, 0); + let mut index = 0u32; + let account_type = unsafe { account_get_account_type(ptr::null(), &mut index) }; + assert_eq!(account_type as u32, FFIAccountType::StandardBIP44 as u32); + assert_eq!(index, 0); - // Test with null out_index - let account_type = account_get_account_type(ptr::null(), ptr::null_mut()); - assert_eq!(account_type as u32, FFIAccountType::StandardBIP44 as u32); + // Test with null out_index + let account_type = unsafe { account_get_account_type(ptr::null(), ptr::null_mut()) }; + assert_eq!(account_type as u32, FFIAccountType::StandardBIP44 as u32); - let is_watch_only = account_get_is_watch_only(ptr::null()); - assert!(!is_watch_only); - } + let is_watch_only = unsafe { account_get_is_watch_only(ptr::null()) }; + assert!(!is_watch_only); } } diff --git a/key-wallet-ffi/src/address.rs b/key-wallet-ffi/src/address.rs index 9eec0f5a6..c79627b63 100644 --- a/key-wallet-ffi/src/address.rs +++ b/key-wallet-ffi/src/address.rs @@ -9,7 +9,8 @@ use std::os::raw::{c_char, c_uchar}; use dashcore::ffi::FFINetwork; -use crate::error::{FFIError, FFIErrorCode}; +use crate::error::FFIError; +use crate::{deref_ptr, unwrap_or_return}; /// Free address string /// @@ -62,56 +63,15 @@ pub unsafe extern "C" fn address_validate( network: FFINetwork, error: *mut FFIError, ) -> bool { - if address.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Address is null".to_string()); - return false; - } - - let address_str = unsafe { - match CStr::from_ptr(address).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in address".to_string(), - ); - return false; - } - } - }; + use std::str::FromStr; + let address = deref_ptr!(address, error); + let address_str = unwrap_or_return!(CStr::from_ptr(address).to_str(), error); let network_rust: key_wallet::Network = network.into(); - use std::str::FromStr; - match key_wallet::Address::from_str(address_str) { - Ok(addr) => { - // Check if address is valid for the given network - let dash_network = network_rust; - match addr.require_network(dash_network) { - Ok(_) => { - FFIError::set_success(error); - true - } - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidAddress, - format!("Address not valid for network {:?}", network_rust), - ); - false - } - } - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidAddress, - format!("Invalid address: {}", e), - ); - false - } - } + let addr = unwrap_or_return!(key_wallet::Address::from_str(address_str), error); + let _ = unwrap_or_return!(addr.require_network(network_rust), error); + true } /// Get address type @@ -132,59 +92,17 @@ pub unsafe extern "C" fn address_get_type( network: FFINetwork, error: *mut FFIError, ) -> c_uchar { - if address.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Address is null".to_string()); - return u8::MAX; - } - - let address_str = unsafe { - match CStr::from_ptr(address).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in address".to_string(), - ); - return u8::MAX; - } - } - }; + use std::str::FromStr; + let address = deref_ptr!(address, error, u8::MAX); + let address_str = unwrap_or_return!(CStr::from_ptr(address).to_str(), error, u8::MAX); let network_rust: key_wallet::Network = network.into(); - use std::str::FromStr; + let addr = unwrap_or_return!(key_wallet::Address::from_str(address_str), error, u8::MAX); + let checked = unwrap_or_return!(addr.require_network(network_rust), error, u8::MAX); - match key_wallet::Address::from_str(address_str) { - Ok(addr) => { - let dash_network = network_rust; - match addr.require_network(dash_network) { - Ok(checked_addr) => { - FFIError::set_success(error); - // Get the actual address type - match checked_addr.address_type() { - Some(key_wallet::AddressType::P2pkh) => 0, - Some(key_wallet::AddressType::P2sh) => 1, - Some(_) => 2, // Other address type - None => 2, // Unknown type - } - } - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidAddress, - "Address not valid for network".to_string(), - ); - u8::MAX // Error - } - } - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidAddress, - format!("Invalid address: {}", e), - ); - u8::MAX // Error - } + match checked.address_type() { + Some(key_wallet::AddressType::P2pkh) => 0, + Some(key_wallet::AddressType::P2sh) => 1, + Some(_) | None => 2, } } diff --git a/key-wallet-ffi/src/address_pool.rs b/key-wallet-ffi/src/address_pool.rs index 0effc9ba5..ce2bc3225 100644 --- a/key-wallet-ffi/src/address_pool.rs +++ b/key-wallet-ffi/src/address_pool.rs @@ -10,6 +10,7 @@ use crate::error::{FFIError, FFIErrorCode}; use crate::managed_wallet::FFIManagedWalletInfo; use crate::types::{FFIAccountType, FFIWallet}; use crate::utils::rust_string_to_c; +use crate::{check_ptr, deref_ptr, deref_ptr_mut, unwrap_or_return}; use key_wallet::account::ManagedAccountCollection; use key_wallet::managed_account::address_pool::{ AddressInfo, AddressPool, KeySource, PublicKeyType, @@ -280,7 +281,7 @@ pub struct FFIAddressPoolInfo { /// /// - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo /// - `info_out` must be a valid pointer to store the pool info -/// - `error` must be a valid pointer to an FFIError or null +/// - `error` must be a valid pointer to an FFIError #[no_mangle] pub unsafe extern "C" fn managed_wallet_get_address_pool_info( managed_wallet: *const FFIManagedWalletInfo, @@ -290,12 +291,8 @@ pub unsafe extern "C" fn managed_wallet_get_address_pool_info( info_out: *mut FFIAddressPoolInfo, error: *mut FFIError, ) -> bool { - if managed_wallet.is_null() || info_out.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } - - let wrapper = &*managed_wallet; + let wrapper = deref_ptr!(managed_wallet, error); + check_ptr!(info_out, error); let managed_wallet = wrapper.inner(); let account_type_rust = account_type.to_account_type(account_index); @@ -305,7 +302,7 @@ pub unsafe extern "C" fn managed_wallet_get_address_pool_info( match get_managed_account_by_type(&managed_wallet.accounts, &account_type_rust) { Some(account) => account, None => { - FFIError::set_error(error, FFIErrorCode::NotFound, "Account not found".to_string()); + (*error).set(FFIErrorCode::NotFound, "Account not found"); return false; } }; @@ -320,11 +317,7 @@ pub unsafe extern "C" fn managed_wallet_get_address_pool_info( } = &managed_account.account_type { external_addresses } else { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Account type does not have external address pool".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "Account type does not have external address pool"); return false; } } @@ -336,11 +329,7 @@ pub unsafe extern "C" fn managed_wallet_get_address_pool_info( } = &managed_account.account_type { internal_addresses } else { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Account type does not have internal address pool".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "Account type does not have internal address pool"); return false; } } @@ -348,11 +337,7 @@ pub unsafe extern "C" fn managed_wallet_get_address_pool_info( // Get the first (and only) address pool for non-standard accounts let pools = managed_account.account_type.address_pools(); if pools.is_empty() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Account has no address pools".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "Account has no address pools"); return false; } pools[0] @@ -375,7 +360,7 @@ pub unsafe extern "C" fn managed_wallet_get_address_pool_info( highest_used_index: pool.highest_used.map(|i| i as i32).unwrap_or(-1), }; - FFIError::set_success(error); + (*error).clean(); true } @@ -387,7 +372,7 @@ pub unsafe extern "C" fn managed_wallet_get_address_pool_info( /// # Safety /// /// - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo -/// - `error` must be a valid pointer to an FFIError or null +/// - `error` must be a valid pointer to an FFIError #[no_mangle] pub unsafe extern "C" fn managed_wallet_set_gap_limit( managed_wallet: *mut FFIManagedWalletInfo, @@ -397,12 +382,7 @@ pub unsafe extern "C" fn managed_wallet_set_gap_limit( gap_limit: c_uint, error: *mut FFIError, ) -> bool { - if managed_wallet.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } - - let managed_wallet = (&mut *managed_wallet).inner_mut(); + let managed_wallet = deref_ptr_mut!(managed_wallet, error).inner_mut(); let account_type_rust = account_type.to_account_type(account_index); @@ -411,7 +391,7 @@ pub unsafe extern "C" fn managed_wallet_set_gap_limit( match get_managed_account_by_type_mut(&mut managed_wallet.accounts, &account_type_rust) { Some(account) => account, None => { - FFIError::set_error(error, FFIErrorCode::NotFound, "Account not found".to_string()); + (*error).set(FFIErrorCode::NotFound, "Account not found"); return false; } }; @@ -426,11 +406,7 @@ pub unsafe extern "C" fn managed_wallet_set_gap_limit( } = &mut managed_account.account_type { external_addresses } else { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Account type does not have external address pool".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "Account type does not have external address pool"); return false; } } @@ -442,11 +418,7 @@ pub unsafe extern "C" fn managed_wallet_set_gap_limit( } = &mut managed_account.account_type { internal_addresses } else { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Account type does not have internal address pool".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "Account type does not have internal address pool"); return false; } } @@ -454,11 +426,7 @@ pub unsafe extern "C" fn managed_wallet_set_gap_limit( // Get the first (and only) address pool for non-standard accounts let pools = managed_account.account_type.address_pools_mut(); if pools.is_empty() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Account has no address pools".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "Account has no address pools"); return false; } pools.into_iter().next().unwrap() @@ -468,7 +436,7 @@ pub unsafe extern "C" fn managed_wallet_set_gap_limit( // Set the gap limit pool.gap_limit = gap_limit; - FFIError::set_success(error); + (*error).clean(); true } @@ -482,7 +450,7 @@ pub unsafe extern "C" fn managed_wallet_set_gap_limit( /// /// - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo /// - `wallet` must be a valid pointer to an FFIWallet (for key derivation) -/// - `error` must be a valid pointer to an FFIError or null +/// - `error` must be a valid pointer to an FFIError #[no_mangle] pub unsafe extern "C" fn managed_wallet_generate_addresses_to_index( managed_wallet: *mut FFIManagedWalletInfo, @@ -493,23 +461,17 @@ pub unsafe extern "C" fn managed_wallet_generate_addresses_to_index( target_index: c_uint, error: *mut FFIError, ) -> bool { - if managed_wallet.is_null() || wallet.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } - - let managed_wallet = (&mut *managed_wallet).inner_mut(); - let wallet = &*wallet; + let managed_wallet = deref_ptr_mut!(managed_wallet, error).inner_mut(); + let wallet = deref_ptr!(wallet, error); let account_type_rust = account_type.to_account_type(account_index); let account_type_to_check = match account_type_rust.try_into() { Ok(check_type) => check_type, Err(_) => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InvalidInput, - "Platform Payment accounts cannot be used for address pool operations".to_string(), + "Platform Payment accounts cannot be used for address pool operations", ); return false; } @@ -522,11 +484,7 @@ pub unsafe extern "C" fn managed_wallet_generate_addresses_to_index( let xpub = match xpub_opt { Some(xpub) => xpub, None => { - FFIError::set_error( - error, - FFIErrorCode::NotFound, - "Account not found in wallet".to_string(), - ); + (*error).set(FFIErrorCode::NotFound, "Account not found in wallet"); return false; } }; @@ -538,7 +496,7 @@ pub unsafe extern "C" fn managed_wallet_generate_addresses_to_index( match get_managed_account_by_type_mut(&mut managed_wallet.accounts, &account_type_rust) { Some(account) => account, None => { - FFIError::set_error(error, FFIErrorCode::NotFound, "Account not found".to_string()); + (*error).set(FFIErrorCode::NotFound, "Account not found"); return false; } }; @@ -561,11 +519,7 @@ pub unsafe extern "C" fn managed_wallet_generate_addresses_to_index( } } } else { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Account type does not have external address pool".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "Account type does not have external address pool"); return false; } } @@ -585,11 +539,7 @@ pub unsafe extern "C" fn managed_wallet_generate_addresses_to_index( } } } else { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Account type does not have internal address pool".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "Account type does not have internal address pool"); return false; } } @@ -597,11 +547,7 @@ pub unsafe extern "C" fn managed_wallet_generate_addresses_to_index( // Get the first (and only) address pool for non-standard accounts let mut pools = managed_account.account_type.address_pools_mut(); if pools.is_empty() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Account has no address pools".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "Account has no address pools"); return false; } { @@ -619,15 +565,12 @@ pub unsafe extern "C" fn managed_wallet_generate_addresses_to_index( match result { Ok(_) => { - FFIError::set_success(error); + (*error).clean(); true } Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to generate addresses: {}", e), - ); + (*error) + .set(FFIErrorCode::WalletError, &format!("Failed to generate addresses: {}", e)); false } } @@ -642,32 +585,17 @@ pub unsafe extern "C" fn managed_wallet_generate_addresses_to_index( /// /// - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo /// - `address` must be a valid C string -/// - `error` must be a valid pointer to an FFIError or null +/// - `error` must be a valid pointer to an FFIError #[no_mangle] pub unsafe extern "C" fn managed_wallet_mark_address_used( managed_wallet: *mut FFIManagedWalletInfo, address: *const c_char, error: *mut FFIError, ) -> bool { - if managed_wallet.is_null() || address.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } + let managed_wallet = deref_ptr_mut!(managed_wallet, error).inner_mut(); + let address = deref_ptr!(address, error); - let managed_wallet = (&mut *managed_wallet).inner_mut(); - - // Parse the address string - let address_str = match std::ffi::CStr::from_ptr(address).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in address".to_string(), - ); - return false; - } - }; + let address_str = unwrap_or_return!(std::ffi::CStr::from_ptr(address).to_str(), error); // Parse address as unchecked first, then convert to the correct network use core::str::FromStr; @@ -676,11 +604,7 @@ pub unsafe extern "C" fn managed_wallet_mark_address_used( let unchecked_addr = match Address::::from_str(address_str) { Ok(addr) => addr, Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - format!("Invalid address: {}", e), - ); + (*error).set(FFIErrorCode::InvalidInput, &format!("Invalid address: {}", e)); return false; } }; @@ -808,14 +732,10 @@ pub unsafe extern "C" fn managed_wallet_mark_address_used( }; if marked { - FFIError::set_success(error); + (*error).clean(); true } else { - FFIError::set_error( - error, - FFIErrorCode::NotFound, - "Address not found in any account".to_string(), - ); + (*error).set(FFIErrorCode::NotFound, "Address not found in any account"); false } } @@ -828,7 +748,7 @@ pub unsafe extern "C" fn managed_wallet_mark_address_used( /// # Safety /// /// - `pool` must be a valid pointer to an FFIAddressPool -/// - `error` must be a valid pointer to an FFIError or null +/// - `error` must be a valid pointer to an FFIError /// - The returned FFIAddressInfo must be freed using `address_info_free` #[no_mangle] pub unsafe extern "C" fn address_pool_get_address_at_index( @@ -836,27 +756,18 @@ pub unsafe extern "C" fn address_pool_get_address_at_index( index: u32, error: *mut FFIError, ) -> *mut FFIAddressInfo { - if pool.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return std::ptr::null_mut(); - } - - let pool = &*pool; + let pool = deref_ptr!(pool, error); let address_pool = &*pool.pool; // Get the address info at the specified index match address_pool.info_at_index(index) { Some(info) => { let ffi_info = address_info_to_ffi(info); - FFIError::set_success(error); + (*error).clean(); Box::into_raw(Box::new(ffi_info)) } None => { - FFIError::set_error( - error, - FFIErrorCode::NotFound, - format!("No address at index {}", index), - ); + (*error).set(FFIErrorCode::NotFound, &format!("No address at index {}", index)); std::ptr::null_mut() } } @@ -874,7 +785,7 @@ pub unsafe extern "C" fn address_pool_get_address_at_index( /// /// - `pool` must be a valid pointer to an FFIAddressPool /// - `count_out` must be a valid pointer to store the count -/// - `error` must be a valid pointer to an FFIError or null +/// - `error` must be a valid pointer to an FFIError /// - The returned array must be freed using `address_info_array_free` #[no_mangle] pub unsafe extern "C" fn address_pool_get_addresses_in_range( @@ -884,14 +795,10 @@ pub unsafe extern "C" fn address_pool_get_addresses_in_range( count_out: *mut usize, error: *mut FFIError, ) -> *mut *mut FFIAddressInfo { - if pool.is_null() || count_out.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return std::ptr::null_mut(); - } + let pool = deref_ptr!(pool, error); + check_ptr!(count_out, error); *count_out = 0; - - let pool = &*pool; let address_pool = &*pool.pool; // Collect address infos in the range @@ -907,11 +814,7 @@ pub unsafe extern "C" fn address_pool_get_addresses_in_range( } else { // Normal range query if end_index <= start_index { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "End index must be greater than start index".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "End index must be greater than start index"); return std::ptr::null_mut(); } @@ -923,18 +826,14 @@ pub unsafe extern "C" fn address_pool_get_addresses_in_range( } if infos.is_empty() { - FFIError::set_error( - error, - FFIErrorCode::NotFound, - "No addresses found in the specified range".to_string(), - ); + (*error).set(FFIErrorCode::NotFound, "No addresses found in the specified range"); return std::ptr::null_mut(); } *count_out = infos.len(); let array_ptr = Box::into_raw(infos.into_boxed_slice()) as *mut *mut FFIAddressInfo; - FFIError::set_success(error); + (*error).clean(); array_ptr } @@ -1087,7 +986,7 @@ mod tests { use std::ptr; let test_mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create wallet manager let manager = wallet_manager_create(FFINetwork::Testnet, &mut error); @@ -1168,7 +1067,6 @@ mod tests { managed_core_account_free(account); wallet_manager_free_wallet_ids(wallet_ids_out, count_out); wallet_manager_free(manager); - error.free_message(); } } @@ -1187,7 +1085,7 @@ mod tests { use std::ptr; let test_mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create wallet manager let manager = wallet_manager_create(FFINetwork::Testnet, &mut error); @@ -1297,7 +1195,6 @@ mod tests { managed_core_account_free(account); wallet_manager_free_wallet_ids(wallet_ids_out, count_out); wallet_manager_free(manager); - error.free_message(); } } } diff --git a/key-wallet-ffi/src/address_tests.rs b/key-wallet-ffi/src/address_tests.rs index 1d9e43dfa..a4d80ba87 100644 --- a/key-wallet-ffi/src/address_tests.rs +++ b/key-wallet-ffi/src/address_tests.rs @@ -11,7 +11,7 @@ mod address_tests { #[test] fn test_address_validation() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test valid testnet address (generated from test mnemonic) @@ -30,13 +30,11 @@ mod address_tests { let is_valid = unsafe { address_validate(ptr::null(), FFINetwork::Testnet, error) }; assert!(!is_valid); assert_eq!(unsafe { (*error).code }, FFIErrorCode::InvalidInput); - - unsafe { (*error).free_message() }; } #[test] fn test_address_get_type() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test P2PKH address (generated from test mnemonic) @@ -46,13 +44,11 @@ mod address_tests { assert_eq!(unsafe { (*error).code }, FFIErrorCode::Success); // Returns 0 for P2PKH assert_eq!(addr_type, 0); - - unsafe { (*error).free_message() }; } #[test] fn test_address_validate_valid() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test with valid testnet address - may fail due to library version differences let addr_str = CString::new("yeRZBWYfeNE4yVUHV4ZLs83Ppn9aMRH57A").unwrap(); @@ -60,13 +56,11 @@ mod address_tests { unsafe { address_validate(addr_str.as_ptr(), FFINetwork::Testnet, &mut error) }; assert!(is_valid); - - unsafe { error.free_message() }; } #[test] fn test_address_validate_invalid() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test with invalid address let addr_str = CString::new("invalid_address").unwrap(); @@ -75,25 +69,21 @@ mod address_tests { assert!(!is_valid); assert_eq!(error.code, FFIErrorCode::InvalidAddress); - - unsafe { error.free_message() }; } #[test] fn test_address_validate_null() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let is_valid = unsafe { address_validate(ptr::null(), FFINetwork::Testnet, &mut error) }; assert!(!is_valid); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_address_get_type_valid() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test P2PKH address type (use same known-valid address from other tests) let addr_str = CString::new("yRd4FhXfVGHXpsuZXPNkMrfD9GVj46pnjt").unwrap(); @@ -108,13 +98,11 @@ mod address_tests { assert!(addr_type <= 2); assert_eq!(error.code, FFIErrorCode::Success); } - - unsafe { error.free_message() }; } #[test] fn test_address_get_type_invalid() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let addr_str = CString::new("invalid_address").unwrap(); let addr_type = @@ -123,21 +111,17 @@ mod address_tests { // Should return 255 (u8::MAX) for invalid assert_eq!(addr_type, 255); assert_eq!(error.code, FFIErrorCode::InvalidAddress); - - unsafe { error.free_message() }; } #[test] fn test_address_get_type_null() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let addr_type = unsafe { address_get_type(ptr::null(), FFINetwork::Testnet, &mut error) }; // Should return 255 (u8::MAX) for null input assert_eq!(addr_type, 255); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] @@ -177,7 +161,7 @@ mod address_tests { #[test] fn test_address_validation_comprehensive() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test various invalid address formats let invalid_addresses = [ @@ -194,14 +178,12 @@ mod address_tests { let is_valid = address_validate(addr_str.as_ptr(), FFINetwork::Testnet, &mut error); assert!(!is_valid); } - - error.free_message(); } } #[test] fn test_address_get_type_comprehensive() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test various address formats let test_addresses = [ @@ -219,8 +201,6 @@ mod address_tests { // Should return a valid type (0, 1, 2) or 255 for error assert!(addr_type <= 2 || addr_type == 255); } - - error.free_message(); } } } diff --git a/key-wallet-ffi/src/bip38.rs b/key-wallet-ffi/src/bip38.rs index 583704abe..3f95c88cd 100644 --- a/key-wallet-ffi/src/bip38.rs +++ b/key-wallet-ffi/src/bip38.rs @@ -1,6 +1,5 @@ //! BIP38 encryption support -use std::ffi::CStr; use std::os::raw::c_char; use std::ptr; @@ -13,66 +12,15 @@ use crate::error::{FFIError, FFIErrorCode}; /// This function is unsafe because it dereferences raw pointers: /// - `private_key` must be a valid, null-terminated C string /// - `passphrase` must be a valid, null-terminated C string -/// - `error` must be a valid pointer to an FFIError or null +/// - `error` must be a valid pointer to an FFIError #[no_mangle] pub unsafe extern "C" fn bip38_encrypt_private_key( - private_key: *const c_char, - passphrase: *const c_char, + _private_key: *const c_char, + _passphrase: *const c_char, error: *mut FFIError, ) -> *mut c_char { - #[cfg(feature = "bip38")] - { - if private_key.is_null() || passphrase.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Null pointer provided".to_string(), - ); - return ptr::null_mut(); - } - - let _privkey_str = match CStr::from_ptr(private_key).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in private key".to_string(), - ); - return ptr::null_mut(); - } - }; - - let _passphrase_str = match CStr::from_ptr(passphrase).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in passphrase".to_string(), - ); - return ptr::null_mut(); - } - }; - - // Note: key_wallet doesn't have built-in BIP38 support - // This would need to be implemented using a BIP38 library - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "BIP38 encryption not yet implemented".to_string(), - ); - ptr::null_mut() - } - #[cfg(not(feature = "bip38"))] - { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "BIP38 support not enabled".to_string(), - ); - ptr::null_mut() - } + (*error).set(FFIErrorCode::InternalError, "BIP38 encryption not yet implemented"); + ptr::null_mut() } /// Decrypt a BIP38 encrypted private key @@ -82,64 +30,13 @@ pub unsafe extern "C" fn bip38_encrypt_private_key( /// This function is unsafe because it dereferences raw pointers: /// - `encrypted_key` must be a valid, null-terminated C string /// - `passphrase` must be a valid, null-terminated C string -/// - `error` must be a valid pointer to an FFIError or null +/// - `error` must be a valid pointer to an FFIError #[no_mangle] pub unsafe extern "C" fn bip38_decrypt_private_key( - encrypted_key: *const c_char, - passphrase: *const c_char, + _encrypted_key: *const c_char, + _passphrase: *const c_char, error: *mut FFIError, ) -> *mut c_char { - #[cfg(feature = "bip38")] - { - if encrypted_key.is_null() || passphrase.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Null pointer provided".to_string(), - ); - return ptr::null_mut(); - } - - let _encrypted_str = match CStr::from_ptr(encrypted_key).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in encrypted key".to_string(), - ); - return ptr::null_mut(); - } - }; - - let _passphrase_str = match CStr::from_ptr(passphrase).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in passphrase".to_string(), - ); - return ptr::null_mut(); - } - }; - - // Note: key_wallet doesn't have built-in BIP38 support - // This would need to be implemented using a BIP38 library - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "BIP38 decryption not yet implemented".to_string(), - ); - ptr::null_mut() - } - #[cfg(not(feature = "bip38"))] - { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "BIP38 support not enabled".to_string(), - ); - ptr::null_mut() - } + (*error).set(FFIErrorCode::InternalError, "BIP38 decryption not yet implemented"); + ptr::null_mut() } diff --git a/key-wallet-ffi/src/derivation.rs b/key-wallet-ffi/src/derivation.rs index b5b0e9b77..57d77ff3a 100644 --- a/key-wallet-ffi/src/derivation.rs +++ b/key-wallet-ffi/src/derivation.rs @@ -3,6 +3,7 @@ use crate::error::{FFIError, FFIErrorCode}; use crate::keys::FFIExtendedPrivKey; use crate::keys::FFIExtendedPubKey; +use crate::{check_ptr, deref_ptr, unwrap_or_return}; use dashcore::ffi::FFINetwork; use dashcore::Network; use key_wallet::{ExtendedPrivKey, ExtendedPubKey}; @@ -40,7 +41,7 @@ pub enum FFIDerivationPathType { /// # Safety /// /// - `seed` must be a valid pointer to a byte array of `seed_len` length -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure the seed pointer remains valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn derivation_new_master_key( @@ -49,47 +50,31 @@ pub unsafe extern "C" fn derivation_new_master_key( network: FFINetwork, error: *mut FFIError, ) -> *mut FFIExtendedPrivKey { - if seed.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Seed is null".to_string()); - return ptr::null_mut(); - } - + check_ptr!(seed, error); let seed_slice = slice::from_raw_parts(seed, seed_len); let network_rust: key_wallet::Network = network.into(); - - match key_wallet::bip32::ExtendedPrivKey::new_master(network_rust, seed_slice) { - Ok(xpriv) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIExtendedPrivKey::from_inner(xpriv))) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to create master key: {:?}", e), - ); - ptr::null_mut() - } - } + let xpriv = unwrap_or_return!( + key_wallet::bip32::ExtendedPrivKey::new_master(network_rust, seed_slice), + error + ); + Box::into_raw(Box::new(FFIExtendedPrivKey::from_inner(xpriv))) } /// Derive a BIP44 account path (m/44'/5'/account') +/// +/// # Safety +/// +/// `path_out` must point to a writable buffer of at least `path_max_len` bytes +/// and `error` must be a valid pointer to an `FFIError`. #[no_mangle] -pub extern "C" fn derivation_bip44_account_path( +pub unsafe extern "C" fn derivation_bip44_account_path( network: FFINetwork, account_index: c_uint, path_out: *mut c_char, path_max_len: usize, error: *mut FFIError, ) -> bool { - if path_out.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Path output buffer is null".to_string(), - ); - return false; - } + check_ptr!(path_out, error); let network_rust: key_wallet::Network = network.into(); @@ -98,24 +83,13 @@ pub extern "C" fn derivation_bip44_account_path( let path_str = format!("{}", derivation); - let c_string = match CString::new(path_str) { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to create C string".to_string(), - ); - return false; - } - }; + let c_string = unwrap_or_return!(CString::new(path_str), error); let bytes = c_string.as_bytes_with_nul(); if bytes.len() > path_max_len { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InvalidInput, - format!("Path too long: {} > {}", bytes.len(), path_max_len), + &format!("Path too long: {} > {}", bytes.len(), path_max_len), ); return false; } @@ -123,14 +97,17 @@ pub extern "C" fn derivation_bip44_account_path( unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), path_out.cast::(), bytes.len()); } - - FFIError::set_success(error); true } /// Derive a BIP44 payment path (m/44'/5'/account'/change/index) +/// +/// # Safety +/// +/// `path_out` must point to a writable buffer of at least `path_max_len` bytes +/// and `error` must be a valid pointer to an `FFIError`. #[no_mangle] -pub extern "C" fn derivation_bip44_payment_path( +pub unsafe extern "C" fn derivation_bip44_payment_path( network: FFINetwork, account_index: c_uint, is_change: bool, @@ -139,14 +116,7 @@ pub extern "C" fn derivation_bip44_payment_path( path_max_len: usize, error: *mut FFIError, ) -> bool { - if path_out.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Path output buffer is null".to_string(), - ); - return false; - } + check_ptr!(path_out, error); let network_rust: key_wallet::Network = network.into(); @@ -156,24 +126,13 @@ pub extern "C" fn derivation_bip44_payment_path( let path_str = format!("{}", derivation); - let c_string = match CString::new(path_str) { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to create C string".to_string(), - ); - return false; - } - }; + let c_string = unwrap_or_return!(CString::new(path_str), error); let bytes = c_string.as_bytes_with_nul(); if bytes.len() > path_max_len { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InvalidInput, - format!("Path too long: {} > {}", bytes.len(), path_max_len), + &format!("Path too long: {} > {}", bytes.len(), path_max_len), ); return false; } @@ -181,28 +140,24 @@ pub extern "C" fn derivation_bip44_payment_path( unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), path_out.cast::(), bytes.len()); } - - FFIError::set_success(error); true } /// Derive CoinJoin path (m/9'/5'/4'/account') +/// +/// # Safety +/// +/// `path_out` must point to a writable buffer of at least `path_max_len` bytes +/// and `error` must be a valid pointer to an `FFIError`. #[no_mangle] -pub extern "C" fn derivation_coinjoin_path( +pub unsafe extern "C" fn derivation_coinjoin_path( network: FFINetwork, account_index: c_uint, path_out: *mut c_char, path_max_len: usize, error: *mut FFIError, ) -> bool { - if path_out.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Path output buffer is null".to_string(), - ); - return false; - } + check_ptr!(path_out, error); let network_rust: key_wallet::Network = network.into(); @@ -211,24 +166,13 @@ pub extern "C" fn derivation_coinjoin_path( let path_str = format!("{}", derivation); - let c_string = match CString::new(path_str) { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to create C string".to_string(), - ); - return false; - } - }; + let c_string = unwrap_or_return!(CString::new(path_str), error); let bytes = c_string.as_bytes_with_nul(); if bytes.len() > path_max_len { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InvalidInput, - format!("Path too long: {} > {}", bytes.len(), path_max_len), + &format!("Path too long: {} > {}", bytes.len(), path_max_len), ); return false; } @@ -236,28 +180,24 @@ pub extern "C" fn derivation_coinjoin_path( unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), path_out.cast::(), bytes.len()); } - - FFIError::set_success(error); true } /// Derive identity registration path (m/9'/5'/5'/1'/index') +/// +/// # Safety +/// +/// `path_out` must point to a writable buffer of at least `path_max_len` bytes +/// and `error` must be a valid pointer to an `FFIError`. #[no_mangle] -pub extern "C" fn derivation_identity_registration_path( +pub unsafe extern "C" fn derivation_identity_registration_path( network: FFINetwork, identity_index: c_uint, path_out: *mut c_char, path_max_len: usize, error: *mut FFIError, ) -> bool { - if path_out.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Path output buffer is null".to_string(), - ); - return false; - } + check_ptr!(path_out, error); let network_rust: key_wallet::Network = network.into(); @@ -266,24 +206,13 @@ pub extern "C" fn derivation_identity_registration_path( let path_str = format!("{}", derivation); - let c_string = match CString::new(path_str) { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to create C string".to_string(), - ); - return false; - } - }; + let c_string = unwrap_or_return!(CString::new(path_str), error); let bytes = c_string.as_bytes_with_nul(); if bytes.len() > path_max_len { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InvalidInput, - format!("Path too long: {} > {}", bytes.len(), path_max_len), + &format!("Path too long: {} > {}", bytes.len(), path_max_len), ); return false; } @@ -291,14 +220,17 @@ pub extern "C" fn derivation_identity_registration_path( unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), path_out.cast::(), bytes.len()); } - - FFIError::set_success(error); true } /// Derive identity top-up path (m/9'/5'/5'/2'/identity_index'/top_up_index') +/// +/// # Safety +/// +/// `path_out` must point to a writable buffer of at least `path_max_len` bytes +/// and `error` must be a valid pointer to an `FFIError`. #[no_mangle] -pub extern "C" fn derivation_identity_topup_path( +pub unsafe extern "C" fn derivation_identity_topup_path( network: FFINetwork, identity_index: c_uint, topup_index: c_uint, @@ -306,14 +238,7 @@ pub extern "C" fn derivation_identity_topup_path( path_max_len: usize, error: *mut FFIError, ) -> bool { - if path_out.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Path output buffer is null".to_string(), - ); - return false; - } + check_ptr!(path_out, error); let network_rust: key_wallet::Network = network.into(); @@ -323,24 +248,13 @@ pub extern "C" fn derivation_identity_topup_path( let path_str = format!("{}", derivation); - let c_string = match CString::new(path_str) { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to create C string".to_string(), - ); - return false; - } - }; + let c_string = unwrap_or_return!(CString::new(path_str), error); let bytes = c_string.as_bytes_with_nul(); if bytes.len() > path_max_len { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InvalidInput, - format!("Path too long: {} > {}", bytes.len(), path_max_len), + &format!("Path too long: {} > {}", bytes.len(), path_max_len), ); return false; } @@ -348,14 +262,17 @@ pub extern "C" fn derivation_identity_topup_path( unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), path_out.cast::(), bytes.len()); } - - FFIError::set_success(error); true } /// Derive identity authentication path (m/9'/5'/5'/0'/identity_index'/key_index') +/// +/// # Safety +/// +/// `path_out` must point to a writable buffer of at least `path_max_len` bytes +/// and `error` must be a valid pointer to an `FFIError`. #[no_mangle] -pub extern "C" fn derivation_identity_authentication_path( +pub unsafe extern "C" fn derivation_identity_authentication_path( network: FFINetwork, identity_index: c_uint, key_index: c_uint, @@ -363,14 +280,7 @@ pub extern "C" fn derivation_identity_authentication_path( path_max_len: usize, error: *mut FFIError, ) -> bool { - if path_out.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Path output buffer is null".to_string(), - ); - return false; - } + check_ptr!(path_out, error); let network_rust: key_wallet::Network = network.into(); @@ -384,24 +294,13 @@ pub extern "C" fn derivation_identity_authentication_path( let path_str = format!("{}", derivation); - let c_string = match CString::new(path_str) { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to create C string".to_string(), - ); - return false; - } - }; + let c_string = unwrap_or_return!(CString::new(path_str), error); let bytes = c_string.as_bytes_with_nul(); if bytes.len() > path_max_len { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InvalidInput, - format!("Path too long: {} > {}", bytes.len(), path_max_len), + &format!("Path too long: {} > {}", bytes.len(), path_max_len), ); return false; } @@ -409,8 +308,6 @@ pub extern "C" fn derivation_identity_authentication_path( unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), path_out.cast::(), bytes.len()); } - - FFIError::set_success(error); true } @@ -420,7 +317,7 @@ pub extern "C" fn derivation_identity_authentication_path( /// /// - `seed` must be a valid pointer to a byte array of `seed_len` length /// - `path` must be a valid pointer to a null-terminated C string -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn derivation_derive_private_key_from_seed( @@ -430,69 +327,21 @@ pub unsafe extern "C" fn derivation_derive_private_key_from_seed( network: FFINetwork, error: *mut FFIError, ) -> *mut FFIExtendedPrivKey { - if seed.is_null() || path.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let seed_slice = slice::from_raw_parts(seed, seed_len); - let network_rust: Network = network.into(); - - let path_str = match CStr::from_ptr(path).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in path".to_string(), - ); - return ptr::null_mut(); - } - }; - use key_wallet::bip32::{DerivationPath, ExtendedPrivKey}; - use secp256k1::Secp256k1; use std::str::FromStr; - let derivation_path = match DerivationPath::from_str(path_str) { - Ok(p) => p, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidDerivationPath, - format!("Invalid derivation path: {:?}", e), - ); - return ptr::null_mut(); - } - }; + check_ptr!(seed, error); + let path = deref_ptr!(path, error); - let secp = Secp256k1::new(); - let master = match ExtendedPrivKey::new_master(network_rust, seed_slice) { - Ok(m) => m, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to create master key: {:?}", e), - ); - return ptr::null_mut(); - } - }; + let seed_slice = slice::from_raw_parts(seed, seed_len); + let network_rust: Network = network.into(); + let path_str = unwrap_or_return!(CStr::from_ptr(path).to_str(), error); + let derivation_path = unwrap_or_return!(DerivationPath::from_str(path_str), error); - match master.derive_priv(&secp, &derivation_path) { - Ok(xpriv) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIExtendedPrivKey::from_inner(xpriv))) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive private key: {:?}", e), - ); - ptr::null_mut() - } - } + let secp = Secp256k1::new(); + let master = unwrap_or_return!(ExtendedPrivKey::new_master(network_rust, seed_slice), error); + let xpriv = unwrap_or_return!(master.derive_priv(&secp, &derivation_path), error); + Box::into_raw(Box::new(FFIExtendedPrivKey::from_inner(xpriv))) } /// Derive public key from extended private key @@ -507,26 +356,11 @@ pub unsafe extern "C" fn derivation_xpriv_to_xpub( xpriv: *const FFIExtendedPrivKey, error: *mut FFIError, ) -> *mut FFIExtendedPubKey { - if xpriv.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Extended private key is null".to_string(), - ); - return ptr::null_mut(); - } - - unsafe { - let xpriv = &*xpriv; - use key_wallet::bip32::ExtendedPubKey; - use secp256k1::Secp256k1; - - let secp = Secp256k1::new(); - let xpub = ExtendedPubKey::from_priv(&secp, xpriv.inner()); - - FFIError::set_success(error); - Box::into_raw(Box::new(FFIExtendedPubKey::from_inner(xpub))) - } + use key_wallet::bip32::ExtendedPubKey; + let xpriv = deref_ptr!(xpriv, error); + let secp = Secp256k1::new(); + let xpub = ExtendedPubKey::from_priv(&secp, xpriv.inner()); + Box::into_raw(Box::new(FFIExtendedPubKey::from_inner(xpub))) } /// Get extended private key as string @@ -541,34 +375,8 @@ pub unsafe extern "C" fn derivation_xpriv_to_string( xpriv: *const FFIExtendedPrivKey, error: *mut FFIError, ) -> *mut c_char { - if xpriv.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Extended private key is null".to_string(), - ); - return ptr::null_mut(); - } - - unsafe { - let xpriv = &*xpriv; - let xpriv_str = xpriv.inner().to_string(); - - match CString::new(xpriv_str) { - Ok(c_str) => { - FFIError::set_success(error); - c_str.into_raw() - } - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } - } + let xpriv = deref_ptr!(xpriv, error); + unwrap_or_return!(CString::new(xpriv.inner().to_string()), error).into_raw() } /// Get extended public key as string @@ -583,34 +391,8 @@ pub unsafe extern "C" fn derivation_xpub_to_string( xpub: *const FFIExtendedPubKey, error: *mut FFIError, ) -> *mut c_char { - if xpub.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Extended public key is null".to_string(), - ); - return ptr::null_mut(); - } - - unsafe { - let xpub = &*xpub; - let xpub_str = xpub.inner().to_string(); - - match CString::new(xpub_str) { - Ok(c_str) => { - FFIError::set_success(error); - c_str.into_raw() - } - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } - } + let xpub = deref_ptr!(xpub, error); + unwrap_or_return!(CString::new(xpub.inner().to_string()), error).into_raw() } /// Get fingerprint from extended public key (4 bytes) @@ -626,21 +408,12 @@ pub unsafe extern "C" fn derivation_xpub_fingerprint( fingerprint_out: *mut u8, error: *mut FFIError, ) -> bool { - if xpub.is_null() || fingerprint_out.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } - - unsafe { - let xpub = &*xpub; - let fingerprint = xpub.inner().fingerprint(); - let bytes = fingerprint.to_bytes(); - - ptr::copy_nonoverlapping(bytes.as_ptr(), fingerprint_out, 4); - - FFIError::set_success(error); - true - } + let xpub = deref_ptr!(xpub, error); + check_ptr!(fingerprint_out, error); + let fingerprint = xpub.inner().fingerprint(); + let bytes = fingerprint.to_bytes(); + ptr::copy_nonoverlapping(bytes.as_ptr(), fingerprint_out, 4); + true } /// Free extended private key diff --git a/key-wallet-ffi/src/derivation_tests.rs b/key-wallet-ffi/src/derivation_tests.rs index dcaad5a3d..c7e43a633 100644 --- a/key-wallet-ffi/src/derivation_tests.rs +++ b/key-wallet-ffi/src/derivation_tests.rs @@ -12,7 +12,7 @@ mod tests { #[test] fn test_master_key_from_seed() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Generate a seed from mnemonic let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); @@ -42,13 +42,12 @@ mod tests { // Clean up unsafe { derivation_xpriv_free(xprv); - error.free_message(); } } #[test] fn test_xpriv_to_xpub() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create master key let mut seed = [0u8; 64]; @@ -69,13 +68,12 @@ mod tests { unsafe { derivation_xpub_free(xpub); derivation_xpriv_free(xprv); - error.free_message(); } } #[test] fn test_xpriv_to_string() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create master key let mut seed = [0u8; 64]; @@ -98,13 +96,12 @@ mod tests { unsafe { derivation_string_free(xprv_str); derivation_xpriv_free(xprv); - error.free_message(); } } #[test] fn test_xpub_to_string() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create master key and get public key let mut seed = [0u8; 64]; @@ -130,13 +127,12 @@ mod tests { derivation_string_free(xpub_str); derivation_xpub_free(xpub); derivation_xpriv_free(xprv); - error.free_message(); } } #[test] fn test_xpub_fingerprint() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create master key let mut seed = [0u8; 64]; @@ -163,23 +159,24 @@ mod tests { unsafe { derivation_xpub_free(xpub); derivation_xpriv_free(xprv); - error.free_message(); } } #[test] fn test_bip44_paths() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test BIP44 account path let mut account_path = vec![0u8; 256]; - let success = derivation_bip44_account_path( - FFINetwork::Testnet, - 0, - account_path.as_mut_ptr() as *mut c_char, - account_path.len(), - &mut error, - ); + let success = unsafe { + derivation_bip44_account_path( + FFINetwork::Testnet, + 0, + account_path.as_mut_ptr() as *mut c_char, + account_path.len(), + &mut error, + ) + }; assert!(success); let path_str = @@ -188,80 +185,86 @@ mod tests { // Test BIP44 payment path let mut payment_path = vec![0u8; 256]; - let success = derivation_bip44_payment_path( - FFINetwork::Testnet, - 0, // account_index - false, // is_change - 0, // address_index - payment_path.as_mut_ptr() as *mut c_char, - payment_path.len(), - &mut error, - ); + let success = unsafe { + derivation_bip44_payment_path( + FFINetwork::Testnet, + 0, // account_index + false, // is_change + 0, // address_index + payment_path.as_mut_ptr() as *mut c_char, + payment_path.len(), + &mut error, + ) + }; assert!(success); let path_str = unsafe { CStr::from_ptr(payment_path.as_ptr() as *const c_char) }.to_str().unwrap(); assert_eq!(path_str, "m/44'/1'/0'/0/0"); - - unsafe { error.free_message() }; } #[test] fn test_special_paths() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test CoinJoin path let mut coinjoin_path = vec![0u8; 256]; - let success = derivation_coinjoin_path( - FFINetwork::Testnet, - 0, // account_index - coinjoin_path.as_mut_ptr() as *mut c_char, - coinjoin_path.len(), - &mut error, - ); + let success = unsafe { + derivation_coinjoin_path( + FFINetwork::Testnet, + 0, // account_index + coinjoin_path.as_mut_ptr() as *mut c_char, + coinjoin_path.len(), + &mut error, + ) + }; assert!(success); // Test identity registration path - takes 2 args: network and identity_index let mut id_reg_path = vec![0u8; 256]; - let success = derivation_identity_registration_path( - FFINetwork::Testnet, - 0, // identity_index - id_reg_path.as_mut_ptr() as *mut c_char, - id_reg_path.len(), - &mut error, - ); + let success = unsafe { + derivation_identity_registration_path( + FFINetwork::Testnet, + 0, // identity_index + id_reg_path.as_mut_ptr() as *mut c_char, + id_reg_path.len(), + &mut error, + ) + }; assert!(success); // Test identity topup path - takes 3 args: network, identity_index, topup_index let mut id_topup_path = vec![0u8; 256]; - let success = derivation_identity_topup_path( - FFINetwork::Testnet, - 0, // identity_index - 2, // topup_index - id_topup_path.as_mut_ptr() as *mut c_char, - id_topup_path.len(), - &mut error, - ); + let success = unsafe { + derivation_identity_topup_path( + FFINetwork::Testnet, + 0, // identity_index + 2, // topup_index + id_topup_path.as_mut_ptr() as *mut c_char, + id_topup_path.len(), + &mut error, + ) + }; assert!(success); // Test identity authentication path - takes 3 args: network, identity_index, key_index let mut id_auth_path = vec![0u8; 256]; - let success = derivation_identity_authentication_path( - FFINetwork::Testnet, - 0, // identity_index - 3, // key_index - id_auth_path.as_mut_ptr() as *mut c_char, - id_auth_path.len(), - &mut error, - ); + let success = unsafe { + derivation_identity_authentication_path( + FFINetwork::Testnet, + 0, // identity_index + 3, // key_index + id_auth_path.as_mut_ptr() as *mut c_char, + id_auth_path.len(), + &mut error, + ) + }; assert!(success); - - unsafe { error.free_message() }; } #[test] fn test_derive_private_key_from_seed() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Generate a seed let mut seed = [0u8; 64]; @@ -288,13 +291,12 @@ mod tests { // Clean up unsafe { derivation_xpriv_free(xpriv); - error.free_message(); } } #[test] fn test_error_handling() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test with null seed let xprv = @@ -304,13 +306,11 @@ mod tests { // Note: The BIP32 implementation actually accepts seeds as small as 16 bytes // so we can't test for invalid seed length error here - - unsafe { error.free_message() }; } #[test] fn test_derivation_string_to_xpub() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Generate a master key and xpub first let mut seed = [0u8; 64]; @@ -333,13 +333,12 @@ mod tests { derivation_string_free(xpub_string); derivation_xpub_free(xpub); derivation_xpriv_free(master_key); - error.free_message(); } } #[test] fn test_derivation_xpriv_string_conversion() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Generate a master key let mut seed = [0u8; 64]; @@ -363,13 +362,12 @@ mod tests { unsafe { derivation_string_free(xpriv_string); derivation_xpriv_free(master_key); - error.free_message(); } } #[test] fn test_derivation_xpub_fingerprint() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Generate a master key and xpub let mut seed = [0u8; 64]; @@ -395,23 +393,24 @@ mod tests { unsafe { derivation_xpub_free(xpub); derivation_xpriv_free(master_key); - error.free_message(); } } #[test] fn test_special_derivation_paths() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test identity registration path let mut buffer = vec![0u8; 256]; - let success = derivation_identity_registration_path( - FFINetwork::Testnet, - 0, // identity_index - buffer.as_mut_ptr() as *mut c_char, - buffer.len(), - &mut error, - ); + let success = unsafe { + derivation_identity_registration_path( + FFINetwork::Testnet, + 0, // identity_index + buffer.as_mut_ptr() as *mut c_char, + buffer.len(), + &mut error, + ) + }; assert!(success); let path_str = @@ -420,14 +419,16 @@ mod tests { // Test identity topup path let mut buffer = vec![0u8; 256]; - let success = derivation_identity_topup_path( - FFINetwork::Testnet, - 0, // identity_index - 0, // topup_index - buffer.as_mut_ptr() as *mut c_char, - buffer.len(), - &mut error, - ); + let success = unsafe { + derivation_identity_topup_path( + FFINetwork::Testnet, + 0, // identity_index + 0, // topup_index + buffer.as_mut_ptr() as *mut c_char, + buffer.len(), + &mut error, + ) + }; assert!(success); let path_str = @@ -436,21 +437,21 @@ mod tests { // Test identity authentication path let mut buffer = vec![0u8; 256]; - let success = derivation_identity_authentication_path( - FFINetwork::Testnet, - 0, // identity_index - 0, // key_index - buffer.as_mut_ptr() as *mut c_char, - buffer.len(), - &mut error, - ); + let success = unsafe { + derivation_identity_authentication_path( + FFINetwork::Testnet, + 0, // identity_index + 0, // key_index + buffer.as_mut_ptr() as *mut c_char, + buffer.len(), + &mut error, + ) + }; assert!(success); let path_str = unsafe { CStr::from_ptr(buffer.as_ptr() as *const c_char) }.to_str().unwrap(); assert!(path_str.contains("m/")); - - unsafe { error.free_message() }; } #[test] @@ -469,7 +470,7 @@ mod tests { #[test] fn test_derivation_new_master_key_edge_cases() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test with null seed let xprv = @@ -477,93 +478,89 @@ mod tests { assert!(xprv.is_null()); assert_eq!(error.code, FFIErrorCode::InvalidInput); - // Test with null error pointer (should not crash) + // Test with a valid seed let seed = [0u8; 64]; let xprv = unsafe { - derivation_new_master_key( - seed.as_ptr(), - seed.len(), - FFINetwork::Testnet, - ptr::null_mut(), - ) + derivation_new_master_key(seed.as_ptr(), seed.len(), FFINetwork::Testnet, &mut error) }; - // Should handle null error gracefully if !xprv.is_null() { unsafe { derivation_xpriv_free(xprv); } } - - unsafe { error.free_message() }; } #[test] fn test_derivation_path_functions_null_inputs() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test BIP44 account path with null buffer - let success = - derivation_bip44_account_path(FFINetwork::Testnet, 0, ptr::null_mut(), 256, &mut error); + let success = unsafe { + derivation_bip44_account_path(FFINetwork::Testnet, 0, ptr::null_mut(), 256, &mut error) + }; assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidInput); // Test BIP44 payment path with null buffer - let success = derivation_bip44_payment_path( - FFINetwork::Testnet, - 0, - false, - 0, - ptr::null_mut(), - 256, - &mut error, - ); + let success = unsafe { + derivation_bip44_payment_path( + FFINetwork::Testnet, + 0, + false, + 0, + ptr::null_mut(), + 256, + &mut error, + ) + }; assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidInput); // Test CoinJoin path with null buffer - let success = - derivation_coinjoin_path(FFINetwork::Testnet, 0, ptr::null_mut(), 256, &mut error); + let success = unsafe { + derivation_coinjoin_path(FFINetwork::Testnet, 0, ptr::null_mut(), 256, &mut error) + }; assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_derivation_path_functions_small_buffer() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test with very small buffer (should fail) let mut small_buffer = [0u8; 5]; - let success = derivation_bip44_account_path( - FFINetwork::Testnet, - 0, - small_buffer.as_mut_ptr() as *mut c_char, - small_buffer.len(), - &mut error, - ); + let success = unsafe { + derivation_bip44_account_path( + FFINetwork::Testnet, + 0, + small_buffer.as_mut_ptr() as *mut c_char, + small_buffer.len(), + &mut error, + ) + }; assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidInput); // Test BIP44 payment path with small buffer - let success = derivation_bip44_payment_path( - FFINetwork::Testnet, - 0, - false, - 0, - small_buffer.as_mut_ptr() as *mut c_char, - small_buffer.len(), - &mut error, - ); + let success = unsafe { + derivation_bip44_payment_path( + FFINetwork::Testnet, + 0, + false, + 0, + small_buffer.as_mut_ptr() as *mut c_char, + small_buffer.len(), + &mut error, + ) + }; assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_derivation_different_networks() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let mut seed = [0u8; 64]; for (i, byte) in seed.iter_mut().enumerate() { *byte = (i % 256) as u8; @@ -599,49 +596,42 @@ mod tests { derivation_string_free(test_str); derivation_xpriv_free(xprv_main); derivation_xpriv_free(xprv_test); - error.free_message(); } } #[test] fn test_derivation_xpriv_to_xpub_null_input() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let xpub = unsafe { derivation_xpriv_to_xpub(ptr::null_mut(), &mut error) }; assert!(xpub.is_null()); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_derivation_xpriv_to_string_null_input() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let xprv_str = unsafe { derivation_xpriv_to_string(ptr::null_mut(), &mut error) }; assert!(xprv_str.is_null()); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_derivation_xpub_to_string_null_input() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let xpub_str = unsafe { derivation_xpub_to_string(ptr::null_mut(), &mut error) }; assert!(xpub_str.is_null()); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_derivation_xpub_fingerprint_null_inputs() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let mut fingerprint = [0u8; 4]; // Test with null xpub @@ -671,13 +661,12 @@ mod tests { unsafe { derivation_xpub_free(xpub); derivation_xpriv_free(xprv); - error.free_message(); } } #[test] fn test_derivation_derive_private_key_from_seed_null_inputs() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let seed = [0u8; 64]; let path = CString::new("m/44'/1'/0'/0/0").unwrap(); @@ -706,13 +695,11 @@ mod tests { }; assert!(xpriv.is_null()); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_derivation_derive_private_key_invalid_path() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let mut seed = [0u8; 64]; for (i, byte) in seed.iter_mut().enumerate() { *byte = (i % 256) as u8; @@ -736,118 +723,128 @@ mod tests { derivation_xpriv_free(xpriv); } } - - unsafe { error.free_message() }; } #[test] fn test_identity_path_functions_null_inputs() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test identity registration with null buffer - let success = derivation_identity_registration_path( - FFINetwork::Testnet, - 0, - ptr::null_mut(), - 256, - &mut error, - ); + let success = unsafe { + derivation_identity_registration_path( + FFINetwork::Testnet, + 0, + ptr::null_mut(), + 256, + &mut error, + ) + }; assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidInput); // Test identity topup with null buffer - let success = derivation_identity_topup_path( - FFINetwork::Testnet, - 0, - 0, - ptr::null_mut(), - 256, - &mut error, - ); + let success = unsafe { + derivation_identity_topup_path( + FFINetwork::Testnet, + 0, + 0, + ptr::null_mut(), + 256, + &mut error, + ) + }; assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidInput); // Test identity authentication with null buffer - let success = derivation_identity_authentication_path( - FFINetwork::Testnet, - 0, - 0, - ptr::null_mut(), - 256, - &mut error, - ); + let success = unsafe { + derivation_identity_authentication_path( + FFINetwork::Testnet, + 0, + 0, + ptr::null_mut(), + 256, + &mut error, + ) + }; assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_identity_path_functions_small_buffer() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let mut small_buffer = [0u8; 5]; // Test identity registration with small buffer - let success = derivation_identity_registration_path( - FFINetwork::Testnet, - 0, - small_buffer.as_mut_ptr() as *mut c_char, - small_buffer.len(), - &mut error, - ); + let success = unsafe { + derivation_identity_registration_path( + FFINetwork::Testnet, + 0, + small_buffer.as_mut_ptr() as *mut c_char, + small_buffer.len(), + &mut error, + ) + }; assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidInput); // Test identity topup with small buffer - let success = derivation_identity_topup_path( - FFINetwork::Testnet, - 0, - 0, - small_buffer.as_mut_ptr() as *mut c_char, - small_buffer.len(), - &mut error, - ); + let success = unsafe { + derivation_identity_topup_path( + FFINetwork::Testnet, + 0, + 0, + small_buffer.as_mut_ptr() as *mut c_char, + small_buffer.len(), + &mut error, + ) + }; assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidInput); // Test identity authentication with small buffer - let success = derivation_identity_authentication_path( - FFINetwork::Testnet, - 0, - 0, - small_buffer.as_mut_ptr() as *mut c_char, - small_buffer.len(), - &mut error, - ); + let success = unsafe { + derivation_identity_authentication_path( + FFINetwork::Testnet, + 0, + 0, + small_buffer.as_mut_ptr() as *mut c_char, + small_buffer.len(), + &mut error, + ) + }; assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_path_functions_different_indices() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let mut buffer1 = vec![0u8; 256]; let mut buffer2 = vec![0u8; 256]; // Test BIP44 account path with different account indices - let success1 = derivation_bip44_account_path( - FFINetwork::Testnet, - 0, - buffer1.as_mut_ptr() as *mut c_char, - buffer1.len(), - &mut error, - ); + let success1 = unsafe { + derivation_bip44_account_path( + FFINetwork::Testnet, + 0, + buffer1.as_mut_ptr() as *mut c_char, + buffer1.len(), + &mut error, + ) + }; assert!(success1); - let success2 = derivation_bip44_account_path( - FFINetwork::Testnet, - 5, - buffer2.as_mut_ptr() as *mut c_char, - buffer2.len(), - &mut error, - ); + let success2 = unsafe { + derivation_bip44_account_path( + FFINetwork::Testnet, + 5, + buffer2.as_mut_ptr() as *mut c_char, + buffer2.len(), + &mut error, + ) + }; assert!(success2); let path1 = unsafe { CStr::from_ptr(buffer1.as_ptr() as *const c_char).to_str().unwrap() }; @@ -856,25 +853,25 @@ mod tests { assert_eq!(path1, "m/44'/1'/0'"); assert_eq!(path2, "m/44'/1'/5'"); assert_ne!(path1, path2); - - unsafe { error.free_message() }; } #[test] fn test_bip44_payment_path_variations() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test receive address path let mut buffer = vec![0u8; 256]; - let success = derivation_bip44_payment_path( - FFINetwork::Testnet, - 0, // account_index - false, // is_change (receive) - 5, // address_index - buffer.as_mut_ptr() as *mut c_char, - buffer.len(), - &mut error, - ); + let success = unsafe { + derivation_bip44_payment_path( + FFINetwork::Testnet, + 0, // account_index + false, // is_change (receive) + 5, // address_index + buffer.as_mut_ptr() as *mut c_char, + buffer.len(), + &mut error, + ) + }; assert!(success); let path_str = unsafe { CStr::from_ptr(buffer.as_ptr() as *const c_char) }.to_str().unwrap(); @@ -882,26 +879,26 @@ mod tests { // Test change address path let mut buffer = vec![0u8; 256]; - let success = derivation_bip44_payment_path( - FFINetwork::Testnet, - 0, // account_index - true, // is_change - 3, // address_index - buffer.as_mut_ptr() as *mut c_char, - buffer.len(), - &mut error, - ); + let success = unsafe { + derivation_bip44_payment_path( + FFINetwork::Testnet, + 0, // account_index + true, // is_change + 3, // address_index + buffer.as_mut_ptr() as *mut c_char, + buffer.len(), + &mut error, + ) + }; assert!(success); let path_str = unsafe { CStr::from_ptr(buffer.as_ptr() as *const c_char) }.to_str().unwrap(); assert_eq!(path_str, "m/44'/1'/0'/1/3"); - - unsafe { error.free_message() }; } #[test] fn test_comprehensive_derivation_workflow() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Generate seed let mut seed = [0u8; 64]; @@ -974,7 +971,6 @@ mod tests { derivation_xpriv_free(child_xprv); derivation_xpub_free(master_xpub); derivation_xpriv_free(master_xprv); - error.free_message(); } } } diff --git a/key-wallet-ffi/src/error.rs b/key-wallet-ffi/src/error.rs index cc942f619..c46cf0ef8 100644 --- a/key-wallet-ffi/src/error.rs +++ b/key-wallet-ffi/src/error.rs @@ -2,7 +2,128 @@ use std::ffi::CString; use std::os::raw::c_char; -use std::ptr; +use std::str::Utf8Error; +use std::{ffi, ptr}; + +/// Dereference a raw `*const` pointer as `&T`, or early-return after writing +/// `InvalidInput` into `*error`. The two-arg form returns `Default::default()`. +#[macro_export] +macro_rules! deref_ptr { + ($ptr:expr, $error:expr, $return_value:expr) => {{ + (*$error).clean(); + + if $ptr.is_null() { + return { + (*$error).set( + $crate::error::FFIErrorCode::InvalidInput, + &format!("{} ptr is null", stringify!($ptr)), + ); + $return_value + }; + } + unsafe { &*$ptr } + }}; + + ($ptr:expr, $error:expr) => {{ + (*$error).clean(); + + if $ptr.is_null() { + return { + (*$error).set( + $crate::error::FFIErrorCode::InvalidInput, + &format!("{} ptr is null", stringify!($ptr)), + ); + Default::default() + }; + } + unsafe { &*$ptr } + }}; +} + +/// Mutable variant of [`deref_ptr!`]: yields `&mut T` on success, otherwise +/// sets `*error` to `InvalidInput` and early-returns. +#[macro_export] +macro_rules! deref_ptr_mut { + ($ptr:expr, $error:expr, $return_value:expr) => {{ + (*$error).clean(); + + if $ptr.is_null() { + return { + (*$error).set( + $crate::error::FFIErrorCode::InvalidInput, + &format!("{} ptr is null", stringify!($ptr)), + ); + $return_value + }; + } + unsafe { &mut *$ptr } + }}; + + ($ptr:expr, $error:expr) => {{ + (*$error).clean(); + + if $ptr.is_null() { + return { + (*$error).set( + $crate::error::FFIErrorCode::InvalidInput, + &format!("{} ptr is null", stringify!($ptr)), + ); + Default::default() + }; + } + unsafe { &mut *$ptr } + }}; +} + +/// Null-check a raw pointer without dereferencing it. On null, sets +/// `*error` to `InvalidInput` and early-returns. Use this for out-parameters +/// where the pointer may point to uninitialized memory and forming a Rust +/// reference would be unsound. +#[macro_export] +macro_rules! check_ptr { + ($ptr:expr, $error:expr, $return_value:expr) => {{ + (*$error).clean(); + + if $ptr.is_null() { + (*$error).set( + $crate::error::FFIErrorCode::InvalidInput, + &format!("{} ptr is null", stringify!($ptr)), + ); + return $return_value; + } + }}; + + ($ptr:expr, $error:expr) => {{ + (*$error).clean(); + + if $ptr.is_null() { + (*$error).set( + $crate::error::FFIErrorCode::InvalidInput, + &format!("{} ptr is null", stringify!($ptr)), + ); + return Default::default(); + } + }}; +} + +/// Unwrap a `Result`/`Option` via [`FfiErrMapper`], writing any error into +/// `*error` and early-returning. The two-arg form returns `Default::default()`. +#[macro_export] +macro_rules! unwrap_or_return { + ($expr:expr, $error:expr, $return_value:expr) => {{ + match $crate::error::FfiErrMapper::map_to_ffi_err($expr, &mut *$error) { + Some(v) => v, + None => return $return_value, + } + }}; + + ($expr:expr, $error:expr) => {{ + match $crate::error::FfiErrMapper::map_to_ffi_err($expr, &mut *$error) { + Some(v) => v, + None => return Default::default(), + } + }}; +} /// FFI Error code #[repr(C)] @@ -21,6 +142,7 @@ pub enum FFIErrorCode { NotFound = 10, InvalidState = 11, InternalError = 12, + NulByteError = 13, } /// FFI Error structure @@ -32,199 +154,231 @@ pub struct FFIError { } impl FFIError { - /// Create a success result - pub fn success() -> Self { - FFIError { - code: FFIErrorCode::Success, - message: ptr::null_mut(), - } - } - - /// Create an error with code and message - pub fn error(code: FFIErrorCode, msg: String) -> Self { - FFIError { - code, - message: CString::new(msg).unwrap_or_default().into_raw(), - } - } - - /// Set error on a mutable pointer if it's not null. - /// Frees any previous error message before setting the new one. - #[allow(clippy::not_unsafe_ptr_arg_deref)] - pub fn set_error(error_ptr: *mut FFIError, code: FFIErrorCode, msg: String) { - if !error_ptr.is_null() { - unsafe { - // Free previous message if present - let prev = &mut *error_ptr; - if !prev.message.is_null() { - let _ = CString::from_raw(prev.message); - } - *error_ptr = Self::error(code, msg); - } - } - } + /// # Safety + /// + /// This will call FFIError::clean, to ensure message is deallocated + /// before poinitng to a new string. FFIError::clean Safety consideation apply here. + pub unsafe fn set(&mut self, code: FFIErrorCode, msg: &str) { + self.clean(); - /// Set success on a mutable pointer if it's not null. - /// Frees any previous error message before setting success. - #[allow(clippy::not_unsafe_ptr_arg_deref)] - pub fn set_success(error_ptr: *mut FFIError) { - if !error_ptr.is_null() { - unsafe { - // Free previous message if present - let prev = &mut *error_ptr; - if !prev.message.is_null() { - let _ = CString::from_raw(prev.message); - } - *error_ptr = Self::success(); - } - } + self.message = CString::new(msg).unwrap_or_default().into_raw(); + self.code = code; } - /// Free the error message if present. - /// Use this in tests to prevent memory leaks. + /// Returns the error to the default state, deallocating the message if it exists /// /// # Safety /// /// The message pointer must have been allocated by this library. - pub unsafe fn free_message(&mut self) { + pub unsafe fn clean(&mut self) { + self.code = FFIErrorCode::Success; + if !self.message.is_null() { - let _ = CString::from_raw(self.message); + let _ = unsafe { CString::from_raw(self.message) }; self.message = ptr::null_mut(); } } } -/// Free an error message -/// -/// # Safety -/// -/// - `message` must be a valid pointer to a C string that was allocated by this library -/// - The pointer must not be used after calling this function -/// - This function must only be called once per allocation -#[no_mangle] -pub unsafe extern "C" fn error_message_free(message: *mut c_char) { - if !message.is_null() { - let _ = CString::from_raw(message); +impl Default for FFIError { + fn default() -> Self { + FFIError { + code: FFIErrorCode::Success, + message: ptr::null_mut(), + } } } -/// Helper macro to convert any error that implements `Into` and set it on the error pointer. -/// Frees any previous error message before setting the new one. -#[macro_export] -macro_rules! ffi_error_set { - ($error_ptr:expr, $err:expr) => {{ - let ffi_error: $crate::error::FFIError = $err.into(); - if !$error_ptr.is_null() { - unsafe { - // Free previous message if present - let prev = &mut *$error_ptr; - if !prev.message.is_null() { - let _ = std::ffi::CString::from_raw(prev.message); - } - *$error_ptr = ffi_error; - } - } - }}; +pub trait FfiErrMapper: Sized { + /// Map `self` into an `FFIError` via `FfiErrMapperImpl`, clearing any prior + /// error message stored in `error` first. + /// + /// # Safety + /// + /// If `error` currently holds a message pointer, it must have been allocated + /// by this library; it will be freed before being overwritten. + unsafe fn map_to_ffi_err(self, error: &mut FFIError) -> Option { + error.clean(); + + self.map_to_ffi_err_impl(error) + } + + fn map_to_ffi_err_impl(self, err: &mut FFIError) -> Option; } -/// Helper macro to handle Result types in FFI functions -#[macro_export] -macro_rules! ffi_result { - ($error_ptr:expr, $result:expr) => { - match $result { - Ok(val) => { - $crate::error::FFIError::set_success($error_ptr); - val - } - Err(err) => { - ffi_error_set!($error_ptr, err); - return std::ptr::null_mut(); +impl FfiErrMapper for Result +where + E: Into, +{ + fn map_to_ffi_err_impl(self, err: &mut FFIError) -> Option { + match self { + Ok(item) => Some(item), + Err(e) => { + *err = e.into(); + None } } - }; - ($error_ptr:expr, $result:expr, $default:expr) => { - match $result { - Ok(val) => { - $crate::error::FFIError::set_success($error_ptr); - val - } - Err(err) => { - ffi_error_set!($error_ptr, err); - return $default; - } + } +} + +impl FfiErrMapper for Option { + fn map_to_ffi_err_impl(self, err: &mut FFIError) -> Option { + if self.is_none() { + err.code = FFIErrorCode::NotFound; + err.message = CString::new("Item not found").unwrap().into_raw(); } - }; + + self + } } -/// Convert key_wallet::Error to FFIError impl From for FFIError { - fn from(err: key_wallet::Error) -> Self { + fn from(value: key_wallet::Error) -> Self { use key_wallet::Error; - let (code, msg) = match &err { - Error::InvalidDerivationPath(_) => { - (FFIErrorCode::InvalidDerivationPath, err.to_string()) + let code = match &value { + Error::InvalidDerivationPath(_) => FFIErrorCode::InvalidDerivationPath, + Error::InvalidMnemonic(_) => FFIErrorCode::InvalidMnemonic, + Error::InvalidParameter(_) => FFIErrorCode::InvalidInput, + Error::InvalidNetwork => FFIErrorCode::InvalidNetwork, + Error::InvalidAddress(_) => FFIErrorCode::InvalidAddress, + Error::Serialization(_) => FFIErrorCode::SerializationError, + Error::WatchOnly | Error::CoinJoinNotEnabled | Error::NoKeySource => { + FFIErrorCode::InvalidState } - Error::InvalidMnemonic(_) => (FFIErrorCode::InvalidMnemonic, err.to_string()), - Error::InvalidNetwork => (FFIErrorCode::InvalidNetwork, "Invalid network".to_string()), - Error::InvalidAddress(_) => (FFIErrorCode::InvalidAddress, err.to_string()), - Error::InvalidParameter(_) => (FFIErrorCode::InvalidInput, err.to_string()), - Error::Serialization(_) => (FFIErrorCode::SerializationError, err.to_string()), - Error::WatchOnly => ( - FFIErrorCode::InvalidState, - "Operation not supported on watch-only wallet".to_string(), - ), - Error::CoinJoinNotEnabled => { - (FFIErrorCode::InvalidState, "CoinJoin not enabled".to_string()) - } - Error::KeyError(_) | Error::Bip32(_) | Error::Secp256k1(_) | Error::Base58 => { - (FFIErrorCode::WalletError, err.to_string()) - } - Error::NoKeySource => { - (FFIErrorCode::InvalidState, "No key source available".to_string()) - } - #[allow(unreachable_patterns)] - _ => (FFIErrorCode::WalletError, err.to_string()), + Error::Bip32(_) + | Error::Slip10(_) + | Error::BLS(_) + | Error::Secp256k1(_) + | Error::Base58 + | Error::KeyError(_) => FFIErrorCode::WalletError, }; - FFIError::error(code, msg) + FFIError { + code, + message: CString::new(value.to_string()) + .unwrap_or( + CString::new("Rust key_wallet::Error message contains null byte").unwrap(), + ) + .into_raw(), + } + } +} + +impl From for FFIError { + fn from(value: key_wallet::bip32::Error) -> Self { + FFIError { + code: FFIErrorCode::InvalidInput, + message: CString::new(value.to_string()) + .unwrap_or( + CString::new("Rust key_wallet::bip32::Error message contains null byte") + .unwrap(), + ) + .into_raw(), + } } } impl From for FFIError { - fn from(err: key_wallet_manager::WalletError) -> Self { + fn from(value: key_wallet_manager::WalletError) -> Self { use key_wallet_manager::WalletError; - let (code, msg) = match &err { - WalletError::WalletCreation(msg) => { - (FFIErrorCode::WalletError, format!("Wallet creation failed: {}", msg)) - } - WalletError::WalletNotFound(_) => (FFIErrorCode::NotFound, err.to_string()), - WalletError::WalletExists(_) => (FFIErrorCode::InvalidState, err.to_string()), - WalletError::InvalidMnemonic(msg) => { - (FFIErrorCode::InvalidMnemonic, format!("Invalid mnemonic: {}", msg)) - } - WalletError::AccountCreation(msg) => { - (FFIErrorCode::WalletError, format!("Account creation failed: {}", msg)) - } - WalletError::AccountNotFound(_) => (FFIErrorCode::NotFound, err.to_string()), - WalletError::AddressGeneration(msg) => { - (FFIErrorCode::InvalidAddress, format!("Address generation failed: {}", msg)) - } - WalletError::InvalidNetwork => { - (FFIErrorCode::InvalidNetwork, "Invalid network".to_string()) - } - WalletError::InvalidParameter(msg) => { - (FFIErrorCode::InvalidInput, format!("Invalid parameter: {}", msg)) - } - WalletError::TransactionBuild(msg) => { - (FFIErrorCode::InvalidTransaction, format!("Transaction build failed: {}", msg)) - } - WalletError::InsufficientFunds => { - (FFIErrorCode::InvalidState, "Insufficient funds".to_string()) - } + let code = match &value { + WalletError::WalletCreation(_) => FFIErrorCode::WalletError, + WalletError::WalletNotFound(_) => FFIErrorCode::NotFound, + WalletError::WalletExists(_) => FFIErrorCode::InvalidState, + WalletError::InvalidMnemonic(_) => FFIErrorCode::InvalidMnemonic, + WalletError::AccountCreation(_) => FFIErrorCode::WalletError, + WalletError::AccountNotFound(_) => FFIErrorCode::NotFound, + WalletError::AddressGeneration(_) => FFIErrorCode::InvalidAddress, + WalletError::InvalidNetwork => FFIErrorCode::InvalidNetwork, + WalletError::InvalidParameter(_) => FFIErrorCode::InvalidInput, + WalletError::TransactionBuild(_) => FFIErrorCode::InvalidTransaction, + WalletError::InsufficientFunds => FFIErrorCode::InvalidState, }; - FFIError::error(code, msg) + FFIError { + code, + message: CString::new(value.to_string()) + .unwrap_or( + CString::new("Rust key_wallet_manager::WalletError message contains null byte") + .unwrap(), + ) + .into_raw(), + } + } +} + +impl From for FFIError { + fn from(value: ffi::NulError) -> Self { + FFIError { + code: FFIErrorCode::NulByteError, + message: CString::new(value.to_string()) + .unwrap_or(CString::new("Rust ffi::NulError message contains null byte").unwrap()) + .into_raw(), + } + } +} + +impl From for FFIError { + fn from(value: Utf8Error) -> Self { + FFIError { + code: FFIErrorCode::InvalidInput, + message: CString::new(value.to_string()) + .unwrap_or(CString::new("Rust Utf8Error message contains null byte").unwrap()) + .into_raw(), + } + } +} + +impl From for FFIError { + fn from(value: dashcore::address::Error) -> Self { + FFIError { + code: FFIErrorCode::InvalidAddress, + message: CString::new(value.to_string()) + .unwrap_or( + CString::new("Rust dashcore::address::Error message contains null byte") + .unwrap(), + ) + .into_raw(), + } + } +} + +impl From for FFIError { + fn from(value: dashcore::consensus::encode::Error) -> Self { + FFIError { + code: FFIErrorCode::InvalidInput, + message: CString::new(value.to_string()) + .unwrap_or( + CString::new( + "Rust dashcore::consensus::encode::Error message contains null byte", + ) + .unwrap(), + ) + .into_raw(), + } + } +} + +impl Drop for FFIError { + fn drop(&mut self) { + unsafe { + self.clean(); + } + } +} + +/// Free an error message +/// +/// # Safety +/// +/// - `message` must be a valid pointer to a C string that was allocated by this library +/// - The pointer must not be used after calling this function +/// - This function must only be called once per allocation +#[no_mangle] +pub unsafe extern "C" fn error_message_free(message: *mut c_char) { + if !message.is_null() { + let _ = CString::from_raw(message); } } diff --git a/key-wallet-ffi/src/keys.rs b/key-wallet-ffi/src/keys.rs index 68f9fa3d3..283fb98cb 100644 --- a/key-wallet-ffi/src/keys.rs +++ b/key-wallet-ffi/src/keys.rs @@ -4,6 +4,7 @@ use dashcore::ffi::FFINetwork; use crate::error::{FFIError, FFIErrorCode}; use crate::types::FFIWallet; +use crate::{check_ptr, deref_ptr, unwrap_or_return}; use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_uint}; use std::ptr; @@ -78,39 +79,17 @@ pub unsafe extern "C" fn wallet_get_account_xpriv( account_index: c_uint, error: *mut FFIError, ) -> *mut c_char { - if wallet.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Wallet is null".to_string()); + let wallet = deref_ptr!(wallet, error); + + let account = unwrap_or_return!(wallet.inner().get_bip44_account(account_index), error); + + if account.is_watch_only { + (*error).set(FFIErrorCode::NotFound, "Private key not available (watch-only wallet)"); return ptr::null_mut(); } - let wallet = unsafe { &*wallet }; - - match wallet.inner().get_bip44_account(account_index) { - Some(account) => { - // Extended private key is not available on Account - // Only the wallet has access to private keys - if account.is_watch_only { - FFIError::set_error( - error, - FFIErrorCode::NotFound, - "Private key not available (watch-only wallet)".to_string(), - ); - ptr::null_mut() - } else { - // Private key extraction not implemented for security reasons - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "Private key extraction not implemented".to_string(), - ); - ptr::null_mut() - } - } - None => { - FFIError::set_error(error, FFIErrorCode::NotFound, "Account not found".to_string()); - ptr::null_mut() - } - } + (*error).set(FFIErrorCode::InternalError, "Private key extraction not implemented"); + ptr::null_mut() } /// Get extended public key for account @@ -126,34 +105,9 @@ pub unsafe extern "C" fn wallet_get_account_xpub( account_index: c_uint, error: *mut FFIError, ) -> *mut c_char { - if wallet.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Wallet is null".to_string()); - return ptr::null_mut(); - } - - let wallet = unsafe { &*wallet }; - - match wallet.inner().get_bip44_account(account_index) { - Some(account) => { - let xpub = account.extended_public_key(); - FFIError::set_success(error); - match CString::new(xpub.to_string()) { - Ok(c_str) => c_str.into_raw(), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } - } - None => { - FFIError::set_error(error, FFIErrorCode::NotFound, "Account not found".to_string()); - ptr::null_mut() - } - } + let wallet = deref_ptr!(wallet, error); + let account = unwrap_or_return!(wallet.inner().get_bip44_account(account_index), error); + unwrap_or_return!(CString::new(account.extended_public_key().to_string()), error).into_raw() } /// Derive private key at a specific path @@ -171,57 +125,17 @@ pub unsafe extern "C" fn wallet_derive_private_key( derivation_path: *const c_char, error: *mut FFIError, ) -> *mut FFIPrivateKey { - if wallet.is_null() || derivation_path.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let path_str = match unsafe { CStr::from_ptr(derivation_path) }.to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in derivation path".to_string(), - ); - return ptr::null_mut(); - } - }; - - // Parse the derivation path use key_wallet::DerivationPath; use std::str::FromStr; - let path = match DerivationPath::from_str(path_str) { - Ok(p) => p, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - format!("Invalid derivation path: {}", e), - ); - return ptr::null_mut(); - } - }; - let wallet = unsafe { &*wallet }; - - // Use the new wallet method to derive the private key - match wallet.inner().derive_private_key(&path) { - Ok(private_key) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIPrivateKey { - inner: private_key, - })) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive private key: {:?}", e), - ); - ptr::null_mut() - } - } + let wallet = deref_ptr!(wallet, error); + let derivation_path = deref_ptr!(derivation_path, error); + let path_str = unwrap_or_return!(CStr::from_ptr(derivation_path).to_str(), error); + let path = unwrap_or_return!(DerivationPath::from_str(path_str), error); + let private_key = unwrap_or_return!(wallet.inner().derive_private_key(&path), error); + Box::into_raw(Box::new(FFIPrivateKey { + inner: private_key, + })) } /// Derive extended private key at a specific path @@ -239,57 +153,18 @@ pub unsafe extern "C" fn wallet_derive_extended_private_key( derivation_path: *const c_char, error: *mut FFIError, ) -> *mut FFIExtendedPrivKey { - if wallet.is_null() || derivation_path.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let path_str = match unsafe { CStr::from_ptr(derivation_path) }.to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in derivation path".to_string(), - ); - return ptr::null_mut(); - } - }; - - // Parse the derivation path use key_wallet::DerivationPath; use std::str::FromStr; - let path = match DerivationPath::from_str(path_str) { - Ok(p) => p, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - format!("Invalid derivation path: {}", e), - ); - return ptr::null_mut(); - } - }; - let wallet = unsafe { &*wallet }; - - // Use the new wallet method to derive the extended private key - match wallet.inner().derive_extended_private_key(&path) { - Ok(extended_private_key) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIExtendedPrivKey { - inner: extended_private_key, - })) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive extended private key: {:?}", e), - ); - ptr::null_mut() - } - } + let wallet = deref_ptr!(wallet, error); + let derivation_path = deref_ptr!(derivation_path, error); + let path_str = unwrap_or_return!(CStr::from_ptr(derivation_path).to_str(), error); + let path = unwrap_or_return!(DerivationPath::from_str(path_str), error); + let extended_private_key = + unwrap_or_return!(wallet.inner().derive_extended_private_key(&path), error); + Box::into_raw(Box::new(FFIExtendedPrivKey { + inner: extended_private_key, + })) } /// Derive private key at a specific path and return as WIF string @@ -306,65 +181,15 @@ pub unsafe extern "C" fn wallet_derive_private_key_as_wif( derivation_path: *const c_char, error: *mut FFIError, ) -> *mut c_char { - if wallet.is_null() || derivation_path.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let path_str = match unsafe { CStr::from_ptr(derivation_path) }.to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in derivation path".to_string(), - ); - return ptr::null_mut(); - } - }; - - // Parse the derivation path use key_wallet::DerivationPath; use std::str::FromStr; - let path = match DerivationPath::from_str(path_str) { - Ok(p) => p, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - format!("Invalid derivation path: {}", e), - ); - return ptr::null_mut(); - } - }; - let wallet = unsafe { &*wallet }; - - // Use the new wallet method to derive the private key as WIF - match wallet.inner().derive_private_key_as_wif(&path) { - Ok(wif) => { - FFIError::set_success(error); - match CString::new(wif) { - Ok(c_str) => c_str.into_raw(), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive private key: {:?}", e), - ); - ptr::null_mut() - } - } + let wallet = deref_ptr!(wallet, error); + let derivation_path = deref_ptr!(derivation_path, error); + let path_str = unwrap_or_return!(CStr::from_ptr(derivation_path).to_str(), error); + let path = unwrap_or_return!(DerivationPath::from_str(path_str), error); + let wif = unwrap_or_return!(wallet.inner().derive_private_key_as_wif(&path), error); + unwrap_or_return!(CString::new(wif), error).into_raw() } /// Free a private key @@ -406,36 +231,12 @@ pub unsafe extern "C" fn extended_private_key_free(key: *mut FFIExtendedPrivKey) #[no_mangle] pub unsafe extern "C" fn extended_private_key_to_string( key: *const FFIExtendedPrivKey, - network: FFINetwork, + _network: FFINetwork, error: *mut FFIError, ) -> *mut c_char { - if key.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Extended private key is null".to_string(), - ); - return ptr::null_mut(); - } - - let key = unsafe { &*key }; - let _ = network; // Network is already encoded in the extended key - - // Convert to string - the network is already encoded in the extended key - let key_string = key.inner.to_string(); - - FFIError::set_success(error); - match CString::new(key_string) { - Ok(c_str) => c_str.into_raw(), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } + // Network is already encoded in the extended key. + let key = deref_ptr!(key, error); + unwrap_or_return!(CString::new(key.inner.to_string()), error).into_raw() } /// Get the private key from an extended private key @@ -452,24 +253,10 @@ pub unsafe extern "C" fn extended_private_key_get_private_key( extended_key: *const FFIExtendedPrivKey, error: *mut FFIError, ) -> *mut FFIPrivateKey { - if extended_key.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Extended private key is null".to_string(), - ); - return ptr::null_mut(); - } - - let extended = unsafe { &*extended_key }; - - // Extract the private key - let private_key = FFIPrivateKey { + let extended = deref_ptr!(extended_key, error); + Box::into_raw(Box::new(FFIPrivateKey { inner: extended.inner.private_key, - }; - - FFIError::set_success(error); - Box::into_raw(Box::new(private_key)) + })) } /// Get private key as WIF string from FFIPrivateKey @@ -485,12 +272,8 @@ pub unsafe extern "C" fn private_key_to_wif( network: FFINetwork, error: *mut FFIError, ) -> *mut c_char { - if key.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Private key is null".to_string()); - return ptr::null_mut(); - } + let key = deref_ptr!(key, error); - let key = unsafe { &*key }; let network_rust: key_wallet::Network = network.into(); // Convert to WIF format @@ -502,18 +285,7 @@ pub unsafe extern "C" fn private_key_to_wif( }; let wif = dash_key.to_wif(); - FFIError::set_success(error); - match CString::new(wif) { - Ok(c_str) => c_str.into_raw(), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } + unwrap_or_return!(CString::new(wif), error).into_raw() } /// Derive public key at a specific path @@ -531,59 +303,17 @@ pub unsafe extern "C" fn wallet_derive_public_key( derivation_path: *const c_char, error: *mut FFIError, ) -> *mut FFIPublicKey { - if wallet.is_null() || derivation_path.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let path_str = match unsafe { CStr::from_ptr(derivation_path) }.to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in derivation path".to_string(), - ); - return ptr::null_mut(); - } - }; - - // Parse the derivation path use key_wallet::DerivationPath; use std::str::FromStr; - let path = match DerivationPath::from_str(path_str) { - Ok(p) => p, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - format!("Invalid derivation path: {}", e), - ); - return ptr::null_mut(); - } - }; - unsafe { - let wallet = &*wallet; - - // Use the new wallet method to derive the public key - match wallet.inner().derive_public_key(&path) { - Ok(public_key) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIPublicKey { - inner: public_key, - })) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive public key: {:?}", e), - ); - ptr::null_mut() - } - } - } + let wallet = deref_ptr!(wallet, error); + let derivation_path = deref_ptr!(derivation_path, error); + let path_str = unwrap_or_return!(CStr::from_ptr(derivation_path).to_str(), error); + let path = unwrap_or_return!(DerivationPath::from_str(path_str), error); + let public_key = unwrap_or_return!(wallet.inner().derive_public_key(&path), error); + Box::into_raw(Box::new(FFIPublicKey { + inner: public_key, + })) } /// Derive extended public key at a specific path @@ -601,59 +331,18 @@ pub unsafe extern "C" fn wallet_derive_extended_public_key( derivation_path: *const c_char, error: *mut FFIError, ) -> *mut FFIExtendedPubKey { - if wallet.is_null() || derivation_path.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let path_str = match unsafe { CStr::from_ptr(derivation_path) }.to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in derivation path".to_string(), - ); - return ptr::null_mut(); - } - }; - - // Parse the derivation path use key_wallet::DerivationPath; use std::str::FromStr; - let path = match DerivationPath::from_str(path_str) { - Ok(p) => p, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - format!("Invalid derivation path: {}", e), - ); - return ptr::null_mut(); - } - }; - unsafe { - let wallet = &*wallet; - - // Use the new wallet method to derive the extended public key - match wallet.inner().derive_extended_public_key(&path) { - Ok(extended_public_key) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIExtendedPubKey { - inner: extended_public_key, - })) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive extended public key: {:?}", e), - ); - ptr::null_mut() - } - } - } + let wallet = deref_ptr!(wallet, error); + let derivation_path = deref_ptr!(derivation_path, error); + let path_str = unwrap_or_return!(CStr::from_ptr(derivation_path).to_str(), error); + let path = unwrap_or_return!(DerivationPath::from_str(path_str), error); + let extended_public_key = + unwrap_or_return!(wallet.inner().derive_extended_public_key(&path), error); + Box::into_raw(Box::new(FFIExtendedPubKey { + inner: extended_public_key, + })) } /// Derive public key at a specific path and return as hex string @@ -670,67 +359,15 @@ pub unsafe extern "C" fn wallet_derive_public_key_as_hex( derivation_path: *const c_char, error: *mut FFIError, ) -> *mut c_char { - if wallet.is_null() || derivation_path.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let path_str = match unsafe { CStr::from_ptr(derivation_path) }.to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in derivation path".to_string(), - ); - return ptr::null_mut(); - } - }; - - // Parse the derivation path use key_wallet::DerivationPath; use std::str::FromStr; - let path = match DerivationPath::from_str(path_str) { - Ok(p) => p, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - format!("Invalid derivation path: {}", e), - ); - return ptr::null_mut(); - } - }; - unsafe { - let wallet = &*wallet; - - // Use the new wallet method to derive the public key as hex - match wallet.inner().derive_public_key_as_hex(&path) { - Ok(hex) => { - FFIError::set_success(error); - match CString::new(hex) { - Ok(c_str) => c_str.into_raw(), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive public key: {:?}", e), - ); - ptr::null_mut() - } - } - } + let wallet = deref_ptr!(wallet, error); + let derivation_path = deref_ptr!(derivation_path, error); + let path_str = unwrap_or_return!(CStr::from_ptr(derivation_path).to_str(), error); + let path = unwrap_or_return!(DerivationPath::from_str(path_str), error); + let hex = unwrap_or_return!(wallet.inner().derive_public_key_as_hex(&path), error); + unwrap_or_return!(CString::new(hex), error).into_raw() } /// Free a public key @@ -776,36 +413,12 @@ pub unsafe extern "C" fn extended_public_key_free(key: *mut FFIExtendedPubKey) { #[no_mangle] pub unsafe extern "C" fn extended_public_key_to_string( key: *const FFIExtendedPubKey, - network: FFINetwork, + _network: FFINetwork, error: *mut FFIError, ) -> *mut c_char { - if key.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Extended public key is null".to_string(), - ); - return ptr::null_mut(); - } - - let key = unsafe { &*key }; - let _ = network; // Network is already encoded in the extended key - - // Convert to string - the network is already encoded in the extended key - let key_string = key.inner.to_string(); - - FFIError::set_success(error); - match CString::new(key_string) { - Ok(c_str) => c_str.into_raw(), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } + // Network is already encoded in the extended key. + let key = deref_ptr!(key, error); + unwrap_or_return!(CString::new(key.inner.to_string()), error).into_raw() } /// Get the public key from an extended public key @@ -822,24 +435,10 @@ pub unsafe extern "C" fn extended_public_key_get_public_key( extended_key: *const FFIExtendedPubKey, error: *mut FFIError, ) -> *mut FFIPublicKey { - if extended_key.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Extended public key is null".to_string(), - ); - return ptr::null_mut(); - } - - let extended = unsafe { &*extended_key }; - - // Extract the public key - let public_key = FFIPublicKey { + let extended = deref_ptr!(extended_key, error); + Box::into_raw(Box::new(FFIPublicKey { inner: extended.inner.public_key, - }; - - FFIError::set_success(error); - Box::into_raw(Box::new(public_key)) + })) } /// Get public key as hex string from FFIPublicKey @@ -854,27 +453,8 @@ pub unsafe extern "C" fn public_key_to_hex( key: *const FFIPublicKey, error: *mut FFIError, ) -> *mut c_char { - if key.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Public key is null".to_string()); - return ptr::null_mut(); - } - - let key = unsafe { &*key }; - let bytes = key.inner.serialize(); - let hex = hex::encode(bytes); - - FFIError::set_success(error); - match CString::new(hex) { - Ok(c_str) => c_str.into_raw(), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } + let key = deref_ptr!(key, error); + unwrap_or_return!(CString::new(hex::encode(key.inner.serialize())), error).into_raw() } /// Convert derivation path string to indices @@ -895,39 +475,15 @@ pub unsafe extern "C" fn derivation_path_parse( count_out: *mut usize, error: *mut FFIError, ) -> bool { - if path.is_null() || indices_out.is_null() || hardened_out.is_null() || count_out.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } - - let path_str = unsafe { - match CStr::from_ptr(path).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in path".to_string(), - ); - return false; - } - } - }; - use key_wallet::DerivationPath; use std::str::FromStr; - let derivation_path = match DerivationPath::from_str(path_str) { - Ok(p) => p, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidDerivationPath, - format!("Invalid derivation path: {}", e), - ); - return false; - } - }; + let path = deref_ptr!(path, error); + check_ptr!(indices_out, error); + check_ptr!(hardened_out, error); + check_ptr!(count_out, error); + let path_str = unwrap_or_return!(CStr::from_ptr(path).to_str(), error); + let derivation_path = unwrap_or_return!(DerivationPath::from_str(path_str), error); let children: Vec<_> = derivation_path.into_iter().collect(); let count = children.len(); @@ -944,11 +500,9 @@ pub unsafe extern "C" fn derivation_path_parse( index, } => (*index, true), _ => { - // Fail fast for unsupported ChildNumber variants - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InvalidDerivationPath, - "Unsupported ChildNumber variant encountered".to_string(), + "Unsupported ChildNumber variant encountered", ); return false; } @@ -963,13 +517,10 @@ pub unsafe extern "C" fn derivation_path_parse( *indices_out = Box::into_raw(indices.into_boxed_slice()) as *mut u32; *hardened_out = Box::into_raw(hardened.into_boxed_slice()) as *mut bool; } else { - // For empty paths, set to null *indices_out = ptr::null_mut(); *hardened_out = ptr::null_mut(); } } - - FFIError::set_success(error); true } diff --git a/key-wallet-ffi/src/keys_tests.rs b/key-wallet-ffi/src/keys_tests.rs index 484351460..7e52054eb 100644 --- a/key-wallet-ffi/src/keys_tests.rs +++ b/key-wallet-ffi/src/keys_tests.rs @@ -13,97 +13,99 @@ mod tests { #[test] fn test_extended_key_string_conversion() { - unsafe { - let mut error = FFIError::success(); + let mut error = FFIError::default(); - // Create a wallet to get extended keys from - let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); - let passphrase = CString::new("").unwrap(); - let wallet = wallet::wallet_create_from_mnemonic( + // Create a wallet to get extended keys from + let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); + let passphrase = CString::new("").unwrap(); + let wallet = unsafe { + wallet::wallet_create_from_mnemonic( mnemonic.as_ptr(), passphrase.as_ptr(), FFINetwork::Testnet, &mut error, - ); - assert!(!wallet.is_null()); - assert_eq!(error.code, FFIErrorCode::Success); - - // Derive an extended private key - let path = CString::new("m/44'/1'/0'").unwrap(); - let ext_priv = wallet_derive_extended_private_key(wallet, path.as_ptr(), &mut error); - assert!(!ext_priv.is_null()); - assert_eq!(error.code, FFIErrorCode::Success); - - // Test extended_private_key_to_string - let xprv_str = - extended_private_key_to_string(ext_priv, FFINetwork::Testnet, &mut error); - assert!(!xprv_str.is_null()); - assert_eq!(error.code, FFIErrorCode::Success); - - let xprv = CStr::from_ptr(xprv_str).to_str().unwrap(); - assert!(xprv.starts_with("tprv")); // Testnet extended private key - crate::utils::string_free(xprv_str); - - // Test extended_private_key_get_private_key - let priv_key = extended_private_key_get_private_key(ext_priv, &mut error); - assert!(!priv_key.is_null()); - assert_eq!(error.code, FFIErrorCode::Success); - - // Get WIF from the extracted private key - let wif = private_key_to_wif(priv_key, FFINetwork::Testnet, &mut error); - assert!(!wif.is_null()); - assert_eq!(error.code, FFIErrorCode::Success); - - let wif_str = CStr::from_ptr(wif).to_str().unwrap(); - // Assert testnet WIF prefix (compressed or uncompressed) - assert!(wif_str.starts_with('c') || wif_str.starts_with('9')); - crate::utils::string_free(wif); - - // Clean up - private_key_free(priv_key); - extended_private_key_free(ext_priv); - - // Now test extended public key - let ext_pub = wallet_derive_extended_public_key(wallet, path.as_ptr(), &mut error); - assert!(!ext_pub.is_null()); - assert_eq!(error.code, FFIErrorCode::Success); - - // Test extended_public_key_to_string - let xpub_str = extended_public_key_to_string(ext_pub, FFINetwork::Testnet, &mut error); - assert!(!xpub_str.is_null()); - assert_eq!(error.code, FFIErrorCode::Success); - - let xpub = CStr::from_ptr(xpub_str).to_str().unwrap(); - assert!(xpub.starts_with("tpub")); // Testnet extended public key - crate::utils::string_free(xpub_str); + ) + }; + assert!(!wallet.is_null()); + assert_eq!(error.code, FFIErrorCode::Success); - // Test extended_public_key_get_public_key - let pub_key = extended_public_key_get_public_key(ext_pub, &mut error); - assert!(!pub_key.is_null()); - assert_eq!(error.code, FFIErrorCode::Success); + // Derive an extended private key + let path = CString::new("m/44'/1'/0'").unwrap(); + let ext_priv = + unsafe { wallet_derive_extended_private_key(wallet, path.as_ptr(), &mut error) }; + assert!(!ext_priv.is_null()); + assert_eq!(error.code, FFIErrorCode::Success); - // Get hex from the extracted public key - let hex = public_key_to_hex(pub_key, &mut error); - assert!(!hex.is_null()); - assert_eq!(error.code, FFIErrorCode::Success); + // Test extended_private_key_to_string + let xprv_str = + unsafe { extended_private_key_to_string(ext_priv, FFINetwork::Testnet, &mut error) }; + assert!(!xprv_str.is_null()); + assert_eq!(error.code, FFIErrorCode::Success); - let hex_str = CStr::from_ptr(hex).to_str().unwrap(); - assert_eq!(hex_str.len(), 66); // 33 bytes = 66 hex chars - crate::utils::string_free(hex); + let xprv = unsafe { CStr::from_ptr(xprv_str) }.to_str().unwrap(); + assert!(xprv.starts_with("tprv")); // Testnet extended private key + unsafe { crate::utils::string_free(xprv_str) }; - // Clean up - public_key_free(pub_key); - extended_public_key_free(ext_pub); - wallet::wallet_free(wallet); - error.free_message(); - } + // Test extended_private_key_get_private_key + let priv_key = unsafe { extended_private_key_get_private_key(ext_priv, &mut error) }; + assert!(!priv_key.is_null()); + assert_eq!(error.code, FFIErrorCode::Success); + + // Get WIF from the extracted private key + let wif = unsafe { private_key_to_wif(priv_key, FFINetwork::Testnet, &mut error) }; + assert!(!wif.is_null()); + assert_eq!(error.code, FFIErrorCode::Success); + + let wif_str = unsafe { CStr::from_ptr(wif) }.to_str().unwrap(); + // Assert testnet WIF prefix (compressed or uncompressed) + assert!(wif_str.starts_with('c') || wif_str.starts_with('9')); + unsafe { crate::utils::string_free(wif) }; + + // Clean up + unsafe { private_key_free(priv_key) }; + unsafe { extended_private_key_free(ext_priv) }; + + // Now test extended public key + let ext_pub = + unsafe { wallet_derive_extended_public_key(wallet, path.as_ptr(), &mut error) }; + assert!(!ext_pub.is_null()); + assert_eq!(error.code, FFIErrorCode::Success); + + // Test extended_public_key_to_string + let xpub_str = + unsafe { extended_public_key_to_string(ext_pub, FFINetwork::Testnet, &mut error) }; + assert!(!xpub_str.is_null()); + assert_eq!(error.code, FFIErrorCode::Success); + + let xpub = unsafe { CStr::from_ptr(xpub_str) }.to_str().unwrap(); + assert!(xpub.starts_with("tpub")); // Testnet extended public key + unsafe { crate::utils::string_free(xpub_str) }; + + // Test extended_public_key_get_public_key + let pub_key = unsafe { extended_public_key_get_public_key(ext_pub, &mut error) }; + assert!(!pub_key.is_null()); + assert_eq!(error.code, FFIErrorCode::Success); + + // Get hex from the extracted public key + let hex = unsafe { public_key_to_hex(pub_key, &mut error) }; + assert!(!hex.is_null()); + assert_eq!(error.code, FFIErrorCode::Success); + + let hex_str = unsafe { CStr::from_ptr(hex) }.to_str().unwrap(); + assert_eq!(hex_str.len(), 66); // 33 bytes = 66 hex chars + unsafe { crate::utils::string_free(hex) }; + + // Clean up + unsafe { public_key_free(pub_key) }; + unsafe { extended_public_key_free(ext_pub) }; + unsafe { wallet::wallet_free(wallet) }; } // Note: wallet_get_account_xpriv is not implemented for security reasons // The function always returns null to prevent private key extraction #[test] fn test_wallet_get_account_xpriv_not_implemented() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create a wallet let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); @@ -124,18 +126,17 @@ mod tests { // Should return null (not implemented for security) assert!(xpriv_str.is_null()); - assert_eq!(error.code, FFIErrorCode::WalletError); + assert_eq!(error.code, FFIErrorCode::InternalError); // Clean up unsafe { wallet::wallet_free(wallet); - error.free_message(); } } #[test] fn test_wallet_get_account_xpub() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create a wallet let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); @@ -163,14 +164,13 @@ mod tests { unsafe { crate::utils::string_free(xpub_str); wallet::wallet_free(wallet); - error.free_message(); } } // wallet_derive_private_key is now implemented #[test] fn test_wallet_derive_private_key_now_implemented() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create a wallet let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); @@ -211,13 +211,12 @@ mod tests { unsafe { private_key_free(privkey_ptr); wallet::wallet_free(wallet); - error.free_message(); } } #[test] fn test_wallet_derive_public_key() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create a wallet let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); @@ -267,48 +266,46 @@ mod tests { // Clean up unsafe { wallet::wallet_free(wallet); - error.free_message(); } } #[test] fn test_wallet_derive_public_key_as_hex() { - unsafe { - let mut error = FFIError::success(); + let mut error = FFIError::default(); - // Create a wallet - let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); - let passphrase = CString::new("").unwrap(); - let wallet = wallet::wallet_create_from_mnemonic( + // Create a wallet + let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); + let passphrase = CString::new("").unwrap(); + let wallet = unsafe { + wallet::wallet_create_from_mnemonic( mnemonic.as_ptr(), passphrase.as_ptr(), FFINetwork::Testnet, &mut error, - ); - assert!(!wallet.is_null()); - assert_eq!(error.code, FFIErrorCode::Success); - - // Derive public key as hex directly - let path = CString::new("m/44'/1'/0'/0/0").unwrap(); - let hex_str = wallet_derive_public_key_as_hex(wallet, path.as_ptr(), &mut error); - assert!(!hex_str.is_null()); - assert_eq!(error.code, FFIErrorCode::Success); - - let hex = CStr::from_ptr(hex_str).to_str().unwrap(); - // Public key should start with 02 or 03 (compressed) - assert!(hex.starts_with("02") || hex.starts_with("03")); - assert_eq!(hex.len(), 66); // 33 bytes * 2 hex chars - - // Clean up - crate::utils::string_free(hex_str); - wallet::wallet_free(wallet); - error.free_message(); - } + ) + }; + assert!(!wallet.is_null()); + assert_eq!(error.code, FFIErrorCode::Success); + + // Derive public key as hex directly + let path = CString::new("m/44'/1'/0'/0/0").unwrap(); + let hex_str = unsafe { wallet_derive_public_key_as_hex(wallet, path.as_ptr(), &mut error) }; + assert!(!hex_str.is_null()); + assert_eq!(error.code, FFIErrorCode::Success); + + let hex = unsafe { CStr::from_ptr(hex_str) }.to_str().unwrap(); + // Public key should start with 02 or 03 (compressed) + assert!(hex.starts_with("02") || hex.starts_with("03")); + assert_eq!(hex.len(), 66); // 33 bytes * 2 hex chars + + // Clean up + unsafe { crate::utils::string_free(hex_str) }; + unsafe { wallet::wallet_free(wallet) }; } #[test] fn test_derivation_path_parse() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Parse a BIP44 path let path = CString::new("m/44'/1'/0'/0/5").unwrap(); @@ -350,13 +347,12 @@ mod tests { // Clean up unsafe { derivation_path_free(indices_out, hardened_out, count_out); - error.free_message(); } } #[test] fn test_derivation_path_parse_root() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Parse root path let path = CString::new("m").unwrap(); @@ -381,13 +377,12 @@ mod tests { // Clean up (should handle null pointers gracefully) unsafe { derivation_path_free(indices_out, hardened_out, count_out); - error.free_message(); } } #[test] fn test_error_handling() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test with null wallet let xpriv = unsafe { wallet_get_account_xpriv(ptr::null(), 0, &mut error) }; @@ -411,13 +406,11 @@ mod tests { }; assert!(!success); - - unsafe { error.free_message() }; } #[test] fn test_wallet_derive_public_key_null_inputs() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test with null wallet (44'/1'/0'/0/0 for Dash) let path = CString::new("m/44'/1'/0'/0/0").unwrap(); @@ -449,13 +442,12 @@ mod tests { // Clean up unsafe { wallet::wallet_free(wallet); - error.free_message(); } } #[test] fn test_derivation_path_parse_null_inputs() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test with null path let mut indices_out: *mut u32 = ptr::null_mut(); @@ -489,13 +481,11 @@ mod tests { assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_derivation_path_complex_cases() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test single hardened index let path = CString::new("m/44'").unwrap(); @@ -557,13 +547,12 @@ mod tests { // Clean up unsafe { derivation_path_free(indices_out, hardened_out, count_out); - error.free_message(); } } #[test] fn test_wallet_get_account_xpub_edge_cases() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create a wallet let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); @@ -603,13 +592,12 @@ mod tests { // Clean up unsafe { wallet::wallet_free(wallet); - error.free_message(); } } #[test] fn test_wallet_derive_public_key_different_paths() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create a wallet let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); @@ -662,7 +650,6 @@ mod tests { // Clean up unsafe { wallet::wallet_free(wallet); - error.free_message(); } } diff --git a/key-wallet-ffi/src/managed_account.rs b/key-wallet-ffi/src/managed_account.rs index ab5f8de19..3f415197b 100644 --- a/key-wallet-ffi/src/managed_account.rs +++ b/key-wallet-ffi/src/managed_account.rs @@ -11,6 +11,7 @@ use dashcore::ffi::FFINetwork; use dashcore::hashes::Hash; use crate::address_pool::{FFIAddressPool, FFIAddressPoolType}; +use crate::check_ptr; use crate::error::{FFIError, FFIErrorCode}; use crate::types::{ FFIAccountType, FFIInputDetail, FFIOutputDetail, FFIOutputRole, FFITransactionContext, @@ -200,7 +201,7 @@ pub unsafe extern "C" fn managed_wallet_get_account( } // Get the managed wallet info from the manager - let mut error = FFIError::success(); + let mut error = FFIError::default(); let managed_wallet_ptr = crate::wallet_manager::wallet_manager_get_managed_wallet_info( manager, wallet_id, &mut error, ); @@ -318,7 +319,7 @@ pub unsafe extern "C" fn managed_wallet_get_top_up_account_with_registration_ind } // Get the managed wallet info from the manager - let mut error = FFIError::success(); + let mut error = FFIError::default(); let managed_wallet_ptr = crate::wallet_manager::wallet_manager_get_managed_wallet_info( manager, wallet_id, &mut error, ); @@ -390,7 +391,7 @@ pub unsafe extern "C" fn managed_wallet_get_dashpay_receiving_account( friend_identity_id: friend_id, }; - let mut error = FFIError::success(); + let mut error = FFIError::default(); let managed_wallet_ptr = crate::wallet_manager::wallet_manager_get_managed_wallet_info( manager, wallet_id, &mut error, ); @@ -451,7 +452,7 @@ pub unsafe extern "C" fn managed_wallet_get_dashpay_external_account( friend_identity_id: friend_id, }; - let mut error = FFIError::success(); + let mut error = FFIError::default(); let managed_wallet_ptr = crate::wallet_manager::wallet_manager_get_managed_wallet_info( manager, wallet_id, &mut error, ); @@ -920,7 +921,7 @@ pub unsafe extern "C" fn managed_core_account_result_free_error( /// /// - `manager` must be a valid pointer to an FFIWalletManager instance /// - `wallet_id` must be a valid pointer to a 32-byte wallet ID -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn managed_wallet_get_account_count( @@ -928,22 +929,16 @@ pub unsafe extern "C" fn managed_wallet_get_account_count( wallet_id: *const u8, error: *mut FFIError, ) -> c_uint { - if manager.is_null() || wallet_id.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return 0; - } + check_ptr!(manager, error); + check_ptr!(wallet_id, error); - // Get the wallet from the manager let wallet_ptr = crate::wallet_manager::wallet_manager_get_wallet(manager, wallet_id, error); - if wallet_ptr.is_null() { // Error already set by wallet_manager_get_wallet return 0; } let wallet = &*wallet_ptr; - - FFIError::set_success(error); let accounts = &wallet.inner().accounts; let count = accounts.standard_bip44_accounts.len() + accounts.standard_bip32_accounts.len() @@ -1213,7 +1208,7 @@ pub unsafe extern "C" fn managed_wallet_get_platform_payment_account( } // Get the managed wallet info from the manager - let mut error = FFIError::success(); + let mut error = FFIError::default(); let managed_wallet_ptr = crate::wallet_manager::wallet_manager_get_managed_wallet_info( manager, wallet_id, &mut error, ); @@ -1479,7 +1474,7 @@ mod tests { #[test] fn test_managed_account_basic() { unsafe { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create wallet manager let manager = wallet_manager_create(FFINetwork::Testnet, &mut error); @@ -1541,7 +1536,7 @@ mod tests { #[test] fn test_managed_account_not_found() { unsafe { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create wallet manager let manager = wallet_manager_create(FFINetwork::Testnet, &mut error); @@ -1607,7 +1602,7 @@ mod tests { #[test] fn test_managed_wallet_get_account_count() { unsafe { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create wallet manager let manager = wallet_manager_create(FFINetwork::Testnet, &mut error); @@ -1668,7 +1663,7 @@ mod tests { #[test] fn test_managed_account_getters() { unsafe { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create wallet manager let manager = wallet_manager_create(FFINetwork::Testnet, &mut error); @@ -1792,7 +1787,7 @@ mod tests { assert_eq!(index, 0); // Test null balance_out - let mut error = FFIError::success(); + let mut error = FFIError::default(); let manager = wallet_manager_create(FFINetwork::Testnet, &mut error); assert!(!manager.is_null()); @@ -1844,7 +1839,7 @@ mod tests { #[test] fn test_managed_account_address_pools() { unsafe { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create wallet manager let mut manager = wallet_manager_create(FFINetwork::Testnet, &mut error); diff --git a/key-wallet-ffi/src/managed_account_collection.rs b/key-wallet-ffi/src/managed_account_collection.rs index 1d4107c82..19d6c6760 100644 --- a/key-wallet-ffi/src/managed_account_collection.rs +++ b/key-wallet-ffi/src/managed_account_collection.rs @@ -9,7 +9,8 @@ use std::ffi::CString; use std::os::raw::{c_char, c_uint}; use std::ptr; -use crate::error::{FFIError, FFIErrorCode}; +use crate::check_ptr; +use crate::error::FFIError; use crate::managed_account::FFIManagedCoreAccount; use crate::wallet_manager::FFIWalletManager; @@ -88,7 +89,7 @@ pub struct FFIManagedCoreAccountCollectionSummary { /// /// - `manager` must be a valid pointer to an FFIWalletManager instance /// - `wallet_id` must be a valid pointer to a 32-byte wallet ID -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The returned pointer must be freed with `managed_account_collection_free` when no longer needed #[no_mangle] pub unsafe extern "C" fn managed_wallet_get_account_collection( @@ -96,10 +97,8 @@ pub unsafe extern "C" fn managed_wallet_get_account_collection( wallet_id: *const u8, error: *mut FFIError, ) -> *mut FFIManagedCoreAccountCollection { - if manager.is_null() || wallet_id.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } + check_ptr!(manager, error); + check_ptr!(wallet_id, error); // Get the managed wallet info from the manager let managed_wallet_ptr = diff --git a/key-wallet-ffi/src/managed_wallet.rs b/key-wallet-ffi/src/managed_wallet.rs index 5b9e51586..05e2c1062 100644 --- a/key-wallet-ffi/src/managed_wallet.rs +++ b/key-wallet-ffi/src/managed_wallet.rs @@ -10,6 +10,7 @@ use std::ptr; use crate::error::{FFIError, FFIErrorCode}; use crate::types::FFIWallet; +use crate::{check_ptr, deref_ptr, deref_ptr_mut}; use key_wallet::managed_account::address_pool::KeySource; use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; @@ -57,32 +58,17 @@ pub unsafe extern "C" fn managed_wallet_get_next_bip44_receive_address( account_index: std::os::raw::c_uint, error: *mut FFIError, ) -> *mut c_char { - if managed_wallet.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Managed wallet is null".to_string(), - ); - return ptr::null_mut(); - } - - if wallet.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Wallet is null".to_string()); - return ptr::null_mut(); - } - - let managed_wallet = unsafe { &mut *managed_wallet }; - let wallet = unsafe { &*wallet }; + let managed_wallet = deref_ptr_mut!(managed_wallet, error); + let wallet = deref_ptr!(wallet, error); // Get the specific managed account (default to BIP44) let managed_account = match managed_wallet.inner_mut().accounts.standard_bip44_accounts.get_mut(&account_index) { Some(account) => account, None => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - format!("Account {} not found", account_index), + &format!("Account {} not found", account_index), ); return ptr::null_mut(); } @@ -92,10 +78,9 @@ pub unsafe extern "C" fn managed_wallet_get_next_bip44_receive_address( let account = match wallet.wallet.accounts.standard_bip44_accounts.get(&account_index) { Some(account) => account, None => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - format!("Account {} not found in wallet", account_index), + &format!("Account {} not found in wallet", account_index), ); return ptr::null_mut(); } @@ -108,24 +93,20 @@ pub unsafe extern "C" fn managed_wallet_get_next_bip44_receive_address( let address_str = address.to_string(); match CString::new(address_str) { Ok(c_str) => { - FFIError::set_success(error); + (*error).clean(); c_str.into_raw() } Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "Failed to convert address to C string".to_string(), - ); + (*error) + .set(FFIErrorCode::WalletError, "Failed to convert address to C string"); ptr::null_mut() } } } Err(e) => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - format!("Failed to generate receive address: {}", e), + &format!("Failed to generate receive address: {}", e), ); ptr::null_mut() } @@ -150,32 +131,17 @@ pub unsafe extern "C" fn managed_wallet_get_next_bip44_change_address( account_index: std::os::raw::c_uint, error: *mut FFIError, ) -> *mut c_char { - if managed_wallet.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Managed wallet is null".to_string(), - ); - return ptr::null_mut(); - } - - if wallet.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Wallet is null".to_string()); - return ptr::null_mut(); - } - - let managed_wallet = unsafe { &mut *managed_wallet }; - let wallet = unsafe { &*wallet }; + let managed_wallet = deref_ptr_mut!(managed_wallet, error); + let wallet = deref_ptr!(wallet, error); // Get the specific managed account (default to BIP44) let managed_account = match managed_wallet.inner_mut().accounts.standard_bip44_accounts.get_mut(&account_index) { Some(account) => account, None => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - format!("Account {} not found", account_index), + &format!("Account {} not found", account_index), ); return ptr::null_mut(); } @@ -185,10 +151,9 @@ pub unsafe extern "C" fn managed_wallet_get_next_bip44_change_address( let account = match wallet.wallet.accounts.standard_bip44_accounts.get(&account_index) { Some(account) => account, None => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - format!("Account {} not found in wallet", account_index), + &format!("Account {} not found in wallet", account_index), ); return ptr::null_mut(); } @@ -201,24 +166,20 @@ pub unsafe extern "C" fn managed_wallet_get_next_bip44_change_address( let address_str = address.to_string(); match CString::new(address_str) { Ok(c_str) => { - FFIError::set_success(error); + (*error).clean(); c_str.into_raw() } Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "Failed to convert address to C string".to_string(), - ); + (*error) + .set(FFIErrorCode::WalletError, "Failed to convert address to C string"); ptr::null_mut() } } } Err(e) => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - format!("Failed to generate change address: {}", e), + &format!("Failed to generate change address: {}", e), ); ptr::null_mut() } @@ -249,45 +210,19 @@ pub unsafe extern "C" fn managed_wallet_get_bip_44_external_address_range( count_out: *mut usize, error: *mut FFIError, ) -> bool { - if addresses_out.is_null() || count_out.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Null output pointer provided".to_string(), - ); - return false; - } - - if managed_wallet.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Managed wallet is null".to_string(), - ); - *count_out = 0; - *addresses_out = ptr::null_mut(); - return false; - } - - if wallet.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Wallet is null".to_string()); - *count_out = 0; - *addresses_out = ptr::null_mut(); - return false; - } - - let managed_wallet = unsafe { &mut *managed_wallet }; - let wallet = unsafe { &*wallet }; + check_ptr!(addresses_out, error); + check_ptr!(count_out, error); + let managed_wallet = deref_ptr_mut!(managed_wallet, error); + let wallet = deref_ptr!(wallet, error); // Get the specific managed account (BIP44) let managed_account = match managed_wallet.inner_mut().accounts.standard_bip44_accounts.get_mut(&account_index) { Some(account) => account, None => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - format!("BIP44 account {} not found", account_index), + &format!("BIP44 account {} not found", account_index), ); *count_out = 0; *addresses_out = ptr::null_mut(); @@ -299,10 +234,9 @@ pub unsafe extern "C" fn managed_wallet_get_bip_44_external_address_range( let account = match wallet.wallet.accounts.standard_bip44_accounts.get(&account_index) { Some(account) => account, None => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - format!("Account {} not found in wallet", account_index), + &format!("Account {} not found in wallet", account_index), ); *count_out = 0; *addresses_out = ptr::null_mut(); @@ -323,22 +257,15 @@ pub unsafe extern "C" fn managed_wallet_get_bip_44_external_address_range( match external_addresses.address_range(start_index, end_index, &key_source) { Ok(addrs) => addrs, Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to get address range: {}", e), - ); + (*error) + .set(FFIErrorCode::WalletError, &format!("Failed to get address range: {}", e)); *count_out = 0; *addresses_out = ptr::null_mut(); return false; } } } else { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "Account is not a standard BIP44 account".to_string(), - ); + (*error).set(FFIErrorCode::WalletError, "Account is not a standard BIP44 account"); *count_out = 0; *addresses_out = ptr::null_mut(); return false; @@ -354,11 +281,7 @@ pub unsafe extern "C" fn managed_wallet_get_bip_44_external_address_range( for ptr in c_addresses { let _ = CString::from_raw(ptr); } - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "Failed to convert address to C string".to_string(), - ); + (*error).set(FFIErrorCode::WalletError, "Failed to convert address to C string"); *count_out = 0; *addresses_out = ptr::null_mut(); return false; @@ -373,7 +296,7 @@ pub unsafe extern "C" fn managed_wallet_get_bip_44_external_address_range( *count_out = len; *addresses_out = ptr; - FFIError::set_success(error); + (*error).clean(); true } @@ -401,45 +324,19 @@ pub unsafe extern "C" fn managed_wallet_get_bip_44_internal_address_range( count_out: *mut usize, error: *mut FFIError, ) -> bool { - if addresses_out.is_null() || count_out.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Null output pointer provided".to_string(), - ); - return false; - } - - if managed_wallet.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Managed wallet is null".to_string(), - ); - *count_out = 0; - *addresses_out = ptr::null_mut(); - return false; - } - - if wallet.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Wallet is null".to_string()); - *count_out = 0; - *addresses_out = ptr::null_mut(); - return false; - } - - let managed_wallet = unsafe { &mut *managed_wallet }; - let wallet = unsafe { &*wallet }; + check_ptr!(addresses_out, error); + check_ptr!(count_out, error); + let managed_wallet = deref_ptr_mut!(managed_wallet, error); + let wallet = deref_ptr!(wallet, error); // Get the specific managed account (BIP44) let managed_account = match managed_wallet.inner_mut().accounts.standard_bip44_accounts.get_mut(&account_index) { Some(account) => account, None => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - format!("BIP44 account {} not found", account_index), + &format!("BIP44 account {} not found", account_index), ); *count_out = 0; *addresses_out = ptr::null_mut(); @@ -451,10 +348,9 @@ pub unsafe extern "C" fn managed_wallet_get_bip_44_internal_address_range( let account = match wallet.wallet.accounts.standard_bip44_accounts.get(&account_index) { Some(account) => account, None => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - format!("Account {} not found in wallet", account_index), + &format!("Account {} not found in wallet", account_index), ); *count_out = 0; *addresses_out = ptr::null_mut(); @@ -475,22 +371,15 @@ pub unsafe extern "C" fn managed_wallet_get_bip_44_internal_address_range( match internal_addresses.address_range(start_index, end_index, &key_source) { Ok(addrs) => addrs, Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to get address range: {}", e), - ); + (*error) + .set(FFIErrorCode::WalletError, &format!("Failed to get address range: {}", e)); *count_out = 0; *addresses_out = ptr::null_mut(); return false; } } } else { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "Account is not a standard BIP44 account".to_string(), - ); + (*error).set(FFIErrorCode::WalletError, "Account is not a standard BIP44 account"); *count_out = 0; *addresses_out = ptr::null_mut(); return false; @@ -506,11 +395,7 @@ pub unsafe extern "C" fn managed_wallet_get_bip_44_internal_address_range( for ptr in c_addresses { let _ = CString::from_raw(ptr); } - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "Failed to convert address to C string".to_string(), - ); + (*error).set(FFIErrorCode::WalletError, "Failed to convert address to C string"); *count_out = 0; *addresses_out = ptr::null_mut(); return false; @@ -525,7 +410,7 @@ pub unsafe extern "C" fn managed_wallet_get_bip_44_internal_address_range( *count_out = len; *addresses_out = ptr; - FFIError::set_success(error); + (*error).clean(); true } @@ -552,41 +437,19 @@ pub unsafe extern "C" fn managed_wallet_get_balance( total_out: *mut u64, error: *mut FFIError, ) -> bool { - if managed_wallet.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Managed wallet is null".to_string(), - ); - return false; - } + let managed_wallet = deref_ptr!(managed_wallet, error); + check_ptr!(confirmed_out, error); + check_ptr!(unconfirmed_out, error); + check_ptr!(immature_out, error); + check_ptr!(locked_out, error); + check_ptr!(total_out, error); - if confirmed_out.is_null() - || unconfirmed_out.is_null() - || immature_out.is_null() - || locked_out.is_null() - || total_out.is_null() - { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Output pointer is null".to_string(), - ); - return false; - } - - let managed_wallet = unsafe { &*managed_wallet }; let balance = &managed_wallet.inner().balance; - - unsafe { - *confirmed_out = balance.confirmed(); - *unconfirmed_out = balance.unconfirmed(); - *immature_out = balance.immature(); - *locked_out = balance.locked(); - *total_out = balance.total(); - } - - FFIError::set_success(error); + *confirmed_out = balance.confirmed(); + *unconfirmed_out = balance.unconfirmed(); + *immature_out = balance.immature(); + *locked_out = balance.locked(); + *total_out = balance.total(); true } @@ -595,23 +458,14 @@ pub unsafe extern "C" fn managed_wallet_get_balance( /// # Safety /// /// - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn managed_wallet_synced_height( managed_wallet: *const FFIManagedWalletInfo, error: *mut FFIError, ) -> c_uint { - if managed_wallet.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Managed wallet is null".to_string(), - ); - return 0; - } - let managed_wallet = unsafe { &*managed_wallet }; - FFIError::set_success(error); + let managed_wallet = deref_ptr!(managed_wallet, error); managed_wallet.inner().synced_height() } @@ -673,7 +527,7 @@ mod tests { #[test] fn test_managed_wallet_get_next_receive_address_null_pointers() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test with null managed wallet let address = unsafe { @@ -687,13 +541,11 @@ mod tests { assert!(address.is_null()); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_managed_wallet_get_next_change_address_null_pointers() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test with null managed wallet let address = unsafe { @@ -707,13 +559,11 @@ mod tests { assert!(address.is_null()); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_managed_wallet_get_bip_44_external_address_range_null_pointers() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let mut addresses_out: *mut *mut c_char = ptr::null_mut(); let mut count_out: usize = 0; @@ -735,13 +585,11 @@ mod tests { assert_eq!(count_out, 0); assert!(addresses_out.is_null()); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_managed_wallet_get_bip_44_internal_address_range_null_pointers() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let mut addresses_out: *mut *mut c_char = ptr::null_mut(); let mut count_out: usize = 0; @@ -763,13 +611,11 @@ mod tests { assert_eq!(count_out, 0); assert!(addresses_out.is_null()); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_managed_wallet_address_generation_with_valid_wallet() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create a wallet let mnemonic = CString::new(TEST_MNEMONIC).unwrap(); @@ -835,7 +681,6 @@ mod tests { unsafe { managed_wallet_free(ffi_managed); wallet::wallet_free(wallet); - error.free_message(); } } @@ -847,7 +692,7 @@ mod tests { use key_wallet::bip32::DerivationPath; use key_wallet::managed_account::address_pool::{AddressPool, AddressPoolType}; - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create a wallet with a known mnemonic let mnemonic = CString::new(TEST_MNEMONIC).unwrap(); @@ -1022,7 +867,6 @@ mod tests { unsafe { managed_wallet_free(ffi_managed); wallet::wallet_free(wallet_ptr); - error.free_message(); } } @@ -1030,7 +874,7 @@ mod tests { fn test_managed_wallet_get_balance() { use key_wallet::wallet::balance::WalletCoreBalance; - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create a wallet let mnemonic = CString::new(TEST_MNEMONIC).unwrap(); @@ -1118,13 +962,12 @@ mod tests { unsafe { managed_wallet_free(ffi_managed_ptr); wallet::wallet_free(wallet_ptr); - error.free_message(); } } #[test] fn test_managed_wallet_get_address_range_null_outputs() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test with null addresses_out for external range let success = unsafe { @@ -1160,8 +1003,6 @@ mod tests { assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } } diff --git a/key-wallet-ffi/src/managed_wallet_tests.rs b/key-wallet-ffi/src/managed_wallet_tests.rs index 1d972554c..8cbfdcd36 100644 --- a/key-wallet-ffi/src/managed_wallet_tests.rs +++ b/key-wallet-ffi/src/managed_wallet_tests.rs @@ -69,7 +69,6 @@ mod tests { managed_wallet: *mut FFIManagedWalletInfo, wallet_ids: *mut u8, id_count: usize, - error: &mut FFIError, ) { if !managed_wallet.is_null() { managed_wallet_info_free(managed_wallet); @@ -83,12 +82,11 @@ mod tests { if !manager.is_null() { wallet_manager_free(manager); } - error.free_message(); } #[test] fn test_managed_wallet_info_from_manager_success() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let (manager, wallet, managed_wallet, wallet_ids, id_count) = unsafe { setup_fixture(&mut error) }; @@ -96,13 +94,13 @@ mod tests { assert_eq!(error.code, FFIErrorCode::Success); unsafe { - cleanup_fixture(manager, wallet, managed_wallet, wallet_ids, id_count, &mut error); + cleanup_fixture(manager, wallet, managed_wallet, wallet_ids, id_count); } } #[test] fn test_managed_wallet_info_from_manager_null_manager() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let wallet_id = [0u8; 32]; let managed_wallet = unsafe { @@ -111,14 +109,12 @@ mod tests { assert!(managed_wallet.is_null()); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_managed_wallet_info_from_manager_unknown_wallet_id() { - let mut error = FFIError::success(); - let manager = wallet_manager_create(FFINetwork::Testnet, &mut error); + let mut error = FFIError::default(); + let manager = unsafe { wallet_manager_create(FFINetwork::Testnet, &mut error) }; assert!(!manager.is_null()); let bogus_wallet_id = [0u8; 32]; @@ -131,13 +127,12 @@ mod tests { unsafe { wallet_manager_free(manager); - error.free_message(); } } #[test] fn test_managed_wallet_mark_address_used_valid() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let (manager, wallet, managed_wallet, wallet_ids, id_count) = unsafe { setup_fixture(&mut error) }; @@ -158,13 +153,13 @@ mod tests { } unsafe { - cleanup_fixture(manager, wallet, managed_wallet, wallet_ids, id_count, &mut error); + cleanup_fixture(manager, wallet, managed_wallet, wallet_ids, id_count); } } #[test] fn test_managed_wallet_mark_address_used_invalid() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let (manager, wallet, managed_wallet, wallet_ids, id_count) = unsafe { setup_fixture(&mut error) }; @@ -177,26 +172,24 @@ mod tests { assert_eq!(error.code, FFIErrorCode::InvalidInput); unsafe { - cleanup_fixture(manager, wallet, managed_wallet, wallet_ids, id_count, &mut error); + cleanup_fixture(manager, wallet, managed_wallet, wallet_ids, id_count); } } #[test] fn test_managed_wallet_mark_address_used_null_inputs() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let success = unsafe { managed_wallet_mark_address_used(ptr::null_mut(), ptr::null(), &mut error) }; assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_managed_wallet_get_next_bip44_receive_address_null_inputs() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let address = unsafe { managed_wallet_get_next_bip44_receive_address( @@ -209,13 +202,11 @@ mod tests { assert!(address.is_null()); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_managed_wallet_get_next_bip44_change_address_null_inputs() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let address = unsafe { managed_wallet_get_next_bip44_change_address( @@ -228,8 +219,6 @@ mod tests { assert!(address.is_null()); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] @@ -243,7 +232,7 @@ mod tests { #[test] fn test_managed_wallet_info_free_valid() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let (manager, wallet, managed_wallet, wallet_ids, id_count) = unsafe { setup_fixture(&mut error) }; assert!(!managed_wallet.is_null()); @@ -253,13 +242,13 @@ mod tests { // Pass null to cleanup_fixture so it doesn't double-free managed_wallet. unsafe { - cleanup_fixture(manager, wallet, ptr::null_mut(), wallet_ids, id_count, &mut error); + cleanup_fixture(manager, wallet, ptr::null_mut(), wallet_ids, id_count); } } #[test] fn test_ffi_managed_wallet_info_methods() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let (manager, wallet, managed_wallet, wallet_ids, id_count) = unsafe { setup_fixture(&mut error) }; assert!(!managed_wallet.is_null()); @@ -274,13 +263,13 @@ mod tests { } unsafe { - cleanup_fixture(manager, wallet, managed_wallet, wallet_ids, id_count, &mut error); + cleanup_fixture(manager, wallet, managed_wallet, wallet_ids, id_count); } } #[test] fn test_managed_wallet_mark_address_used_utf8_error() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let (manager, wallet, managed_wallet, wallet_ids, id_count) = unsafe { setup_fixture(&mut error) }; @@ -298,13 +287,13 @@ mod tests { assert_eq!(error.code, FFIErrorCode::InvalidInput); unsafe { - cleanup_fixture(manager, wallet, managed_wallet, wallet_ids, id_count, &mut error); + cleanup_fixture(manager, wallet, managed_wallet, wallet_ids, id_count); } } #[test] fn test_managed_wallet_address_operations_with_real_wallet() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let (manager, wallet, managed_wallet, wallet_ids, id_count) = unsafe { setup_fixture(&mut error) }; assert!(!managed_wallet.is_null()); @@ -332,7 +321,7 @@ mod tests { } unsafe { - cleanup_fixture(manager, wallet, managed_wallet, wallet_ids, id_count, &mut error); + cleanup_fixture(manager, wallet, managed_wallet, wallet_ids, id_count); } } } diff --git a/key-wallet-ffi/src/mnemonic.rs b/key-wallet-ffi/src/mnemonic.rs index 6bf6ea8f1..55da2c1a2 100644 --- a/key-wallet-ffi/src/mnemonic.rs +++ b/key-wallet-ffi/src/mnemonic.rs @@ -11,6 +11,7 @@ use std::ptr; use key_wallet::Mnemonic; use crate::error::{FFIError, FFIErrorCode}; +use crate::{check_ptr, deref_ptr, unwrap_or_return}; /// Language enumeration for mnemonic generation /// @@ -69,8 +70,16 @@ impl From for FFILanguage { } /// Generate a new mnemonic with specified word count (12, 15, 18, 21, or 24) +/// +/// # Safety +/// +/// `error` must be a valid pointer to an `FFIError`. The returned string must be +/// freed with `mnemonic_free`. #[no_mangle] -pub extern "C" fn mnemonic_generate(word_count: c_uint, error: *mut FFIError) -> *mut c_char { +pub unsafe extern "C" fn mnemonic_generate( + word_count: c_uint, + error: *mut FFIError, +) -> *mut c_char { let entropy_bits = match word_count { 12 => 128, 15 => 160, @@ -78,10 +87,9 @@ pub extern "C" fn mnemonic_generate(word_count: c_uint, error: *mut FFIError) -> 21 => 224, 24 => 256, _ => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InvalidInput, - format!("Invalid word count: {}. Must be 12, 15, 18, 21, or 24", word_count), + &format!("Invalid word count: {}. Must be 12, 15, 18, 21, or 24", word_count), ); return ptr::null_mut(); } @@ -96,35 +104,18 @@ pub extern "C" fn mnemonic_generate(word_count: c_uint, error: *mut FFIError) -> 256 => 24, _ => 12, }; - match Mnemonic::generate(word_count, Language::English) { - Ok(mnemonic) => { - FFIError::set_success(error); - match CString::new(mnemonic.to_string()) { - Ok(c_str) => c_str.into_raw(), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidMnemonic, - format!("Failed to generate mnemonic: {}", e), - ); - ptr::null_mut() - } - } + let mnemonic = unwrap_or_return!(Mnemonic::generate(word_count, Language::English), error); + unwrap_or_return!(CString::new(mnemonic.to_string()), error).into_raw() } /// Generate a new mnemonic with specified language and word count +/// +/// # Safety +/// +/// `error` must be a valid pointer to an `FFIError`. The returned string must be +/// freed with `mnemonic_free`. #[no_mangle] -pub extern "C" fn mnemonic_generate_with_language( +pub unsafe extern "C" fn mnemonic_generate_with_language( word_count: c_uint, language: FFILanguage, error: *mut FFIError, @@ -136,16 +127,14 @@ pub extern "C" fn mnemonic_generate_with_language( 21 => 224, 24 => 256, _ => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InvalidInput, - format!("Invalid word count: {}. Must be 12, 15, 18, 21, or 24", word_count), + &format!("Invalid word count: {}. Must be 12, 15, 18, 21, or 24", word_count), ); return ptr::null_mut(); } }; - // Convert FFILanguage to key_wallet Language use key_wallet::mnemonic::Language; let lang: Language = language.into(); let word_count = match entropy_bits { @@ -156,30 +145,8 @@ pub extern "C" fn mnemonic_generate_with_language( 256 => 24, _ => 12, }; - match Mnemonic::generate(word_count, lang) { - Ok(mnemonic) => { - FFIError::set_success(error); - match CString::new(mnemonic.to_string()) { - Ok(c_str) => c_str.into_raw(), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidMnemonic, - format!("Failed to generate mnemonic: {}", e), - ); - ptr::null_mut() - } - } + let mnemonic = unwrap_or_return!(Mnemonic::generate(word_count, lang), error); + unwrap_or_return!(CString::new(mnemonic.to_string()), error).into_raw() } /// Validate a mnemonic phrase @@ -190,24 +157,8 @@ pub extern "C" fn mnemonic_generate_with_language( /// - `error` must be a valid pointer to an FFIError #[no_mangle] pub unsafe extern "C" fn mnemonic_validate(mnemonic: *const c_char, error: *mut FFIError) -> bool { - if mnemonic.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Mnemonic is null".to_string()); - return false; - } - - let mnemonic_str = unsafe { - match CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in mnemonic".to_string(), - ); - return false; - } - } - }; + let mnemonic = deref_ptr!(mnemonic, error); + let mnemonic_str = unwrap_or_return!(CStr::from_ptr(mnemonic).to_str(), error); use key_wallet::mnemonic::Language; @@ -227,16 +178,12 @@ pub unsafe extern "C" fn mnemonic_validate(mnemonic: *const c_char, error: *mut for language in languages.iter() { if Mnemonic::validate(mnemonic_str, *language) { - FFIError::set_success(error); return true; } } - - // If no language validates, return error - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InvalidMnemonic, - "Invalid mnemonic: does not match any supported language".to_string(), + "Invalid mnemonic: does not match any supported language", ); false } @@ -258,75 +205,31 @@ pub unsafe extern "C" fn mnemonic_to_seed( seed_len: *mut usize, error: *mut FFIError, ) -> bool { - if mnemonic.is_null() || seed_out.is_null() || seed_len.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } - - let mnemonic_str = unsafe { - match CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in mnemonic".to_string(), - ); - return false; - } - } - }; + let mnemonic = deref_ptr!(mnemonic, error); + check_ptr!(seed_out, error); + check_ptr!(seed_len, error); + let mnemonic_str = unwrap_or_return!(CStr::from_ptr(mnemonic).to_str(), error); let passphrase_str = if passphrase.is_null() { "" } else { - unsafe { - match CStr::from_ptr(passphrase).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in passphrase".to_string(), - ); - return false; - } - } - } + unwrap_or_return!(CStr::from_ptr(passphrase).to_str(), error) }; use key_wallet::mnemonic::Language; - match Mnemonic::from_phrase(mnemonic_str, Language::English) { - Ok(m) => { - let seed = m.to_seed(passphrase_str); - let seed_bytes: &[u8] = seed.as_ref(); + let m = unwrap_or_return!(Mnemonic::from_phrase(mnemonic_str, Language::English), error); + let seed = m.to_seed(passphrase_str); + let seed_bytes: &[u8] = seed.as_ref(); - unsafe { - *seed_len = seed_bytes.len(); - if *seed_len > 64 { - FFIError::set_error( - error, - FFIErrorCode::InvalidState, - "Seed too large".to_string(), - ); - return false; - } - - std::ptr::copy_nonoverlapping(seed_bytes.as_ptr(), seed_out, seed_bytes.len()); - } - - FFIError::set_success(error); - true - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidMnemonic, - format!("Invalid mnemonic: {}", e), - ); - false + unsafe { + *seed_len = seed_bytes.len(); + if *seed_len > 64 { + (*error).set(FFIErrorCode::InvalidState, "Seed too large"); + return false; } + std::ptr::copy_nonoverlapping(seed_bytes.as_ptr(), seed_out, seed_bytes.len()); } + true } /// Get word count from mnemonic @@ -340,28 +243,9 @@ pub unsafe extern "C" fn mnemonic_word_count( mnemonic: *const c_char, error: *mut FFIError, ) -> c_uint { - if mnemonic.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Mnemonic is null".to_string()); - return 0; - } - - let mnemonic_str = unsafe { - match CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in mnemonic".to_string(), - ); - return 0; - } - } - }; - - let word_count = mnemonic_str.split_whitespace().count() as c_uint; - FFIError::set_success(error); - word_count + let mnemonic = deref_ptr!(mnemonic, error); + let mnemonic_str = unwrap_or_return!(CStr::from_ptr(mnemonic).to_str(), error); + mnemonic_str.split_whitespace().count() as c_uint } /// Free a mnemonic string diff --git a/key-wallet-ffi/src/mnemonic_tests.rs b/key-wallet-ffi/src/mnemonic_tests.rs index 4ea3e8638..18ab54af0 100644 --- a/key-wallet-ffi/src/mnemonic_tests.rs +++ b/key-wallet-ffi/src/mnemonic_tests.rs @@ -14,7 +14,7 @@ mod tests { #[test] fn test_mnemonic_validation() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test valid 12-word mnemonic @@ -38,17 +38,15 @@ mod tests { let is_valid = unsafe { mnemonic::mnemonic_validate(ptr::null(), error) }; assert!(!is_valid); assert_eq!(unsafe { (*error).code }, FFIErrorCode::InvalidInput); - - unsafe { (*error).free_message() }; } #[test] fn test_mnemonic_generation() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test 12-word generation - let mnemonic_12 = mnemonic::mnemonic_generate(12, error); + let mnemonic_12 = unsafe { mnemonic::mnemonic_generate(12, error) }; assert!(!mnemonic_12.is_null()); assert_eq!(unsafe { (*error).code }, FFIErrorCode::Success); @@ -65,7 +63,7 @@ mod tests { } // Test 24-word generation - let mnemonic_24 = mnemonic::mnemonic_generate(24, error); + let mnemonic_24 = unsafe { mnemonic::mnemonic_generate(24, error) }; assert!(!mnemonic_24.is_null()); let mnemonic_str = unsafe { std::ffi::CStr::from_ptr(mnemonic_24).to_str().unwrap() }; @@ -77,16 +75,14 @@ mod tests { } // Test invalid word count - let invalid = mnemonic::mnemonic_generate(13, error); + let invalid = unsafe { mnemonic::mnemonic_generate(13, error) }; assert!(invalid.is_null()); assert_eq!(unsafe { (*error).code }, FFIErrorCode::InvalidInput); - - unsafe { (*error).free_message() }; } #[test] fn test_mnemonic_to_seed() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let mnemonic = CString::new(TEST_MNEMONIC).unwrap(); @@ -125,20 +121,18 @@ mod tests { assert!(success); assert_ne!(seed, seed_with_pass); // Different passphrase should produce different seed - - unsafe { (*error).free_message() }; } #[test] fn test_mnemonic_word_counts() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test all valid word counts let valid_counts = [12, 15, 18, 21, 24]; for count in valid_counts.iter() { - let mnemonic = mnemonic::mnemonic_generate(*count, error); + let mnemonic = unsafe { mnemonic::mnemonic_generate(*count, error) }; assert!(!mnemonic.is_null()); let mnemonic_str = unsafe { std::ffi::CStr::from_ptr(mnemonic).to_str().unwrap() }; @@ -149,30 +143,26 @@ mod tests { mnemonic::mnemonic_free(mnemonic); } } - - unsafe { (*error).free_message() }; } #[test] fn test_mnemonic_invalid_word_count() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test invalid word counts let invalid_counts = [0, 1, 11, 13, 14, 16, 17, 19, 20, 22, 23, 25, 100]; for count in invalid_counts.iter() { - let mnemonic = mnemonic::mnemonic_generate(*count, error); + let mnemonic = unsafe { mnemonic::mnemonic_generate(*count, error) }; assert!(mnemonic.is_null()); assert_eq!(unsafe { (*error).code }, FFIErrorCode::InvalidInput); } - - unsafe { (*error).free_message() }; } #[test] fn test_mnemonic_edge_cases() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test with null mnemonic @@ -205,13 +195,11 @@ mod tests { }; assert!(success); assert_eq!(seed_len, 64); - - unsafe { (*error).free_message() }; } #[test] fn test_mnemonic_generate_with_language() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test generating with different languages let languages = [ @@ -242,13 +230,11 @@ mod tests { mnemonic::mnemonic_free(mnemonic_ptr); } } - - unsafe { error.free_message() }; } #[test] fn test_mnemonic_czech_portuguese_languages() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test Czech language specifically unsafe { @@ -283,20 +269,20 @@ mod tests { mnemonic::mnemonic_free(portuguese_mnemonic); } - - unsafe { error.free_message() }; } #[test] fn test_mnemonic_generate_and_validate_languages() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Generate a mnemonic with a specific language - let mnemonic_ptr = mnemonic::mnemonic_generate_with_language( - 12, - mnemonic::FFILanguage::Spanish, - &mut error, - ); + let mnemonic_ptr = unsafe { + mnemonic::mnemonic_generate_with_language( + 12, + mnemonic::FFILanguage::Spanish, + &mut error, + ) + }; assert!(!mnemonic_ptr.is_null()); // Validate it (validation doesn't need language since it checks all word lists) @@ -307,8 +293,6 @@ mod tests { unsafe { mnemonic::mnemonic_free(mnemonic_ptr); } - - unsafe { error.free_message() }; } #[test] @@ -321,7 +305,7 @@ mod tests { #[test] fn test_seed_from_mnemonic_with_different_passphrases() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let mnemonic = CString::new(TEST_MNEMONIC).unwrap(); // Test with empty passphrase @@ -358,13 +342,11 @@ mod tests { // Seeds should be different assert_ne!(seed1, seed2); - - unsafe { error.free_message() }; } #[test] fn test_mnemonic_word_count_function() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test different mnemonics let test_cases = [ @@ -383,26 +365,22 @@ mod tests { assert_eq!(count, expected_count); assert_eq!(error.code, FFIErrorCode::Success); } - - error.free_message(); } } #[test] fn test_mnemonic_word_count_null_input() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let count = unsafe { mnemonic::mnemonic_word_count(ptr::null(), &mut error) }; assert_eq!(count, 0); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_mnemonic_word_count_utf8_error() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create invalid UTF-8 string let invalid_utf8 = [0xFF, 0xFE, 0xFD, 0x00]; @@ -415,13 +393,11 @@ mod tests { assert_eq!(count, 0); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_mnemonic_to_seed_null_inputs() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let mut seed = [0u8; 64]; let mut seed_len = 0usize; @@ -464,13 +440,11 @@ mod tests { }; assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_mnemonic_to_seed_invalid_mnemonic() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let mut seed = [0u8; 64]; let mut seed_len = 0usize; @@ -487,13 +461,11 @@ mod tests { assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidMnemonic); - - unsafe { error.free_message() }; } #[test] fn test_mnemonic_to_seed_utf8_errors() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let mut seed = [0u8; 64]; let mut seed_len = 0usize; @@ -524,13 +496,11 @@ mod tests { }; assert!(!success); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_mnemonic_validate_utf8_error() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create invalid UTF-8 string let invalid_utf8 = [0xFF, 0xFE, 0xFD, 0x00]; @@ -543,40 +513,40 @@ mod tests { assert!(!is_valid); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_mnemonic_generate_with_language_invalid_word_count() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test invalid word count with language - let mnemonic = mnemonic::mnemonic_generate_with_language( - 13, - mnemonic::FFILanguage::English, - &mut error, - ); + let mnemonic = unsafe { + mnemonic::mnemonic_generate_with_language( + 13, + mnemonic::FFILanguage::English, + &mut error, + ) + }; assert!(mnemonic.is_null()); assert_eq!(error.code, FFIErrorCode::InvalidInput); - - unsafe { error.free_message() }; } #[test] fn test_mnemonic_generate_with_language_all_word_counts() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test all valid word counts with language let valid_counts = [12, 15, 18, 21, 24]; for word_count in valid_counts { - let mnemonic = mnemonic::mnemonic_generate_with_language( - word_count, - mnemonic::FFILanguage::English, - &mut error, - ); + let mnemonic = unsafe { + mnemonic::mnemonic_generate_with_language( + word_count, + mnemonic::FFILanguage::English, + &mut error, + ) + }; assert!(!mnemonic.is_null()); assert_eq!(error.code, FFIErrorCode::Success); @@ -588,13 +558,11 @@ mod tests { mnemonic::mnemonic_free(mnemonic); } } - - unsafe { error.free_message() }; } #[test] fn test_mnemonic_generate_different_languages() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test generating with all supported languages let languages = [ @@ -609,7 +577,8 @@ mod tests { ]; for lang in languages { - let mnemonic_ptr = mnemonic::mnemonic_generate_with_language(12, lang, &mut error); + let mnemonic_ptr = + unsafe { mnemonic::mnemonic_generate_with_language(12, lang, &mut error) }; // Some languages might not be fully supported by the underlying library unsafe { @@ -627,16 +596,14 @@ mod tests { } } } - - unsafe { error.free_message() }; } #[test] fn test_generated_mnemonic_deterministic_seed() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Generate mnemonic - let mnemonic = mnemonic::mnemonic_generate(12, &mut error); + let mnemonic = unsafe { mnemonic::mnemonic_generate(12, &mut error) }; assert!(!mnemonic.is_null()); // Generate seed twice with same passphrase - should be identical @@ -674,16 +641,15 @@ mod tests { unsafe { mnemonic::mnemonic_free(mnemonic); - error.free_message(); } } #[test] fn test_mnemonic_comprehensive_workflow() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Generate -> Validate -> Get word count -> Convert to seed -> Free - let mnemonic = mnemonic::mnemonic_generate(15, &mut error); + let mnemonic = unsafe { mnemonic::mnemonic_generate(15, &mut error) }; assert!(!mnemonic.is_null()); assert_eq!(error.code, FFIErrorCode::Success); @@ -720,7 +686,6 @@ mod tests { // Free unsafe { mnemonic::mnemonic_free(mnemonic); - error.free_message(); } } } diff --git a/key-wallet-ffi/src/transaction.rs b/key-wallet-ffi/src/transaction.rs index e2e325a37..cd8917155 100644 --- a/key-wallet-ffi/src/transaction.rs +++ b/key-wallet-ffi/src/transaction.rs @@ -22,7 +22,8 @@ use crate::error::{FFIError, FFIErrorCode}; use crate::types::{ transaction_context_from_ffi, FFIBlockInfo, FFITransactionContextType, FFIWallet, }; -use crate::FFIWalletManager; +use crate::{check_ptr, FFIWalletManager}; +use crate::{deref_ptr, deref_ptr_mut, unwrap_or_return}; // MARK: - Transaction Types @@ -98,33 +99,21 @@ pub unsafe extern "C" fn wallet_build_and_sign_transaction( tx_len_out: *mut usize, error: *mut FFIError, ) -> bool { - // Validate inputs - if manager.is_null() - || wallet.is_null() - || outputs.is_null() - || tx_bytes_out.is_null() - || tx_len_out.is_null() - || fee_out.is_null() - { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } + let manager_ref = deref_ptr!(manager, error); + let wallet_ref = deref_ptr!(wallet, error); + check_ptr!(outputs, error); + check_ptr!(tx_bytes_out, error); + check_ptr!(tx_len_out, error); + check_ptr!(fee_out, error); if outputs_count == 0 { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "At least one output required".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "At least one output required"); return false; } unsafe { use key_wallet::wallet::managed_wallet_info::coin_selection::SelectionStrategy; use key_wallet::wallet::managed_wallet_info::transaction_builder::TransactionBuilder; - - let manager_ref = &*manager; - let wallet_ref = &*wallet; let network_rust = wallet_ref.inner().network; let outputs_slice = slice::from_raw_parts(outputs, outputs_count); @@ -142,19 +131,14 @@ pub unsafe extern "C" fn wallet_build_and_sign_transaction( Ok(result) => match result.address { Some(addr) => addr, None => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "No change address available".to_string(), - ); + (*error).set(FFIErrorCode::WalletError, "No change address available"); return false; } }, Err(e) => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - format!("Failed to get change address: {}", e), + &format!("Failed to get change address: {}", e), ); return false; } @@ -164,10 +148,9 @@ pub unsafe extern "C" fn wallet_build_and_sign_transaction( let managed_wallet = match manager.get_wallet_info_mut(&wallet_id) { Some(info) => info, None => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InvalidInput, - "Could not obtain ManagedWalletInfo for the provided wallet".to_string(), + "Could not obtain ManagedWalletInfo for the provided wallet", ); return false; } @@ -177,10 +160,9 @@ pub unsafe extern "C" fn wallet_build_and_sign_transaction( match managed_wallet.accounts.standard_bip44_accounts.get_mut(&account_index) { Some(account) => account, None => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - format!("Account {} not found", account_index), + &format!("Account {} not found", account_index), ); return false; } @@ -191,11 +173,7 @@ pub unsafe extern "C" fn wallet_build_and_sign_transaction( for output in outputs_slice { if output.address.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Output address pointer is null".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "Output address pointer is null"); return false; } @@ -203,50 +181,23 @@ pub unsafe extern "C" fn wallet_build_and_sign_transaction( let address_str = match CStr::from_ptr(output.address).to_str() { Ok(s) => s, Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in output address".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "Invalid UTF-8 in output address"); return false; } }; // Parse address using dashcore use std::str::FromStr; - let address = match dashcore::Address::from_str(address_str) { - Ok(addr) => { - // Verify network matches - let addr_network = addr.require_network(network_rust).map_err(|e| { - FFIError::set_error( - error, - FFIErrorCode::InvalidAddress, - format!("Address network mismatch: {}", e), - ); - }); - if addr_network.is_err() { - return false; - } - addr_network.unwrap() - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidAddress, - format!("Invalid address: {}", e), - ); - return false; - } - }; + let parsed = unwrap_or_return!(dashcore::Address::from_str(address_str), error); + let address = unwrap_or_return!(parsed.require_network(network_rust), error); // Add output tx_builder = match tx_builder.add_output(&address, output.amount) { Ok(builder) => builder, Err(e) => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - format!("Failed to add output: {}", e), + &format!("Failed to add output: {}", e), ); return false; } @@ -274,11 +225,7 @@ pub unsafe extern "C" fn wallet_build_and_sign_transaction( } => root_extended_private_key, WalletType::ExtendedPrivKey(root_extended_private_key) => root_extended_private_key, _ => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "Cannot sign with watch-only wallet".to_string(), - ); + (*error).set(FFIErrorCode::WalletError, "Cannot sign with watch-only wallet"); return false; } }; @@ -314,11 +261,8 @@ pub unsafe extern "C" fn wallet_build_and_sign_transaction( ) { Ok(builder) => builder, Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Coin selection failed: {}", e), - ); + (*error) + .set(FFIErrorCode::WalletError, &format!("Coin selection failed: {}", e)); return false; } }; @@ -327,10 +271,9 @@ pub unsafe extern "C" fn wallet_build_and_sign_transaction( let transaction = match tx_builder_with_inputs.build() { Ok(tx) => tx, Err(e) => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::WalletError, - format!("Failed to build transaction: {}", e), + &format!("Failed to build transaction: {}", e), ); return false; } @@ -359,7 +302,7 @@ pub unsafe extern "C" fn wallet_build_and_sign_transaction( *tx_bytes_out = tx_bytes; *tx_len_out = size; - FFIError::set_success(error); + (*error).clean(); true }) } @@ -401,28 +344,16 @@ pub unsafe extern "C" fn wallet_check_transaction( result_out: *mut FFITransactionCheckResult, error: *mut FFIError, ) -> bool { - if wallet.is_null() || tx_bytes.is_null() || result_out.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } + let wallet = deref_ptr_mut!(wallet, error); + check_ptr!(tx_bytes, error); + check_ptr!(result_out, error); unsafe { - let wallet = &mut *wallet; let tx_slice = slice::from_raw_parts(tx_bytes, tx_len); - // Parse the transaction use dashcore::consensus::Decodable; - let tx = match dashcore::Transaction::consensus_decode(&mut &tx_slice[..]) { - Ok(tx) => tx, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - format!("Failed to decode transaction: {}", e), - ); - return false; - } - }; + let tx = + unwrap_or_return!(dashcore::Transaction::consensus_decode(&mut &tx_slice[..]), error); // Build the transaction context let context = match transaction_context_from_ffi( @@ -433,11 +364,7 @@ pub unsafe extern "C" fn wallet_check_transaction( ) { Some(ctx) => ctx, None => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid transaction context: block info is zeroed for a confirmed context, or IS lock data is missing/malformed for InstantSend".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "Invalid transaction context: block info is zeroed for a confirmed context, or IS lock data is missing/malformed for InstantSend"); return false; } }; @@ -452,10 +379,9 @@ pub unsafe extern "C" fn wallet_check_transaction( let wallet_mut = match wallet.inner_mut() { Some(w) => w, None => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InternalError, - "Cannot get mutable wallet reference (Arc has multiple owners)".to_string(), + "Cannot get mutable wallet reference (Arc has multiple owners)", ); return false; } @@ -478,7 +404,7 @@ pub unsafe extern "C" fn wallet_check_transaction( affected_accounts_count: check_result.affected_accounts.len() as u32, }; - FFIError::set_success(error); + (*error).clean(); true } } @@ -649,48 +575,10 @@ pub unsafe extern "C" fn transaction_get_txid_from_bytes( tx_len: usize, error: *mut FFIError, ) -> *mut c_char { - if tx_bytes.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Transaction bytes is null".to_string(), - ); - return ptr::null_mut(); - } - + check_ptr!(tx_bytes, error); let tx_slice = slice::from_raw_parts(tx_bytes, tx_len); - - // Deserialize the transaction - let tx: Transaction = match consensus::deserialize(tx_slice) { - Ok(t) => t, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::SerializationError, - format!("Failed to deserialize transaction: {}", e), - ); - return ptr::null_mut(); - } - }; - - // Get TXID and convert to hex string - let txid = tx.txid(); - let txid_hex = txid.to_string(); - - match CString::new(txid_hex) { - Ok(c_str) => { - FFIError::set_success(error); - c_str.into_raw() - } - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::SerializationError, - "Failed to convert TXID to C string".to_string(), - ); - ptr::null_mut() - } - } + let tx: Transaction = unwrap_or_return!(consensus::deserialize(tx_slice), error); + unwrap_or_return!(CString::new(tx.txid().to_string()), error).into_raw() } /// Serialize a transaction @@ -1057,28 +945,20 @@ pub unsafe extern "C" fn wallet_build_and_sign_asset_lock_transaction( private_keys_out: *mut [u8; 32], error: *mut FFIError, ) -> bool { - if manager.is_null() - || wallet.is_null() - || funding_types.is_null() - || identity_indices.is_null() - || credit_output_scripts.is_null() - || credit_output_script_lens.is_null() - || credit_output_amounts.is_null() - || tx_bytes_out.is_null() - || tx_len_out.is_null() - || fee_out.is_null() - || private_keys_out.is_null() - { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } + check_ptr!(manager, error); + check_ptr!(wallet, error); + check_ptr!(funding_types, error); + check_ptr!(identity_indices, error); + check_ptr!(credit_output_scripts, error); + check_ptr!(credit_output_script_lens, error); + check_ptr!(credit_output_amounts, error); + check_ptr!(tx_bytes_out, error); + check_ptr!(tx_len_out, error); + check_ptr!(fee_out, error); + check_ptr!(private_keys_out, error); if credit_outputs_count == 0 { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "At least one credit output required".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "At least one credit output required"); return false; } @@ -1096,10 +976,9 @@ pub unsafe extern "C" fn wallet_build_and_sign_asset_lock_transaction( let mut fundings = Vec::with_capacity(credit_outputs_count); for i in 0..credit_outputs_count { if scripts_slice[i].is_null() { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InvalidInput, - format!("Credit output script {} is null", i), + &format!("Credit output script {} is null", i), ); return false; } @@ -1121,11 +1000,7 @@ pub unsafe extern "C" fn wallet_build_and_sign_asset_lock_transaction( let managed_wallet = match manager.get_wallet_info_mut(&wallet_id) { Some(info) => info, None => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Wallet not found".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "Wallet not found"); return false; } }; @@ -1138,7 +1013,7 @@ pub unsafe extern "C" fn wallet_build_and_sign_asset_lock_transaction( ) { Ok(r) => r, Err(e) => { - FFIError::set_error(error, FFIErrorCode::WalletError, format!("{}", e)); + (*error).set(FFIErrorCode::WalletError, &format!("{}", e)); return false; } }; @@ -1151,11 +1026,7 @@ pub unsafe extern "C" fn wallet_build_and_sign_asset_lock_transaction( let private_keys = match &result.keys { key_wallet::wallet::managed_wallet_info::asset_lock_builder::AssetLockCreditKeys::Private(k) => k, key_wallet::wallet::managed_wallet_info::asset_lock_builder::AssetLockCreditKeys::Public(_) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "Unexpected public-key result from build_asset_lock".to_string(), - ); + (*error).set(FFIErrorCode::WalletError, "Unexpected public-key result from build_asset_lock"); return false; } }; @@ -1172,7 +1043,7 @@ pub unsafe extern "C" fn wallet_build_and_sign_asset_lock_transaction( *tx_bytes_out = Box::into_raw(boxed) as *mut u8; *tx_len_out = size; - FFIError::set_success(error); + (*error).clean(); true }) } diff --git a/key-wallet-ffi/src/transaction_checking.rs b/key-wallet-ffi/src/transaction_checking.rs index 6bf7c38a5..2c2081f6c 100644 --- a/key-wallet-ffi/src/transaction_checking.rs +++ b/key-wallet-ffi/src/transaction_checking.rs @@ -13,6 +13,7 @@ use crate::managed_wallet::{managed_wallet_info_free, FFIManagedWalletInfo}; use crate::types::{ transaction_context_from_ffi, FFIBlockInfo, FFITransactionContextType, FFIWallet, }; +use crate::{check_ptr, deref_ptr, deref_ptr_mut, unwrap_or_return}; use dashcore::consensus::Decodable; use dashcore::Transaction; use key_wallet::transaction_checking::{ @@ -70,28 +71,16 @@ pub struct FFITransactionCheckResult { /// # Safety /// /// - `wallet` must be a valid pointer to an FFIWallet -/// - `error` must be a valid pointer to an FFIError or null +/// - `error` must be a valid pointer to an FFIError /// - The returned pointer must be freed with `managed_wallet_info_free` (or `ffi_managed_wallet_free` for compatibility) #[no_mangle] pub unsafe extern "C" fn wallet_create_managed_wallet( wallet: *const FFIWallet, error: *mut FFIError, ) -> *mut FFIManagedWalletInfo { - if wallet.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Wallet is null".to_string()); - return std::ptr::null_mut(); - } - - let wallet = &*wallet; - - // Create managed wallet info from the wallet + let wallet = deref_ptr!(wallet, error); let managed_info = ManagedWalletInfo::from_wallet(wallet.inner()); - - // Box it and return raw pointer - let managed_wallet = Box::new(FFIManagedWalletInfo::new(managed_info)); - - FFIError::set_success(error); - Box::into_raw(managed_wallet) + Box::into_raw(Box::new(FFIManagedWalletInfo::new(managed_info))) } /// Check if a transaction belongs to the wallet @@ -121,26 +110,13 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( result_out: *mut FFITransactionCheckResult, error: *mut FFIError, ) -> bool { - if managed_wallet.is_null() || tx_bytes.is_null() || result_out.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } + let managed_wallet: &mut ManagedWalletInfo = deref_ptr_mut!(managed_wallet, error).inner_mut(); + check_ptr!(tx_bytes, error); + check_ptr!(result_out, error); - let managed_wallet: &mut ManagedWalletInfo = (*managed_wallet).inner_mut(); let tx_slice = slice::from_raw_parts(tx_bytes, tx_len); - // Parse the transaction - let tx = match Transaction::consensus_decode(&mut &tx_slice[..]) { - Ok(tx) => tx, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - format!("Failed to decode transaction: {}", e), - ); - return false; - } - }; + let tx = unwrap_or_return!(Transaction::consensus_decode(&mut &tx_slice[..]), error); // Build the transaction context let context = match transaction_context_from_ffi( @@ -151,43 +127,24 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ) { Some(ctx) => ctx, None => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid transaction context: block info is zeroed for a confirmed context, or IS lock data is missing/malformed for InstantSend".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "Invalid transaction context: block info is zeroed for a confirmed context, or IS lock data is missing/malformed for InstantSend"); return false; } }; if let TransactionContext::InstantSend(ref lock) = context { if lock.txid != tx.txid() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "InstantLock txid does not match transaction".to_string(), - ); + (*error).set(FFIErrorCode::InvalidInput, "InstantLock txid does not match transaction"); return false; } } - // Check the transaction - wallet is now required - if wallet.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Wallet pointer is required".to_string(), - ); - return false; - } - - let wallet_mut = match (*wallet).inner_mut() { + let wallet_mut = match deref_ptr_mut!(wallet, error).inner_mut() { Some(w) => w, None => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InternalError, - "Cannot get mutable wallet reference (Arc has multiple owners)".to_string(), + "Cannot get mutable wallet reference (Arc has multiple owners)", ); return false; } @@ -493,7 +450,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( affected_accounts_count: check_result.affected_accounts.len() as c_uint, }; - FFIError::set_success(error); + (*error).clean(); true } @@ -540,7 +497,7 @@ pub unsafe extern "C" fn ffi_managed_wallet_free(managed_wallet: *mut FFIManaged /// # Safety /// /// - `tx_bytes` must be a valid pointer to transaction bytes with at least `tx_len` bytes -/// - `error` must be a valid pointer to an FFIError or null +/// - `error` must be a valid pointer to an FFIError /// - The returned string must be freed by the caller #[no_mangle] pub unsafe extern "C" fn transaction_classify( @@ -548,50 +505,13 @@ pub unsafe extern "C" fn transaction_classify( tx_len: usize, error: *mut FFIError, ) -> *mut c_char { - if tx_bytes.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Transaction bytes are null".to_string(), - ); - return std::ptr::null_mut(); - } - + check_ptr!(tx_bytes, error); let tx_slice = slice::from_raw_parts(tx_bytes, tx_len); + let tx = unwrap_or_return!(Transaction::consensus_decode(&mut &tx_slice[..]), error); - // Parse the transaction - let tx = match Transaction::consensus_decode(&mut &tx_slice[..]) { - Ok(tx) => tx, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - format!("Failed to decode transaction: {}", e), - ); - return std::ptr::null_mut(); - } - }; - - // Classify the transaction use key_wallet::transaction_checking::transaction_router::TransactionRouter; let tx_type = TransactionRouter::classify_transaction(&tx); - - let type_str = format!("{:?}", tx_type); - - match CString::new(type_str) { - Ok(c_str) => { - FFIError::set_success(error); - c_str.into_raw() - } - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Failed to convert transaction type to C string".to_string(), - ); - std::ptr::null_mut() - } - } + unwrap_or_return!(CString::new(format!("{:?}", tx_type)), error).into_raw() } #[cfg(test)] diff --git a/key-wallet-ffi/src/utxo.rs b/key-wallet-ffi/src/utxo.rs index 5fd757be2..5150adb7a 100644 --- a/key-wallet-ffi/src/utxo.rs +++ b/key-wallet-ffi/src/utxo.rs @@ -1,7 +1,8 @@ //! UTXO management -use crate::error::{FFIError, FFIErrorCode}; +use crate::error::FFIError; use crate::managed_wallet::FFIManagedWalletInfo; +use crate::{check_ptr, deref_ptr}; use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; use std::ffi::CString; use std::os::raw::c_char; @@ -81,7 +82,7 @@ impl FFIUTXO { /// - `managed_info` must be a valid pointer to an FFIManagedWalletInfo instance /// - `utxos_out` must be a valid pointer to store the UTXO array pointer /// - `count_out` must be a valid pointer to store the UTXO count -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call /// - The returned UTXO array must be freed with `utxo_array_free` when no longer needed #[no_mangle] @@ -91,12 +92,9 @@ pub unsafe extern "C" fn managed_wallet_get_utxos( count_out: *mut usize, error: *mut FFIError, ) -> bool { - if managed_info.is_null() || utxos_out.is_null() || count_out.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } - - let managed_info = &*managed_info; + let managed_info = deref_ptr!(managed_info, error); + check_ptr!(utxos_out, error); + check_ptr!(count_out, error); // Get UTXOs from the managed wallet info let utxos = managed_info.inner().utxos(); @@ -141,8 +139,6 @@ pub unsafe extern "C" fn managed_wallet_get_utxos( let ptr = Box::into_raw(boxed_utxos) as *mut FFIUTXO; *utxos_out = ptr; } - - FFIError::set_success(error); true } @@ -160,16 +156,11 @@ pub unsafe extern "C" fn wallet_get_utxos( count_out: *mut usize, error: *mut FFIError, ) -> bool { - if utxos_out.is_null() || count_out.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } + check_ptr!(utxos_out, error); + check_ptr!(count_out, error); - // Return empty list for backwards compatibility *count_out = 0; *utxos_out = ptr::null_mut(); - - FFIError::set_success(error); true } diff --git a/key-wallet-ffi/src/utxo_tests.rs b/key-wallet-ffi/src/utxo_tests.rs index f73a0e606..d6cba5c58 100644 --- a/key-wallet-ffi/src/utxo_tests.rs +++ b/key-wallet-ffi/src/utxo_tests.rs @@ -73,7 +73,7 @@ mod utxo_tests { #[test] fn test_deprecated_wallet_get_utxos() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let mut utxos_out: *mut FFIUTXO = ptr::null_mut(); let mut count_out: usize = 0; @@ -91,7 +91,7 @@ mod utxo_tests { #[test] fn test_managed_wallet_get_utxos_null() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let mut utxos_out: *mut FFIUTXO = ptr::null_mut(); let mut count_out: usize = 0; @@ -101,8 +101,6 @@ mod utxo_tests { unsafe { managed_wallet_get_utxos(ptr::null(), &mut utxos_out, &mut count_out, error) }; assert!(!result); assert_eq!(unsafe { (*error).code }, FFIErrorCode::InvalidInput); - - unsafe { (*error).free_message() }; } #[test] @@ -111,7 +109,7 @@ mod utxo_tests { use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet::Network; - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let mut utxos_out: *mut FFIUTXO = ptr::null_mut(); let mut count_out: usize = 0; @@ -182,7 +180,7 @@ mod utxo_tests { use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet::Network; - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let mut utxos_out: *mut FFIUTXO = ptr::null_mut(); let mut count_out: usize = 0; @@ -286,7 +284,7 @@ mod utxo_tests { use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet::Network; - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let mut utxos_out: *mut FFIUTXO = ptr::null_mut(); let mut count_out: usize = 0; @@ -389,7 +387,7 @@ mod utxo_tests { use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet::Network; - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let mut utxos_out: *mut FFIUTXO = ptr::null_mut(); let mut count_out: usize = 0; @@ -528,7 +526,7 @@ mod utxo_tests { use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet::Network; - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let mut count_out: usize = 0; @@ -552,7 +550,6 @@ mod utxo_tests { unsafe { crate::managed_wallet::managed_wallet_free(ffi_managed_info); - (*error).free_message(); } } diff --git a/key-wallet-ffi/src/wallet.rs b/key-wallet-ffi/src/wallet.rs index 8eb9deb5b..f3567d55f 100644 --- a/key-wallet-ffi/src/wallet.rs +++ b/key-wallet-ffi/src/wallet.rs @@ -16,6 +16,7 @@ use key_wallet::{Mnemonic, Seed, Wallet}; use crate::error::{FFIError, FFIErrorCode}; use crate::types::{FFIWallet, FFIWalletAccountCreationOptions}; +use crate::{check_ptr, deref_ptr, unwrap_or_return}; use key_wallet::Network; /// Create a new wallet from mnemonic with options @@ -25,7 +26,7 @@ use key_wallet::Network; /// - `mnemonic` must be a valid pointer to a null-terminated C string /// - `passphrase` must be a valid pointer to a null-terminated C string or null /// - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call /// - The returned pointer must be freed with `wallet_free` when no longer needed #[no_mangle] @@ -36,99 +37,40 @@ pub unsafe extern "C" fn wallet_create_from_mnemonic_with_options( account_options: *const FFIWalletAccountCreationOptions, error: *mut FFIError, ) -> *mut FFIWallet { - if mnemonic.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Mnemonic is null".to_string()); - return ptr::null_mut(); - } + use key_wallet::mnemonic::Language; - let mnemonic_str = unsafe { - match CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in mnemonic".to_string(), - ); - return ptr::null_mut(); - } - } - }; + let mnemonic = deref_ptr!(mnemonic, error); + let mnemonic_str = unwrap_or_return!(CStr::from_ptr(mnemonic).to_str(), error); let passphrase_str = if passphrase.is_null() { "" } else { - unsafe { - match CStr::from_ptr(passphrase).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in passphrase".to_string(), - ); - return ptr::null_mut(); - } - } - } + unwrap_or_return!(CStr::from_ptr(passphrase).to_str(), error) }; - use key_wallet::mnemonic::Language; - let mnemonic = match Mnemonic::from_phrase(mnemonic_str, Language::English) { - Ok(m) => m, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidMnemonic, - format!("Invalid mnemonic: {}", e), - ); - return ptr::null_mut(); - } - }; + let mnemonic = unwrap_or_return!(Mnemonic::from_phrase(mnemonic_str, Language::English), error); let network_rust: Network = network.into(); - - // Convert account creation options let creation_options = if account_options.is_null() { WalletAccountCreationOptions::Default } else { - unsafe { (*account_options).to_wallet_options() } + (*account_options).to_wallet_options() }; let wallet = if passphrase_str.is_empty() { - match Wallet::from_mnemonic(mnemonic, network_rust, creation_options) { - Ok(w) => w, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to create wallet: {}", e), - ); - return ptr::null_mut(); - } - } + unwrap_or_return!(Wallet::from_mnemonic(mnemonic, network_rust, creation_options), error) } else { - // For wallets with passphrase, we need to handle account creation differently - // First create the wallet without accounts - match Wallet::from_mnemonic_with_passphrase( - mnemonic, - passphrase_str.to_string(), - network_rust, - creation_options, - ) { - Ok(w) => w, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to create wallet with passphrase: {}", e), - ); - return ptr::null_mut(); - } - } + unwrap_or_return!( + Wallet::from_mnemonic_with_passphrase( + mnemonic, + passphrase_str.to_string(), + network_rust, + creation_options, + ), + error + ) }; - FFIError::set_success(error); Box::into_raw(Box::new(FFIWallet::new(wallet))) } @@ -138,7 +80,7 @@ pub unsafe extern "C" fn wallet_create_from_mnemonic_with_options( /// /// - `mnemonic` must be a valid pointer to a null-terminated C string /// - `passphrase` must be a valid pointer to a null-terminated C string or null -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call /// - The returned pointer must be freed with `wallet_free` when no longer needed #[no_mangle] @@ -163,7 +105,7 @@ pub unsafe extern "C" fn wallet_create_from_mnemonic( /// /// - `seed` must be a valid pointer to a byte array of `seed_len` length /// - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn wallet_create_from_seed_with_options( @@ -173,16 +115,11 @@ pub unsafe extern "C" fn wallet_create_from_seed_with_options( account_options: *const FFIWalletAccountCreationOptions, error: *mut FFIError, ) -> *mut FFIWallet { - if seed.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Seed is null".to_string()); - return ptr::null_mut(); - } - + let _seed_byte = deref_ptr!(seed, error); if seed_len != 64 { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InvalidInput, - format!("Invalid seed length: {}, expected 64", seed_len), + &format!("Invalid seed length: {}, expected 64", seed_len), ); return ptr::null_mut(); } @@ -193,28 +130,14 @@ pub unsafe extern "C" fn wallet_create_from_seed_with_options( let seed = Seed::new(seed_array); let network_rust: Network = network.into(); - - // Convert account creation options let creation_options = if account_options.is_null() { WalletAccountCreationOptions::Default } else { (*account_options).to_wallet_options() }; - match Wallet::from_seed(seed, network_rust, creation_options) { - Ok(wallet) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIWallet::new(wallet))) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to create wallet from seed: {}", e), - ); - ptr::null_mut() - } - } + let wallet = unwrap_or_return!(Wallet::from_seed(seed, network_rust, creation_options), error); + Box::into_raw(Box::new(FFIWallet::new(wallet))) } /// Create a new wallet from seed (backward compatibility) @@ -222,7 +145,7 @@ pub unsafe extern "C" fn wallet_create_from_seed_with_options( /// # Safety /// /// - `seed` must be a valid pointer to a byte array of `seed_len` length -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn wallet_create_from_seed( @@ -245,7 +168,7 @@ pub unsafe extern "C" fn wallet_create_from_seed( /// # Safety /// /// - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn wallet_create_random_with_options( @@ -262,27 +185,15 @@ pub unsafe extern "C" fn wallet_create_random_with_options( (*account_options).to_wallet_options() }; - match Wallet::new_random(network_rust, creation_options) { - Ok(wallet) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIWallet::new(wallet))) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to create random wallet: {}", e), - ); - ptr::null_mut() - } - } + let wallet = unwrap_or_return!(Wallet::new_random(network_rust, creation_options), error); + Box::into_raw(Box::new(FFIWallet::new(wallet))) } /// Create a new random wallet (backward compatibility) /// /// # Safety /// -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure the pointer remains valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn wallet_create_random( @@ -302,7 +213,7 @@ pub unsafe extern "C" fn wallet_create_random( /// /// - `wallet` must be a valid pointer to an FFIWallet /// - `id_out` must be a valid pointer to a 32-byte buffer -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn wallet_get_id( @@ -310,16 +221,10 @@ pub unsafe extern "C" fn wallet_get_id( id_out: *mut u8, error: *mut FFIError, ) -> bool { - if wallet.is_null() || id_out.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } - - let wallet = &*wallet; + let wallet = deref_ptr!(wallet, error); + check_ptr!(id_out, error); let wallet_id = wallet.inner().wallet_id; - ptr::copy_nonoverlapping(wallet_id.as_ptr(), id_out, 32); - FFIError::set_success(error); true } @@ -328,23 +233,15 @@ pub unsafe extern "C" fn wallet_get_id( /// # Safety /// /// - `wallet` must be a valid pointer to an FFIWallet instance -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn wallet_has_mnemonic( wallet: *const FFIWallet, error: *mut FFIError, ) -> bool { - if wallet.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Wallet is null".to_string()); - return false; - } - - unsafe { - let wallet = &*wallet; - FFIError::set_success(error); - wallet.inner().has_mnemonic() - } + let wallet = deref_ptr!(wallet, error); + wallet.inner().has_mnemonic() } /// Check if wallet is watch-only @@ -352,23 +249,15 @@ pub unsafe extern "C" fn wallet_has_mnemonic( /// # Safety /// /// - `wallet` must be a valid pointer to an FFIWallet instance -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn wallet_is_watch_only( wallet: *const FFIWallet, error: *mut FFIError, ) -> bool { - if wallet.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Wallet is null".to_string()); - return false; - } - - unsafe { - let wallet = &*wallet; - FFIError::set_success(error); - wallet.inner().is_watch_only() - } + let wallet = deref_ptr!(wallet, error); + wallet.inner().is_watch_only() } /// Get extended public key for account @@ -376,7 +265,7 @@ pub unsafe extern "C" fn wallet_is_watch_only( /// # Safety /// /// - `wallet` must be a valid pointer to an FFIWallet instance -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call /// - The returned C string must be freed by the caller when no longer needed #[no_mangle] @@ -385,36 +274,9 @@ pub unsafe extern "C" fn wallet_get_xpub( account_index: c_uint, error: *mut FFIError, ) -> *mut c_char { - if wallet.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Wallet is null".to_string()); - return ptr::null_mut(); - } - - unsafe { - let wallet = &*wallet; - - match wallet.inner().get_bip44_account(account_index) { - Some(account) => { - let xpub = account.extended_public_key(); - FFIError::set_success(error); - match CString::new(xpub.to_string()) { - Ok(c_str) => c_str.into_raw(), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } - } - None => { - FFIError::set_error(error, FFIErrorCode::NotFound, "Account not found".to_string()); - ptr::null_mut() - } - } - } + let wallet = deref_ptr!(wallet, error); + let account = unwrap_or_return!(wallet.inner().get_bip44_account(account_index), error); + unwrap_or_return!(CString::new(account.extended_public_key().to_string()), error).into_raw() } /// Free a wallet diff --git a/key-wallet-ffi/src/wallet_manager.rs b/key-wallet-ffi/src/wallet_manager.rs index 50f2548d7..2cb4cbc92 100644 --- a/key-wallet-ffi/src/wallet_manager.rs +++ b/key-wallet-ffi/src/wallet_manager.rs @@ -16,6 +16,7 @@ use std::sync::Arc; use tokio::sync::RwLock; use crate::error::{FFIError, FFIErrorCode}; +use crate::{check_ptr, deref_ptr, deref_ptr_mut, unwrap_or_return}; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet_manager::WalletInterface; use key_wallet_manager::WalletManager; @@ -65,12 +66,7 @@ pub unsafe extern "C" fn wallet_manager_describe( manager: *const FFIWalletManager, error: *mut FFIError, ) -> *mut c_char { - if manager.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let manager_ref = &*manager; + let manager_ref = deref_ptr!(manager, error); let runtime = manager_ref.runtime.clone(); let manager_arc = manager_ref.manager.clone(); @@ -79,20 +75,7 @@ pub unsafe extern "C" fn wallet_manager_describe( guard.describe().await }); - match CString::new(description) { - Ok(c_string) => { - FFIError::set_success(error); - c_string.into_raw() - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidState, - format!("Failed to create description string: {}", e), - ); - ptr::null_mut() - } - } + unwrap_or_return!(CString::new(description), error).into_raw() } /// Free a string previously returned by wallet manager APIs. @@ -112,8 +95,13 @@ pub unsafe extern "C" fn wallet_manager_free_string(value: *mut c_char) { } /// Create a new wallet manager +/// +/// # Safety +/// +/// `error` must be a valid pointer to an `FFIError`. The returned pointer must be +/// freed with `wallet_manager_free`. #[no_mangle] -pub extern "C" fn wallet_manager_create( +pub unsafe extern "C" fn wallet_manager_create( network: FFINetwork, error: *mut FFIError, ) -> *mut FFIWalletManager { @@ -121,15 +109,12 @@ pub extern "C" fn wallet_manager_create( let runtime = match tokio::runtime::Runtime::new() { Ok(rt) => Arc::new(rt), Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - format!("Failed to create runtime: {}", e), - ); + (*error) + .set(FFIErrorCode::AllocationFailed, &format!("Failed to create runtime: {}", e)); return ptr::null_mut(); } }; - FFIError::set_success(error); + (*error).clean(); Box::into_raw(Box::new(FFIWalletManager { network, manager: Arc::new(RwLock::new(manager)), @@ -145,7 +130,7 @@ pub extern "C" fn wallet_manager_create( /// - `mnemonic` must be a valid pointer to a null-terminated C string /// - `passphrase` must be a valid pointer to a null-terminated C string or null /// - `account_options` must be a valid pointer to FFIWalletAccountCreationOptions or null -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn wallet_manager_add_wallet_from_mnemonic_with_options( @@ -155,81 +140,27 @@ pub unsafe extern "C" fn wallet_manager_add_wallet_from_mnemonic_with_options( account_options: *const crate::types::FFIWalletAccountCreationOptions, error: *mut FFIError, ) -> bool { - if manager.is_null() || mnemonic.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } - - let mnemonic_str = unsafe { - match CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in mnemonic".to_string(), - ); - return false; - } - } - }; - + let manager_ref = deref_ptr!(manager, error); + let mnemonic = deref_ptr!(mnemonic, error); + let mnemonic_str = unwrap_or_return!(CStr::from_ptr(mnemonic).to_str(), error); let passphrase_str = if passphrase.is_null() { "" } else { - unsafe { - match CStr::from_ptr(passphrase).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in passphrase".to_string(), - ); - return false; - } - } - } + unwrap_or_return!(CStr::from_ptr(passphrase).to_str(), error) }; - unsafe { - let manager_ref = &*manager; - - // Convert account creation options - let creation_options = if account_options.is_null() { - key_wallet::wallet::initialization::WalletAccountCreationOptions::Default - } else { - (*account_options).to_wallet_options() - }; - - // Use the runtime to execute async code - let result = manager_ref.runtime.block_on(async { - let mut manager_guard = manager_ref.manager.write().await; - - // Use the WalletManager's public method to create the wallet - manager_guard.create_wallet_from_mnemonic( - mnemonic_str, - passphrase_str, - 0, - creation_options, - ) - }); + let creation_options = if account_options.is_null() { + key_wallet::wallet::initialization::WalletAccountCreationOptions::Default + } else { + (*account_options).to_wallet_options() + }; - match result { - Ok(_wallet_id) => { - FFIError::set_success(error); - true - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to create wallet: {:?}", e), - ); - false - } - } - } + let result = manager_ref.runtime.block_on(async { + let mut manager_guard = manager_ref.manager.write().await; + manager_guard.create_wallet_from_mnemonic(mnemonic_str, passphrase_str, 0, creation_options) + }); + let _ = unwrap_or_return!(result, error); + true } /// Add a wallet from mnemonic to the manager (backward compatibility) @@ -239,7 +170,7 @@ pub unsafe extern "C" fn wallet_manager_add_wallet_from_mnemonic_with_options( /// - `manager` must be a valid pointer to an FFIWalletManager instance /// - `mnemonic` must be a valid pointer to a null-terminated C string /// - `passphrase` must be a valid pointer to a null-terminated C string or null -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn wallet_manager_add_wallet_from_mnemonic( @@ -274,7 +205,7 @@ pub unsafe extern "C" fn wallet_manager_add_wallet_from_mnemonic( /// - `wallet_bytes_out` must be a valid pointer to a pointer that will receive the serialized bytes /// - `wallet_bytes_len_out` must be a valid pointer that will receive the byte length /// - `wallet_id_out` must be a valid pointer to a 32-byte array that will receive the wallet ID -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call /// - The caller must free the returned wallet_bytes using wallet_manager_free_wallet_bytes() #[cfg(feature = "bincode")] @@ -292,61 +223,25 @@ pub unsafe extern "C" fn wallet_manager_add_wallet_from_mnemonic_return_serializ wallet_id_out: *mut u8, error: *mut FFIError, ) -> bool { - // Validate input parameters - if manager.is_null() - || mnemonic.is_null() - || wallet_bytes_out.is_null() - || wallet_bytes_len_out.is_null() - || wallet_id_out.is_null() - { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } + let manager_ref = deref_ptr!(manager, error); + let mnemonic = deref_ptr!(mnemonic, error); + check_ptr!(wallet_bytes_out, error); + check_ptr!(wallet_bytes_len_out, error); + check_ptr!(wallet_id_out, error); - // Parse mnemonic string - let mnemonic_str = unsafe { - match CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in mnemonic".to_string(), - ); - return false; - } - } - }; - - // Parse passphrase string + let mnemonic_str = unwrap_or_return!(CStr::from_ptr(mnemonic).to_str(), error); let passphrase_str = if passphrase.is_null() { "" } else { - unsafe { - match CStr::from_ptr(passphrase).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in passphrase".to_string(), - ); - return false; - } - } - } + unwrap_or_return!(CStr::from_ptr(passphrase).to_str(), error) }; - // Convert account creation options let creation_options = if account_options.is_null() { key_wallet::wallet::initialization::WalletAccountCreationOptions::Default } else { - unsafe { (*account_options).to_wallet_options() } + (*account_options).to_wallet_options() }; - // Get the manager and call the proper method - let manager_ref = unsafe { &*manager }; - let result = manager_ref.runtime.block_on(async { let mut manager_guard = manager_ref.manager.write().await; @@ -360,18 +255,7 @@ pub unsafe extern "C" fn wallet_manager_add_wallet_from_mnemonic_return_serializ ) }); - let (serialized, wallet_id) = match result { - Ok(result) => result, - Err(e) => { - let ffi_error: FFIError = e.into(); - if !error.is_null() { - unsafe { - *error = ffi_error; - } - } - return false; - } - }; + let (serialized, wallet_id) = unwrap_or_return!(result, error); // Allocate memory for the serialized bytes let boxed_bytes = serialized.into_boxed_slice(); @@ -385,7 +269,7 @@ pub unsafe extern "C" fn wallet_manager_add_wallet_from_mnemonic_return_serializ ptr::copy_nonoverlapping(wallet_id.as_ptr(), wallet_id_out, 32); } - FFIError::set_success(error); + (*error).clean(); true } @@ -420,7 +304,7 @@ pub unsafe extern "C" fn wallet_manager_free_wallet_bytes(wallet_bytes: *mut u8, /// - `wallet_bytes` must be a valid pointer to bincode-serialized wallet bytes /// - `wallet_bytes_len` must be the exact length of the wallet bytes /// - `wallet_id_out` must be a valid pointer to a 32-byte array that will receive the wallet ID -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[cfg(feature = "bincode")] #[no_mangle] @@ -431,25 +315,11 @@ pub unsafe extern "C" fn wallet_manager_import_wallet_from_bytes( wallet_id_out: *mut u8, error: *mut FFIError, ) -> bool { - // Validate input parameters - if manager.is_null() - || wallet_bytes.is_null() - || wallet_bytes_len == 0 - || wallet_id_out.is_null() - { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Null pointer or invalid length provided".to_string(), - ); - return false; - } - - // Create a byte slice from the raw pointer - let wallet_bytes_slice = unsafe { std::slice::from_raw_parts(wallet_bytes, wallet_bytes_len) }; + let manager_ref = deref_ptr!(manager, error); + check_ptr!(wallet_bytes, error); + check_ptr!(wallet_id_out, error); - // Get the manager reference - let manager_ref = unsafe { &*manager }; + let wallet_bytes_slice = std::slice::from_raw_parts(wallet_bytes, wallet_bytes_len); // Import the wallet using async runtime let result = manager_ref.runtime.block_on(async { @@ -457,44 +327,12 @@ pub unsafe extern "C" fn wallet_manager_import_wallet_from_bytes( manager_guard.import_wallet_from_bytes(wallet_bytes_slice) }); - match result { - Ok(wallet_id) => { - // Copy the wallet ID to the output buffer - unsafe { - ptr::copy_nonoverlapping(wallet_id.as_ptr(), wallet_id_out, 32); - } - - FFIError::set_success(error); - true - } - Err(e) => { - // Convert the error to FFI error - match e { - key_wallet_manager::WalletError::WalletExists(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidState, - "Wallet already exists in the manager".to_string(), - ); - } - key_wallet_manager::WalletError::InvalidParameter(msg) => { - FFIError::set_error( - error, - FFIErrorCode::SerializationError, - format!("Failed to deserialize wallet: {}", msg), - ); - } - _ => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to import wallet: {:?}", e), - ); - } - } - false - } + let wallet_id = unwrap_or_return!(result, error); + // Copy the wallet ID to the output buffer + unsafe { + ptr::copy_nonoverlapping(wallet_id.as_ptr(), wallet_id_out, 32); } + true } /// Get wallet IDs @@ -504,7 +342,7 @@ pub unsafe extern "C" fn wallet_manager_import_wallet_from_bytes( /// - `manager` must be a valid pointer to an FFIWalletManager /// - `wallet_ids_out` must be a valid pointer to a pointer that will receive the wallet IDs /// - `count_out` must be a valid pointer to receive the count -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn wallet_manager_get_wallet_ids( @@ -513,12 +351,9 @@ pub unsafe extern "C" fn wallet_manager_get_wallet_ids( count_out: *mut usize, error: *mut FFIError, ) -> bool { - if manager.is_null() || wallet_ids_out.is_null() || count_out.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } - - let manager_ref = &*manager; + let manager_ref = deref_ptr!(manager, error); + check_ptr!(wallet_ids_out, error); + check_ptr!(count_out, error); // Get wallet IDs from the manager let wallet_ids = manager_ref.runtime.block_on(async { @@ -543,8 +378,6 @@ pub unsafe extern "C" fn wallet_manager_get_wallet_ids( *wallet_ids_out = ids_ptr; *count_out = count; } - - FFIError::set_success(error); true } @@ -556,7 +389,7 @@ pub unsafe extern "C" fn wallet_manager_get_wallet_ids( /// /// - `manager` must be a valid pointer to an FFIWalletManager instance /// - `wallet_id` must be a valid pointer to a 32-byte wallet ID -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call /// - The returned wallet must be freed with wallet_free_const() #[no_mangle] @@ -565,42 +398,18 @@ pub unsafe extern "C" fn wallet_manager_get_wallet( wallet_id: *const u8, error: *mut FFIError, ) -> *const crate::types::FFIWallet { - if manager.is_null() || wallet_id.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null(); - } + let manager_ref = deref_ptr!(manager, error); + check_ptr!(wallet_id, error); - // Convert wallet_id pointer to array let mut wallet_id_array = [0u8; 32]; - unsafe { - ptr::copy_nonoverlapping(wallet_id, wallet_id_array.as_mut_ptr(), 32); - } - - // Get the manager - let manager_ref = unsafe { &*manager }; + ptr::copy_nonoverlapping(wallet_id, wallet_id_array.as_mut_ptr(), 32); - // Get the wallet using async runtime let wallet_opt = manager_ref.runtime.block_on(async { let manager_guard = manager_ref.manager.read().await; manager_guard.get_wallet(&wallet_id_array).cloned() }); - - // Return the wallet - match wallet_opt { - Some(wallet) => { - // Create an FFIWallet wrapper - // Note: We need to store this somewhere that will outlive this function - // For now, we'll return a raw pointer to the wallet - // In a real implementation, you might want to store these in the FFIWalletManager - let ffi_wallet = Box::new(crate::types::FFIWallet::new(wallet.clone())); - FFIError::set_success(error); - Box::into_raw(ffi_wallet) - } - None => { - FFIError::set_error(error, FFIErrorCode::NotFound, "Wallet not found".to_string()); - ptr::null() - } - } + let wallet = unwrap_or_return!(wallet_opt, error); + Box::into_raw(Box::new(crate::types::FFIWallet::new(wallet))) } /// Get managed wallet info from the manager @@ -611,7 +420,7 @@ pub unsafe extern "C" fn wallet_manager_get_wallet( /// /// - `manager` must be a valid pointer to an FFIWalletManager instance /// - `wallet_id` must be a valid pointer to a 32-byte wallet ID -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call /// - The returned managed wallet info must be freed with managed_wallet_info_free() #[no_mangle] @@ -620,40 +429,18 @@ pub unsafe extern "C" fn wallet_manager_get_managed_wallet_info( wallet_id: *const u8, error: *mut FFIError, ) -> *mut crate::managed_wallet::FFIManagedWalletInfo { - if manager.is_null() || wallet_id.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } + let manager_ref = deref_ptr!(manager, error); + check_ptr!(wallet_id, error); - // Convert wallet_id pointer to array let mut wallet_id_array = [0u8; 32]; - unsafe { - ptr::copy_nonoverlapping(wallet_id, wallet_id_array.as_mut_ptr(), 32); - } - - // Get the manager - let manager_ref = unsafe { &*manager }; + ptr::copy_nonoverlapping(wallet_id, wallet_id_array.as_mut_ptr(), 32); - // Get the wallet info using async runtime let wallet_info_opt = manager_ref.runtime.block_on(async { let manager_guard = manager_ref.manager.read().await; manager_guard.get_wallet_info(&wallet_id_array).cloned() }); - - // Return the wallet info - match wallet_info_opt { - Some(wallet_info) => { - // Create an FFIManagedWalletInfo wrapper - let ffi_wallet_info = - Box::new(crate::managed_wallet::FFIManagedWalletInfo::new(wallet_info.clone())); - FFIError::set_success(error); - Box::into_raw(ffi_wallet_info) - } - None => { - FFIError::set_error(error, FFIErrorCode::NotFound, "Wallet info not found".to_string()); - ptr::null_mut() - } - } + let wallet_info = unwrap_or_return!(wallet_info_opt, error); + Box::into_raw(Box::new(crate::managed_wallet::FFIManagedWalletInfo::new(wallet_info))) } /// Get wallet balance @@ -666,7 +453,7 @@ pub unsafe extern "C" fn wallet_manager_get_managed_wallet_info( /// - `wallet_id` must be a valid pointer to a 32-byte wallet ID /// - `confirmed_out` must be a valid pointer to a u64 (maps to C uint64_t) /// - `unconfirmed_out` must be a valid pointer to a u64 (maps to C uint64_t) -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn wallet_manager_get_wallet_balance( @@ -676,48 +463,22 @@ pub unsafe extern "C" fn wallet_manager_get_wallet_balance( unconfirmed_out: *mut u64, error: *mut FFIError, ) -> bool { - if manager.is_null() - || wallet_id.is_null() - || confirmed_out.is_null() - || unconfirmed_out.is_null() - { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } + let manager_ref = deref_ptr!(manager, error); + check_ptr!(wallet_id, error); + check_ptr!(confirmed_out, error); + check_ptr!(unconfirmed_out, error); - // Convert wallet_id pointer to array let mut wallet_id_array = [0u8; 32]; - unsafe { - ptr::copy_nonoverlapping(wallet_id, wallet_id_array.as_mut_ptr(), 32); - } - - // Get the manager - let manager_ref = unsafe { &*manager }; + ptr::copy_nonoverlapping(wallet_id, wallet_id_array.as_mut_ptr(), 32); - // Get the wallet balance using async runtime let result = manager_ref.runtime.block_on(async { let manager_guard = manager_ref.manager.read().await; manager_guard.get_wallet_balance(&wallet_id_array) }); - - match result { - Ok(balance) => { - unsafe { - *confirmed_out = balance.confirmed(); - *unconfirmed_out = balance.unconfirmed(); - } - FFIError::set_success(error); - true - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to get wallet balance: {}", e), - ); - false - } - } + let balance = unwrap_or_return!(result, error); + *confirmed_out = balance.confirmed(); + *unconfirmed_out = balance.unconfirmed(); + true } /// Process a transaction through all wallets @@ -732,7 +493,7 @@ pub unsafe extern "C" fn wallet_manager_get_wallet_balance( /// - `tx_len` must be the length of the transaction bytes /// - `context` must be a valid pointer to FFITransactionContext /// - `update_state_if_found` indicates whether to update wallet state when transaction is relevant -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn wallet_manager_process_transaction( @@ -743,50 +504,29 @@ pub unsafe extern "C" fn wallet_manager_process_transaction( update_state_if_found: bool, error: *mut FFIError, ) -> bool { - if manager.is_null() || tx_bytes.is_null() || tx_len == 0 || context.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Null pointer or empty transaction provided".to_string(), - ); - return false; - } + let manager_ref = deref_ptr_mut!(manager, error); + check_ptr!(tx_bytes, error); + check_ptr!(context, error); - // Convert transaction bytes to slice - let tx_slice = unsafe { std::slice::from_raw_parts(tx_bytes, tx_len) }; + let tx_slice = std::slice::from_raw_parts(tx_bytes, tx_len); - // Deserialize the transaction use dashcore::blockdata::transaction::Transaction; use dashcore::consensus::encode::deserialize; - let tx: Transaction = match deserialize(tx_slice) { - Ok(tx) => tx, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - format!("Failed to deserialize transaction: {}", e), - ); - return false; - } - }; + let tx: Transaction = unwrap_or_return!(deserialize::(tx_slice), error); // Convert FFI context to native TransactionContext let context = match unsafe { (*context).to_transaction_context() } { Some(ctx) => ctx, None => { - FFIError::set_error( - error, + (*error).set( FFIErrorCode::InvalidInput, - "Block info must not be zeroed for confirmed contexts".to_string(), + "Block info must not be zeroed for confirmed contexts", ); return false; } }; - // Get the manager - let manager_ref = unsafe { &mut *manager }; - // Process the transaction using async runtime let result = manager_ref.runtime.block_on(async { let mut manager_guard = manager_ref.manager.write().await; @@ -795,7 +535,7 @@ pub unsafe extern "C" fn wallet_manager_process_transaction( .await }); - FFIError::set_success(error); + (*error).clean(); !result.affected_wallets.is_empty() } @@ -804,20 +544,14 @@ pub unsafe extern "C" fn wallet_manager_process_transaction( /// # Safety /// /// - `manager` must be a valid pointer to an FFIWalletManager -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn wallet_manager_network( manager: *const FFIWalletManager, error: *mut FFIError, ) -> FFINetwork { - if manager.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Manager is null".to_string()); - return FFINetwork::Mainnet; // Default fallback - } - - let manager_ref = &*manager; - FFIError::set_success(error); + let manager_ref = deref_ptr!(manager, error, FFINetwork::Mainnet); manager_ref.network() } @@ -826,28 +560,18 @@ pub unsafe extern "C" fn wallet_manager_network( /// # Safety /// /// - `manager` must be a valid pointer to an FFIWalletManager -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn wallet_manager_current_height( manager: *const FFIWalletManager, error: *mut FFIError, ) -> c_uint { - if manager.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Manager is null".to_string()); - return 0; - } - - let manager_ref = &*manager; - - // Get current height from network state if it exists - let height = manager_ref.runtime.block_on(async { + let manager_ref = deref_ptr!(manager, error); + manager_ref.runtime.block_on(async { let manager_guard = manager_ref.manager.read().await; manager_guard.synced_height() - }); - - FFIError::set_success(error); - height + }) } /// Get wallet count @@ -855,29 +579,18 @@ pub unsafe extern "C" fn wallet_manager_current_height( /// # Safety /// /// - `manager` must be a valid pointer to an FFIWalletManager instance -/// - `error` must be a valid pointer to an FFIError structure or null +/// - `error` must be a valid pointer to an FFIError structure /// - The caller must ensure all pointers remain valid for the duration of this call #[no_mangle] pub unsafe extern "C" fn wallet_manager_wallet_count( manager: *const FFIWalletManager, error: *mut FFIError, ) -> usize { - if manager.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Manager is null".to_string()); - return 0; - } - - unsafe { - let manager_ref = &*manager; - - let count = manager_ref.runtime.block_on(async { - let manager_guard = manager_ref.manager.read().await; - manager_guard.wallet_count() - }); - - FFIError::set_success(error); - count - } + let manager_ref = deref_ptr!(manager, error); + manager_ref.runtime.block_on(async { + let manager_guard = manager_ref.manager.read().await; + manager_guard.wallet_count() + }) } /// Free wallet manager diff --git a/key-wallet-ffi/src/wallet_manager_serialization_tests.rs b/key-wallet-ffi/src/wallet_manager_serialization_tests.rs index 0979d9abc..dd80c290f 100644 --- a/key-wallet-ffi/src/wallet_manager_serialization_tests.rs +++ b/key-wallet-ffi/src/wallet_manager_serialization_tests.rs @@ -14,11 +14,11 @@ mod tests { #[test] fn test_create_wallet_return_serialized_bytes_full_wallet() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Create a wallet manager - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); let mnemonic = CString::new(TEST_MNEMONIC).unwrap(); @@ -60,17 +60,16 @@ mod tests { wallet_bytes_len_out, ); wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } #[test] fn test_create_wallet_return_serialized_bytes_watch_only() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Create a wallet manager - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); let mnemonic = CString::new(TEST_MNEMONIC).unwrap(); @@ -109,17 +108,16 @@ mod tests { wallet_bytes_len_out, ); wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } #[test] fn test_create_wallet_return_serialized_bytes_externally_signable() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Create a wallet manager - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); let mnemonic = CString::new(TEST_MNEMONIC).unwrap(); @@ -158,17 +156,16 @@ mod tests { wallet_bytes_len_out, ); wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } #[test] fn test_create_wallet_with_passphrase() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Create a wallet manager - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); let mnemonic = CString::new(TEST_MNEMONIC).unwrap(); @@ -207,17 +204,16 @@ mod tests { wallet_bytes_len_out, ); wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } #[test] fn test_import_serialized_wallet() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Create a wallet manager - let manager1 = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager1 = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager1.is_null()); let mnemonic = CString::new(TEST_MNEMONIC).unwrap(); @@ -248,7 +244,7 @@ mod tests { assert!(!wallet_bytes_out.is_null()); // Now import the wallet into a new manager - let manager2 = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager2 = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager2.is_null()); let mut imported_wallet_id = [0u8; 32]; @@ -276,17 +272,16 @@ mod tests { ); wallet_manager::wallet_manager_free(manager1); wallet_manager::wallet_manager_free(manager2); - (*error).free_message(); } } #[test] fn test_invalid_mnemonic() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Create a wallet manager - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); let invalid_mnemonic = CString::new("invalid mnemonic phrase").unwrap(); @@ -320,17 +315,16 @@ mod tests { // Clean up unsafe { wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } #[test] fn test_null_mnemonic() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Create a wallet manager - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); let mut wallet_bytes_out: *mut u8 = ptr::null_mut(); @@ -359,7 +353,6 @@ mod tests { // Clean up unsafe { wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } @@ -367,11 +360,11 @@ mod tests { fn test_create_wallet_with_custom_account_options() { use crate::types::FFIAccountCreationOptionType; - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Create a wallet manager - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); let mnemonic = CString::new(TEST_MNEMONIC).unwrap(); @@ -428,7 +421,6 @@ mod tests { wallet_bytes_len_out, ); wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } } diff --git a/key-wallet-ffi/src/wallet_manager_tests.rs b/key-wallet-ffi/src/wallet_manager_tests.rs index 07e074236..5c1ba66a5 100644 --- a/key-wallet-ffi/src/wallet_manager_tests.rs +++ b/key-wallet-ffi/src/wallet_manager_tests.rs @@ -18,11 +18,11 @@ mod tests { #[test] fn test_wallet_manager_creation() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Create a wallet manager - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert_eq!(unsafe { (*manager).network() }, FFINetwork::Testnet); assert!(!manager.is_null()); @@ -35,16 +35,15 @@ mod tests { // Clean up unsafe { wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } #[test] fn test_add_wallet_from_mnemonic() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); // Add a wallet from mnemonic @@ -70,16 +69,15 @@ mod tests { // Clean up unsafe { wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } #[test] fn test_get_wallet_ids() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); // Add multiple wallets @@ -146,16 +144,15 @@ mod tests { unsafe { wallet_manager::wallet_manager_free_wallet_ids(wallet_ids, count); wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } #[test] fn test_wallet_balance() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); // Add a wallet @@ -206,13 +203,12 @@ mod tests { unsafe { wallet_manager::wallet_manager_free_wallet_ids(wallet_ids, count); wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } #[test] fn test_error_handling() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test with null manager @@ -221,7 +217,7 @@ mod tests { assert_eq!(unsafe { (*error).code }, FFIErrorCode::InvalidInput); // Test with invalid mnemonic - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); let invalid_mnemonic = CString::new("invalid mnemonic").unwrap(); @@ -234,23 +230,20 @@ mod tests { ) }; assert!(!success); - // The WalletManager returns WalletError for invalid mnemonics, not InvalidMnemonic - // because it wraps the mnemonic error in a WalletCreation error - assert_eq!(unsafe { (*error).code }, FFIErrorCode::WalletError); + assert_eq!(unsafe { (*error).code }, FFIErrorCode::InvalidMnemonic); // Clean up unsafe { wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } #[test] fn test_wallet_manager_add_wallet_with_account_count() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); // Add a wallet with account count @@ -274,16 +267,15 @@ mod tests { // Clean up unsafe { wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } #[test] fn test_wallet_manager_get_wallet() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); // Add a wallet @@ -327,16 +319,15 @@ mod tests { unsafe { wallet_manager::wallet_manager_free_wallet_ids(wallet_ids, id_count); wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } #[test] fn test_wallet_manager_get_wallet_balance() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); // Add wallet @@ -387,7 +378,6 @@ mod tests { unsafe { wallet_manager::wallet_manager_free_wallet_ids(wallet_ids, id_count); wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } @@ -395,7 +385,7 @@ mod tests { #[test] fn test_wallet_manager_null_inputs() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test null manager operations @@ -430,8 +420,6 @@ mod tests { ) }; assert!(!success); - - unsafe { (*error).free_message() }; } #[test] @@ -444,10 +432,10 @@ mod tests { #[test] fn test_wallet_manager_synced_height() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); // Get initial height @@ -472,16 +460,15 @@ mod tests { // Clean up unsafe { wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } #[test] fn test_wallet_manager_get_wallet_balance_implementation() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); // Add a wallet from mnemonic @@ -586,22 +573,21 @@ mod tests { ) }; assert!(!success); - assert_eq!(unsafe { (*error).code }, FFIErrorCode::WalletError); + assert_eq!(unsafe { (*error).code }, FFIErrorCode::NotFound); // Clean up unsafe { wallet_manager::wallet_manager_free_wallet_ids(wallet_ids, id_count); wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } #[test] fn test_wallet_manager_process_transaction() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); // Add a wallet from mnemonic @@ -748,16 +734,15 @@ mod tests { // Clean up unsafe { wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } #[test] fn test_wallet_manager_get_wallet_and_info() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); // Add a wallet from mnemonic @@ -857,18 +842,17 @@ mod tests { wallet_manager::wallet_manager_free_wallet_ids(wallet_ids, id_count); // Free the manager wallet_manager::wallet_manager_free(manager); - (*error).free_message(); } } #[cfg(feature = "bincode")] #[test] fn test_create_wallet_from_mnemonic_return_serialized_bytes() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Create a wallet manager - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); // Test basic wallet creation and serialization @@ -913,7 +897,7 @@ mod tests { } // Test with downgrade to watch-only wallet (create new manager to avoid duplicate wallet ID) - let manager2 = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager2 = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager2.is_null()); let mut wallet_bytes_out: *mut u8 = ptr::null_mut(); @@ -952,7 +936,7 @@ mod tests { assert_eq!(wallet_id_out, original_wallet_id); // Import the watch-only wallet to verify it works (create third manager for import) - let manager3 = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager3 = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager3.is_null()); let wallet_bytes_slice = @@ -983,7 +967,7 @@ mod tests { } // Test with externally signable wallet (create fourth manager) - let manager4 = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager4 = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager4.is_null()); let mut wallet_bytes_out: *mut u8 = ptr::null_mut(); @@ -1021,7 +1005,7 @@ mod tests { } // Test with invalid mnemonic (create fifth manager) - let manager5 = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager5 = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager5.is_null()); let invalid_mnemonic = CString::new("invalid mnemonic phrase").unwrap(); @@ -1055,18 +1039,17 @@ mod tests { crate::wallet_manager::wallet_manager_free(manager); crate::wallet_manager::wallet_manager_free(manager4); crate::wallet_manager::wallet_manager_free(manager5); - (*error).free_message(); } } #[cfg(feature = "bincode")] #[test] fn test_serialized_wallet_across_managers() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Create first wallet manager - let manager1 = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager1 = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager1.is_null()); let mnemonic = CString::new(TEST_MNEMONIC).unwrap(); @@ -1115,7 +1098,7 @@ mod tests { } // Create a completely new wallet manager - let manager2 = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager2 = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager2.is_null()); // Import the wallet using the serialized bytes in the new manager @@ -1156,7 +1139,6 @@ mod tests { wallet_bytes_len_out, ); crate::wallet_manager::wallet_manager_free(manager2); - (*error).free_message(); } } } diff --git a/key-wallet-ffi/src/wallet_tests.rs b/key-wallet-ffi/src/wallet_tests.rs index 0f360b2b0..0340be035 100644 --- a/key-wallet-ffi/src/wallet_tests.rs +++ b/key-wallet-ffi/src/wallet_tests.rs @@ -15,7 +15,7 @@ mod wallet_tests { #[test] fn test_wallet_creation_from_mnemonic() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let mnemonic = CString::new(TEST_MNEMONIC).unwrap(); @@ -36,13 +36,12 @@ mod wallet_tests { // Clean up unsafe { wallet::wallet_free(wallet); - (*error).free_message(); } } #[test] fn test_wallet_creation_from_seed() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let seed = [0x01u8; 64]; @@ -57,13 +56,12 @@ mod wallet_tests { // Clean up unsafe { wallet::wallet_free(wallet); - (*error).free_message(); } } #[test] fn test_wallet_creation_methods() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test random wallet creation @@ -78,13 +76,12 @@ mod wallet_tests { // Clean up unsafe { wallet::wallet_free(random_wallet); - (*error).free_message(); } } #[test] fn test_wallet_multiple_accounts() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let seed = [0x03u8; 64]; @@ -105,14 +102,12 @@ mod wallet_tests { // Clean up wallet::wallet_free(wallet); } - - (*error).free_message(); } } #[test] fn test_wallet_with_passphrase() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let mnemonic = CString::new(TEST_MNEMONIC).unwrap(); @@ -133,13 +128,12 @@ mod wallet_tests { // Clean up unsafe { wallet::wallet_free(wallet); - (*error).free_message(); } } #[test] fn test_wallet_error_cases() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test with null mnemonic @@ -172,13 +166,11 @@ mod wallet_tests { unsafe { wallet::wallet_create_from_seed(ptr::null(), 64, FFINetwork::Testnet, error) }; assert!(wallet.is_null()); assert_eq!(unsafe { (*error).code }, FFIErrorCode::InvalidInput); - - unsafe { (*error).free_message() }; } #[test] fn test_wallet_id_operations() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let wallet = unsafe { wallet::wallet_create_random(FFINetwork::Testnet, error) }; @@ -200,13 +192,12 @@ mod wallet_tests { // Clean up unsafe { wallet::wallet_free(wallet); - (*error).free_message(); } } #[test] fn test_wallet_create_from_seed_bytes() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Create seed bytes directly @@ -227,13 +218,12 @@ mod wallet_tests { // Clean up unsafe { wallet::wallet_free(wallet); - (*error).free_message(); } } #[test] fn test_wallet_create_from_seed_bytes_null() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test with null seed bytes @@ -242,13 +232,11 @@ mod wallet_tests { assert!(wallet.is_null()); assert_eq!(unsafe { (*error).code }, FFIErrorCode::InvalidInput); - - unsafe { (*error).free_message() }; } #[test] fn test_wallet_has_mnemonic() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Create wallet from mnemonic @@ -273,26 +261,23 @@ mod wallet_tests { // Clean up unsafe { wallet::wallet_free(wallet_with_mnemonic); - (*error).free_message(); } } #[test] fn test_wallet_has_mnemonic_null() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test with null wallet let has_mnemonic = unsafe { wallet::wallet_has_mnemonic(ptr::null(), error) }; assert!(!has_mnemonic); assert_eq!(unsafe { (*error).code }, FFIErrorCode::InvalidInput); - - unsafe { (*error).free_message() }; } #[test] fn test_wallet_add_account() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let wallet = unsafe { wallet::wallet_create_random(FFINetwork::Testnet, error) }; @@ -322,7 +307,6 @@ mod wallet_tests { // Clean up unsafe { wallet::wallet_free(wallet); - (*error).free_message(); } } @@ -345,7 +329,7 @@ mod wallet_tests { #[test] fn test_wallet_create_edge_cases() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test creating from normal seed size @@ -379,13 +363,11 @@ mod wallet_tests { wallet::wallet_free(wallet); } } - - unsafe { (*error).free_message() }; } #[test] fn test_wallet_xpub_operations() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let wallet = unsafe { wallet::wallet_create_random(FFINetwork::Testnet, error) }; @@ -404,21 +386,18 @@ mod wallet_tests { unsafe { let _ = CString::from_raw(xpub); wallet::wallet_free(wallet); - (*error).free_message(); } } #[test] fn test_wallet_xpub_null_wallet() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test with null wallet let xpub = unsafe { wallet::wallet_get_xpub(ptr::null(), 0, error) }; assert!(xpub.is_null()); assert_eq!(unsafe { (*error).code }, FFIErrorCode::InvalidInput); - - unsafe { (*error).free_message() }; } #[test] diff --git a/key-wallet-ffi/tests/debug_wallet_add.rs b/key-wallet-ffi/tests/debug_wallet_add.rs index abb940568..e5409fd00 100644 --- a/key-wallet-ffi/tests/debug_wallet_add.rs +++ b/key-wallet-ffi/tests/debug_wallet_add.rs @@ -6,10 +6,10 @@ fn test_debug_wallet_add() { use key_wallet_ffi::wallet_manager; use std::ffi::CString; - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) }; assert!(!manager.is_null()); println!("Manager created successfully"); diff --git a/key-wallet-ffi/tests/integration_test.rs b/key-wallet-ffi/tests/integration_test.rs index c7fdb1adb..fe160dd40 100644 --- a/key-wallet-ffi/tests/integration_test.rs +++ b/key-wallet-ffi/tests/integration_test.rs @@ -12,11 +12,11 @@ const TEST_MNEMONIC: &str = #[test] fn test_full_wallet_workflow() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // 1. Generate a mnemonic - let mnemonic = key_wallet_ffi::mnemonic::mnemonic_generate(12, error); + let mnemonic = unsafe { key_wallet_ffi::mnemonic::mnemonic_generate(12, error) }; assert!(!mnemonic.is_null()); assert_eq!(unsafe { (*error).code }, FFIErrorCode::Success); @@ -25,7 +25,9 @@ fn test_full_wallet_workflow() { assert!(is_valid); // 3. Create wallet manager - let manager = key_wallet_ffi::wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { + key_wallet_ffi::wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) + }; assert!(!manager.is_null()); // 4. Add wallet to manager @@ -82,7 +84,7 @@ fn test_full_wallet_workflow() { #[test] fn test_seed_to_wallet_workflow() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // 1. Convert mnemonic to seed @@ -123,20 +125,22 @@ fn test_seed_to_wallet_workflow() { #[test] fn test_derivation_paths() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test BIP44 paths let mut path_buffer = vec![0u8; 256]; // Account path - let success = key_wallet_ffi::derivation::derivation_bip44_account_path( - FFINetwork::Mainnet, - 0, - path_buffer.as_mut_ptr() as *mut std::os::raw::c_char, - path_buffer.len(), - error, - ); + let success = unsafe { + key_wallet_ffi::derivation::derivation_bip44_account_path( + FFINetwork::Mainnet, + 0, + path_buffer.as_mut_ptr() as *mut std::os::raw::c_char, + path_buffer.len(), + error, + ) + }; assert!(success); let path_str = unsafe { @@ -148,15 +152,17 @@ fn test_derivation_paths() { // Payment path path_buffer.fill(0); - let success = key_wallet_ffi::derivation::derivation_bip44_payment_path( - FFINetwork::Mainnet, - 0, - false, - 5, - path_buffer.as_mut_ptr() as *mut std::os::raw::c_char, - path_buffer.len(), - error, - ); + let success = unsafe { + key_wallet_ffi::derivation::derivation_bip44_payment_path( + FFINetwork::Mainnet, + 0, + false, + 5, + path_buffer.as_mut_ptr() as *mut std::os::raw::c_char, + path_buffer.len(), + error, + ) + }; assert!(success); let path_str = unsafe { @@ -169,7 +175,7 @@ fn test_derivation_paths() { #[test] fn test_error_handling() { - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Test various error conditions @@ -211,6 +217,4 @@ fn test_error_handling() { }; assert!(wallet.is_null()); assert_eq!(unsafe { (*error).code }, FFIErrorCode::InvalidInput); - - unsafe { (*error).free_message() }; } diff --git a/key-wallet-ffi/tests/test_account_collection.rs b/key-wallet-ffi/tests/test_account_collection.rs index 7e18d9688..014e6a18a 100644 --- a/key-wallet-ffi/tests/test_account_collection.rs +++ b/key-wallet-ffi/tests/test_account_collection.rs @@ -5,6 +5,7 @@ use key_wallet_ffi::account::account_free; use key_wallet_ffi::account_collection::*; use key_wallet_ffi::types::{FFIAccountCreationOptionType, FFIWalletAccountCreationOptions}; use key_wallet_ffi::wallet::{wallet_create_from_mnemonic_with_options, wallet_free}; +use key_wallet_ffi::FFIError; use std::ffi::CString; use std::ptr; @@ -15,6 +16,7 @@ fn test_account_collection_comprehensive() { let mnemonic = CString::new( "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" ).unwrap(); + let error = &mut FFIError::default(); // Create wallet with various account types let account_options = FFIWalletAccountCreationOptions { @@ -38,12 +40,12 @@ fn test_account_collection_comprehensive() { ptr::null(), FFINetwork::Testnet, &account_options, - ptr::null_mut(), + error, ); assert!(!wallet.is_null()); // Get account collection for testnet - let collection = wallet_get_account_collection(wallet, ptr::null_mut()); + let collection = wallet_get_account_collection(wallet, error); assert!(!collection.is_null()); // Test account count @@ -148,6 +150,7 @@ fn test_account_collection_minimal() { let mnemonic = CString::new( "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" ).unwrap(); + let test = &mut FFIError::default(); // Create wallet with minimal accounts (default) let wallet = wallet_create_from_mnemonic_with_options( @@ -155,12 +158,12 @@ fn test_account_collection_minimal() { ptr::null(), FFINetwork::Testnet, ptr::null(), // Use default options - ptr::null_mut(), + test, ); assert!(!wallet.is_null()); // Get account collection - let collection = wallet_get_account_collection(wallet, ptr::null_mut()); + let collection = wallet_get_account_collection(wallet, test); assert!(!collection.is_null()); // Should have at least some default accounts diff --git a/key-wallet-ffi/tests/test_addr_simple.rs b/key-wallet-ffi/tests/test_addr_simple.rs index 1804267f4..c13002b88 100644 --- a/key-wallet-ffi/tests/test_addr_simple.rs +++ b/key-wallet-ffi/tests/test_addr_simple.rs @@ -4,7 +4,7 @@ use dashcore::ffi::FFINetwork; fn test_address_simple() { use key_wallet_ffi::error::FFIError; - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Create a wallet to get a valid address diff --git a/key-wallet-ffi/tests/test_error_conversions.rs b/key-wallet-ffi/tests/test_error_conversions.rs index d9af8a761..f1df4a5ec 100644 --- a/key-wallet-ffi/tests/test_error_conversions.rs +++ b/key-wallet-ffi/tests/test_error_conversions.rs @@ -3,9 +3,8 @@ use key_wallet_ffi::error::{FFIError, FFIErrorCode}; /// Helper to test an FFIError conversion and clean up the message -fn assert_ffi_error_code(mut ffi_err: FFIError, expected: FFIErrorCode) { +fn assert_ffi_error_code(ffi_err: FFIError, expected: FFIErrorCode) { assert_eq!(ffi_err.code, expected); - unsafe { ffi_err.free_message() }; } #[test] @@ -192,7 +191,7 @@ fn test_error_message_consistency() { #[test] fn test_ffi_error_success() { // Test creating a success FFIError - let err = FFIError::success(); + let err = FFIError::default(); assert_eq!(err.code, FFIErrorCode::Success); assert!(err.message.is_null()); } @@ -200,14 +199,11 @@ fn test_ffi_error_success() { #[test] fn test_ffi_error_with_message() { // Test creating an error with a message - let err = FFIError::error(FFIErrorCode::InvalidInput, "Test error".to_string()); - assert_eq!(err.code, FFIErrorCode::InvalidInput); - assert!(!err.message.is_null()); - - // Clean up the allocated message + let mut err = FFIError::default(); unsafe { - if !err.message.is_null() { - let _ = std::ffi::CString::from_raw(err.message); - } + err.set(FFIErrorCode::InvalidInput, "Test error"); } + + assert_eq!(err.code, FFIErrorCode::InvalidInput); + assert!(!err.message.is_null()); } diff --git a/key-wallet-ffi/tests/test_import_wallet.rs b/key-wallet-ffi/tests/test_import_wallet.rs index a91bf454d..dabd00eeb 100644 --- a/key-wallet-ffi/tests/test_import_wallet.rs +++ b/key-wallet-ffi/tests/test_import_wallet.rs @@ -14,7 +14,7 @@ mod tests { fn test_import_wallet_from_bytes() { unsafe { // Create a wallet manager - let mut error = FFIError::success(); + let mut error = FFIError::default(); let manager = wallet_manager_create(FFINetwork::Testnet, &mut error); assert_eq!(error.code, FFIErrorCode::Success); assert!(!manager.is_null()); @@ -67,7 +67,6 @@ mod tests { assert_eq!(error.code, FFIErrorCode::InvalidInput); // Clean up - error.free_message(); wallet_free_const(wallet_ptr); wallet_manager_free_wallet_ids(wallet_ids_ptr, count); wallet_manager_free(manager); diff --git a/key-wallet-ffi/tests/test_managed_account_collection.rs b/key-wallet-ffi/tests/test_managed_account_collection.rs index fdbcee635..5a16c6470 100644 --- a/key-wallet-ffi/tests/test_managed_account_collection.rs +++ b/key-wallet-ffi/tests/test_managed_account_collection.rs @@ -17,7 +17,7 @@ const TEST_MNEMONIC: &str = #[test] fn test_managed_account_collection_basic() { unsafe { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create wallet manager let manager = wallet_manager_create(FFINetwork::Testnet, &mut error); @@ -86,7 +86,7 @@ fn test_managed_account_collection_basic() { #[test] fn test_managed_account_collection_with_special_accounts() { unsafe { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create wallet manager let manager = wallet_manager_create(FFINetwork::Testnet, &mut error); @@ -214,7 +214,7 @@ fn test_managed_account_collection_summary() { unsafe { use std::ffi::CStr; - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create wallet manager let manager = wallet_manager_create(FFINetwork::Testnet, &mut error); @@ -295,7 +295,7 @@ fn test_managed_account_collection_summary() { #[test] fn test_managed_account_collection_summary_data() { unsafe { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create wallet manager let manager = wallet_manager_create(FFINetwork::Testnet, &mut error); @@ -396,14 +396,13 @@ fn test_managed_account_collection_summary_data() { #[test] fn test_managed_account_collection_null_safety() { unsafe { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Test with null manager let collection = managed_wallet_get_account_collection(ptr::null(), ptr::null(), &mut error); assert!(collection.is_null()); assert_eq!(error.code, FFIErrorCode::InvalidInput); - error.free_message(); // Test with null collection for various functions assert_eq!(managed_account_collection_count(ptr::null()), 0); @@ -421,7 +420,7 @@ fn test_managed_account_collection_null_safety() { #[test] fn test_managed_account_collection_nonexistent_accounts() { unsafe { - let mut error = FFIError::success(); + let mut error = FFIError::default(); // Create wallet manager let manager = wallet_manager_create(FFINetwork::Testnet, &mut error); diff --git a/key-wallet-ffi/tests/test_passphrase_wallets.rs b/key-wallet-ffi/tests/test_passphrase_wallets.rs index 3faddf146..627ac9404 100644 --- a/key-wallet-ffi/tests/test_passphrase_wallets.rs +++ b/key-wallet-ffi/tests/test_passphrase_wallets.rs @@ -9,7 +9,7 @@ use std::ffi::CString; fn test_ffi_wallet_create_from_mnemonic_with_passphrase() { // This test verifies that wallets with passphrases now work correctly through FFI - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); @@ -53,11 +53,13 @@ fn test_ffi_wallet_create_from_mnemonic_with_passphrase() { fn test_ffi_wallet_manager_add_wallet_with_passphrase() { // This test shows the issue when adding a wallet with passphrase to the wallet manager - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Create wallet manager - let manager = key_wallet_ffi::wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); + let manager = unsafe { + key_wallet_ffi::wallet_manager::wallet_manager_create(FFINetwork::Testnet, error) + }; assert!(!manager.is_null()); let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); @@ -106,7 +108,7 @@ fn test_ffi_wallet_manager_add_wallet_with_passphrase() { fn test_ffi_wallet_with_passphrase_ideal_workflow() { // This test demonstrates what the ideal workflow should be for wallets with passphrases - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; let mnemonic = CString::new("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about").unwrap(); @@ -143,7 +145,7 @@ fn test_ffi_wallet_with_passphrase_ideal_workflow() { fn test_demonstrate_passphrase_issue_with_account_creation() { // This test verifies that the passphrase wallet issue has been FIXED - let mut error = FFIError::success(); + let mut error = FFIError::default(); let error = &mut error as *mut FFIError; // Create two wallets: one without passphrase, one with