diff --git a/key-wallet-ffi/FFI_API.md b/key-wallet-ffi/FFI_API.md index 43163223c..e1f514166 100644 --- a/key-wallet-ffi/FFI_API.md +++ b/key-wallet-ffi/FFI_API.md @@ -4,7 +4,7 @@ This document provides a comprehensive reference for all FFI (Foreign Function I **Auto-generated**: This documentation is automatically generated from the source code. Do not edit manually. -**Total Functions**: 243 +**Total Functions**: 261 ## Table of Contents @@ -32,13 +32,14 @@ Functions: 2 ### Error Handling -Functions: 3 +Functions: 4 | Function | Description | Module | |----------|-------------|--------| | `account_result_free_error` | Free an account result's error message (if any) Note: This does NOT free the... | account | | `error_message_free` | Free an error message # Safety - `message` must be a valid pointer to a C... | error | -| `managed_account_result_free_error` | Free a managed account result's error message (if any) Note: This does NOT... | managed_account | +| `managed_core_account_result_free_error` | Free a managed account result's error message (if any) Note: This does NOT... | managed_account | +| `managed_platform_account_result_free_error` | Free a managed platform account result's error message (if any) Note: This... | managed_account | ### Wallet Manager @@ -68,7 +69,7 @@ Functions: 19 ### Wallet Operations -Functions: 63 +Functions: 65 | Function | Description | Module | |----------|-------------|--------| @@ -79,7 +80,7 @@ Functions: 63 | `key_wallet_derive_address_from_key` | Derive an address from a private key # Safety - `private_key` must be a... | derivation | | `key_wallet_derive_address_from_seed` | Derive an address from a seed at a specific derivation path # Safety -... | derivation | | `key_wallet_derive_private_key_from_seed` | Derive a private key from a seed at a specific derivation path # Safety -... | derivation | -| `managed_account_get_parent_wallet_id` | Get the parent wallet ID of a managed account Note: ManagedAccount doesn't... | managed_account | +| `managed_core_account_get_parent_wallet_id` | Get the parent wallet ID of a managed account Note: ManagedAccount doesn't... | managed_account | | `managed_wallet_check_transaction` | Check if a transaction belongs to the wallet This function checks a... | transaction_checking | | `managed_wallet_free` | Free managed wallet info # Safety - `managed_wallet` must be a valid... | managed_wallet | | `managed_wallet_generate_addresses_to_index` | Generate addresses up to a specific index in a pool This ensures that... | address_pool | @@ -94,6 +95,7 @@ Functions: 63 | `managed_wallet_get_dashpay_receiving_account` | Get a managed DashPay receiving funds account by composite key # Safety -... | managed_account | | `managed_wallet_get_next_bip44_change_address` | Get the next unused change address Generates the next unused change address... | managed_wallet | | `managed_wallet_get_next_bip44_receive_address` | Get the next unused receive address Generates the next unused receive... | managed_wallet | +| `managed_wallet_get_platform_payment_account` | Get a managed platform payment account from a managed wallet Platform... | managed_account | | `managed_wallet_get_top_up_account_with_registration_index` | Get a managed IdentityTopUp account with a specific registration index This... | managed_account | | `managed_wallet_get_utxos` | Get all UTXOs from managed wallet info # Safety - `managed_info` must be a... | utxo | | `managed_wallet_info_free` | Free managed wallet info returned by wallet_manager_get_managed_wallet_info ... | managed_wallet | @@ -105,6 +107,7 @@ Functions: 63 | `wallet_add_account_with_xpub_bytes` | Add an account to the wallet with xpub as byte array # Safety This... | wallet | | `wallet_add_dashpay_external_account_with_xpub_bytes` | Add a DashPay external (watch-only) account with xpub bytes # Safety -... | wallet | | `wallet_add_dashpay_receiving_account` | Add a DashPay receiving funds account # Safety - `wallet` must be a valid... | wallet | +| `wallet_add_platform_payment_account` | Add a Platform Payment account (DIP-17) to the wallet Platform Payment... | wallet | | `wallet_build_and_sign_transaction` | Build and sign a transaction using the wallet's managed info This is the... | transaction | | `wallet_build_transaction` | Build a transaction (unsigned) This creates an unsigned transaction | transaction | | `wallet_check_transaction` | Check if a transaction belongs to the wallet using ManagedWalletInfo #... | transaction | @@ -138,7 +141,7 @@ Functions: 63 ### Account Management -Functions: 94 +Functions: 109 | Function | Description | Module | |----------|-------------|--------| @@ -198,6 +201,7 @@ Functions: 94 | `eddsa_account_get_network` | No description | account | | `managed_account_collection_count` | Get the total number of accounts in the managed collection # Safety -... | managed_account_collection | | `managed_account_collection_free` | Free a managed account collection handle # Safety - `collection` must be a... | managed_account_collection | +| `managed_account_collection_free_platform_payment_keys` | Free platform payment keys array returned by managed_account_collection_get_p... | managed_account_collection | | `managed_account_collection_get_bip32_account` | Get a BIP32 account by index from the managed collection # Safety -... | managed_account_collection | | `managed_account_collection_get_bip32_indices` | Get all BIP32 account indices from managed collection # Safety -... | managed_account_collection | | `managed_account_collection_get_bip44_account` | Get a BIP44 account by index from the managed collection # Safety -... | managed_account_collection | @@ -209,6 +213,8 @@ Functions: 94 | `managed_account_collection_get_identity_topup` | Get an identity topup account by registration index from managed collection ... | managed_account_collection | | `managed_account_collection_get_identity_topup_indices` | Get all identity topup registration indices from managed collection #... | managed_account_collection | | `managed_account_collection_get_identity_topup_not_bound` | Get the identity topup not bound account if it exists in managed collection ... | managed_account_collection | +| `managed_account_collection_get_platform_payment_account` | Get a Platform Payment account by account index and key class from the... | managed_account_collection | +| `managed_account_collection_get_platform_payment_keys` | Get all Platform Payment account keys from managed collection Returns an... | managed_account_collection | | `managed_account_collection_get_provider_operator_keys` | Get the provider operator keys account if it exists in managed collection... | managed_account_collection | | `managed_account_collection_get_provider_owner_keys` | Get the provider owner keys account if it exists in managed collection #... | managed_account_collection | | `managed_account_collection_get_provider_platform_keys` | Get the provider platform keys account if it exists in managed collection... | managed_account_collection | @@ -216,26 +222,38 @@ Functions: 94 | `managed_account_collection_has_identity_invitation` | Check if identity invitation account exists in managed collection # Safety ... | managed_account_collection | | `managed_account_collection_has_identity_registration` | Check if identity registration account exists in managed collection #... | managed_account_collection | | `managed_account_collection_has_identity_topup_not_bound` | Check if identity topup not bound account exists in managed collection #... | managed_account_collection | +| `managed_account_collection_has_platform_payment_accounts` | Check if there are any Platform Payment accounts in the managed collection ... | managed_account_collection | | `managed_account_collection_has_provider_operator_keys` | Check if provider operator keys account exists in managed collection #... | managed_account_collection | | `managed_account_collection_has_provider_owner_keys` | Check if provider owner keys account exists in managed collection # Safety ... | managed_account_collection | | `managed_account_collection_has_provider_platform_keys` | Check if provider platform keys account exists in managed collection #... | managed_account_collection | | `managed_account_collection_has_provider_voting_keys` | Check if provider voting keys account exists in managed collection # Safety... | managed_account_collection | +| `managed_account_collection_platform_payment_count` | Get the number of Platform Payment accounts in the managed collection #... | managed_account_collection | | `managed_account_collection_summary` | Get a human-readable summary of all accounts in the managed collection ... | managed_account_collection | | `managed_account_collection_summary_data` | Get structured account collection summary data for managed collection ... | managed_account_collection | | `managed_account_collection_summary_free` | Free a managed account collection summary and all its allocated memory #... | managed_account_collection | -| `managed_account_free` | Free a managed account handle # Safety - `account` must be a valid pointer... | managed_account | -| `managed_account_free_transactions` | Free transactions array returned by managed_account_get_transactions #... | managed_account | -| `managed_account_get_account_type` | Get the account type of a managed account # Safety - `account` must be a... | managed_account | -| `managed_account_get_address_pool` | Get an address pool from a managed account by type This function returns... | managed_account | -| `managed_account_get_balance` | Get the balance of a managed account # Safety - `account` must be a valid... | managed_account | -| `managed_account_get_external_address_pool` | Get the external address pool from a managed account This function returns... | managed_account | -| `managed_account_get_index` | Get the account index from a managed account Returns the primary account... | managed_account | -| `managed_account_get_internal_address_pool` | Get the internal address pool from a managed account This function returns... | managed_account | -| `managed_account_get_is_watch_only` | Check if a managed account is watch-only # Safety - `account` must be a... | managed_account | -| `managed_account_get_network` | Get the network of a managed account # Safety - `account` must be a valid... | managed_account | -| `managed_account_get_transaction_count` | Get the number of transactions in a managed account # Safety - `account`... | managed_account | -| `managed_account_get_transactions` | Get all transactions from a managed account Returns an array of... | managed_account | -| `managed_account_get_utxo_count` | Get the number of UTXOs in a managed account # Safety - `account` must be... | managed_account | +| `managed_core_account_free` | Free a managed account handle # Safety - `account` must be a valid pointer... | managed_account | +| `managed_core_account_free_transactions` | Free transactions array returned by managed_core_account_get_transactions #... | managed_account | +| `managed_core_account_get_account_type` | Get the account type of a managed account # Safety - `account` must be a... | managed_account | +| `managed_core_account_get_address_pool` | Get an address pool from a managed account by type This function returns... | managed_account | +| `managed_core_account_get_balance` | Get the balance of a managed account # Safety - `account` must be a valid... | managed_account | +| `managed_core_account_get_external_address_pool` | Get the external address pool from a managed account This function returns... | managed_account | +| `managed_core_account_get_index` | Get the account index from a managed account Returns the primary account... | managed_account | +| `managed_core_account_get_internal_address_pool` | Get the internal address pool from a managed account This function returns... | managed_account | +| `managed_core_account_get_is_watch_only` | Check if a managed account is watch-only # Safety - `account` must be a... | managed_account | +| `managed_core_account_get_network` | Get the network of a managed account # Safety - `account` must be a valid... | managed_account | +| `managed_core_account_get_transaction_count` | Get the number of transactions in a managed account # Safety - `account`... | managed_account | +| `managed_core_account_get_transactions` | Get all transactions from a managed account Returns an array of... | managed_account | +| `managed_core_account_get_utxo_count` | Get the number of UTXOs in a managed account # Safety - `account` must be... | managed_account | +| `managed_platform_account_free` | Free a managed platform account handle # Safety - `account` must be a... | managed_account | +| `managed_platform_account_get_account_index` | Get the account index of a managed platform account # Safety - `account`... | managed_account | +| `managed_platform_account_get_address_pool` | Get the address pool from a managed platform account Platform accounts only... | managed_account | +| `managed_platform_account_get_credit_balance` | Get the total credit balance of a managed platform account Returns the... | managed_account | +| `managed_platform_account_get_duff_balance` | Get the total balance in duffs of a managed platform account Returns the... | managed_account | +| `managed_platform_account_get_funded_address_count` | Get the number of funded addresses in a managed platform account # Safety ... | managed_account | +| `managed_platform_account_get_is_watch_only` | Check if a managed platform account is watch-only # Safety - `account`... | managed_account | +| `managed_platform_account_get_key_class` | Get the key class of a managed platform account # Safety - `account` must... | managed_account | +| `managed_platform_account_get_network` | Get the network of a managed platform account # Safety - `account` must be... | managed_account | +| `managed_platform_account_get_total_address_count` | Get the total number of addresses in a managed platform account # Safety -... | managed_account | ### Address Management @@ -398,17 +416,33 @@ Free an error message # Safety - `message` must be a valid pointer to a C stri --- -#### `managed_account_result_free_error` +#### `managed_core_account_result_free_error` ```c -managed_account_result_free_error(result: *mut FFIManagedAccountResult) -> () +managed_core_account_result_free_error(result: *mut FFIManagedCoreAccountResult,) -> () ``` **Description:** -Free a managed account result's error message (if any) Note: This does NOT free the account handle itself - use managed_account_free for that # Safety - `result` must be a valid pointer to an FFIManagedAccountResult - The error_message field must be either null or a valid CString allocated by this library - The caller must ensure the result pointer remains valid for the duration of this call +Free a managed account result's error message (if any) Note: This does NOT free the account handle itself - use managed_core_account_free for that # Safety - `result` must be a valid pointer to an FFIManagedCoreAccountResult - The error_message field must be either null or a valid CString allocated by this library - The caller must ensure the result pointer remains valid for the duration of this call **Safety:** -- `result` must be a valid pointer to an FFIManagedAccountResult - The error_message field must be either null or a valid CString allocated by this library - The caller must ensure the result pointer remains valid for the duration of this call +- `result` must be a valid pointer to an FFIManagedCoreAccountResult - The error_message field must be either null or a valid CString allocated by this library - The caller must ensure the result pointer remains valid for the duration of this call + +**Module:** `managed_account` + +--- + +#### `managed_platform_account_result_free_error` + +```c +managed_platform_account_result_free_error(result: *mut FFIManagedPlatformAccountResult,) -> () +``` + +**Description:** +Free a managed platform account result's error message (if any) Note: This does NOT free the account handle itself - use managed_platform_account_free for that # Safety - `result` must be a valid pointer to an FFIManagedPlatformAccountResult - The error_message field must be either null or a valid CString allocated by this library - The caller must ensure the result pointer remains valid for the duration of this call + +**Safety:** +- `result` must be a valid pointer to an FFIManagedPlatformAccountResult - The error_message field must be either null or a valid CString allocated by this library - The caller must ensure the result pointer remains valid for the duration of this call **Module:** `managed_account` @@ -801,10 +835,10 @@ Derive a private key from a seed at a specific derivation path # Safety - `seed --- -#### `managed_account_get_parent_wallet_id` +#### `managed_core_account_get_parent_wallet_id` ```c -managed_account_get_parent_wallet_id(wallet_id: *const u8) -> *const u8 +managed_core_account_get_parent_wallet_id(wallet_id: *const u8,) -> *const u8 ``` **Description:** @@ -868,14 +902,14 @@ Generate addresses up to a specific index in a pool This ensures that addresses #### `managed_wallet_get_account` ```c -managed_wallet_get_account(manager: *const FFIWalletManager, wallet_id: *const u8, account_index: c_uint, account_type: FFIAccountType,) -> FFIManagedAccountResult +managed_wallet_get_account(manager: *const FFIWalletManager, wallet_id: *const u8, account_index: c_uint, account_type: FFIAccountType,) -> FFIManagedCoreAccountResult ``` **Description:** -Get a managed account from a managed wallet This function gets a ManagedAccount from the wallet manager's managed wallet info, returning a managed account handle that wraps the ManagedAccount. # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - The caller must ensure all pointers remain valid for the duration of this call - The returned account must be freed with `managed_account_free` when no longer needed +Get a managed account from a managed wallet This function gets a ManagedAccount from the wallet manager's managed wallet info, returning a managed account handle that wraps the ManagedAccount. # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - The caller must ensure all pointers remain valid for the duration of this call - The returned account must be freed with `managed_core_account_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 - The caller must ensure all pointers remain valid for the duration of this call - The returned account must be freed with `managed_account_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 - The caller must ensure all pointers remain valid for the duration of this call - The returned account must be freed with `managed_core_account_free` when no longer needed **Module:** `managed_account` @@ -884,7 +918,7 @@ Get a managed account from a managed wallet This function gets a ManagedAccount #### `managed_wallet_get_account_collection` ```c -managed_wallet_get_account_collection(manager: *const FFIWalletManager, wallet_id: *const u8, error: *mut FFIError,) -> *mut FFIManagedAccountCollection +managed_wallet_get_account_collection(manager: *const FFIWalletManager, wallet_id: *const u8, error: *mut FFIError,) -> *mut FFIManagedCoreAccountCollection ``` **Description:** @@ -980,7 +1014,7 @@ Get BIP44 internal (change) addresses in the specified range Returns internal a #### `managed_wallet_get_dashpay_external_account` ```c -managed_wallet_get_dashpay_external_account(manager: *const FFIWalletManager, wallet_id: *const u8, account_index: c_uint, user_identity_id: *const u8, friend_identity_id: *const u8,) -> FFIManagedAccountResult +managed_wallet_get_dashpay_external_account(manager: *const FFIWalletManager, wallet_id: *const u8, account_index: c_uint, user_identity_id: *const u8, friend_identity_id: *const u8,) -> FFIManagedCoreAccountResult ``` **Description:** @@ -996,7 +1030,7 @@ Get a managed DashPay external account by composite key # Safety - Pointers mus #### `managed_wallet_get_dashpay_receiving_account` ```c -managed_wallet_get_dashpay_receiving_account(manager: *const FFIWalletManager, wallet_id: *const u8, account_index: c_uint, user_identity_id: *const u8, friend_identity_id: *const u8,) -> FFIManagedAccountResult +managed_wallet_get_dashpay_receiving_account(manager: *const FFIWalletManager, wallet_id: *const u8, account_index: c_uint, user_identity_id: *const u8, friend_identity_id: *const u8,) -> FFIManagedCoreAccountResult ``` **Description:** @@ -1041,17 +1075,33 @@ Get the next unused receive address Generates the next unused receive address f --- +#### `managed_wallet_get_platform_payment_account` + +```c +managed_wallet_get_platform_payment_account(manager: *const FFIWalletManager, wallet_id: *const u8, account_index: c_uint, key_class: c_uint,) -> FFIManagedPlatformAccountResult +``` + +**Description:** +Get a managed platform payment account from a managed wallet Platform Payment accounts (DIP-17) are identified by account index and key_class. Returns a platform account handle that wraps the ManagedPlatformAccount. # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - The caller must ensure all pointers remain valid for the duration of this call - The returned account must be freed with `managed_platform_account_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 - The caller must ensure all pointers remain valid for the duration of this call - The returned account must be freed with `managed_platform_account_free` when no longer needed + +**Module:** `managed_account` + +--- + #### `managed_wallet_get_top_up_account_with_registration_index` ```c -managed_wallet_get_top_up_account_with_registration_index(manager: *const FFIWalletManager, wallet_id: *const u8, registration_index: c_uint,) -> FFIManagedAccountResult +managed_wallet_get_top_up_account_with_registration_index(manager: *const FFIWalletManager, wallet_id: *const u8, registration_index: c_uint,) -> FFIManagedCoreAccountResult ``` **Description:** -Get a managed IdentityTopUp account with a specific registration index This is used for top-up accounts that are bound to a specific identity. Returns a managed account handle that wraps the ManagedAccount. # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - The caller must ensure all pointers remain valid for the duration of this call - The returned account must be freed with `managed_account_free` when no longer needed +Get a managed IdentityTopUp account with a specific registration index This is used for top-up accounts that are bound to a specific identity. Returns a managed account handle that wraps the ManagedAccount. # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - The caller must ensure all pointers remain valid for the duration of this call - The returned account must be freed with `managed_core_account_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 - The caller must ensure all pointers remain valid for the duration of this call - The returned account must be freed with `managed_account_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 - The caller must ensure all pointers remain valid for the duration of this call - The returned account must be freed with `managed_core_account_free` when no longer needed **Module:** `managed_account` @@ -1144,7 +1194,7 @@ wallet_add_account(wallet: *mut FFIWallet, account_type: crate::types::FFIAccoun ``` **Description:** -Add an account to the wallet without xpub # Safety This function dereferences a raw pointer to FFIWallet. The caller must ensure that: - The wallet pointer is either null or points to a valid FFIWallet - The FFIWallet remains valid for the duration of this call +Add an account to the wallet without xpub # Safety This function dereferences a raw pointer to FFIWallet. The caller must ensure that: - The wallet pointer is either null or points to a valid FFIWallet - The FFIWallet remains valid for the duration of this call # Note This function does NOT support the following account types: - `PlatformPayment`: Use `wallet_add_platform_payment_account()` instead - `DashpayReceivingFunds`: Use `wallet_add_dashpay_receiving_account()` instead - `DashpayExternalAccount`: Use `wallet_add_dashpay_external_account_with_xpub_bytes()` instead **Safety:** This function dereferences a raw pointer to FFIWallet. The caller must ensure that: - The wallet pointer is either null or points to a valid FFIWallet - The FFIWallet remains valid for the duration of this call @@ -1160,7 +1210,7 @@ wallet_add_account_with_string_xpub(wallet: *mut FFIWallet, account_type: crate: ``` **Description:** -Add an account to the wallet with xpub as string # Safety This function dereferences raw pointers. The caller must ensure that: - The wallet pointer is either null or points to a valid FFIWallet - The xpub_string pointer is either null or points to a valid null-terminated C string - The FFIWallet remains valid for the duration of this call +Add an account to the wallet with xpub as string # Safety This function dereferences raw pointers. The caller must ensure that: - The wallet pointer is either null or points to a valid FFIWallet - The xpub_string pointer is either null or points to a valid null-terminated C string - The FFIWallet remains valid for the duration of this call # Note This function does NOT support the following account types: - `PlatformPayment`: Use `wallet_add_platform_payment_account()` instead - `DashpayReceivingFunds`: Use `wallet_add_dashpay_receiving_account()` instead - `DashpayExternalAccount`: Use `wallet_add_dashpay_external_account_with_xpub_bytes()` instead **Safety:** This function dereferences raw pointers. The caller must ensure that: - The wallet pointer is either null or points to a valid FFIWallet - The xpub_string pointer is either null or points to a valid null-terminated C string - The FFIWallet remains valid for the duration of this call @@ -1176,7 +1226,7 @@ wallet_add_account_with_xpub_bytes(wallet: *mut FFIWallet, account_type: crate:: ``` **Description:** -Add an account to the wallet with xpub as byte array # Safety This function dereferences raw pointers. The caller must ensure that: - The wallet pointer is either null or points to a valid FFIWallet - The xpub_bytes pointer is either null or points to at least xpub_len bytes - The FFIWallet remains valid for the duration of this call +Add an account to the wallet with xpub as byte array # Safety This function dereferences raw pointers. The caller must ensure that: - The wallet pointer is either null or points to a valid FFIWallet - The xpub_bytes pointer is either null or points to at least xpub_len bytes - The FFIWallet remains valid for the duration of this call # Note This function does NOT support the following account types: - `PlatformPayment`: Use `wallet_add_platform_payment_account()` instead - `DashpayReceivingFunds`: Use `wallet_add_dashpay_receiving_account()` instead - `DashpayExternalAccount`: Use `wallet_add_dashpay_external_account_with_xpub_bytes()` instead **Safety:** This function dereferences raw pointers. The caller must ensure that: - The wallet pointer is either null or points to a valid FFIWallet - The xpub_bytes pointer is either null or points to at least xpub_len bytes - The FFIWallet remains valid for the duration of this call @@ -1217,6 +1267,22 @@ Add a DashPay receiving funds account # Safety - `wallet` must be a valid point --- +#### `wallet_add_platform_payment_account` + +```c +wallet_add_platform_payment_account(wallet: *mut FFIWallet, account_index: c_uint, key_class: c_uint,) -> crate::types::FFIAccountResult +``` + +**Description:** +Add a Platform Payment account (DIP-17) to the wallet Platform Payment accounts use the derivation path: `m/9'/coin_type'/17'/account'/key_class'/index` # Arguments * `wallet` - Pointer to the wallet * `account_index` - The account index (hardened) in the derivation path * `key_class` - The key class (hardened) - typically 0' for main addresses # Safety This function dereferences a raw pointer to FFIWallet. The caller must ensure that: - The wallet pointer is either null or points to a valid FFIWallet - The FFIWallet remains valid for the duration of this call + +**Safety:** +This function dereferences a raw pointer to FFIWallet. The caller must ensure that: - The wallet pointer is either null or points to a valid FFIWallet - The FFIWallet remains valid for the duration of this call + +**Module:** `wallet` + +--- + #### `wallet_build_and_sign_transaction` ```c @@ -2479,14 +2545,14 @@ eddsa_account_get_network(account: *const FFIEdDSAAccount) -> FFINetwork #### `managed_account_collection_count` ```c -managed_account_collection_count(collection: *const FFIManagedAccountCollection,) -> c_uint +managed_account_collection_count(collection: *const FFIManagedCoreAccountCollection,) -> c_uint ``` **Description:** -Get the total number of accounts in the managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection +Get the total number of accounts in the managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection **Module:** `managed_account_collection` @@ -2495,14 +2561,30 @@ Get the total number of accounts in the managed collection # Safety - `collect #### `managed_account_collection_free` ```c -managed_account_collection_free(collection: *mut FFIManagedAccountCollection,) -> () +managed_account_collection_free(collection: *mut FFIManagedCoreAccountCollection,) -> () ``` **Description:** -Free a managed account collection handle # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection created by this library - `collection` must not be used after calling this function +Free a managed account collection handle # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection created by this library - `collection` must not be used after calling this function **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection created by this library - `collection` must not be used after calling this function +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection created by this library - `collection` must not be used after calling this function + +**Module:** `managed_account_collection` + +--- + +#### `managed_account_collection_free_platform_payment_keys` + +```c +managed_account_collection_free_platform_payment_keys(keys: *mut crate::managed_account::FFIPlatformPaymentAccountKey, count: usize,) -> () +``` + +**Description:** +Free platform payment keys array returned by managed_account_collection_get_platform_payment_keys # Safety - `keys` must be a pointer returned by `managed_account_collection_get_platform_payment_keys` - `count` must be the count returned by `managed_account_collection_get_platform_payment_keys` - This function must only be called once per allocation + +**Safety:** +- `keys` must be a pointer returned by `managed_account_collection_get_platform_payment_keys` - `count` must be the count returned by `managed_account_collection_get_platform_payment_keys` - This function must only be called once per allocation **Module:** `managed_account_collection` @@ -2511,14 +2593,14 @@ Free a managed account collection handle # Safety - `collection` must be a val #### `managed_account_collection_get_bip32_account` ```c -managed_account_collection_get_bip32_account(collection: *const FFIManagedAccountCollection, index: c_uint,) -> *mut FFIManagedAccount +managed_account_collection_get_bip32_account(collection: *const FFIManagedCoreAccountCollection, index: c_uint,) -> *mut FFIManagedCoreAccount ``` **Description:** -Get a BIP32 account by index from the managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed +Get a BIP32 account by index from the managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Module:** `managed_account_collection` @@ -2527,14 +2609,14 @@ Get a BIP32 account by index from the managed collection # Safety - `collectio #### `managed_account_collection_get_bip32_indices` ```c -managed_account_collection_get_bip32_indices(collection: *const FFIManagedAccountCollection, out_indices: *mut *mut c_uint, out_count: *mut usize,) -> bool +managed_account_collection_get_bip32_indices(collection: *const FFIManagedCoreAccountCollection, out_indices: *mut *mut c_uint, out_count: *mut usize,) -> bool ``` **Description:** -Get all BIP32 account indices from managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed +Get all BIP32 account indices from managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed **Module:** `managed_account_collection` @@ -2543,14 +2625,14 @@ Get all BIP32 account indices from managed collection # Safety - `collection` #### `managed_account_collection_get_bip44_account` ```c -managed_account_collection_get_bip44_account(collection: *const FFIManagedAccountCollection, index: c_uint,) -> *mut FFIManagedAccount +managed_account_collection_get_bip44_account(collection: *const FFIManagedCoreAccountCollection, index: c_uint,) -> *mut FFIManagedCoreAccount ``` **Description:** -Get a BIP44 account by index from the managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed +Get a BIP44 account by index from the managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Module:** `managed_account_collection` @@ -2559,14 +2641,14 @@ Get a BIP44 account by index from the managed collection # Safety - `collectio #### `managed_account_collection_get_bip44_indices` ```c -managed_account_collection_get_bip44_indices(collection: *const FFIManagedAccountCollection, out_indices: *mut *mut c_uint, out_count: *mut usize,) -> bool +managed_account_collection_get_bip44_indices(collection: *const FFIManagedCoreAccountCollection, out_indices: *mut *mut c_uint, out_count: *mut usize,) -> bool ``` **Description:** -Get all BIP44 account indices from managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed +Get all BIP44 account indices from managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed **Module:** `managed_account_collection` @@ -2575,14 +2657,14 @@ Get all BIP44 account indices from managed collection # Safety - `collection` #### `managed_account_collection_get_coinjoin_account` ```c -managed_account_collection_get_coinjoin_account(collection: *const FFIManagedAccountCollection, index: c_uint,) -> *mut FFIManagedAccount +managed_account_collection_get_coinjoin_account(collection: *const FFIManagedCoreAccountCollection, index: c_uint,) -> *mut FFIManagedCoreAccount ``` **Description:** -Get a CoinJoin account by index from the managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed +Get a CoinJoin account by index from the managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Module:** `managed_account_collection` @@ -2591,14 +2673,14 @@ Get a CoinJoin account by index from the managed collection # Safety - `collec #### `managed_account_collection_get_coinjoin_indices` ```c -managed_account_collection_get_coinjoin_indices(collection: *const FFIManagedAccountCollection, out_indices: *mut *mut c_uint, out_count: *mut usize,) -> bool +managed_account_collection_get_coinjoin_indices(collection: *const FFIManagedCoreAccountCollection, out_indices: *mut *mut c_uint, out_count: *mut usize,) -> bool ``` **Description:** -Get all CoinJoin account indices from managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed +Get all CoinJoin account indices from managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed **Module:** `managed_account_collection` @@ -2607,14 +2689,14 @@ Get all CoinJoin account indices from managed collection # Safety - `collectio #### `managed_account_collection_get_identity_invitation` ```c -managed_account_collection_get_identity_invitation(collection: *const FFIManagedAccountCollection,) -> *mut FFIManagedAccount +managed_account_collection_get_identity_invitation(collection: *const FFIManagedCoreAccountCollection,) -> *mut FFIManagedCoreAccount ``` **Description:** -Get the identity invitation account if it exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed +Get the identity invitation account if it exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Module:** `managed_account_collection` @@ -2623,14 +2705,14 @@ Get the identity invitation account if it exists in managed collection # Safety #### `managed_account_collection_get_identity_registration` ```c -managed_account_collection_get_identity_registration(collection: *const FFIManagedAccountCollection,) -> *mut FFIManagedAccount +managed_account_collection_get_identity_registration(collection: *const FFIManagedCoreAccountCollection,) -> *mut FFIManagedCoreAccount ``` **Description:** -Get the identity registration account if it exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed +Get the identity registration account if it exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Module:** `managed_account_collection` @@ -2639,14 +2721,14 @@ Get the identity registration account if it exists in managed collection # Safe #### `managed_account_collection_get_identity_topup` ```c -managed_account_collection_get_identity_topup(collection: *const FFIManagedAccountCollection, registration_index: c_uint,) -> *mut FFIManagedAccount +managed_account_collection_get_identity_topup(collection: *const FFIManagedCoreAccountCollection, registration_index: c_uint,) -> *mut FFIManagedCoreAccount ``` **Description:** -Get an identity topup account by registration index from managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed +Get an identity topup account by registration index from managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Module:** `managed_account_collection` @@ -2655,14 +2737,14 @@ Get an identity topup account by registration index from managed collection # S #### `managed_account_collection_get_identity_topup_indices` ```c -managed_account_collection_get_identity_topup_indices(collection: *const FFIManagedAccountCollection, out_indices: *mut *mut c_uint, out_count: *mut usize,) -> bool +managed_account_collection_get_identity_topup_indices(collection: *const FFIManagedCoreAccountCollection, out_indices: *mut *mut c_uint, out_count: *mut usize,) -> bool ``` **Description:** -Get all identity topup registration indices from managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed +Get all identity topup registration indices from managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed **Module:** `managed_account_collection` @@ -2671,14 +2753,46 @@ Get all identity topup registration indices from managed collection # Safety - #### `managed_account_collection_get_identity_topup_not_bound` ```c -managed_account_collection_get_identity_topup_not_bound(collection: *const FFIManagedAccountCollection,) -> *mut FFIManagedAccount +managed_account_collection_get_identity_topup_not_bound(collection: *const FFIManagedCoreAccountCollection,) -> *mut FFIManagedCoreAccount ``` **Description:** -Get the identity topup not bound account if it exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - `manager` must be a valid pointer to an FFIWalletManager - The returned pointer must be freed with `managed_account_free` when no longer needed +Get the identity topup not bound account if it exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `manager` must be a valid pointer to an FFIWalletManager - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - `manager` must be a valid pointer to an FFIWalletManager - The returned pointer must be freed with `managed_account_free` when no longer needed +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `manager` must be a valid pointer to an FFIWalletManager - The returned pointer must be freed with `managed_core_account_free` when no longer needed + +**Module:** `managed_account_collection` + +--- + +#### `managed_account_collection_get_platform_payment_account` + +```c +managed_account_collection_get_platform_payment_account(collection: *const FFIManagedCoreAccountCollection, account_index: c_uint, key_class: c_uint,) -> *mut crate::managed_account::FFIManagedPlatformAccount +``` + +**Description:** +Get a Platform Payment account by account index and key class from the managed collection Platform Payment accounts (DIP-17) are identified by two indices: - account_index: The account' level in the derivation path - key_class: The key_class' level in the derivation path (typically 0) # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_platform_account_free` when no longer needed + +**Safety:** +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_platform_account_free` when no longer needed + +**Module:** `managed_account_collection` + +--- + +#### `managed_account_collection_get_platform_payment_keys` + +```c +managed_account_collection_get_platform_payment_keys(collection: *const FFIManagedCoreAccountCollection, out_keys: *mut *mut crate::managed_account::FFIPlatformPaymentAccountKey, out_count: *mut usize,) -> bool +``` + +**Description:** +Get all Platform Payment account keys from managed collection Returns an array of FFIPlatformPaymentAccountKey structures. # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `out_keys` must be a valid pointer to store the keys array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `managed_account_collection_free_platform_payment_keys` when no longer needed + +**Safety:** +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `out_keys` must be a valid pointer to store the keys array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `managed_account_collection_free_platform_payment_keys` when no longer needed **Module:** `managed_account_collection` @@ -2687,14 +2801,14 @@ Get the identity topup not bound account if it exists in managed collection # S #### `managed_account_collection_get_provider_operator_keys` ```c -managed_account_collection_get_provider_operator_keys(collection: *const FFIManagedAccountCollection,) -> *mut std::os::raw::c_void +managed_account_collection_get_provider_operator_keys(collection: *const FFIManagedCoreAccountCollection,) -> *mut std::os::raw::c_void ``` **Description:** -Get the provider operator keys account if it exists in managed collection Note: Returns null if the `bls` feature is not enabled # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed (when BLS is enabled) +Get the provider operator keys account if it exists in managed collection Note: Returns null if the `bls` feature is not enabled # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed (when BLS is enabled) **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed (when BLS is enabled) +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed (when BLS is enabled) **Module:** `managed_account_collection` @@ -2703,14 +2817,14 @@ Get the provider operator keys account if it exists in managed collection Note: #### `managed_account_collection_get_provider_owner_keys` ```c -managed_account_collection_get_provider_owner_keys(collection: *const FFIManagedAccountCollection,) -> *mut FFIManagedAccount +managed_account_collection_get_provider_owner_keys(collection: *const FFIManagedCoreAccountCollection,) -> *mut FFIManagedCoreAccount ``` **Description:** -Get the provider owner keys account if it exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed +Get the provider owner keys account if it exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Module:** `managed_account_collection` @@ -2719,14 +2833,14 @@ Get the provider owner keys account if it exists in managed collection # Safety #### `managed_account_collection_get_provider_platform_keys` ```c -managed_account_collection_get_provider_platform_keys(collection: *const FFIManagedAccountCollection,) -> *mut std::os::raw::c_void +managed_account_collection_get_provider_platform_keys(collection: *const FFIManagedCoreAccountCollection,) -> *mut std::os::raw::c_void ``` **Description:** -Get the provider platform keys account if it exists in managed collection Note: Returns null if the `eddsa` feature is not enabled # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed (when EdDSA is enabled) +Get the provider platform keys account if it exists in managed collection Note: Returns null if the `eddsa` feature is not enabled # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed (when EdDSA is enabled) **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed (when EdDSA is enabled) +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed (when EdDSA is enabled) **Module:** `managed_account_collection` @@ -2735,14 +2849,14 @@ Get the provider platform keys account if it exists in managed collection Note: #### `managed_account_collection_get_provider_voting_keys` ```c -managed_account_collection_get_provider_voting_keys(collection: *const FFIManagedAccountCollection,) -> *mut FFIManagedAccount +managed_account_collection_get_provider_voting_keys(collection: *const FFIManagedCoreAccountCollection,) -> *mut FFIManagedCoreAccount ``` **Description:** -Get the provider voting keys account if it exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed +Get the provider voting keys account if it exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_free` when no longer needed +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_core_account_free` when no longer needed **Module:** `managed_account_collection` @@ -2751,14 +2865,14 @@ Get the provider voting keys account if it exists in managed collection # Safet #### `managed_account_collection_has_identity_invitation` ```c -managed_account_collection_has_identity_invitation(collection: *const FFIManagedAccountCollection,) -> bool +managed_account_collection_has_identity_invitation(collection: *const FFIManagedCoreAccountCollection,) -> bool ``` **Description:** -Check if identity invitation account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection +Check if identity invitation account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection **Module:** `managed_account_collection` @@ -2767,14 +2881,14 @@ Check if identity invitation account exists in managed collection # Safety - ` #### `managed_account_collection_has_identity_registration` ```c -managed_account_collection_has_identity_registration(collection: *const FFIManagedAccountCollection,) -> bool +managed_account_collection_has_identity_registration(collection: *const FFIManagedCoreAccountCollection,) -> bool ``` **Description:** -Check if identity registration account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection +Check if identity registration account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection **Module:** `managed_account_collection` @@ -2783,14 +2897,30 @@ Check if identity registration account exists in managed collection # Safety - #### `managed_account_collection_has_identity_topup_not_bound` ```c -managed_account_collection_has_identity_topup_not_bound(collection: *const FFIManagedAccountCollection,) -> bool +managed_account_collection_has_identity_topup_not_bound(collection: *const FFIManagedCoreAccountCollection,) -> bool +``` + +**Description:** +Check if identity topup not bound account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + +**Safety:** +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + +**Module:** `managed_account_collection` + +--- + +#### `managed_account_collection_has_platform_payment_accounts` + +```c +managed_account_collection_has_platform_payment_accounts(collection: *const FFIManagedCoreAccountCollection,) -> bool ``` **Description:** -Check if identity topup not bound account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection +Check if there are any Platform Payment accounts in the managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection **Module:** `managed_account_collection` @@ -2799,14 +2929,14 @@ Check if identity topup not bound account exists in managed collection # Safety #### `managed_account_collection_has_provider_operator_keys` ```c -managed_account_collection_has_provider_operator_keys(collection: *const FFIManagedAccountCollection,) -> bool +managed_account_collection_has_provider_operator_keys(collection: *const FFIManagedCoreAccountCollection,) -> bool ``` **Description:** -Check if provider operator keys account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection +Check if provider operator keys account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection **Module:** `managed_account_collection` @@ -2815,14 +2945,14 @@ Check if provider operator keys account exists in managed collection # Safety #### `managed_account_collection_has_provider_owner_keys` ```c -managed_account_collection_has_provider_owner_keys(collection: *const FFIManagedAccountCollection,) -> bool +managed_account_collection_has_provider_owner_keys(collection: *const FFIManagedCoreAccountCollection,) -> bool ``` **Description:** -Check if provider owner keys account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection +Check if provider owner keys account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection **Module:** `managed_account_collection` @@ -2831,14 +2961,14 @@ Check if provider owner keys account exists in managed collection # Safety - ` #### `managed_account_collection_has_provider_platform_keys` ```c -managed_account_collection_has_provider_platform_keys(collection: *const FFIManagedAccountCollection,) -> bool +managed_account_collection_has_provider_platform_keys(collection: *const FFIManagedCoreAccountCollection,) -> bool ``` **Description:** -Check if provider platform keys account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection +Check if provider platform keys account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection **Module:** `managed_account_collection` @@ -2847,14 +2977,30 @@ Check if provider platform keys account exists in managed collection # Safety #### `managed_account_collection_has_provider_voting_keys` ```c -managed_account_collection_has_provider_voting_keys(collection: *const FFIManagedAccountCollection,) -> bool +managed_account_collection_has_provider_voting_keys(collection: *const FFIManagedCoreAccountCollection,) -> bool ``` **Description:** -Check if provider voting keys account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection +Check if provider voting keys account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + +**Module:** `managed_account_collection` + +--- + +#### `managed_account_collection_platform_payment_count` + +```c +managed_account_collection_platform_payment_count(collection: *const FFIManagedCoreAccountCollection,) -> c_uint +``` + +**Description:** +Get the number of Platform Payment accounts in the managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + +**Safety:** +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection **Module:** `managed_account_collection` @@ -2863,14 +3009,14 @@ Check if provider voting keys account exists in managed collection # Safety - #### `managed_account_collection_summary` ```c -managed_account_collection_summary(collection: *const FFIManagedAccountCollection,) -> *mut c_char +managed_account_collection_summary(collection: *const FFIManagedCoreAccountCollection,) -> *mut c_char ``` **Description:** -Get a human-readable summary of all accounts in the managed collection Returns a formatted string showing all account types and their indices. The format is designed to be clear and readable for end users. # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned string must be freed with `string_free` when no longer needed - Returns null if the collection pointer is null +Get a human-readable summary of all accounts in the managed collection Returns a formatted string showing all account types and their indices. The format is designed to be clear and readable for end users. # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned string must be freed with `string_free` when no longer needed - Returns null if the collection pointer is null **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned string must be freed with `string_free` when no longer needed - Returns null if the collection pointer is null +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned string must be freed with `string_free` when no longer needed - Returns null if the collection pointer is null **Module:** `managed_account_collection` @@ -2879,14 +3025,14 @@ Get a human-readable summary of all accounts in the managed collection Returns #### `managed_account_collection_summary_data` ```c -managed_account_collection_summary_data(collection: *const FFIManagedAccountCollection,) -> *mut FFIManagedAccountCollectionSummary +managed_account_collection_summary_data(collection: *const FFIManagedCoreAccountCollection,) -> *mut FFIManagedCoreAccountCollectionSummary ``` **Description:** -Get structured account collection summary data for managed collection Returns a struct containing arrays of indices for each account type and boolean flags for special accounts. This provides Swift with programmatic access to account information. # Safety - `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_collection_summary_free` when no longer needed - Returns null if the collection pointer is null +Get structured account collection summary data for managed collection Returns a struct containing arrays of indices for each account type and boolean flags for special accounts. This provides Swift with programmatic access to account information. # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_account_collection_summary_free` when no longer needed - Returns null if the collection pointer is null **Safety:** -- `collection` must be a valid pointer to an FFIManagedAccountCollection - The returned pointer must be freed with `managed_account_collection_summary_free` when no longer needed - Returns null if the collection pointer is null +- `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_account_collection_summary_free` when no longer needed - Returns null if the collection pointer is null **Module:** `managed_account_collection` @@ -2895,222 +3041,382 @@ Get structured account collection summary data for managed collection Returns a #### `managed_account_collection_summary_free` ```c -managed_account_collection_summary_free(summary: *mut FFIManagedAccountCollectionSummary,) -> () +managed_account_collection_summary_free(summary: *mut FFIManagedCoreAccountCollectionSummary,) -> () ``` **Description:** -Free a managed account collection summary and all its allocated memory # Safety - `summary` must be a valid pointer to an FFIManagedAccountCollectionSummary created by `managed_account_collection_summary_data` - `summary` must not be used after calling this function +Free a managed account collection summary and all its allocated memory # Safety - `summary` must be a valid pointer to an FFIManagedCoreAccountCollectionSummary created by `managed_account_collection_summary_data` - `summary` must not be used after calling this function **Safety:** -- `summary` must be a valid pointer to an FFIManagedAccountCollectionSummary created by `managed_account_collection_summary_data` - `summary` must not be used after calling this function +- `summary` must be a valid pointer to an FFIManagedCoreAccountCollectionSummary created by `managed_account_collection_summary_data` - `summary` must not be used after calling this function **Module:** `managed_account_collection` --- -#### `managed_account_free` +#### `managed_core_account_free` + +```c +managed_core_account_free(account: *mut FFIManagedCoreAccount) -> () +``` + +**Description:** +Free a managed account handle # Safety - `account` must be a valid pointer to an FFIManagedCoreAccount 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 + +**Safety:** +- `account` must be a valid pointer to an FFIManagedCoreAccount 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 + +**Module:** `managed_account` + +--- + +#### `managed_core_account_free_transactions` + +```c +managed_core_account_free_transactions(transactions: *mut FFITransactionRecord, count: usize,) -> () +``` + +**Description:** +Free transactions array returned by managed_core_account_get_transactions # Safety - `transactions` must be a pointer returned by `managed_core_account_get_transactions` - `count` must be the count returned by `managed_core_account_get_transactions` - This function must only be called once per allocation + +**Safety:** +- `transactions` must be a pointer returned by `managed_core_account_get_transactions` - `count` must be the count returned by `managed_core_account_get_transactions` - This function must only be called once per allocation + +**Module:** `managed_account` + +--- + +#### `managed_core_account_get_account_type` + +```c +managed_core_account_get_account_type(account: *const FFIManagedCoreAccount, index_out: *mut c_uint,) -> FFIAccountType +``` + +**Description:** +Get the account type of a managed account # Safety - `account` must be a valid pointer to an FFIManagedCoreAccount instance - `index_out` must be a valid pointer to receive the account index (or null) + +**Safety:** +- `account` must be a valid pointer to an FFIManagedCoreAccount instance - `index_out` must be a valid pointer to receive the account index (or null) + +**Module:** `managed_account` + +--- + +#### `managed_core_account_get_address_pool` + +```c +managed_core_account_get_address_pool(account: *const FFIManagedCoreAccount, pool_type: FFIAddressPoolType,) -> *mut FFIAddressPool +``` + +**Description:** +Get an address pool from a managed account by type This function returns the appropriate address pool based on the pool type parameter. For Standard accounts with External/Internal pool types, returns the corresponding pool. For non-standard accounts with Single pool type, returns their single address pool. # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `account` must be a valid pointer to an FFIManagedCoreAccount instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - The returned pool must be freed with `address_pool_free` when no longer needed + +**Safety:** +- `manager` must be a valid pointer to an FFIWalletManager instance - `account` must be a valid pointer to an FFIManagedCoreAccount instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - The returned pool must be freed with `address_pool_free` when no longer needed + +**Module:** `managed_account` + +--- + +#### `managed_core_account_get_balance` + +```c +managed_core_account_get_balance(account: *const FFIManagedCoreAccount, balance_out: *mut crate::types::FFIBalance,) -> bool +``` + +**Description:** +Get the balance of a managed account # Safety - `account` must be a valid pointer to an FFIManagedCoreAccount instance - `balance_out` must be a valid pointer to an FFIBalance structure + +**Safety:** +- `account` must be a valid pointer to an FFIManagedCoreAccount instance - `balance_out` must be a valid pointer to an FFIBalance structure + +**Module:** `managed_account` + +--- + +#### `managed_core_account_get_external_address_pool` + +```c +managed_core_account_get_external_address_pool(account: *const FFIManagedCoreAccount,) -> *mut FFIAddressPool +``` + +**Description:** +Get the external address pool from a managed account This function returns the external (receive) address pool for Standard accounts. Returns NULL for account types that don't have separate external/internal pools. # Safety - `account` must be a valid pointer to an FFIManagedCoreAccount instance - The returned pool must be freed with `address_pool_free` when no longer needed + +**Safety:** +- `account` must be a valid pointer to an FFIManagedCoreAccount instance - The returned pool must be freed with `address_pool_free` when no longer needed + +**Module:** `managed_account` + +--- + +#### `managed_core_account_get_index` + +```c +managed_core_account_get_index(account: *const FFIManagedCoreAccount,) -> c_uint +``` + +**Description:** +Get the account index from a managed account Returns the primary account index for Standard and CoinJoin accounts. Returns 0 for account types that don't have an index (like Identity or Provider accounts). # Safety - `account` must be a valid pointer to an FFIManagedCoreAccount instance + +**Safety:** +- `account` must be a valid pointer to an FFIManagedCoreAccount instance + +**Module:** `managed_account` + +--- + +#### `managed_core_account_get_internal_address_pool` + +```c +managed_core_account_get_internal_address_pool(account: *const FFIManagedCoreAccount,) -> *mut FFIAddressPool +``` + +**Description:** +Get the internal address pool from a managed account This function returns the internal (change) address pool for Standard accounts. Returns NULL for account types that don't have separate external/internal pools. # Safety - `account` must be a valid pointer to an FFIManagedCoreAccount instance - The returned pool must be freed with `address_pool_free` when no longer needed + +**Safety:** +- `account` must be a valid pointer to an FFIManagedCoreAccount instance - The returned pool must be freed with `address_pool_free` when no longer needed + +**Module:** `managed_account` + +--- + +#### `managed_core_account_get_is_watch_only` + +```c +managed_core_account_get_is_watch_only(account: *const FFIManagedCoreAccount,) -> bool +``` + +**Description:** +Check if a managed account is watch-only # Safety - `account` must be a valid pointer to an FFIManagedCoreAccount instance + +**Safety:** +- `account` must be a valid pointer to an FFIManagedCoreAccount instance + +**Module:** `managed_account` + +--- + +#### `managed_core_account_get_network` + +```c +managed_core_account_get_network(account: *const FFIManagedCoreAccount,) -> FFINetwork +``` + +**Description:** +Get the network of a managed account # Safety - `account` must be a valid pointer to an FFIManagedCoreAccount instance - Returns `FFINetwork::Dash` if the account is null + +**Safety:** +- `account` must be a valid pointer to an FFIManagedCoreAccount instance - Returns `FFINetwork::Dash` if the account is null + +**Module:** `managed_account` + +--- + +#### `managed_core_account_get_transaction_count` ```c -managed_account_free(account: *mut FFIManagedAccount) -> () +managed_core_account_get_transaction_count(account: *const FFIManagedCoreAccount,) -> c_uint ``` **Description:** -Free a managed account handle # Safety - `account` must be a valid pointer to an FFIManagedAccount 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 +Get the number of transactions in a managed account # Safety - `account` must be a valid pointer to an FFIManagedCoreAccount instance **Safety:** -- `account` must be a valid pointer to an FFIManagedAccount 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 +- `account` must be a valid pointer to an FFIManagedCoreAccount instance **Module:** `managed_account` --- -#### `managed_account_free_transactions` +#### `managed_core_account_get_transactions` ```c -managed_account_free_transactions(transactions: *mut FFITransactionRecord, count: usize,) -> () +managed_core_account_get_transactions(account: *const FFIManagedCoreAccount, transactions_out: *mut *mut FFITransactionRecord, count_out: *mut usize,) -> bool ``` **Description:** -Free transactions array returned by managed_account_get_transactions # Safety - `transactions` must be a pointer returned by `managed_account_get_transactions` - `count` must be the count returned by `managed_account_get_transactions` - This function must only be called once per allocation +Get all transactions from a managed account Returns an array of FFITransactionRecord structures. # Safety - `account` must be a valid pointer to an FFIManagedCoreAccount instance - `transactions_out` must be a valid pointer to receive the transactions array pointer - `count_out` must be a valid pointer to receive the count - The caller must free the returned array using `managed_core_account_free_transactions` **Safety:** -- `transactions` must be a pointer returned by `managed_account_get_transactions` - `count` must be the count returned by `managed_account_get_transactions` - This function must only be called once per allocation +- `account` must be a valid pointer to an FFIManagedCoreAccount instance - `transactions_out` must be a valid pointer to receive the transactions array pointer - `count_out` must be a valid pointer to receive the count - The caller must free the returned array using `managed_core_account_free_transactions` **Module:** `managed_account` --- -#### `managed_account_get_account_type` +#### `managed_core_account_get_utxo_count` ```c -managed_account_get_account_type(account: *const FFIManagedAccount, index_out: *mut c_uint,) -> FFIAccountType +managed_core_account_get_utxo_count(account: *const FFIManagedCoreAccount,) -> c_uint ``` **Description:** -Get the account type of a managed account # Safety - `account` must be a valid pointer to an FFIManagedAccount instance - `index_out` must be a valid pointer to receive the account index (or null) +Get the number of UTXOs in a managed account # Safety - `account` must be a valid pointer to an FFIManagedCoreAccount instance **Safety:** -- `account` must be a valid pointer to an FFIManagedAccount instance - `index_out` must be a valid pointer to receive the account index (or null) +- `account` must be a valid pointer to an FFIManagedCoreAccount instance **Module:** `managed_account` --- -#### `managed_account_get_address_pool` +#### `managed_platform_account_free` ```c -managed_account_get_address_pool(account: *const FFIManagedAccount, pool_type: FFIAddressPoolType,) -> *mut FFIAddressPool +managed_platform_account_free(account: *mut FFIManagedPlatformAccount) -> () ``` **Description:** -Get an address pool from a managed account by type This function returns the appropriate address pool based on the pool type parameter. For Standard accounts with External/Internal pool types, returns the corresponding pool. For non-standard accounts with Single pool type, returns their single address pool. # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - `account` must be a valid pointer to an FFIManagedAccount instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - The returned pool must be freed with `address_pool_free` when no longer needed +Free a managed platform account handle # Safety - `account` must be a valid pointer to an FFIManagedPlatformAccount 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 **Safety:** -- `manager` must be a valid pointer to an FFIWalletManager instance - `account` must be a valid pointer to an FFIManagedAccount instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - The returned pool must be freed with `address_pool_free` when no longer needed +- `account` must be a valid pointer to an FFIManagedPlatformAccount 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 **Module:** `managed_account` --- -#### `managed_account_get_balance` +#### `managed_platform_account_get_account_index` ```c -managed_account_get_balance(account: *const FFIManagedAccount, balance_out: *mut crate::types::FFIBalance,) -> bool +managed_platform_account_get_account_index(account: *const FFIManagedPlatformAccount,) -> c_uint ``` **Description:** -Get the balance of a managed account # Safety - `account` must be a valid pointer to an FFIManagedAccount instance - `balance_out` must be a valid pointer to an FFIBalance structure +Get the account index of a managed platform account # Safety - `account` must be a valid pointer to an FFIManagedPlatformAccount instance **Safety:** -- `account` must be a valid pointer to an FFIManagedAccount instance - `balance_out` must be a valid pointer to an FFIBalance structure +- `account` must be a valid pointer to an FFIManagedPlatformAccount instance **Module:** `managed_account` --- -#### `managed_account_get_external_address_pool` +#### `managed_platform_account_get_address_pool` ```c -managed_account_get_external_address_pool(account: *const FFIManagedAccount,) -> *mut FFIAddressPool +managed_platform_account_get_address_pool(account: *const FFIManagedPlatformAccount,) -> *mut FFIAddressPool ``` **Description:** -Get the external address pool from a managed account This function returns the external (receive) address pool for Standard accounts. Returns NULL for account types that don't have separate external/internal pools. # Safety - `account` must be a valid pointer to an FFIManagedAccount instance - The returned pool must be freed with `address_pool_free` when no longer needed +Get the address pool from a managed platform account Platform accounts only have a single address pool. # Safety - `account` must be a valid pointer to an FFIManagedPlatformAccount instance - The returned pool must be freed with `address_pool_free` when no longer needed **Safety:** -- `account` must be a valid pointer to an FFIManagedAccount instance - The returned pool must be freed with `address_pool_free` when no longer needed +- `account` must be a valid pointer to an FFIManagedPlatformAccount instance - The returned pool must be freed with `address_pool_free` when no longer needed **Module:** `managed_account` --- -#### `managed_account_get_index` +#### `managed_platform_account_get_credit_balance` ```c -managed_account_get_index(account: *const FFIManagedAccount) -> c_uint +managed_platform_account_get_credit_balance(account: *const FFIManagedPlatformAccount,) -> u64 ``` **Description:** -Get the account index from a managed account Returns the primary account index for Standard and CoinJoin accounts. Returns 0 for account types that don't have an index (like Identity or Provider accounts). # Safety - `account` must be a valid pointer to an FFIManagedAccount instance +Get the total credit balance of a managed platform account Returns the balance in credits (1000 credits = 1 duff) # Safety - `account` must be a valid pointer to an FFIManagedPlatformAccount instance **Safety:** -- `account` must be a valid pointer to an FFIManagedAccount instance +- `account` must be a valid pointer to an FFIManagedPlatformAccount instance **Module:** `managed_account` --- -#### `managed_account_get_internal_address_pool` +#### `managed_platform_account_get_duff_balance` ```c -managed_account_get_internal_address_pool(account: *const FFIManagedAccount,) -> *mut FFIAddressPool +managed_platform_account_get_duff_balance(account: *const FFIManagedPlatformAccount,) -> u64 ``` **Description:** -Get the internal address pool from a managed account This function returns the internal (change) address pool for Standard accounts. Returns NULL for account types that don't have separate external/internal pools. # Safety - `account` must be a valid pointer to an FFIManagedAccount instance - The returned pool must be freed with `address_pool_free` when no longer needed +Get the total balance in duffs of a managed platform account Returns the balance in duffs (credit_balance / 1000) # Safety - `account` must be a valid pointer to an FFIManagedPlatformAccount instance **Safety:** -- `account` must be a valid pointer to an FFIManagedAccount instance - The returned pool must be freed with `address_pool_free` when no longer needed +- `account` must be a valid pointer to an FFIManagedPlatformAccount instance **Module:** `managed_account` --- -#### `managed_account_get_is_watch_only` +#### `managed_platform_account_get_funded_address_count` ```c -managed_account_get_is_watch_only(account: *const FFIManagedAccount,) -> bool +managed_platform_account_get_funded_address_count(account: *const FFIManagedPlatformAccount,) -> c_uint ``` **Description:** -Check if a managed account is watch-only # Safety - `account` must be a valid pointer to an FFIManagedAccount instance +Get the number of funded addresses in a managed platform account # Safety - `account` must be a valid pointer to an FFIManagedPlatformAccount instance **Safety:** -- `account` must be a valid pointer to an FFIManagedAccount instance +- `account` must be a valid pointer to an FFIManagedPlatformAccount instance **Module:** `managed_account` --- -#### `managed_account_get_network` +#### `managed_platform_account_get_is_watch_only` ```c -managed_account_get_network(account: *const FFIManagedAccount,) -> FFINetwork +managed_platform_account_get_is_watch_only(account: *const FFIManagedPlatformAccount,) -> bool ``` **Description:** -Get the network of a managed account # Safety - `account` must be a valid pointer to an FFIManagedAccount instance - Returns `FFINetwork::Dash` if the account is null +Check if a managed platform account is watch-only # Safety - `account` must be a valid pointer to an FFIManagedPlatformAccount instance **Safety:** -- `account` must be a valid pointer to an FFIManagedAccount instance - Returns `FFINetwork::Dash` if the account is null +- `account` must be a valid pointer to an FFIManagedPlatformAccount instance **Module:** `managed_account` --- -#### `managed_account_get_transaction_count` +#### `managed_platform_account_get_key_class` ```c -managed_account_get_transaction_count(account: *const FFIManagedAccount,) -> c_uint +managed_platform_account_get_key_class(account: *const FFIManagedPlatformAccount,) -> c_uint ``` **Description:** -Get the number of transactions in a managed account # Safety - `account` must be a valid pointer to an FFIManagedAccount instance +Get the key class of a managed platform account # Safety - `account` must be a valid pointer to an FFIManagedPlatformAccount instance **Safety:** -- `account` must be a valid pointer to an FFIManagedAccount instance +- `account` must be a valid pointer to an FFIManagedPlatformAccount instance **Module:** `managed_account` --- -#### `managed_account_get_transactions` +#### `managed_platform_account_get_network` ```c -managed_account_get_transactions(account: *const FFIManagedAccount, transactions_out: *mut *mut FFITransactionRecord, count_out: *mut usize,) -> bool +managed_platform_account_get_network(account: *const FFIManagedPlatformAccount,) -> FFINetwork ``` **Description:** -Get all transactions from a managed account Returns an array of FFITransactionRecord structures. # Safety - `account` must be a valid pointer to an FFIManagedAccount instance - `transactions_out` must be a valid pointer to receive the transactions array pointer - `count_out` must be a valid pointer to receive the count - The caller must free the returned array using `managed_account_free_transactions` +Get the network of a managed platform account # Safety - `account` must be a valid pointer to an FFIManagedPlatformAccount instance - Returns `FFINetwork::Dash` if the account is null **Safety:** -- `account` must be a valid pointer to an FFIManagedAccount instance - `transactions_out` must be a valid pointer to receive the transactions array pointer - `count_out` must be a valid pointer to receive the count - The caller must free the returned array using `managed_account_free_transactions` +- `account` must be a valid pointer to an FFIManagedPlatformAccount instance - Returns `FFINetwork::Dash` if the account is null **Module:** `managed_account` --- -#### `managed_account_get_utxo_count` +#### `managed_platform_account_get_total_address_count` ```c -managed_account_get_utxo_count(account: *const FFIManagedAccount,) -> c_uint +managed_platform_account_get_total_address_count(account: *const FFIManagedPlatformAccount,) -> c_uint ``` **Description:** -Get the number of UTXOs in a managed account # Safety - `account` must be a valid pointer to an FFIManagedAccount instance +Get the total number of addresses in a managed platform account # Safety - `account` must be a valid pointer to an FFIManagedPlatformAccount instance **Safety:** -- `account` must be a valid pointer to an FFIManagedAccount instance +- `account` must be a valid pointer to an FFIManagedPlatformAccount instance **Module:** `managed_account` diff --git a/key-wallet-ffi/cbindgen.toml b/key-wallet-ffi/cbindgen.toml index a0348dd69..3ac2f0fad 100644 --- a/key-wallet-ffi/cbindgen.toml +++ b/key-wallet-ffi/cbindgen.toml @@ -34,21 +34,22 @@ exclude = [] prefix = "" item_types = ["functions", "enums", "structs", "typedefs", "opaque", "constants"] -# Force these to be forward-declared only: +# Opaque types with Swift-compatible bodies (unsigned char _private[0] allows Swift to import) [export.body] -"FFIPrivateKey" = "" -"FFIExtendedPrivateKey" = "" -"FFIPublicKey" = "" -"FFIExtendedPublicKey" = "" -"FFIWalletManager" = "" -"FFIWallet" = "" -"FFIAccount" = "" -"FFIBLSAccount" = "" -"FFIEdDSAAccount" = "" -"FFIManagedAccount" = "" -"FFIAccountCollection" = "" -"FFIManagedAccountCollection" = "" -"FFIAddressPool" = "" +"FFIPrivateKey" = "{ unsigned char _private[0]; }" +"FFIExtendedPrivateKey" = "{ unsigned char _private[0]; }" +"FFIPublicKey" = "{ unsigned char _private[0]; }" +"FFIExtendedPublicKey" = "{ unsigned char _private[0]; }" +"FFIWalletManager" = "{ unsigned char _private[0]; }" +"FFIWallet" = "{ unsigned char _private[0]; }" +"FFIAccount" = "{ unsigned char _private[0]; }" +"FFIBLSAccount" = "{ unsigned char _private[0]; }" +"FFIEdDSAAccount" = "{ unsigned char _private[0]; }" +"FFIManagedAccount" = "{ unsigned char _private[0]; }" +"FFIManagedPlatformAccount" = "{ unsigned char _private[0]; }" +"FFIAccountCollection" = "{ unsigned char _private[0]; }" +"FFIManagedAccountCollection" = "{ unsigned char _private[0]; }" +"FFIAddressPool" = "{ unsigned char _private[0]; }" [export.rename] # Rename types to match C conventions diff --git a/key-wallet-ffi/include/key_wallet_ffi.h b/key-wallet-ffi/include/key_wallet_ffi.h index 4a9a132a8..2804cebb4 100644 --- a/key-wallet-ffi/include/key_wallet_ffi.h +++ b/key-wallet-ffi/include/key_wallet_ffi.h @@ -256,12 +256,23 @@ typedef struct FFIExtendedPublicKey FFIExtendedPublicKey; /* Opaque managed account handle that wraps ManagedAccount */ -typedef struct FFIManagedAccount FFIManagedAccount; +typedef struct FFIManagedCoreAccount FFIManagedCoreAccount; /* Opaque handle to a managed account collection */ -typedef struct FFIManagedAccountCollection FFIManagedAccountCollection; +typedef struct FFIManagedCoreAccountCollection FFIManagedCoreAccountCollection; + +/* + Opaque managed platform account handle that wraps ManagedPlatformAccount + + This is different from FFIManagedCoreAccount because ManagedPlatformAccount + has a different structure optimized for Platform Payment accounts (DIP-17): + - Simple u64 credit balance instead of WalletCoreBalance + - Per-address balances tracked directly + - No transactions or UTXOs (Platform handles these) + */ +typedef struct FFIManagedPlatformAccount FFIManagedPlatformAccount; /* Opaque type for a private key (SecretKey) @@ -498,7 +509,7 @@ typedef struct { /* The managed account handle if successful, NULL if error */ - FFIManagedAccount *account; + FFIManagedCoreAccount *account; /* Error code (0 = success) */ @@ -507,7 +518,7 @@ typedef struct { Error message (NULL if success, must be freed by caller if not NULL) */ char *error_message; -} FFIManagedAccountResult; +} FFIManagedCoreAccountResult; /* FFI Balance type for representing wallet balances @@ -569,6 +580,38 @@ typedef struct { bool is_ours; } FFITransactionRecord; +/* + FFI Result type for ManagedPlatformAccount operations + */ +typedef struct { + /* + The managed platform account handle if successful, NULL if error + */ + FFIManagedPlatformAccount *account; + /* + Error code (0 = success) + */ + int32_t error_code; + /* + Error message (NULL if success, must be freed by caller if not NULL) + */ + char *error_message; +} FFIManagedPlatformAccountResult; + +/* + C-compatible platform payment account key + */ +typedef struct { + /* + Account index (hardened) + */ + unsigned int account; + /* + Key class (hardened) + */ + unsigned int key_class; +} FFIPlatformPaymentAccountKey; + /* C-compatible summary of all accounts in a managed collection @@ -637,7 +680,15 @@ typedef struct { Whether provider platform keys account exists */ bool has_provider_platform_keys; -} FFIManagedAccountCollectionSummary; + /* + Array of Platform Payment account keys (account, key_class pairs) + */ + FFIPlatformPaymentAccountKey *platform_payment_keys; + /* + Number of Platform Payment accounts + */ + size_t platform_payment_count; +} FFIManagedCoreAccountCollectionSummary; /* Transaction output for building (legacy structure) @@ -2390,13 +2441,13 @@ bool derivation_path_parse(const char *path, - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - The caller must ensure all pointers remain valid for the duration of this call - - The returned account must be freed with `managed_account_free` when no longer needed + - The returned account must be freed with `managed_core_account_free` when no longer needed */ -FFIManagedAccountResult managed_wallet_get_account(const FFIWalletManager *manager, - const uint8_t *wallet_id, - unsigned int account_index, - FFIAccountType account_type) +FFIManagedCoreAccountResult managed_wallet_get_account(const FFIWalletManager *manager, + const uint8_t *wallet_id, + unsigned int account_index, + FFIAccountType account_type) ; /* @@ -2410,12 +2461,12 @@ FFIManagedAccountResult managed_wallet_get_account(const FFIWalletManager *manag - `manager` must be a valid pointer to an FFIWalletManager instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - The caller must ensure all pointers remain valid for the duration of this call - - The returned account must be freed with `managed_account_free` when no longer needed + - The returned account must be freed with `managed_core_account_free` when no longer needed */ -FFIManagedAccountResult managed_wallet_get_top_up_account_with_registration_index(const FFIWalletManager *manager, - const uint8_t *wallet_id, - unsigned int registration_index) +FFIManagedCoreAccountResult managed_wallet_get_top_up_account_with_registration_index(const FFIWalletManager *manager, + const uint8_t *wallet_id, + unsigned int registration_index) ; /* @@ -2426,11 +2477,11 @@ FFIManagedAccountResult managed_wallet_get_top_up_account_with_registration_inde - `user_identity_id` and `friend_identity_id` must each point to 32 bytes */ -FFIManagedAccountResult managed_wallet_get_dashpay_receiving_account(const FFIWalletManager *manager, - const uint8_t *wallet_id, - unsigned int account_index, - const uint8_t *user_identity_id, - const uint8_t *friend_identity_id) +FFIManagedCoreAccountResult managed_wallet_get_dashpay_receiving_account(const FFIWalletManager *manager, + const uint8_t *wallet_id, + unsigned int account_index, + const uint8_t *user_identity_id, + const uint8_t *friend_identity_id) ; /* @@ -2440,11 +2491,11 @@ FFIManagedAccountResult managed_wallet_get_dashpay_receiving_account(const FFIWa - Pointers must be valid */ -FFIManagedAccountResult managed_wallet_get_dashpay_external_account(const FFIWalletManager *manager, - const uint8_t *wallet_id, - unsigned int account_index, - const uint8_t *user_identity_id, - const uint8_t *friend_identity_id) +FFIManagedCoreAccountResult managed_wallet_get_dashpay_external_account(const FFIWalletManager *manager, + const uint8_t *wallet_id, + unsigned int account_index, + const uint8_t *user_identity_id, + const uint8_t *friend_identity_id) ; /* @@ -2452,10 +2503,10 @@ FFIManagedAccountResult managed_wallet_get_dashpay_external_account(const FFIWal # Safety - - `account` must be a valid pointer to an FFIManagedAccount instance + - `account` must be a valid pointer to an FFIManagedCoreAccount instance - Returns `FFINetwork::Dash` if the account is null */ - FFINetwork managed_account_get_network(const FFIManagedAccount *account) ; + FFINetwork managed_core_account_get_network(const FFIManagedCoreAccount *account) ; /* Get the parent wallet ID of a managed account @@ -2470,7 +2521,7 @@ FFIManagedAccountResult managed_wallet_get_dashpay_external_account(const FFIWal - The caller must not free the returned pointer as it's the same as the input */ -const uint8_t *managed_account_get_parent_wallet_id(const uint8_t *wallet_id) +const uint8_t *managed_core_account_get_parent_wallet_id(const uint8_t *wallet_id) ; /* @@ -2478,12 +2529,12 @@ const uint8_t *managed_account_get_parent_wallet_id(const uint8_t *wallet_id) # Safety - - `account` must be a valid pointer to an FFIManagedAccount instance + - `account` must be a valid pointer to an FFIManagedCoreAccount instance - `index_out` must be a valid pointer to receive the account index (or null) */ -FFIAccountType managed_account_get_account_type(const FFIManagedAccount *account, - unsigned int *index_out) +FFIAccountType managed_core_account_get_account_type(const FFIManagedCoreAccount *account, + unsigned int *index_out) ; /* @@ -2491,37 +2542,40 @@ FFIAccountType managed_account_get_account_type(const FFIManagedAccount *account # Safety - - `account` must be a valid pointer to an FFIManagedAccount instance + - `account` must be a valid pointer to an FFIManagedCoreAccount instance */ - bool managed_account_get_is_watch_only(const FFIManagedAccount *account) ; + bool managed_core_account_get_is_watch_only(const FFIManagedCoreAccount *account) ; /* Get the balance of a managed account # Safety - - `account` must be a valid pointer to an FFIManagedAccount instance + - `account` must be a valid pointer to an FFIManagedCoreAccount instance - `balance_out` must be a valid pointer to an FFIBalance structure */ - bool managed_account_get_balance(const FFIManagedAccount *account, FFIBalance *balance_out) ; + +bool managed_core_account_get_balance(const FFIManagedCoreAccount *account, + FFIBalance *balance_out) +; /* Get the number of transactions in a managed account # Safety - - `account` must be a valid pointer to an FFIManagedAccount instance + - `account` must be a valid pointer to an FFIManagedCoreAccount instance */ - unsigned int managed_account_get_transaction_count(const FFIManagedAccount *account) ; + unsigned int managed_core_account_get_transaction_count(const FFIManagedCoreAccount *account) ; /* Get the number of UTXOs in a managed account # Safety - - `account` must be a valid pointer to an FFIManagedAccount instance + - `account` must be a valid pointer to an FFIManagedCoreAccount instance */ - unsigned int managed_account_get_utxo_count(const FFIManagedAccount *account) ; + unsigned int managed_core_account_get_utxo_count(const FFIManagedCoreAccount *account) ; /* Get all transactions from a managed account @@ -2530,50 +2584,50 @@ FFIAccountType managed_account_get_account_type(const FFIManagedAccount *account # Safety - - `account` must be a valid pointer to an FFIManagedAccount instance + - `account` must be a valid pointer to an FFIManagedCoreAccount instance - `transactions_out` must be a valid pointer to receive the transactions array pointer - `count_out` must be a valid pointer to receive the count - - The caller must free the returned array using `managed_account_free_transactions` + - The caller must free the returned array using `managed_core_account_free_transactions` */ -bool managed_account_get_transactions(const FFIManagedAccount *account, - FFITransactionRecord **transactions_out, - size_t *count_out) +bool managed_core_account_get_transactions(const FFIManagedCoreAccount *account, + FFITransactionRecord **transactions_out, + size_t *count_out) ; /* - Free transactions array returned by managed_account_get_transactions + Free transactions array returned by managed_core_account_get_transactions # Safety - - `transactions` must be a pointer returned by `managed_account_get_transactions` - - `count` must be the count returned by `managed_account_get_transactions` + - `transactions` must be a pointer returned by `managed_core_account_get_transactions` + - `count` must be the count returned by `managed_core_account_get_transactions` - This function must only be called once per allocation */ - void managed_account_free_transactions(FFITransactionRecord *transactions, size_t count) ; + void managed_core_account_free_transactions(FFITransactionRecord *transactions, size_t count) ; /* Free a managed account handle # Safety - - `account` must be a valid pointer to an FFIManagedAccount that was allocated by this library + - `account` must be a valid pointer to an FFIManagedCoreAccount 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 */ - void managed_account_free(FFIManagedAccount *account) ; + void managed_core_account_free(FFIManagedCoreAccount *account) ; /* Free a managed account result's error message (if any) - Note: This does NOT free the account handle itself - use managed_account_free for that + Note: This does NOT free the account handle itself - use managed_core_account_free for that # Safety - - `result` must be a valid pointer to an FFIManagedAccountResult + - `result` must be a valid pointer to an FFIManagedCoreAccountResult - The error_message field must be either null or a valid CString allocated by this library - The caller must ensure the result pointer remains valid for the duration of this call */ - void managed_account_result_free_error(FFIManagedAccountResult *result) ; + void managed_core_account_result_free_error(FFIManagedCoreAccountResult *result) ; /* Get number of accounts in a managed wallet @@ -2599,9 +2653,9 @@ unsigned int managed_wallet_get_account_count(const FFIWalletManager *manager, # Safety - - `account` must be a valid pointer to an FFIManagedAccount instance + - `account` must be a valid pointer to an FFIManagedCoreAccount instance */ - unsigned int managed_account_get_index(const FFIManagedAccount *account) ; + unsigned int managed_core_account_get_index(const FFIManagedCoreAccount *account) ; /* Get the external address pool from a managed account @@ -2611,10 +2665,12 @@ unsigned int managed_wallet_get_account_count(const FFIWalletManager *manager, # Safety - - `account` must be a valid pointer to an FFIManagedAccount instance + - `account` must be a valid pointer to an FFIManagedCoreAccount instance - The returned pool must be freed with `address_pool_free` when no longer needed */ - FFIAddressPool *managed_account_get_external_address_pool(const FFIManagedAccount *account) ; + +FFIAddressPool *managed_core_account_get_external_address_pool(const FFIManagedCoreAccount *account) +; /* Get the internal address pool from a managed account @@ -2624,10 +2680,12 @@ unsigned int managed_wallet_get_account_count(const FFIWalletManager *manager, # Safety - - `account` must be a valid pointer to an FFIManagedAccount instance + - `account` must be a valid pointer to an FFIManagedCoreAccount instance - The returned pool must be freed with `address_pool_free` when no longer needed */ - FFIAddressPool *managed_account_get_internal_address_pool(const FFIManagedAccount *account) ; + +FFIAddressPool *managed_core_account_get_internal_address_pool(const FFIManagedCoreAccount *account) +; /* Get an address pool from a managed account by type @@ -2639,15 +2697,155 @@ unsigned int managed_wallet_get_account_count(const FFIWalletManager *manager, # Safety - `manager` must be a valid pointer to an FFIWalletManager instance - - `account` must be a valid pointer to an FFIManagedAccount instance + - `account` must be a valid pointer to an FFIManagedCoreAccount instance - `wallet_id` must be a valid pointer to a 32-byte wallet ID - The returned pool must be freed with `address_pool_free` when no longer needed */ -FFIAddressPool *managed_account_get_address_pool(const FFIManagedAccount *account, - FFIAddressPoolType pool_type) +FFIAddressPool *managed_core_account_get_address_pool(const FFIManagedCoreAccount *account, + FFIAddressPoolType pool_type) +; + +/* + Get a managed platform payment account from a managed wallet + + Platform Payment accounts (DIP-17) are identified by account index and key_class. + Returns a platform account handle that wraps the ManagedPlatformAccount. + + # Safety + + - `manager` must be a valid pointer to an FFIWalletManager instance + - `wallet_id` must be a valid pointer to a 32-byte wallet ID + - The caller must ensure all pointers remain valid for the duration of this call + - The returned account must be freed with `managed_platform_account_free` when no longer needed + */ + +FFIManagedPlatformAccountResult managed_wallet_get_platform_payment_account(const FFIWalletManager *manager, + const uint8_t *wallet_id, + unsigned int account_index, + unsigned int key_class) ; +/* + Get the network of a managed platform account + + # Safety + + - `account` must be a valid pointer to an FFIManagedPlatformAccount instance + - Returns `FFINetwork::Dash` if the account is null + */ + FFINetwork managed_platform_account_get_network(const FFIManagedPlatformAccount *account) ; + +/* + Get the account index of a managed platform account + + # Safety + + - `account` must be a valid pointer to an FFIManagedPlatformAccount instance + */ + unsigned int managed_platform_account_get_account_index(const FFIManagedPlatformAccount *account) ; + +/* + Get the key class of a managed platform account + + # Safety + + - `account` must be a valid pointer to an FFIManagedPlatformAccount instance + */ + unsigned int managed_platform_account_get_key_class(const FFIManagedPlatformAccount *account) ; + +/* + Get the total credit balance of a managed platform account + + Returns the balance in credits (1000 credits = 1 duff) + + # Safety + + - `account` must be a valid pointer to an FFIManagedPlatformAccount instance + */ + uint64_t managed_platform_account_get_credit_balance(const FFIManagedPlatformAccount *account) ; + +/* + Get the total balance in duffs of a managed platform account + + Returns the balance in duffs (credit_balance / 1000) + + # Safety + + - `account` must be a valid pointer to an FFIManagedPlatformAccount instance + */ + uint64_t managed_platform_account_get_duff_balance(const FFIManagedPlatformAccount *account) ; + +/* + Get the number of funded addresses in a managed platform account + + # Safety + + - `account` must be a valid pointer to an FFIManagedPlatformAccount instance + */ + +unsigned int managed_platform_account_get_funded_address_count(const FFIManagedPlatformAccount *account) +; + +/* + Get the total number of addresses in a managed platform account + + # Safety + + - `account` must be a valid pointer to an FFIManagedPlatformAccount instance + */ + +unsigned int managed_platform_account_get_total_address_count(const FFIManagedPlatformAccount *account) +; + +/* + Check if a managed platform account is watch-only + + # Safety + + - `account` must be a valid pointer to an FFIManagedPlatformAccount instance + */ + bool managed_platform_account_get_is_watch_only(const FFIManagedPlatformAccount *account) ; + +/* + Get the address pool from a managed platform account + + Platform accounts only have a single address pool. + + # Safety + + - `account` must be a valid pointer to an FFIManagedPlatformAccount instance + - The returned pool must be freed with `address_pool_free` when no longer needed + */ + +FFIAddressPool *managed_platform_account_get_address_pool(const FFIManagedPlatformAccount *account) +; + +/* + Free a managed platform account handle + + # Safety + + - `account` must be a valid pointer to an FFIManagedPlatformAccount 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 + */ + +void managed_platform_account_free(FFIManagedPlatformAccount *account) +; + +/* + Free a managed platform account result's error message (if any) + Note: This does NOT free the account handle itself - use managed_platform_account_free for that + + # Safety + + - `result` must be a valid pointer to an FFIManagedPlatformAccountResult + - The error_message field must be either null or a valid CString allocated by this library + - The caller must ensure the result pointer remains valid for the duration of this call + */ + void managed_platform_account_result_free_error(FFIManagedPlatformAccountResult *result) ; + /* Get managed account collection for a specific network from wallet manager @@ -2659,9 +2857,9 @@ FFIAddressPool *managed_account_get_address_pool(const FFIManagedAccount *accoun - The returned pointer must be freed with `managed_account_collection_free` when no longer needed */ -FFIManagedAccountCollection *managed_wallet_get_account_collection(const FFIWalletManager *manager, - const uint8_t *wallet_id, - FFIError *error) +FFIManagedCoreAccountCollection *managed_wallet_get_account_collection(const FFIWalletManager *manager, + const uint8_t *wallet_id, + FFIError *error) ; /* @@ -2669,22 +2867,24 @@ FFIManagedAccountCollection *managed_wallet_get_account_collection(const FFIWall # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection created by this library + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection created by this library - `collection` must not be used after calling this function */ - void managed_account_collection_free(FFIManagedAccountCollection *collection) ; + +void managed_account_collection_free(FFIManagedCoreAccountCollection *collection) +; /* Get a BIP44 account by index from the managed collection # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection - - The returned pointer must be freed with `managed_account_free` when no longer needed + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + - The returned pointer must be freed with `managed_core_account_free` when no longer needed */ -FFIManagedAccount *managed_account_collection_get_bip44_account(const FFIManagedAccountCollection *collection, - unsigned int index) +FFIManagedCoreAccount *managed_account_collection_get_bip44_account(const FFIManagedCoreAccountCollection *collection, + unsigned int index) ; /* @@ -2692,13 +2892,13 @@ FFIManagedAccount *managed_account_collection_get_bip44_account(const FFIManaged # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed */ -bool managed_account_collection_get_bip44_indices(const FFIManagedAccountCollection *collection, +bool managed_account_collection_get_bip44_indices(const FFIManagedCoreAccountCollection *collection, unsigned int **out_indices, size_t *out_count) ; @@ -2708,12 +2908,12 @@ bool managed_account_collection_get_bip44_indices(const FFIManagedAccountCollect # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection - - The returned pointer must be freed with `managed_account_free` when no longer needed + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + - The returned pointer must be freed with `managed_core_account_free` when no longer needed */ -FFIManagedAccount *managed_account_collection_get_bip32_account(const FFIManagedAccountCollection *collection, - unsigned int index) +FFIManagedCoreAccount *managed_account_collection_get_bip32_account(const FFIManagedCoreAccountCollection *collection, + unsigned int index) ; /* @@ -2721,13 +2921,13 @@ FFIManagedAccount *managed_account_collection_get_bip32_account(const FFIManaged # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed */ -bool managed_account_collection_get_bip32_indices(const FFIManagedAccountCollection *collection, +bool managed_account_collection_get_bip32_indices(const FFIManagedCoreAccountCollection *collection, unsigned int **out_indices, size_t *out_count) ; @@ -2737,12 +2937,12 @@ bool managed_account_collection_get_bip32_indices(const FFIManagedAccountCollect # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection - - The returned pointer must be freed with `managed_account_free` when no longer needed + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + - The returned pointer must be freed with `managed_core_account_free` when no longer needed */ -FFIManagedAccount *managed_account_collection_get_coinjoin_account(const FFIManagedAccountCollection *collection, - unsigned int index) +FFIManagedCoreAccount *managed_account_collection_get_coinjoin_account(const FFIManagedCoreAccountCollection *collection, + unsigned int index) ; /* @@ -2750,13 +2950,13 @@ FFIManagedAccount *managed_account_collection_get_coinjoin_account(const FFIMana # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed */ -bool managed_account_collection_get_coinjoin_indices(const FFIManagedAccountCollection *collection, +bool managed_account_collection_get_coinjoin_indices(const FFIManagedCoreAccountCollection *collection, unsigned int **out_indices, size_t *out_count) ; @@ -2766,11 +2966,11 @@ bool managed_account_collection_get_coinjoin_indices(const FFIManagedAccountColl # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection - - The returned pointer must be freed with `managed_account_free` when no longer needed + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + - The returned pointer must be freed with `managed_core_account_free` when no longer needed */ -FFIManagedAccount *managed_account_collection_get_identity_registration(const FFIManagedAccountCollection *collection) +FFIManagedCoreAccount *managed_account_collection_get_identity_registration(const FFIManagedCoreAccountCollection *collection) ; /* @@ -2778,10 +2978,10 @@ FFIManagedAccount *managed_account_collection_get_identity_registration(const FF # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection */ -bool managed_account_collection_has_identity_registration(const FFIManagedAccountCollection *collection) +bool managed_account_collection_has_identity_registration(const FFIManagedCoreAccountCollection *collection) ; /* @@ -2789,12 +2989,12 @@ bool managed_account_collection_has_identity_registration(const FFIManagedAccoun # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection - - The returned pointer must be freed with `managed_account_free` when no longer needed + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + - The returned pointer must be freed with `managed_core_account_free` when no longer needed */ -FFIManagedAccount *managed_account_collection_get_identity_topup(const FFIManagedAccountCollection *collection, - unsigned int registration_index) +FFIManagedCoreAccount *managed_account_collection_get_identity_topup(const FFIManagedCoreAccountCollection *collection, + unsigned int registration_index) ; /* @@ -2802,13 +3002,13 @@ FFIManagedAccount *managed_account_collection_get_identity_topup(const FFIManage # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `out_indices` must be a valid pointer to store the indices array - `out_count` must be a valid pointer to store the count - The returned array must be freed with `free_u32_array` when no longer needed */ -bool managed_account_collection_get_identity_topup_indices(const FFIManagedAccountCollection *collection, +bool managed_account_collection_get_identity_topup_indices(const FFIManagedCoreAccountCollection *collection, unsigned int **out_indices, size_t *out_count) ; @@ -2818,12 +3018,12 @@ bool managed_account_collection_get_identity_topup_indices(const FFIManagedAccou # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - `manager` must be a valid pointer to an FFIWalletManager - - The returned pointer must be freed with `managed_account_free` when no longer needed + - The returned pointer must be freed with `managed_core_account_free` when no longer needed */ -FFIManagedAccount *managed_account_collection_get_identity_topup_not_bound(const FFIManagedAccountCollection *collection) +FFIManagedCoreAccount *managed_account_collection_get_identity_topup_not_bound(const FFIManagedCoreAccountCollection *collection) ; /* @@ -2831,10 +3031,10 @@ FFIManagedAccount *managed_account_collection_get_identity_topup_not_bound(const # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection */ -bool managed_account_collection_has_identity_topup_not_bound(const FFIManagedAccountCollection *collection) +bool managed_account_collection_has_identity_topup_not_bound(const FFIManagedCoreAccountCollection *collection) ; /* @@ -2842,11 +3042,11 @@ bool managed_account_collection_has_identity_topup_not_bound(const FFIManagedAcc # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection - - The returned pointer must be freed with `managed_account_free` when no longer needed + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + - The returned pointer must be freed with `managed_core_account_free` when no longer needed */ -FFIManagedAccount *managed_account_collection_get_identity_invitation(const FFIManagedAccountCollection *collection) +FFIManagedCoreAccount *managed_account_collection_get_identity_invitation(const FFIManagedCoreAccountCollection *collection) ; /* @@ -2854,10 +3054,10 @@ FFIManagedAccount *managed_account_collection_get_identity_invitation(const FFIM # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection */ -bool managed_account_collection_has_identity_invitation(const FFIManagedAccountCollection *collection) +bool managed_account_collection_has_identity_invitation(const FFIManagedCoreAccountCollection *collection) ; /* @@ -2865,11 +3065,11 @@ bool managed_account_collection_has_identity_invitation(const FFIManagedAccountC # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection - - The returned pointer must be freed with `managed_account_free` when no longer needed + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + - The returned pointer must be freed with `managed_core_account_free` when no longer needed */ -FFIManagedAccount *managed_account_collection_get_provider_voting_keys(const FFIManagedAccountCollection *collection) +FFIManagedCoreAccount *managed_account_collection_get_provider_voting_keys(const FFIManagedCoreAccountCollection *collection) ; /* @@ -2877,10 +3077,10 @@ FFIManagedAccount *managed_account_collection_get_provider_voting_keys(const FFI # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection */ -bool managed_account_collection_has_provider_voting_keys(const FFIManagedAccountCollection *collection) +bool managed_account_collection_has_provider_voting_keys(const FFIManagedCoreAccountCollection *collection) ; /* @@ -2888,11 +3088,11 @@ bool managed_account_collection_has_provider_voting_keys(const FFIManagedAccount # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection - - The returned pointer must be freed with `managed_account_free` when no longer needed + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + - The returned pointer must be freed with `managed_core_account_free` when no longer needed */ -FFIManagedAccount *managed_account_collection_get_provider_owner_keys(const FFIManagedAccountCollection *collection) +FFIManagedCoreAccount *managed_account_collection_get_provider_owner_keys(const FFIManagedCoreAccountCollection *collection) ; /* @@ -2900,10 +3100,10 @@ FFIManagedAccount *managed_account_collection_get_provider_owner_keys(const FFIM # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection */ -bool managed_account_collection_has_provider_owner_keys(const FFIManagedAccountCollection *collection) +bool managed_account_collection_has_provider_owner_keys(const FFIManagedCoreAccountCollection *collection) ; /* @@ -2912,11 +3112,11 @@ bool managed_account_collection_has_provider_owner_keys(const FFIManagedAccountC # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection - - The returned pointer must be freed with `managed_account_free` when no longer needed (when BLS is enabled) + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + - The returned pointer must be freed with `managed_core_account_free` when no longer needed (when BLS is enabled) */ -void *managed_account_collection_get_provider_operator_keys(const FFIManagedAccountCollection *collection) +void *managed_account_collection_get_provider_operator_keys(const FFIManagedCoreAccountCollection *collection) ; /* @@ -2924,10 +3124,10 @@ void *managed_account_collection_get_provider_operator_keys(const FFIManagedAcco # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection */ -bool managed_account_collection_has_provider_operator_keys(const FFIManagedAccountCollection *collection) +bool managed_account_collection_has_provider_operator_keys(const FFIManagedCoreAccountCollection *collection) ; /* @@ -2936,11 +3136,11 @@ bool managed_account_collection_has_provider_operator_keys(const FFIManagedAccou # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection - - The returned pointer must be freed with `managed_account_free` when no longer needed (when EdDSA is enabled) + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + - The returned pointer must be freed with `managed_core_account_free` when no longer needed (when EdDSA is enabled) */ -void *managed_account_collection_get_provider_platform_keys(const FFIManagedAccountCollection *collection) +void *managed_account_collection_get_provider_platform_keys(const FFIManagedCoreAccountCollection *collection) ; /* @@ -2948,10 +3148,82 @@ void *managed_account_collection_get_provider_platform_keys(const FFIManagedAcco # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection */ -bool managed_account_collection_has_provider_platform_keys(const FFIManagedAccountCollection *collection) +bool managed_account_collection_has_provider_platform_keys(const FFIManagedCoreAccountCollection *collection) +; + +/* + Get a Platform Payment account by account index and key class from the managed collection + + Platform Payment accounts (DIP-17) are identified by two indices: + - account_index: The account' level in the derivation path + - key_class: The key_class' level in the derivation path (typically 0) + + # Safety + + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + - The returned pointer must be freed with `managed_platform_account_free` when no longer needed + */ + +FFIManagedPlatformAccount *managed_account_collection_get_platform_payment_account(const FFIManagedCoreAccountCollection *collection, + unsigned int account_index, + unsigned int key_class) +; + +/* + Get all Platform Payment account keys from managed collection + + Returns an array of FFIPlatformPaymentAccountKey structures. + + # Safety + + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + - `out_keys` must be a valid pointer to store the keys array + - `out_count` must be a valid pointer to store the count + - The returned array must be freed with `managed_account_collection_free_platform_payment_keys` when no longer needed + */ + +bool managed_account_collection_get_platform_payment_keys(const FFIManagedCoreAccountCollection *collection, + FFIPlatformPaymentAccountKey **out_keys, + size_t *out_count) +; + +/* + Free platform payment keys array returned by managed_account_collection_get_platform_payment_keys + + # Safety + + - `keys` must be a pointer returned by `managed_account_collection_get_platform_payment_keys` + - `count` must be the count returned by `managed_account_collection_get_platform_payment_keys` + - This function must only be called once per allocation + */ + +void managed_account_collection_free_platform_payment_keys(FFIPlatformPaymentAccountKey *keys, + size_t count) +; + +/* + Check if there are any Platform Payment accounts in the managed collection + + # Safety + + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + */ + +bool managed_account_collection_has_platform_payment_accounts(const FFIManagedCoreAccountCollection *collection) +; + +/* + Get the number of Platform Payment accounts in the managed collection + + # Safety + + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + */ + +unsigned int managed_account_collection_platform_payment_count(const FFIManagedCoreAccountCollection *collection) ; /* @@ -2959,9 +3231,9 @@ bool managed_account_collection_has_provider_platform_keys(const FFIManagedAccou # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection */ - unsigned int managed_account_collection_count(const FFIManagedAccountCollection *collection) ; + unsigned int managed_account_collection_count(const FFIManagedCoreAccountCollection *collection) ; /* Get a human-readable summary of all accounts in the managed collection @@ -2971,11 +3243,11 @@ bool managed_account_collection_has_provider_platform_keys(const FFIManagedAccou # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned string must be freed with `string_free` when no longer needed - Returns null if the collection pointer is null */ - char *managed_account_collection_summary(const FFIManagedAccountCollection *collection) ; + char *managed_account_collection_summary(const FFIManagedCoreAccountCollection *collection) ; /* Get structured account collection summary data for managed collection @@ -2986,12 +3258,12 @@ bool managed_account_collection_has_provider_platform_keys(const FFIManagedAccou # Safety - - `collection` must be a valid pointer to an FFIManagedAccountCollection + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - The returned pointer must be freed with `managed_account_collection_summary_free` when no longer needed - Returns null if the collection pointer is null */ -FFIManagedAccountCollectionSummary *managed_account_collection_summary_data(const FFIManagedAccountCollection *collection) +FFIManagedCoreAccountCollectionSummary *managed_account_collection_summary_data(const FFIManagedCoreAccountCollection *collection) ; /* @@ -2999,11 +3271,11 @@ FFIManagedAccountCollectionSummary *managed_account_collection_summary_data(cons # Safety - - `summary` must be a valid pointer to an FFIManagedAccountCollectionSummary created by `managed_account_collection_summary_data` + - `summary` must be a valid pointer to an FFIManagedCoreAccountCollectionSummary created by `managed_account_collection_summary_data` - `summary` must not be used after calling this function */ -void managed_account_collection_summary_free(FFIManagedAccountCollectionSummary *summary) +void managed_account_collection_summary_free(FFIManagedCoreAccountCollectionSummary *summary) ; /* @@ -3820,6 +4092,13 @@ FFIWallet *wallet_create_random_with_options(FFINetwork network, The caller must ensure that: - The wallet pointer is either null or points to a valid FFIWallet - The FFIWallet remains valid for the duration of this call + + # Note + + This function does NOT support the following account types: + - `PlatformPayment`: Use `wallet_add_platform_payment_account()` instead + - `DashpayReceivingFunds`: Use `wallet_add_dashpay_receiving_account()` instead + - `DashpayExternalAccount`: Use `wallet_add_dashpay_external_account_with_xpub_bytes()` instead */ FFIAccountResult wallet_add_account(FFIWallet *wallet, @@ -3867,6 +4146,13 @@ FFIAccountResult wallet_add_dashpay_external_account_with_xpub_bytes(FFIWallet * - The wallet pointer is either null or points to a valid FFIWallet - The xpub_bytes pointer is either null or points to at least xpub_len bytes - The FFIWallet remains valid for the duration of this call + + # Note + + This function does NOT support the following account types: + - `PlatformPayment`: Use `wallet_add_platform_payment_account()` instead + - `DashpayReceivingFunds`: Use `wallet_add_dashpay_receiving_account()` instead + - `DashpayExternalAccount`: Use `wallet_add_dashpay_external_account_with_xpub_bytes()` instead */ FFIAccountResult wallet_add_account_with_xpub_bytes(FFIWallet *wallet, @@ -3886,6 +4172,13 @@ FFIAccountResult wallet_add_account_with_xpub_bytes(FFIWallet *wallet, - The wallet pointer is either null or points to a valid FFIWallet - The xpub_string pointer is either null or points to a valid null-terminated C string - The FFIWallet remains valid for the duration of this call + + # Note + + This function does NOT support the following account types: + - `PlatformPayment`: Use `wallet_add_platform_payment_account()` instead + - `DashpayReceivingFunds`: Use `wallet_add_dashpay_receiving_account()` instead + - `DashpayExternalAccount`: Use `wallet_add_dashpay_external_account_with_xpub_bytes()` instead */ FFIAccountResult wallet_add_account_with_string_xpub(FFIWallet *wallet, @@ -3894,6 +4187,30 @@ FFIAccountResult wallet_add_account_with_string_xpub(FFIWallet *wallet, const char *xpub_string) ; +/* + Add a Platform Payment account (DIP-17) to the wallet + + Platform Payment accounts use the derivation path: + `m/9'/coin_type'/17'/account'/key_class'/index` + + # Arguments + * `wallet` - Pointer to the wallet + * `account_index` - The account index (hardened) in the derivation path + * `key_class` - The key class (hardened) - typically 0' for main addresses + + # Safety + + This function dereferences a raw pointer to FFIWallet. + The caller must ensure that: + - The wallet pointer is either null or points to a valid FFIWallet + - The FFIWallet remains valid for the duration of this call + */ + +FFIAccountResult wallet_add_platform_payment_account(FFIWallet *wallet, + unsigned int account_index, + unsigned int key_class) +; + /* Describe the wallet manager for a given network and return a newly allocated C string. diff --git a/key-wallet-ffi/src/address_pool.rs b/key-wallet-ffi/src/address_pool.rs index b255efac7..98ce14942 100644 --- a/key-wallet-ffi/src/address_pool.rs +++ b/key-wallet-ffi/src/address_pool.rs @@ -14,14 +14,14 @@ use key_wallet::account::ManagedAccountCollection; use key_wallet::managed_account::address_pool::{ AddressInfo, AddressPool, KeySource, PublicKeyType, }; -use key_wallet::managed_account::ManagedAccount; +use key_wallet::managed_account::ManagedCoreAccount; use key_wallet::AccountType; // Helper functions to get managed accounts by type fn get_managed_account_by_type<'a>( collection: &'a ManagedAccountCollection, account_type: &AccountType, -) -> Option<&'a ManagedAccount> { +) -> Option<&'a ManagedCoreAccount> { match account_type { AccountType::Standard { index, @@ -70,7 +70,7 @@ fn get_managed_account_by_type<'a>( fn get_managed_account_by_type_mut<'a>( collection: &'a mut ManagedAccountCollection, account_type: &AccountType, -) -> Option<&'a mut ManagedAccount> { +) -> Option<&'a mut ManagedCoreAccount> { match account_type { AccountType::Standard { index, @@ -495,7 +495,17 @@ pub unsafe extern "C" fn managed_wallet_generate_addresses_to_index( let account_type_rust = account_type.to_account_type(account_index); - let account_type_to_check = account_type_rust.into(); + let account_type_to_check = match account_type_rust.try_into() { + Ok(check_type) => check_type, + Err(_) => { + FFIError::set_error( + error, + FFIErrorCode::InvalidInput, + "Platform Payment accounts cannot be used for address pool operations".to_string(), + ); + return false; + } + }; let xpub_opt = wallet .inner() @@ -1044,7 +1054,7 @@ mod tests { // Test the simplified address_pool_get_address_at_index function unsafe { use crate::managed_account::{ - managed_account_free, managed_account_get_external_address_pool, + managed_core_account_free, managed_core_account_get_external_address_pool, }; use crate::wallet_manager::{ wallet_manager_add_wallet_from_mnemonic_with_options, wallet_manager_create, @@ -1103,7 +1113,7 @@ mod tests { let account = result.account; // Get external address pool - let external_pool = managed_account_get_external_address_pool(account); + let external_pool = managed_core_account_get_external_address_pool(account); assert!(!external_pool.is_null()); // Test getting address at index 0 (should exist by default) @@ -1132,7 +1142,7 @@ mod tests { // Clean up address_pool_free(external_pool); - managed_account_free(account); + managed_core_account_free(account); wallet_manager_free_wallet_ids(wallet_ids_out, count_out); wallet_manager_free(manager); error.free_message(); @@ -1144,7 +1154,7 @@ mod tests { // Test the simplified address_pool_get_addresses_in_range function unsafe { use crate::managed_account::{ - managed_account_free, managed_account_get_external_address_pool, + managed_core_account_free, managed_core_account_get_external_address_pool, }; use crate::wallet_manager::{ wallet_manager_add_wallet_from_mnemonic_with_options, wallet_manager_create, @@ -1203,7 +1213,7 @@ mod tests { let account = result.account; // Get external address pool - let external_pool = managed_account_get_external_address_pool(account); + let external_pool = managed_core_account_get_external_address_pool(account); assert!(!external_pool.is_null()); // Test getting a range of addresses @@ -1261,7 +1271,7 @@ mod tests { // Clean up address_pool_free(external_pool); - managed_account_free(account); + 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/managed_account.rs b/key-wallet-ffi/src/managed_account.rs index 54c9bbbef..dafd3f907 100644 --- a/key-wallet-ffi/src/managed_account.rs +++ b/key-wallet-ffi/src/managed_account.rs @@ -1,7 +1,7 @@ //! Managed account FFI bindings //! //! This module provides FFI-compatible managed account functionality that wraps -//! ManagedAccount instances from the key-wallet crate. FFIManagedAccount is a +//! ManagedAccount instances from the key-wallet crate. FFIManagedCoreAccount is a //! simple wrapper around `Arc` without additional fields. use std::os::raw::c_uint; @@ -14,46 +14,136 @@ use crate::error::{FFIError, FFIErrorCode}; use crate::types::FFIAccountType; use crate::wallet_manager::FFIWalletManager; use crate::FFINetwork; -use key_wallet::account::account_collection::DashpayAccountKey; +use key_wallet::account::account_collection::{DashpayAccountKey, PlatformPaymentAccountKey}; use key_wallet::managed_account::address_pool::AddressPool; -use key_wallet::managed_account::ManagedAccount; +use key_wallet::managed_account::managed_platform_account::ManagedPlatformAccount; +use key_wallet::managed_account::ManagedCoreAccount; use key_wallet::AccountType; /// Opaque managed account handle that wraps ManagedAccount -pub struct FFIManagedAccount { +pub struct FFIManagedCoreAccount { /// The underlying managed account - pub(crate) account: Arc, + pub(crate) account: Arc, } -impl FFIManagedAccount { +impl FFIManagedCoreAccount { /// Create a new FFI managed account handle - pub fn new(account: &ManagedAccount) -> Self { - FFIManagedAccount { + pub fn new(account: &ManagedCoreAccount) -> Self { + FFIManagedCoreAccount { account: Arc::new(account.clone()), } } /// Get a reference to the inner managed account - pub fn inner(&self) -> &ManagedAccount { + pub fn inner(&self) -> &ManagedCoreAccount { self.account.as_ref() } } +/// Opaque managed platform account handle that wraps ManagedPlatformAccount +/// +/// This is different from FFIManagedCoreAccount because ManagedPlatformAccount +/// has a different structure optimized for Platform Payment accounts (DIP-17): +/// - Simple u64 credit balance instead of WalletCoreBalance +/// - Per-address balances tracked directly +/// - No transactions or UTXOs (Platform handles these) +pub struct FFIManagedPlatformAccount { + /// The underlying managed platform account + pub(crate) account: Arc, +} + +impl FFIManagedPlatformAccount { + /// Create a new FFI managed platform account handle + pub fn new(account: &ManagedPlatformAccount) -> Self { + FFIManagedPlatformAccount { + account: Arc::new(account.clone()), + } + } + + /// Get a reference to the inner managed platform account + pub fn inner(&self) -> &ManagedPlatformAccount { + self.account.as_ref() + } +} + +/// FFI Result type for ManagedPlatformAccount operations +#[repr(C)] +pub struct FFIManagedPlatformAccountResult { + /// The managed platform account handle if successful, NULL if error + pub account: *mut FFIManagedPlatformAccount, + /// Error code (0 = success) + pub error_code: i32, + /// Error message (NULL if success, must be freed by caller if not NULL) + pub error_message: *mut std::os::raw::c_char, +} + +impl FFIManagedPlatformAccountResult { + /// Create a success result + pub fn success(account: *mut FFIManagedPlatformAccount) -> Self { + FFIManagedPlatformAccountResult { + account, + error_code: 0, + error_message: std::ptr::null_mut(), + } + } + + /// Create an error result + pub fn error(code: FFIErrorCode, message: String) -> Self { + use std::ffi::CString; + let c_message = CString::new(message).unwrap_or_else(|_| { + CString::new("Unknown error").expect("Hardcoded string should never fail") + }); + FFIManagedPlatformAccountResult { + account: std::ptr::null_mut(), + error_code: code as i32, + error_message: c_message.into_raw(), + } + } +} + +/// C-compatible platform payment account key +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct FFIPlatformPaymentAccountKey { + /// Account index (hardened) + pub account: c_uint, + /// Key class (hardened) + pub key_class: c_uint, +} + +impl From<&PlatformPaymentAccountKey> for FFIPlatformPaymentAccountKey { + fn from(key: &PlatformPaymentAccountKey) -> Self { + FFIPlatformPaymentAccountKey { + account: key.account, + key_class: key.key_class, + } + } +} + +impl From for PlatformPaymentAccountKey { + fn from(key: FFIPlatformPaymentAccountKey) -> Self { + PlatformPaymentAccountKey { + account: key.account, + key_class: key.key_class, + } + } +} + /// FFI Result type for ManagedAccount operations #[repr(C)] -pub struct FFIManagedAccountResult { +pub struct FFIManagedCoreAccountResult { /// The managed account handle if successful, NULL if error - pub account: *mut FFIManagedAccount, + pub account: *mut FFIManagedCoreAccount, /// Error code (0 = success) pub error_code: i32, /// Error message (NULL if success, must be freed by caller if not NULL) pub error_message: *mut std::os::raw::c_char, } -impl FFIManagedAccountResult { +impl FFIManagedCoreAccountResult { /// Create a success result - pub fn success(account: *mut FFIManagedAccount) -> Self { - FFIManagedAccountResult { + pub fn success(account: *mut FFIManagedCoreAccount) -> Self { + FFIManagedCoreAccountResult { account, error_code: 0, error_message: std::ptr::null_mut(), @@ -66,7 +156,7 @@ impl FFIManagedAccountResult { let c_message = CString::new(message).unwrap_or_else(|_| { CString::new("Unknown error").expect("Hardcoded string should never fail") }); - FFIManagedAccountResult { + FFIManagedCoreAccountResult { account: std::ptr::null_mut(), error_code: code as i32, error_message: c_message.into_raw(), @@ -84,23 +174,23 @@ impl FFIManagedAccountResult { /// - `manager` must be a valid pointer to an FFIWalletManager instance /// - `wallet_id` must be a valid pointer to a 32-byte wallet ID /// - The caller must ensure all pointers remain valid for the duration of this call -/// - The returned account must be freed with `managed_account_free` when no longer needed +/// - The returned account must be freed with `managed_core_account_free` when no longer needed #[no_mangle] pub unsafe extern "C" fn managed_wallet_get_account( manager: *const FFIWalletManager, wallet_id: *const u8, account_index: c_uint, account_type: FFIAccountType, -) -> FFIManagedAccountResult { +) -> FFIManagedCoreAccountResult { if manager.is_null() { - return FFIManagedAccountResult::error( + return FFIManagedCoreAccountResult::error( FFIErrorCode::InvalidInput, "Manager is null".to_string(), ); } if wallet_id.is_null() { - return FFIManagedAccountResult::error( + return FFIManagedCoreAccountResult::error( FFIErrorCode::InvalidInput, "Wallet ID is null".to_string(), ); @@ -113,7 +203,7 @@ pub unsafe extern "C" fn managed_wallet_get_account( ); if managed_wallet_ptr.is_null() { - return FFIManagedAccountResult::error( + return FFIManagedCoreAccountResult::error( error.code, if error.message.is_null() { "Failed to get managed wallet info".to_string() @@ -171,10 +261,10 @@ pub unsafe extern "C" fn managed_wallet_get_account( match managed_account { Some(account) => { - let ffi_account = FFIManagedAccount::new(account); - FFIManagedAccountResult::success(Box::into_raw(Box::new(ffi_account))) + let ffi_account = FFIManagedCoreAccount::new(account); + FFIManagedCoreAccountResult::success(Box::into_raw(Box::new(ffi_account))) } - None => FFIManagedAccountResult::error( + None => FFIManagedCoreAccountResult::error( FFIErrorCode::NotFound, "Account not found".to_string(), ), @@ -197,22 +287,22 @@ pub unsafe extern "C" fn managed_wallet_get_account( /// - `manager` must be a valid pointer to an FFIWalletManager instance /// - `wallet_id` must be a valid pointer to a 32-byte wallet ID /// - The caller must ensure all pointers remain valid for the duration of this call -/// - The returned account must be freed with `managed_account_free` when no longer needed +/// - The returned account must be freed with `managed_core_account_free` when no longer needed #[no_mangle] pub unsafe extern "C" fn managed_wallet_get_top_up_account_with_registration_index( manager: *const FFIWalletManager, wallet_id: *const u8, registration_index: c_uint, -) -> FFIManagedAccountResult { +) -> FFIManagedCoreAccountResult { if manager.is_null() { - return FFIManagedAccountResult::error( + return FFIManagedCoreAccountResult::error( FFIErrorCode::InvalidInput, "Manager is null".to_string(), ); } if wallet_id.is_null() { - return FFIManagedAccountResult::error( + return FFIManagedCoreAccountResult::error( FFIErrorCode::InvalidInput, "Wallet ID is null".to_string(), ); @@ -225,7 +315,7 @@ pub unsafe extern "C" fn managed_wallet_get_top_up_account_with_registration_ind ); if managed_wallet_ptr.is_null() { - return FFIManagedAccountResult::error( + return FFIManagedCoreAccountResult::error( error.code, if error.message.is_null() { "Failed to get managed wallet info".to_string() @@ -240,10 +330,10 @@ pub unsafe extern "C" fn managed_wallet_get_top_up_account_with_registration_ind let result = match managed_wallet.inner().accounts.identity_topup.get(®istration_index) { Some(account) => { - let ffi_account = FFIManagedAccount::new(account); - FFIManagedAccountResult::success(Box::into_raw(Box::new(ffi_account))) + let ffi_account = FFIManagedCoreAccount::new(account); + FFIManagedCoreAccountResult::success(Box::into_raw(Box::new(ffi_account))) } - None => FFIManagedAccountResult::error( + None => FFIManagedCoreAccountResult::error( FFIErrorCode::NotFound, format!( "IdentityTopUp account for registration index {} not found", @@ -270,13 +360,13 @@ pub unsafe extern "C" fn managed_wallet_get_dashpay_receiving_account( account_index: c_uint, user_identity_id: *const u8, friend_identity_id: *const u8, -) -> FFIManagedAccountResult { +) -> FFIManagedCoreAccountResult { if manager.is_null() || wallet_id.is_null() || user_identity_id.is_null() || friend_identity_id.is_null() { - return FFIManagedAccountResult::error( + return FFIManagedCoreAccountResult::error( FFIErrorCode::InvalidInput, "Null pointer provided".to_string(), ); @@ -296,7 +386,7 @@ pub unsafe extern "C" fn managed_wallet_get_dashpay_receiving_account( manager, wallet_id, &mut error, ); if managed_wallet_ptr.is_null() { - return FFIManagedAccountResult::error( + return FFIManagedCoreAccountResult::error( error.code, if error.message.is_null() { "Failed to get managed wallet info".to_string() @@ -308,12 +398,13 @@ pub unsafe extern "C" fn managed_wallet_get_dashpay_receiving_account( let managed_wallet = &*managed_wallet_ptr; let result = match managed_wallet.inner().accounts.dashpay_receival_accounts.get(&key) { - Some(account) => FFIManagedAccountResult::success(Box::into_raw(Box::new( - FFIManagedAccount::new(account), + Some(account) => FFIManagedCoreAccountResult::success(Box::into_raw(Box::new( + FFIManagedCoreAccount::new(account), ))), - None => { - FFIManagedAccountResult::error(FFIErrorCode::NotFound, "Account not found".to_string()) - } + None => FFIManagedCoreAccountResult::error( + FFIErrorCode::NotFound, + "Account not found".to_string(), + ), }; crate::managed_wallet::managed_wallet_info_free(managed_wallet_ptr); result @@ -330,13 +421,13 @@ pub unsafe extern "C" fn managed_wallet_get_dashpay_external_account( account_index: c_uint, user_identity_id: *const u8, friend_identity_id: *const u8, -) -> FFIManagedAccountResult { +) -> FFIManagedCoreAccountResult { if manager.is_null() || wallet_id.is_null() || user_identity_id.is_null() || friend_identity_id.is_null() { - return FFIManagedAccountResult::error( + return FFIManagedCoreAccountResult::error( FFIErrorCode::InvalidInput, "Null pointer provided".to_string(), ); @@ -356,7 +447,7 @@ pub unsafe extern "C" fn managed_wallet_get_dashpay_external_account( manager, wallet_id, &mut error, ); if managed_wallet_ptr.is_null() { - return FFIManagedAccountResult::error( + return FFIManagedCoreAccountResult::error( error.code, if error.message.is_null() { "Failed to get managed wallet info".to_string() @@ -368,12 +459,13 @@ pub unsafe extern "C" fn managed_wallet_get_dashpay_external_account( let managed_wallet = &*managed_wallet_ptr; let result = match managed_wallet.inner().accounts.dashpay_external_accounts.get(&key) { - Some(account) => FFIManagedAccountResult::success(Box::into_raw(Box::new( - FFIManagedAccount::new(account), + Some(account) => FFIManagedCoreAccountResult::success(Box::into_raw(Box::new( + FFIManagedCoreAccount::new(account), ))), - None => { - FFIManagedAccountResult::error(FFIErrorCode::NotFound, "Account not found".to_string()) - } + None => FFIManagedCoreAccountResult::error( + FFIErrorCode::NotFound, + "Account not found".to_string(), + ), }; crate::managed_wallet::managed_wallet_info_free(managed_wallet_ptr); result @@ -383,11 +475,11 @@ pub unsafe extern "C" fn managed_wallet_get_dashpay_external_account( /// /// # Safety /// -/// - `account` must be a valid pointer to an FFIManagedAccount instance +/// - `account` must be a valid pointer to an FFIManagedCoreAccount instance /// - Returns `FFINetwork::Dash` if the account is null #[no_mangle] -pub unsafe extern "C" fn managed_account_get_network( - account: *const FFIManagedAccount, +pub unsafe extern "C" fn managed_core_account_get_network( + account: *const FFIManagedCoreAccount, ) -> FFINetwork { if account.is_null() { return FFINetwork::Dash; @@ -408,7 +500,9 @@ pub unsafe extern "C" fn managed_account_get_network( /// - The returned pointer is the same as the input pointer for convenience /// - The caller must not free the returned pointer as it's the same as the input #[no_mangle] -pub unsafe extern "C" fn managed_account_get_parent_wallet_id(wallet_id: *const u8) -> *const u8 { +pub unsafe extern "C" fn managed_core_account_get_parent_wallet_id( + wallet_id: *const u8, +) -> *const u8 { // Simply return the wallet_id that was passed in // This function exists for API consistency but ManagedAccount doesn't store parent wallet ID wallet_id @@ -418,11 +512,11 @@ pub unsafe extern "C" fn managed_account_get_parent_wallet_id(wallet_id: *const /// /// # Safety /// -/// - `account` must be a valid pointer to an FFIManagedAccount instance +/// - `account` must be a valid pointer to an FFIManagedCoreAccount instance /// - `index_out` must be a valid pointer to receive the account index (or null) #[no_mangle] -pub unsafe extern "C" fn managed_account_get_account_type( - account: *const FFIManagedAccount, +pub unsafe extern "C" fn managed_core_account_get_account_type( + account: *const FFIManagedCoreAccount, index_out: *mut c_uint, ) -> FFIAccountType { if account.is_null() { @@ -481,10 +575,10 @@ pub unsafe extern "C" fn managed_account_get_account_type( /// /// # Safety /// -/// - `account` must be a valid pointer to an FFIManagedAccount instance +/// - `account` must be a valid pointer to an FFIManagedCoreAccount instance #[no_mangle] -pub unsafe extern "C" fn managed_account_get_is_watch_only( - account: *const FFIManagedAccount, +pub unsafe extern "C" fn managed_core_account_get_is_watch_only( + account: *const FFIManagedCoreAccount, ) -> bool { if account.is_null() { return false; @@ -498,11 +592,11 @@ pub unsafe extern "C" fn managed_account_get_is_watch_only( /// /// # Safety /// -/// - `account` must be a valid pointer to an FFIManagedAccount instance +/// - `account` must be a valid pointer to an FFIManagedCoreAccount instance /// - `balance_out` must be a valid pointer to an FFIBalance structure #[no_mangle] -pub unsafe extern "C" fn managed_account_get_balance( - account: *const FFIManagedAccount, +pub unsafe extern "C" fn managed_core_account_get_balance( + account: *const FFIManagedCoreAccount, balance_out: *mut crate::types::FFIBalance, ) -> bool { if account.is_null() || balance_out.is_null() { @@ -527,10 +621,10 @@ pub unsafe extern "C" fn managed_account_get_balance( /// /// # Safety /// -/// - `account` must be a valid pointer to an FFIManagedAccount instance +/// - `account` must be a valid pointer to an FFIManagedCoreAccount instance #[no_mangle] -pub unsafe extern "C" fn managed_account_get_transaction_count( - account: *const FFIManagedAccount, +pub unsafe extern "C" fn managed_core_account_get_transaction_count( + account: *const FFIManagedCoreAccount, ) -> c_uint { if account.is_null() { return 0; @@ -544,10 +638,10 @@ pub unsafe extern "C" fn managed_account_get_transaction_count( /// /// # Safety /// -/// - `account` must be a valid pointer to an FFIManagedAccount instance +/// - `account` must be a valid pointer to an FFIManagedCoreAccount instance #[no_mangle] -pub unsafe extern "C" fn managed_account_get_utxo_count( - account: *const FFIManagedAccount, +pub unsafe extern "C" fn managed_core_account_get_utxo_count( + account: *const FFIManagedCoreAccount, ) -> c_uint { if account.is_null() { return 0; @@ -582,13 +676,13 @@ pub struct FFITransactionRecord { /// /// # Safety /// -/// - `account` must be a valid pointer to an FFIManagedAccount instance +/// - `account` must be a valid pointer to an FFIManagedCoreAccount instance /// - `transactions_out` must be a valid pointer to receive the transactions array pointer /// - `count_out` must be a valid pointer to receive the count -/// - The caller must free the returned array using `managed_account_free_transactions` +/// - The caller must free the returned array using `managed_core_account_free_transactions` #[no_mangle] -pub unsafe extern "C" fn managed_account_get_transactions( - account: *const FFIManagedAccount, +pub unsafe extern "C" fn managed_core_account_get_transactions( + account: *const FFIManagedCoreAccount, transactions_out: *mut *mut FFITransactionRecord, count_out: *mut usize, ) -> bool { @@ -652,15 +746,15 @@ pub unsafe extern "C" fn managed_account_get_transactions( true } -/// Free transactions array returned by managed_account_get_transactions +/// Free transactions array returned by managed_core_account_get_transactions /// /// # Safety /// -/// - `transactions` must be a pointer returned by `managed_account_get_transactions` -/// - `count` must be the count returned by `managed_account_get_transactions` +/// - `transactions` must be a pointer returned by `managed_core_account_get_transactions` +/// - `count` must be the count returned by `managed_core_account_get_transactions` /// - This function must only be called once per allocation #[no_mangle] -pub unsafe extern "C" fn managed_account_free_transactions( +pub unsafe extern "C" fn managed_core_account_free_transactions( transactions: *mut FFITransactionRecord, count: usize, ) { @@ -677,26 +771,28 @@ pub unsafe extern "C" fn managed_account_free_transactions( /// /// # Safety /// -/// - `account` must be a valid pointer to an FFIManagedAccount that was allocated by this library +/// - `account` must be a valid pointer to an FFIManagedCoreAccount 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 managed_account_free(account: *mut FFIManagedAccount) { +pub unsafe extern "C" fn managed_core_account_free(account: *mut FFIManagedCoreAccount) { if !account.is_null() { let _ = Box::from_raw(account); } } /// Free a managed account result's error message (if any) -/// Note: This does NOT free the account handle itself - use managed_account_free for that +/// Note: This does NOT free the account handle itself - use managed_core_account_free for that /// /// # Safety /// -/// - `result` must be a valid pointer to an FFIManagedAccountResult +/// - `result` must be a valid pointer to an FFIManagedCoreAccountResult /// - The error_message field must be either null or a valid CString allocated by this library /// - The caller must ensure the result pointer remains valid for the duration of this call #[no_mangle] -pub unsafe extern "C" fn managed_account_result_free_error(result: *mut FFIManagedAccountResult) { +pub unsafe extern "C" fn managed_core_account_result_free_error( + result: *mut FFIManagedCoreAccountResult, +) { if !result.is_null() { let result = &mut *result; if !result.error_message.is_null() { @@ -749,7 +845,7 @@ pub unsafe extern "C" fn managed_wallet_get_account_count( count as c_uint } -// Note: BLS and EdDSA accounts are handled through regular FFIManagedAccount +// Note: BLS and EdDSA accounts are handled through regular FFIManagedCoreAccount // since ManagedAccountCollection stores all accounts as ManagedAccount type /// Get the account index from a managed account @@ -759,9 +855,11 @@ pub unsafe extern "C" fn managed_wallet_get_account_count( /// /// # Safety /// -/// - `account` must be a valid pointer to an FFIManagedAccount instance +/// - `account` must be a valid pointer to an FFIManagedCoreAccount instance #[no_mangle] -pub unsafe extern "C" fn managed_account_get_index(account: *const FFIManagedAccount) -> c_uint { +pub unsafe extern "C" fn managed_core_account_get_index( + account: *const FFIManagedCoreAccount, +) -> c_uint { if account.is_null() { return 0; } @@ -777,11 +875,11 @@ pub unsafe extern "C" fn managed_account_get_index(account: *const FFIManagedAcc /// /// # Safety /// -/// - `account` must be a valid pointer to an FFIManagedAccount instance +/// - `account` must be a valid pointer to an FFIManagedCoreAccount instance /// - The returned pool must be freed with `address_pool_free` when no longer needed #[no_mangle] -pub unsafe extern "C" fn managed_account_get_external_address_pool( - account: *const FFIManagedAccount, +pub unsafe extern "C" fn managed_core_account_get_external_address_pool( + account: *const FFIManagedCoreAccount, ) -> *mut FFIAddressPool { if account.is_null() { return std::ptr::null_mut(); @@ -813,11 +911,11 @@ pub unsafe extern "C" fn managed_account_get_external_address_pool( /// /// # Safety /// -/// - `account` must be a valid pointer to an FFIManagedAccount instance +/// - `account` must be a valid pointer to an FFIManagedCoreAccount instance /// - The returned pool must be freed with `address_pool_free` when no longer needed #[no_mangle] -pub unsafe extern "C" fn managed_account_get_internal_address_pool( - account: *const FFIManagedAccount, +pub unsafe extern "C" fn managed_core_account_get_internal_address_pool( + account: *const FFIManagedCoreAccount, ) -> *mut FFIAddressPool { if account.is_null() { return std::ptr::null_mut(); @@ -851,12 +949,12 @@ pub unsafe extern "C" fn managed_account_get_internal_address_pool( /// # Safety /// /// - `manager` must be a valid pointer to an FFIWalletManager instance -/// - `account` must be a valid pointer to an FFIManagedAccount instance +/// - `account` must be a valid pointer to an FFIManagedCoreAccount instance /// - `wallet_id` must be a valid pointer to a 32-byte wallet ID /// - The returned pool must be freed with `address_pool_free` when no longer needed #[no_mangle] -pub unsafe extern "C" fn managed_account_get_address_pool( - account: *const FFIManagedAccount, +pub unsafe extern "C" fn managed_core_account_get_address_pool( + account: *const FFIManagedCoreAccount, pool_type: FFIAddressPoolType, ) -> *mut FFIAddressPool { if account.is_null() { @@ -962,6 +1060,286 @@ pub unsafe extern "C" fn managed_account_get_address_pool( } } +// ==================== Platform Payment Account Functions ==================== + +/// Get a managed platform payment account from a managed wallet +/// +/// Platform Payment accounts (DIP-17) are identified by account index and key_class. +/// Returns a platform account handle that wraps the ManagedPlatformAccount. +/// +/// # Safety +/// +/// - `manager` must be a valid pointer to an FFIWalletManager instance +/// - `wallet_id` must be a valid pointer to a 32-byte wallet ID +/// - The caller must ensure all pointers remain valid for the duration of this call +/// - The returned account must be freed with `managed_platform_account_free` when no longer needed +#[no_mangle] +pub unsafe extern "C" fn managed_wallet_get_platform_payment_account( + manager: *const FFIWalletManager, + wallet_id: *const u8, + account_index: c_uint, + key_class: c_uint, +) -> FFIManagedPlatformAccountResult { + if manager.is_null() { + return FFIManagedPlatformAccountResult::error( + FFIErrorCode::InvalidInput, + "Manager is null".to_string(), + ); + } + + if wallet_id.is_null() { + return FFIManagedPlatformAccountResult::error( + FFIErrorCode::InvalidInput, + "Wallet ID is null".to_string(), + ); + } + + // Get the managed wallet info from the manager + let mut error = FFIError::success(); + let managed_wallet_ptr = crate::wallet_manager::wallet_manager_get_managed_wallet_info( + manager, wallet_id, &mut error, + ); + + if managed_wallet_ptr.is_null() { + return FFIManagedPlatformAccountResult::error( + error.code, + if error.message.is_null() { + "Failed to get managed wallet info".to_string() + } else { + let c_str = std::ffi::CStr::from_ptr(error.message); + c_str.to_string_lossy().to_string() + }, + ); + } + + let managed_wallet = &*managed_wallet_ptr; + let key = PlatformPaymentAccountKey { + account: account_index, + key_class, + }; + + let result = match managed_wallet.inner().accounts.platform_payment_accounts.get(&key) { + Some(account) => { + let ffi_account = FFIManagedPlatformAccount::new(account); + FFIManagedPlatformAccountResult::success(Box::into_raw(Box::new(ffi_account))) + } + None => FFIManagedPlatformAccountResult::error( + FFIErrorCode::NotFound, + format!( + "Platform Payment account (account: {}, key_class: {}) not found", + account_index, key_class + ), + ), + }; + + // Clean up the managed wallet pointer + crate::managed_wallet::managed_wallet_info_free(managed_wallet_ptr); + + result +} + +/// Get the network of a managed platform account +/// +/// # Safety +/// +/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance +/// - Returns `FFINetwork::Dash` if the account is null +#[no_mangle] +pub unsafe extern "C" fn managed_platform_account_get_network( + account: *const FFIManagedPlatformAccount, +) -> FFINetwork { + if account.is_null() { + return FFINetwork::Dash; + } + + let account = &*account; + account.inner().network.into() +} + +/// Get the account index of a managed platform account +/// +/// # Safety +/// +/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance +#[no_mangle] +pub unsafe extern "C" fn managed_platform_account_get_account_index( + account: *const FFIManagedPlatformAccount, +) -> c_uint { + if account.is_null() { + return 0; + } + + let account = &*account; + account.inner().account +} + +/// Get the key class of a managed platform account +/// +/// # Safety +/// +/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance +#[no_mangle] +pub unsafe extern "C" fn managed_platform_account_get_key_class( + account: *const FFIManagedPlatformAccount, +) -> c_uint { + if account.is_null() { + return 0; + } + + let account = &*account; + account.inner().key_class +} + +/// Get the total credit balance of a managed platform account +/// +/// Returns the balance in credits (1000 credits = 1 duff) +/// +/// # Safety +/// +/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance +#[no_mangle] +pub unsafe extern "C" fn managed_platform_account_get_credit_balance( + account: *const FFIManagedPlatformAccount, +) -> u64 { + if account.is_null() { + return 0; + } + + let account = &*account; + account.inner().total_credit_balance() +} + +/// Get the total balance in duffs of a managed platform account +/// +/// Returns the balance in duffs (credit_balance / 1000) +/// +/// # Safety +/// +/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance +#[no_mangle] +pub unsafe extern "C" fn managed_platform_account_get_duff_balance( + account: *const FFIManagedPlatformAccount, +) -> u64 { + if account.is_null() { + return 0; + } + + let account = &*account; + account.inner().duff_balance() +} + +/// Get the number of funded addresses in a managed platform account +/// +/// # Safety +/// +/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance +#[no_mangle] +pub unsafe extern "C" fn managed_platform_account_get_funded_address_count( + account: *const FFIManagedPlatformAccount, +) -> c_uint { + if account.is_null() { + return 0; + } + + let account = &*account; + account.inner().funded_address_count() as c_uint +} + +/// Get the total number of addresses in a managed platform account +/// +/// # Safety +/// +/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance +#[no_mangle] +pub unsafe extern "C" fn managed_platform_account_get_total_address_count( + account: *const FFIManagedPlatformAccount, +) -> c_uint { + if account.is_null() { + return 0; + } + + let account = &*account; + account.inner().total_address_count() as c_uint +} + +/// Check if a managed platform account is watch-only +/// +/// # Safety +/// +/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance +#[no_mangle] +pub unsafe extern "C" fn managed_platform_account_get_is_watch_only( + account: *const FFIManagedPlatformAccount, +) -> bool { + if account.is_null() { + return false; + } + + let account = &*account; + account.inner().is_watch_only +} + +/// Get the address pool from a managed platform account +/// +/// Platform accounts only have a single address pool. +/// +/// # Safety +/// +/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance +/// - The returned pool must be freed with `address_pool_free` when no longer needed +#[no_mangle] +pub unsafe extern "C" fn managed_platform_account_get_address_pool( + account: *const FFIManagedPlatformAccount, +) -> *mut FFIAddressPool { + if account.is_null() { + return std::ptr::null_mut(); + } + + let account = &*account; + let pool_ref = &account.inner().addresses; + + let ffi_pool = FFIAddressPool { + pool: pool_ref as *const AddressPool as *mut AddressPool, + pool_type: FFIAddressPoolType::Single, + }; + Box::into_raw(Box::new(ffi_pool)) +} + +/// Free a managed platform account handle +/// +/// # Safety +/// +/// - `account` must be a valid pointer to an FFIManagedPlatformAccount 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 managed_platform_account_free(account: *mut FFIManagedPlatformAccount) { + if !account.is_null() { + let _ = Box::from_raw(account); + } +} + +/// Free a managed platform account result's error message (if any) +/// Note: This does NOT free the account handle itself - use managed_platform_account_free for that +/// +/// # Safety +/// +/// - `result` must be a valid pointer to an FFIManagedPlatformAccountResult +/// - The error_message field must be either null or a valid CString allocated by this library +/// - The caller must ensure the result pointer remains valid for the duration of this call +#[no_mangle] +pub unsafe extern "C" fn managed_platform_account_result_free_error( + result: *mut FFIManagedPlatformAccountResult, +) { + if !result.is_null() { + let result = &mut *result; + if !result.error_message.is_null() { + let _ = std::ffi::CString::from_raw(result.error_message); + result.error_message = std::ptr::null_mut(); + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -1033,7 +1411,7 @@ mod tests { assert!(!account.inner().is_watch_only); // Clean up - managed_account_free(result.account); + managed_core_account_free(result.account); wallet_manager_free_wallet_ids(wallet_ids_out, count_out); wallet_manager_free(manager); } @@ -1089,7 +1467,7 @@ mod tests { assert!(!result.error_message.is_null()); // Clean up error message - managed_account_result_free_error(&mut result as *mut _); + managed_core_account_result_free_error(&mut result as *mut _); // Clean up wallet_manager_free_wallet_ids(wallet_ids_out, count_out); @@ -1098,10 +1476,10 @@ mod tests { } #[test] - fn test_managed_account_free_null() { + fn test_managed_core_account_free_null() { unsafe { // Should not crash when freeing null - managed_account_free(ptr::null_mut()); + managed_core_account_free(ptr::null_mut()); } } @@ -1219,17 +1597,17 @@ mod tests { let account = result.account; // Test get_network - let network = managed_account_get_network(account); + let network = managed_core_account_get_network(account); assert_eq!(network, FFINetwork::Testnet); // Test get_account_type let mut index_out: c_uint = 999; // Initialize with unexpected value - let account_type = managed_account_get_account_type(account, &mut index_out); + let account_type = managed_core_account_get_account_type(account, &mut index_out); assert_eq!(account_type, FFIAccountType::StandardBIP44); assert_eq!(index_out, 0); // Test get_is_watch_only - let is_watch_only = managed_account_get_is_watch_only(account); + let is_watch_only = managed_core_account_get_is_watch_only(account); assert!(!is_watch_only); // Test get_balance @@ -1240,7 +1618,7 @@ mod tests { locked: 999, total: 999, }; - let success = managed_account_get_balance(account, &mut balance_out); + let success = managed_core_account_get_balance(account, &mut balance_out); assert!(success); // Initially, balance should be 0 assert_eq!(balance_out.confirmed, 0); @@ -1250,19 +1628,19 @@ mod tests { assert_eq!(balance_out.total, 0); // Test get_transaction_count - let tx_count = managed_account_get_transaction_count(account); + let tx_count = managed_core_account_get_transaction_count(account); assert_eq!(tx_count, 0); // Initially no transactions // Test get_utxo_count - let utxo_count = managed_account_get_utxo_count(account); + let utxo_count = managed_core_account_get_utxo_count(account); assert_eq!(utxo_count, 0); // Initially no UTXOs // Test get_parent_wallet_id - let parent_id = managed_account_get_parent_wallet_id(wallet_ids_out); + let parent_id = managed_core_account_get_parent_wallet_id(wallet_ids_out); assert_eq!(parent_id, wallet_ids_out); // Should return the same pointer // Clean up - managed_account_free(account); + managed_core_account_free(account); wallet_manager_free_wallet_ids(wallet_ids_out, count_out); wallet_manager_free(manager); } @@ -1272,24 +1650,24 @@ mod tests { fn test_managed_account_getter_edge_cases() { unsafe { // Test null account for get_network - let network = managed_account_get_network(ptr::null()); + let network = managed_core_account_get_network(ptr::null()); assert_eq!(network, FFINetwork::Dash); let mut index_out: c_uint = 0; - let account_type = managed_account_get_account_type(ptr::null(), &mut index_out); + let account_type = managed_core_account_get_account_type(ptr::null(), &mut index_out); assert_eq!(account_type, FFIAccountType::StandardBIP44); // Default type - let is_watch_only = managed_account_get_is_watch_only(ptr::null()); + let is_watch_only = managed_core_account_get_is_watch_only(ptr::null()); assert!(!is_watch_only); - let tx_count = managed_account_get_transaction_count(ptr::null()); + let tx_count = managed_core_account_get_transaction_count(ptr::null()); assert_eq!(tx_count, 0); - let utxo_count = managed_account_get_utxo_count(ptr::null()); + let utxo_count = managed_core_account_get_utxo_count(ptr::null()); assert_eq!(utxo_count, 0); // Test new getters with null account - let index = managed_account_get_index(ptr::null()); + let index = managed_core_account_get_index(ptr::null()); assert_eq!(index, 0); // Test null balance_out @@ -1332,11 +1710,11 @@ mod tests { assert!(!result.account.is_null()); // Test balance with null output - let success = managed_account_get_balance(result.account, ptr::null_mut()); + let success = managed_core_account_get_balance(result.account, ptr::null_mut()); assert!(!success); // Clean up - managed_account_free(result.account); + managed_core_account_free(result.account); wallet_manager_free_wallet_ids(wallet_ids_out, count_out); wallet_manager_free(manager); } @@ -1394,29 +1772,30 @@ mod tests { let account = result.account; // Test get_index - let index = managed_account_get_index(account); + let index = managed_core_account_get_index(account); assert_eq!(index, 0); // Test get_external_address_pool - let external_pool = managed_account_get_external_address_pool(account); + let external_pool = managed_core_account_get_external_address_pool(account); assert!(!external_pool.is_null()); // Test get_internal_address_pool - let internal_pool = managed_account_get_internal_address_pool(account); + let internal_pool = managed_core_account_get_internal_address_pool(account); assert!(!internal_pool.is_null()); // Test get_address_pool with External type let external_pool2 = - managed_account_get_address_pool(account, FFIAddressPoolType::External); + managed_core_account_get_address_pool(account, FFIAddressPoolType::External); assert!(!external_pool2.is_null()); // Test get_address_pool with Internal type let internal_pool2 = - managed_account_get_address_pool(account, FFIAddressPoolType::Internal); + managed_core_account_get_address_pool(account, FFIAddressPoolType::Internal); assert!(!internal_pool2.is_null()); // Test get_address_pool with Single type (should return null for Standard account) - let single_pool = managed_account_get_address_pool(account, FFIAddressPoolType::Single); + let single_pool = + managed_core_account_get_address_pool(account, FFIAddressPoolType::Single); assert!(single_pool.is_null()); // Clean up address pools @@ -1426,7 +1805,7 @@ mod tests { address_pool_free(internal_pool2); // Clean up account - managed_account_free(account); + managed_core_account_free(account); // Now test with different account types from the same wallet // The default wallet should have been created with StandardBIP44 index 0 @@ -1476,20 +1855,20 @@ mod tests { let cj_account = cj_result.account; // Test that external/internal return null for CoinJoin account - let cj_external = managed_account_get_external_address_pool(cj_account); + let cj_external = managed_core_account_get_external_address_pool(cj_account); assert!(cj_external.is_null()); - let cj_internal = managed_account_get_internal_address_pool(cj_account); + let cj_internal = managed_core_account_get_internal_address_pool(cj_account); assert!(cj_internal.is_null()); // Test that Single pool works for CoinJoin account let cj_single = - managed_account_get_address_pool(cj_account, FFIAddressPoolType::Single); + managed_core_account_get_address_pool(cj_account, FFIAddressPoolType::Single); assert!(!cj_single.is_null()); // Clean up address_pool_free(cj_single); - managed_account_free(cj_account); + managed_core_account_free(cj_account); wallet_manager_free_wallet_ids(wallet_ids_out, count_out); wallet_manager_free(manager); } diff --git a/key-wallet-ffi/src/managed_account_collection.rs b/key-wallet-ffi/src/managed_account_collection.rs index 60913d516..1d4107c82 100644 --- a/key-wallet-ffi/src/managed_account_collection.rs +++ b/key-wallet-ffi/src/managed_account_collection.rs @@ -10,21 +10,21 @@ use std::os::raw::{c_char, c_uint}; use std::ptr; use crate::error::{FFIError, FFIErrorCode}; -use crate::managed_account::FFIManagedAccount; +use crate::managed_account::FFIManagedCoreAccount; use crate::wallet_manager::FFIWalletManager; /// Opaque handle to a managed account collection -pub struct FFIManagedAccountCollection { +pub struct FFIManagedCoreAccountCollection { /// The underlying managed account collection collection: key_wallet::managed_account::managed_account_collection::ManagedAccountCollection, } -impl FFIManagedAccountCollection { +impl FFIManagedCoreAccountCollection { /// Create a new FFI managed account collection pub fn new( collection: &key_wallet::managed_account::managed_account_collection::ManagedAccountCollection, ) -> Self { - FFIManagedAccountCollection { + FFIManagedCoreAccountCollection { collection: collection.clone(), } } @@ -36,7 +36,7 @@ impl FFIManagedAccountCollection { /// that exist in the managed collection, allowing programmatic access to account /// indices and presence information. #[repr(C)] -pub struct FFIManagedAccountCollectionSummary { +pub struct FFIManagedCoreAccountCollectionSummary { /// Array of BIP44 account indices pub bip44_indices: *mut c_uint, /// Number of BIP44 accounts @@ -75,6 +75,11 @@ pub struct FFIManagedAccountCollectionSummary { #[cfg(feature = "eddsa")] /// Whether provider platform keys account exists pub has_provider_platform_keys: bool, + + /// Array of Platform Payment account keys (account, key_class pairs) + pub platform_payment_keys: *mut crate::managed_account::FFIPlatformPaymentAccountKey, + /// Number of Platform Payment accounts + pub platform_payment_count: usize, } /// Get managed account collection for a specific network from wallet manager @@ -90,7 +95,7 @@ pub unsafe extern "C" fn managed_wallet_get_account_collection( manager: *const FFIWalletManager, wallet_id: *const u8, error: *mut FFIError, -) -> *mut FFIManagedAccountCollection { +) -> *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(); @@ -108,7 +113,7 @@ pub unsafe extern "C" fn managed_wallet_get_account_collection( // Get the managed account collection from the managed wallet info let managed_wallet = &*managed_wallet_ptr; - let ffi_collection = FFIManagedAccountCollection::new(&managed_wallet.inner().accounts); + let ffi_collection = FFIManagedCoreAccountCollection::new(&managed_wallet.inner().accounts); // Clean up the managed wallet pointer since we've extracted what we need crate::managed_wallet::managed_wallet_info_free(managed_wallet_ptr); @@ -120,11 +125,11 @@ pub unsafe extern "C" fn managed_wallet_get_account_collection( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection created by this library +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection created by this library /// - `collection` must not be used after calling this function #[no_mangle] pub unsafe extern "C" fn managed_account_collection_free( - collection: *mut FFIManagedAccountCollection, + collection: *mut FFIManagedCoreAccountCollection, ) { if !collection.is_null() { let _ = Box::from_raw(collection); @@ -137,13 +142,13 @@ pub unsafe extern "C" fn managed_account_collection_free( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection -/// - The returned pointer must be freed with `managed_account_free` when no longer needed +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection +/// - The returned pointer must be freed with `managed_core_account_free` when no longer needed #[no_mangle] pub unsafe extern "C" fn managed_account_collection_get_bip44_account( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, index: c_uint, -) -> *mut FFIManagedAccount { +) -> *mut FFIManagedCoreAccount { if collection.is_null() { return ptr::null_mut(); } @@ -152,7 +157,7 @@ pub unsafe extern "C" fn managed_account_collection_get_bip44_account( match collection.collection.standard_bip44_accounts.get(&index) { Some(account) => { // Get the network from the account - let ffi_account = FFIManagedAccount::new(account); + let ffi_account = FFIManagedCoreAccount::new(account); Box::into_raw(Box::new(ffi_account)) } None => ptr::null_mut(), @@ -163,13 +168,13 @@ pub unsafe extern "C" fn managed_account_collection_get_bip44_account( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection /// - `out_indices` must be a valid pointer to store the indices array /// - `out_count` must be a valid pointer to store the count /// - The returned array must be freed with `free_u32_array` when no longer needed #[no_mangle] pub unsafe extern "C" fn managed_account_collection_get_bip44_indices( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, out_indices: *mut *mut c_uint, out_count: *mut usize, ) -> bool { @@ -205,13 +210,13 @@ pub unsafe extern "C" fn managed_account_collection_get_bip44_indices( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection -/// - The returned pointer must be freed with `managed_account_free` when no longer needed +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection +/// - The returned pointer must be freed with `managed_core_account_free` when no longer needed #[no_mangle] pub unsafe extern "C" fn managed_account_collection_get_bip32_account( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, index: c_uint, -) -> *mut FFIManagedAccount { +) -> *mut FFIManagedCoreAccount { if collection.is_null() { return ptr::null_mut(); } @@ -219,7 +224,7 @@ pub unsafe extern "C" fn managed_account_collection_get_bip32_account( let collection = &*collection; match collection.collection.standard_bip32_accounts.get(&index) { Some(account) => { - let ffi_account = FFIManagedAccount::new(account); + let ffi_account = FFIManagedCoreAccount::new(account); Box::into_raw(Box::new(ffi_account)) } None => ptr::null_mut(), @@ -230,13 +235,13 @@ pub unsafe extern "C" fn managed_account_collection_get_bip32_account( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection /// - `out_indices` must be a valid pointer to store the indices array /// - `out_count` must be a valid pointer to store the count /// - The returned array must be freed with `free_u32_array` when no longer needed #[no_mangle] pub unsafe extern "C" fn managed_account_collection_get_bip32_indices( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, out_indices: *mut *mut c_uint, out_count: *mut usize, ) -> bool { @@ -270,13 +275,13 @@ pub unsafe extern "C" fn managed_account_collection_get_bip32_indices( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection -/// - The returned pointer must be freed with `managed_account_free` when no longer needed +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection +/// - The returned pointer must be freed with `managed_core_account_free` when no longer needed #[no_mangle] pub unsafe extern "C" fn managed_account_collection_get_coinjoin_account( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, index: c_uint, -) -> *mut FFIManagedAccount { +) -> *mut FFIManagedCoreAccount { if collection.is_null() { return ptr::null_mut(); } @@ -284,7 +289,7 @@ pub unsafe extern "C" fn managed_account_collection_get_coinjoin_account( let collection = &*collection; match collection.collection.coinjoin_accounts.get(&index) { Some(account) => { - let ffi_account = FFIManagedAccount::new(account); + let ffi_account = FFIManagedCoreAccount::new(account); Box::into_raw(Box::new(ffi_account)) } None => ptr::null_mut(), @@ -295,13 +300,13 @@ pub unsafe extern "C" fn managed_account_collection_get_coinjoin_account( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection /// - `out_indices` must be a valid pointer to store the indices array /// - `out_count` must be a valid pointer to store the count /// - The returned array must be freed with `free_u32_array` when no longer needed #[no_mangle] pub unsafe extern "C" fn managed_account_collection_get_coinjoin_indices( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, out_indices: *mut *mut c_uint, out_count: *mut usize, ) -> bool { @@ -337,12 +342,12 @@ pub unsafe extern "C" fn managed_account_collection_get_coinjoin_indices( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection -/// - The returned pointer must be freed with `managed_account_free` when no longer needed +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection +/// - The returned pointer must be freed with `managed_core_account_free` when no longer needed #[no_mangle] pub unsafe extern "C" fn managed_account_collection_get_identity_registration( - collection: *const FFIManagedAccountCollection, -) -> *mut FFIManagedAccount { + collection: *const FFIManagedCoreAccountCollection, +) -> *mut FFIManagedCoreAccount { if collection.is_null() { return ptr::null_mut(); } @@ -350,7 +355,7 @@ pub unsafe extern "C" fn managed_account_collection_get_identity_registration( let collection = &*collection; match &collection.collection.identity_registration { Some(account) => { - let ffi_account = FFIManagedAccount::new(account); + let ffi_account = FFIManagedCoreAccount::new(account); Box::into_raw(Box::new(ffi_account)) } None => ptr::null_mut(), @@ -361,10 +366,10 @@ pub unsafe extern "C" fn managed_account_collection_get_identity_registration( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection #[no_mangle] pub unsafe extern "C" fn managed_account_collection_has_identity_registration( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, ) -> bool { if collection.is_null() { return false; @@ -378,13 +383,13 @@ pub unsafe extern "C" fn managed_account_collection_has_identity_registration( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection -/// - The returned pointer must be freed with `managed_account_free` when no longer needed +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection +/// - The returned pointer must be freed with `managed_core_account_free` when no longer needed #[no_mangle] pub unsafe extern "C" fn managed_account_collection_get_identity_topup( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, registration_index: c_uint, -) -> *mut FFIManagedAccount { +) -> *mut FFIManagedCoreAccount { if collection.is_null() { return ptr::null_mut(); } @@ -392,7 +397,7 @@ pub unsafe extern "C" fn managed_account_collection_get_identity_topup( let collection = &*collection; match collection.collection.identity_topup.get(®istration_index) { Some(account) => { - let ffi_account = FFIManagedAccount::new(account); + let ffi_account = FFIManagedCoreAccount::new(account); Box::into_raw(Box::new(ffi_account)) } None => ptr::null_mut(), @@ -403,13 +408,13 @@ pub unsafe extern "C" fn managed_account_collection_get_identity_topup( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection /// - `out_indices` must be a valid pointer to store the indices array /// - `out_count` must be a valid pointer to store the count /// - The returned array must be freed with `free_u32_array` when no longer needed #[no_mangle] pub unsafe extern "C" fn managed_account_collection_get_identity_topup_indices( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, out_indices: *mut *mut c_uint, out_count: *mut usize, ) -> bool { @@ -442,13 +447,13 @@ pub unsafe extern "C" fn managed_account_collection_get_identity_topup_indices( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection /// - `manager` must be a valid pointer to an FFIWalletManager -/// - The returned pointer must be freed with `managed_account_free` when no longer needed +/// - The returned pointer must be freed with `managed_core_account_free` when no longer needed #[no_mangle] pub unsafe extern "C" fn managed_account_collection_get_identity_topup_not_bound( - collection: *const FFIManagedAccountCollection, -) -> *mut FFIManagedAccount { + collection: *const FFIManagedCoreAccountCollection, +) -> *mut FFIManagedCoreAccount { if collection.is_null() { return ptr::null_mut(); } @@ -456,7 +461,7 @@ pub unsafe extern "C" fn managed_account_collection_get_identity_topup_not_bound let collection = &*collection; match &collection.collection.identity_topup_not_bound { Some(account) => { - let ffi_account = FFIManagedAccount::new(account); + let ffi_account = FFIManagedCoreAccount::new(account); Box::into_raw(Box::new(ffi_account)) } None => ptr::null_mut(), @@ -467,10 +472,10 @@ pub unsafe extern "C" fn managed_account_collection_get_identity_topup_not_bound /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection #[no_mangle] pub unsafe extern "C" fn managed_account_collection_has_identity_topup_not_bound( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, ) -> bool { if collection.is_null() { return false; @@ -484,12 +489,12 @@ pub unsafe extern "C" fn managed_account_collection_has_identity_topup_not_bound /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection -/// - The returned pointer must be freed with `managed_account_free` when no longer needed +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection +/// - The returned pointer must be freed with `managed_core_account_free` when no longer needed #[no_mangle] pub unsafe extern "C" fn managed_account_collection_get_identity_invitation( - collection: *const FFIManagedAccountCollection, -) -> *mut FFIManagedAccount { + collection: *const FFIManagedCoreAccountCollection, +) -> *mut FFIManagedCoreAccount { if collection.is_null() { return ptr::null_mut(); } @@ -497,7 +502,7 @@ pub unsafe extern "C" fn managed_account_collection_get_identity_invitation( let collection = &*collection; match &collection.collection.identity_invitation { Some(account) => { - let ffi_account = FFIManagedAccount::new(account); + let ffi_account = FFIManagedCoreAccount::new(account); Box::into_raw(Box::new(ffi_account)) } None => ptr::null_mut(), @@ -508,10 +513,10 @@ pub unsafe extern "C" fn managed_account_collection_get_identity_invitation( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection #[no_mangle] pub unsafe extern "C" fn managed_account_collection_has_identity_invitation( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, ) -> bool { if collection.is_null() { return false; @@ -527,12 +532,12 @@ pub unsafe extern "C" fn managed_account_collection_has_identity_invitation( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection -/// - The returned pointer must be freed with `managed_account_free` when no longer needed +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection +/// - The returned pointer must be freed with `managed_core_account_free` when no longer needed #[no_mangle] pub unsafe extern "C" fn managed_account_collection_get_provider_voting_keys( - collection: *const FFIManagedAccountCollection, -) -> *mut FFIManagedAccount { + collection: *const FFIManagedCoreAccountCollection, +) -> *mut FFIManagedCoreAccount { if collection.is_null() { return ptr::null_mut(); } @@ -540,7 +545,7 @@ pub unsafe extern "C" fn managed_account_collection_get_provider_voting_keys( let collection = &*collection; match &collection.collection.provider_voting_keys { Some(account) => { - let ffi_account = FFIManagedAccount::new(account); + let ffi_account = FFIManagedCoreAccount::new(account); Box::into_raw(Box::new(ffi_account)) } None => ptr::null_mut(), @@ -551,10 +556,10 @@ pub unsafe extern "C" fn managed_account_collection_get_provider_voting_keys( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection #[no_mangle] pub unsafe extern "C" fn managed_account_collection_has_provider_voting_keys( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, ) -> bool { if collection.is_null() { return false; @@ -568,12 +573,12 @@ pub unsafe extern "C" fn managed_account_collection_has_provider_voting_keys( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection -/// - The returned pointer must be freed with `managed_account_free` when no longer needed +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection +/// - The returned pointer must be freed with `managed_core_account_free` when no longer needed #[no_mangle] pub unsafe extern "C" fn managed_account_collection_get_provider_owner_keys( - collection: *const FFIManagedAccountCollection, -) -> *mut FFIManagedAccount { + collection: *const FFIManagedCoreAccountCollection, +) -> *mut FFIManagedCoreAccount { if collection.is_null() { return ptr::null_mut(); } @@ -581,7 +586,7 @@ pub unsafe extern "C" fn managed_account_collection_get_provider_owner_keys( let collection = &*collection; match &collection.collection.provider_owner_keys { Some(account) => { - let ffi_account = FFIManagedAccount::new(account); + let ffi_account = FFIManagedCoreAccount::new(account); Box::into_raw(Box::new(ffi_account)) } None => ptr::null_mut(), @@ -592,10 +597,10 @@ pub unsafe extern "C" fn managed_account_collection_get_provider_owner_keys( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection #[no_mangle] pub unsafe extern "C" fn managed_account_collection_has_provider_owner_keys( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, ) -> bool { if collection.is_null() { return false; @@ -610,11 +615,11 @@ pub unsafe extern "C" fn managed_account_collection_has_provider_owner_keys( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection -/// - The returned pointer must be freed with `managed_account_free` when no longer needed (when BLS is enabled) +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection +/// - The returned pointer must be freed with `managed_core_account_free` when no longer needed (when BLS is enabled) #[no_mangle] pub unsafe extern "C" fn managed_account_collection_get_provider_operator_keys( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, ) -> *mut std::os::raw::c_void { #[cfg(feature = "bls")] { @@ -625,7 +630,7 @@ pub unsafe extern "C" fn managed_account_collection_get_provider_operator_keys( let collection = &*collection; match &collection.collection.provider_operator_keys { Some(account) => { - let ffi_account = FFIManagedAccount::new(account); + let ffi_account = FFIManagedCoreAccount::new(account); Box::into_raw(Box::new(ffi_account)) as *mut std::os::raw::c_void } None => ptr::null_mut(), @@ -644,10 +649,10 @@ pub unsafe extern "C" fn managed_account_collection_get_provider_operator_keys( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection #[no_mangle] pub unsafe extern "C" fn managed_account_collection_has_provider_operator_keys( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, ) -> bool { if collection.is_null() { return false; @@ -670,11 +675,11 @@ pub unsafe extern "C" fn managed_account_collection_has_provider_operator_keys( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection -/// - The returned pointer must be freed with `managed_account_free` when no longer needed (when EdDSA is enabled) +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection +/// - The returned pointer must be freed with `managed_core_account_free` when no longer needed (when EdDSA is enabled) #[no_mangle] pub unsafe extern "C" fn managed_account_collection_get_provider_platform_keys( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, ) -> *mut std::os::raw::c_void { #[cfg(feature = "eddsa")] { @@ -685,7 +690,7 @@ pub unsafe extern "C" fn managed_account_collection_get_provider_platform_keys( let collection = &*collection; match &collection.collection.provider_platform_keys { Some(account) => { - let ffi_account = FFIManagedAccount::new(account); + let ffi_account = FFIManagedCoreAccount::new(account); Box::into_raw(Box::new(ffi_account)) as *mut std::os::raw::c_void } None => ptr::null_mut(), @@ -704,10 +709,10 @@ pub unsafe extern "C" fn managed_account_collection_get_provider_platform_keys( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection #[no_mangle] pub unsafe extern "C" fn managed_account_collection_has_provider_platform_keys( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, ) -> bool { if collection.is_null() { return false; @@ -725,16 +730,148 @@ pub unsafe extern "C" fn managed_account_collection_has_provider_platform_keys( } } +// Platform Payment accounts functions + +/// Get a Platform Payment account by account index and key class from the managed collection +/// +/// Platform Payment accounts (DIP-17) are identified by two indices: +/// - account_index: The account' level in the derivation path +/// - key_class: The key_class' level in the derivation path (typically 0) +/// +/// # Safety +/// +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection +/// - The returned pointer must be freed with `managed_platform_account_free` when no longer needed +#[no_mangle] +pub unsafe extern "C" fn managed_account_collection_get_platform_payment_account( + collection: *const FFIManagedCoreAccountCollection, + account_index: c_uint, + key_class: c_uint, +) -> *mut crate::managed_account::FFIManagedPlatformAccount { + if collection.is_null() { + return ptr::null_mut(); + } + + let collection = &*collection; + let key = key_wallet::account::account_collection::PlatformPaymentAccountKey { + account: account_index, + key_class, + }; + + match collection.collection.platform_payment_accounts.get(&key) { + Some(account) => { + let ffi_account = crate::managed_account::FFIManagedPlatformAccount::new(account); + Box::into_raw(Box::new(ffi_account)) + } + None => ptr::null_mut(), + } +} + +/// Get all Platform Payment account keys from managed collection +/// +/// Returns an array of FFIPlatformPaymentAccountKey structures. +/// +/// # Safety +/// +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection +/// - `out_keys` must be a valid pointer to store the keys array +/// - `out_count` must be a valid pointer to store the count +/// - The returned array must be freed with `managed_account_collection_free_platform_payment_keys` when no longer needed +#[no_mangle] +pub unsafe extern "C" fn managed_account_collection_get_platform_payment_keys( + collection: *const FFIManagedCoreAccountCollection, + out_keys: *mut *mut crate::managed_account::FFIPlatformPaymentAccountKey, + out_count: *mut usize, +) -> bool { + if collection.is_null() || out_keys.is_null() || out_count.is_null() { + return false; + } + + let collection = &*collection; + let keys: Vec = collection + .collection + .platform_payment_accounts + .keys() + .map(crate::managed_account::FFIPlatformPaymentAccountKey::from) + .collect(); + + if keys.is_empty() { + *out_keys = ptr::null_mut(); + *out_count = 0; + return true; + } + + let mut boxed_slice = keys.into_boxed_slice(); + let ptr = boxed_slice.as_mut_ptr(); + let len = boxed_slice.len(); + std::mem::forget(boxed_slice); + + *out_keys = ptr; + *out_count = len; + true +} + +/// Free platform payment keys array returned by managed_account_collection_get_platform_payment_keys +/// +/// # Safety +/// +/// - `keys` must be a pointer returned by `managed_account_collection_get_platform_payment_keys` +/// - `count` must be the count returned by `managed_account_collection_get_platform_payment_keys` +/// - This function must only be called once per allocation +#[no_mangle] +pub unsafe extern "C" fn managed_account_collection_free_platform_payment_keys( + keys: *mut crate::managed_account::FFIPlatformPaymentAccountKey, + count: usize, +) { + if !keys.is_null() && count > 0 { + let _ = Vec::from_raw_parts(keys, count, count); + } +} + +/// Check if there are any Platform Payment accounts in the managed collection +/// +/// # Safety +/// +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection +#[no_mangle] +pub unsafe extern "C" fn managed_account_collection_has_platform_payment_accounts( + collection: *const FFIManagedCoreAccountCollection, +) -> bool { + if collection.is_null() { + return false; + } + + let collection = &*collection; + !collection.collection.platform_payment_accounts.is_empty() +} + +/// Get the number of Platform Payment accounts in the managed collection +/// +/// # Safety +/// +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection +#[no_mangle] +pub unsafe extern "C" fn managed_account_collection_platform_payment_count( + collection: *const FFIManagedCoreAccountCollection, +) -> c_uint { + if collection.is_null() { + return 0; + } + + let collection = &*collection; + collection.collection.platform_payment_accounts.len() as c_uint +} + // Utility functions /// Get the total number of accounts in the managed collection /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection #[no_mangle] pub unsafe extern "C" fn managed_account_collection_count( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, ) -> c_uint { if collection.is_null() { return 0; @@ -774,6 +911,9 @@ pub unsafe extern "C" fn managed_account_collection_count( count += 1; } + // Platform payment accounts + count += collection.collection.platform_payment_accounts.len() as u32; + count } @@ -784,12 +924,12 @@ pub unsafe extern "C" fn managed_account_collection_count( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection /// - The returned string must be freed with `string_free` when no longer needed /// - Returns null if the collection pointer is null #[no_mangle] pub unsafe extern "C" fn managed_account_collection_summary( - collection: *const FFIManagedAccountCollection, + collection: *const FFIManagedCoreAccountCollection, ) -> *mut c_char { if collection.is_null() { return ptr::null_mut(); @@ -906,6 +1046,27 @@ pub unsafe extern "C" fn managed_account_collection_summary( summary_parts.push("• Provider Platform Keys Account (EdDSA)".to_string()); } + // Platform Payment Accounts + if !collection.collection.platform_payment_accounts.is_empty() { + let count = collection.collection.platform_payment_accounts.len(); + let keys: Vec = collection + .collection + .platform_payment_accounts + .keys() + .map(|k| format!("({},{})", k.account, k.key_class)) + .collect(); + summary_parts.push(format!( + "• Platform Payment: {} {} at keys {}", + count, + if count == 1 { + "account" + } else { + "accounts" + }, + keys.join(", ") + )); + } + // If there are no accounts at all if summary_parts.len() == 1 { summary_parts.push("No accounts configured".to_string()); @@ -927,13 +1088,13 @@ pub unsafe extern "C" fn managed_account_collection_summary( /// /// # Safety /// -/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection /// - The returned pointer must be freed with `managed_account_collection_summary_free` when no longer needed /// - Returns null if the collection pointer is null #[no_mangle] pub unsafe extern "C" fn managed_account_collection_summary_data( - collection: *const FFIManagedAccountCollection, -) -> *mut FFIManagedAccountCollectionSummary { + collection: *const FFIManagedCoreAccountCollection, +) -> *mut FFIManagedCoreAccountCollectionSummary { if collection.is_null() { return ptr::null_mut(); } @@ -996,8 +1157,26 @@ pub unsafe extern "C" fn managed_account_collection_summary_data( (ptr, count) }; + // Collect platform payment keys + let platform_payment_keys: Vec = + collection + .collection + .platform_payment_accounts + .keys() + .map(crate::managed_account::FFIPlatformPaymentAccountKey::from) + .collect(); + let (platform_payment_ptr, platform_payment_count) = if platform_payment_keys.is_empty() { + (ptr::null_mut(), 0) + } else { + let count = platform_payment_keys.len(); + let mut boxed_slice = platform_payment_keys.into_boxed_slice(); + let ptr = boxed_slice.as_mut_ptr(); + std::mem::forget(boxed_slice); + (ptr, count) + }; + // Create the summary struct - let summary = FFIManagedAccountCollectionSummary { + let summary = FFIManagedCoreAccountCollectionSummary { bip44_indices: bip44_ptr, bip44_count, bip32_indices: bip32_ptr, @@ -1015,6 +1194,8 @@ pub unsafe extern "C" fn managed_account_collection_summary_data( has_provider_operator_keys: collection.collection.provider_operator_keys.is_some(), #[cfg(feature = "eddsa")] has_provider_platform_keys: collection.collection.provider_platform_keys.is_some(), + platform_payment_keys: platform_payment_ptr, + platform_payment_count, }; Box::into_raw(Box::new(summary)) @@ -1024,11 +1205,11 @@ pub unsafe extern "C" fn managed_account_collection_summary_data( /// /// # Safety /// -/// - `summary` must be a valid pointer to an FFIManagedAccountCollectionSummary created by `managed_account_collection_summary_data` +/// - `summary` must be a valid pointer to an FFIManagedCoreAccountCollectionSummary created by `managed_account_collection_summary_data` /// - `summary` must not be used after calling this function #[no_mangle] pub unsafe extern "C" fn managed_account_collection_summary_free( - summary: *mut FFIManagedAccountCollectionSummary, + summary: *mut FFIManagedCoreAccountCollectionSummary, ) { if !summary.is_null() { let summary = Box::from_raw(summary); @@ -1066,6 +1247,14 @@ pub unsafe extern "C" fn managed_account_collection_summary_free( ); } + if !summary.platform_payment_keys.is_null() && summary.platform_payment_count > 0 { + let _ = Vec::from_raw_parts( + summary.platform_payment_keys, + summary.platform_payment_count, + summary.platform_payment_count, + ); + } + // The summary struct itself is dropped automatically when the Box is dropped } } diff --git a/key-wallet-ffi/src/managed_wallet.rs b/key-wallet-ffi/src/managed_wallet.rs index 00b0bcf65..cc3927e6a 100644 --- a/key-wallet-ffi/src/managed_wallet.rs +++ b/key-wallet-ffi/src/managed_wallet.rs @@ -841,7 +841,9 @@ mod tests { #[test] fn test_comprehensive_address_generation() { - use key_wallet::account::{ManagedAccount, ManagedAccountCollection, StandardAccountType}; + use key_wallet::account::{ + ManagedAccountCollection, ManagedCoreAccount, StandardAccountType, + }; use key_wallet::bip32::DerivationPath; use key_wallet::managed_account::address_pool::{AddressPool, AddressPoolType}; @@ -894,7 +896,7 @@ mod tests { ) .expect("Failed to create internal pool"); - let managed_account = ManagedAccount::new( + let managed_account = ManagedCoreAccount::new( ManagedAccountType::Standard { index: 0, standard_account_type: StandardAccountType::BIP44Account, @@ -907,7 +909,10 @@ mod tests { managed_collection.standard_bip44_accounts.insert(0, managed_account.clone()); // Insert the managed account directly into managed_info's accounts - managed_info.accounts.insert(managed_account); + managed_info + .accounts + .insert(managed_account) + .expect("insert should succeed for Standard account"); // Create wrapper for managed info heap-allocated like C would do let ffi_managed = Box::into_raw(Box::new(FFIManagedWalletInfo::new(managed_info))); @@ -1023,7 +1028,7 @@ mod tests { #[test] fn test_managed_wallet_get_balance() { - use key_wallet::wallet::balance::WalletBalance; + use key_wallet::wallet::balance::WalletCoreBalance; let mut error = FFIError::success(); @@ -1046,7 +1051,7 @@ mod tests { let mut managed_info = ManagedWalletInfo::from_wallet(wallet_arc); // Set some test balance values - managed_info.balance = WalletBalance::new(1000000, 50000, 10000, 25000); + managed_info.balance = WalletCoreBalance::new(1000000, 50000, 10000, 25000); let ffi_managed = FFIManagedWalletInfo::new(managed_info); let ffi_managed_ptr = Box::into_raw(Box::new(ffi_managed)); diff --git a/key-wallet-ffi/src/transaction.rs b/key-wallet-ffi/src/transaction.rs index 9a53e4c9b..10ace390e 100644 --- a/key-wallet-ffi/src/transaction.rs +++ b/key-wallet-ffi/src/transaction.rs @@ -549,7 +549,7 @@ pub unsafe extern "C" fn wallet_check_transaction( // Block on the async check_transaction call let check_result = tokio::runtime::Handle::current() - .block_on(managed_info.check_transaction(&tx, context, wallet_mut, update_state)); + .block_on(managed_info.check_core_transaction(&tx, context, wallet_mut, update_state)); // If we updated state, we need to update the wallet's managed info // Note: This would require storing ManagedWalletInfo in FFIWallet diff --git a/key-wallet-ffi/src/transaction_checking.rs b/key-wallet-ffi/src/transaction_checking.rs index 8586bd993..af1b6b21b 100644 --- a/key-wallet-ffi/src/transaction_checking.rs +++ b/key-wallet-ffi/src/transaction_checking.rs @@ -14,7 +14,7 @@ use crate::types::{FFITransactionContext, FFIWallet}; use dashcore::consensus::Decodable; use dashcore::Transaction; use key_wallet::transaction_checking::{ - account_checker::AccountTypeMatch, TransactionContext, WalletTransactionChecker, + account_checker::CoreAccountTypeMatch, TransactionContext, WalletTransactionChecker, }; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; @@ -209,7 +209,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( // Block on the async check_transaction call let check_result = tokio::runtime::Handle::current() - .block_on(managed_wallet.check_transaction(&tx, context, wallet_mut, update_state)); + .block_on(managed_wallet.check_core_transaction(&tx, context, wallet_mut, update_state)); // Convert the result to FFI format let affected_accounts = if check_result.affected_accounts.is_empty() { @@ -219,7 +219,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( for account_match in &check_result.affected_accounts { match &account_match.account_type_match { - AccountTypeMatch::StandardBIP44 { + CoreAccountTypeMatch::StandardBIP44 { account_index, involved_receive_addresses, involved_change_addresses, @@ -240,7 +240,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ffi_accounts.push(ffi_match); continue; } - AccountTypeMatch::StandardBIP32 { + CoreAccountTypeMatch::StandardBIP32 { account_index, involved_receive_addresses, involved_change_addresses, @@ -261,7 +261,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ffi_accounts.push(ffi_match); continue; } - AccountTypeMatch::CoinJoin { + CoreAccountTypeMatch::CoinJoin { account_index, involved_addresses, } => { @@ -279,7 +279,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ffi_accounts.push(ffi_match); continue; } - AccountTypeMatch::IdentityRegistration { + CoreAccountTypeMatch::IdentityRegistration { involved_addresses, } => { let ffi_match = FFIAccountMatch { @@ -296,7 +296,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ffi_accounts.push(ffi_match); continue; } - AccountTypeMatch::IdentityTopUp { + CoreAccountTypeMatch::IdentityTopUp { account_index, involved_addresses, } => { @@ -314,7 +314,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ffi_accounts.push(ffi_match); continue; } - AccountTypeMatch::IdentityTopUpNotBound { + CoreAccountTypeMatch::IdentityTopUpNotBound { involved_addresses, } => { let ffi_match = FFIAccountMatch { @@ -331,7 +331,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ffi_accounts.push(ffi_match); continue; } - AccountTypeMatch::IdentityInvitation { + CoreAccountTypeMatch::IdentityInvitation { involved_addresses, } => { let ffi_match = FFIAccountMatch { @@ -348,7 +348,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ffi_accounts.push(ffi_match); continue; } - AccountTypeMatch::ProviderVotingKeys { + CoreAccountTypeMatch::ProviderVotingKeys { involved_addresses, } => { let ffi_match = FFIAccountMatch { @@ -365,7 +365,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ffi_accounts.push(ffi_match); continue; } - AccountTypeMatch::ProviderOwnerKeys { + CoreAccountTypeMatch::ProviderOwnerKeys { involved_addresses, } => { let ffi_match = FFIAccountMatch { @@ -382,7 +382,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ffi_accounts.push(ffi_match); continue; } - AccountTypeMatch::ProviderOperatorKeys { + CoreAccountTypeMatch::ProviderOperatorKeys { involved_addresses, } => { let ffi_match = FFIAccountMatch { @@ -399,7 +399,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ffi_accounts.push(ffi_match); continue; } - AccountTypeMatch::ProviderPlatformKeys { + CoreAccountTypeMatch::ProviderPlatformKeys { involved_addresses, } => { let ffi_match = FFIAccountMatch { @@ -416,7 +416,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ffi_accounts.push(ffi_match); continue; } - AccountTypeMatch::DashpayReceivingFunds { + CoreAccountTypeMatch::DashpayReceivingFunds { account_index, involved_addresses, } => { @@ -434,7 +434,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ffi_accounts.push(ffi_match); continue; } - AccountTypeMatch::DashpayExternalAccount { + CoreAccountTypeMatch::DashpayExternalAccount { account_index, involved_addresses, } => { @@ -452,27 +452,6 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ffi_accounts.push(ffi_match); continue; } - AccountTypeMatch::PlatformPayment { - account_index, - involved_addresses, - .. - } => { - // Note: Platform Payment addresses are NOT used in Core chain transactions - // per DIP-17. This branch should never be reached in practice. - let ffi_match = FFIAccountMatch { - account_type: 13, // PlatformPayment - account_index: *account_index, - registration_index: 0, - received: account_match.received, - sent: account_match.sent, - external_addresses_count: involved_addresses.len() as c_uint, - internal_addresses_count: 0, - has_external_addresses: !involved_addresses.is_empty(), - has_internal_addresses: false, - }; - ffi_accounts.push(ffi_match); - continue; - } } } diff --git a/key-wallet-ffi/src/types.rs b/key-wallet-ffi/src/types.rs index 241702752..bb85cda0d 100644 --- a/key-wallet-ffi/src/types.rs +++ b/key-wallet-ffi/src/types.rs @@ -63,8 +63,8 @@ pub struct FFIBalance { pub total: u64, } -impl From for FFIBalance { - fn from(balance: key_wallet::WalletBalance) -> Self { +impl From for FFIBalance { + fn from(balance: key_wallet::WalletCoreBalance) -> Self { FFIBalance { confirmed: balance.spendable(), unconfirmed: balance.unconfirmed(), diff --git a/key-wallet-ffi/src/utxo_tests.rs b/key-wallet-ffi/src/utxo_tests.rs index ee01038d1..f73a0e606 100644 --- a/key-wallet-ffi/src/utxo_tests.rs +++ b/key-wallet-ffi/src/utxo_tests.rs @@ -177,7 +177,7 @@ mod utxo_tests { use dashcore::blockdata::script::ScriptBuf; use dashcore::{Address, OutPoint, TxOut, Txid}; use key_wallet::account::account_type::StandardAccountType; - use key_wallet::managed_account::ManagedAccount; + use key_wallet::managed_account::ManagedCoreAccount; use key_wallet::utxo::Utxo; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet::Network; @@ -191,7 +191,7 @@ mod utxo_tests { let mut managed_info = ManagedWalletInfo::new(Network::Testnet, [1u8; 32]); // Create a BIP44 account with UTXOs - let mut bip44_account = ManagedAccount::new( + let mut bip44_account = ManagedCoreAccount::new( ManagedAccountType::Standard { index: 0, standard_account_type: StandardAccountType::BIP44Account, @@ -232,7 +232,7 @@ mod utxo_tests { bip44_account.utxos.insert(outpoint, utxo); } - managed_info.accounts.insert(bip44_account); + managed_info.accounts.insert(bip44_account).unwrap(); let ffi_managed_info = Box::into_raw(Box::new(FFIManagedWalletInfo::new(managed_info))); unsafe { (*ffi_managed_info).inner_mut() }.update_synced_height(300); @@ -282,7 +282,7 @@ mod utxo_tests { fn test_managed_wallet_get_utxos_multiple_accounts() { use crate::managed_wallet::FFIManagedWalletInfo; use key_wallet::account::account_type::StandardAccountType; - use key_wallet::managed_account::ManagedAccount; + use key_wallet::managed_account::ManagedCoreAccount; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet::Network; @@ -294,7 +294,7 @@ mod utxo_tests { let mut managed_info = ManagedWalletInfo::new(Network::Testnet, [2u8; 32]); // Create BIP44 account with 2 UTXOs - let mut bip44_account = ManagedAccount::new( + let mut bip44_account = ManagedCoreAccount::new( ManagedAccountType::Standard { index: 0, standard_account_type: StandardAccountType::BIP44Account, @@ -317,10 +317,10 @@ mod utxo_tests { for utxo in utxos { bip44_account.utxos.insert(utxo.outpoint, utxo); } - managed_info.accounts.insert(bip44_account); + managed_info.accounts.insert(bip44_account).unwrap(); // Create BIP32 account with 1 UTXO - let mut bip32_account = ManagedAccount::new( + let mut bip32_account = ManagedCoreAccount::new( ManagedAccountType::Standard { index: 0, standard_account_type: StandardAccountType::BIP32Account, @@ -343,10 +343,10 @@ mod utxo_tests { for utxo in utxos { bip32_account.utxos.insert(utxo.outpoint, utxo); } - managed_info.accounts.insert(bip32_account); + managed_info.accounts.insert(bip32_account).unwrap(); // Create CoinJoin account with 2 UTXOs - let mut coinjoin_account = ManagedAccount::new( + let mut coinjoin_account = ManagedCoreAccount::new( ManagedAccountType::CoinJoin { index: 0, addresses: key_wallet::managed_account::address_pool::AddressPoolBuilder::default() @@ -362,7 +362,7 @@ mod utxo_tests { for utxo in utxos { coinjoin_account.utxos.insert(utxo.outpoint, utxo); } - managed_info.accounts.insert(coinjoin_account); + managed_info.accounts.insert(coinjoin_account).unwrap(); let ffi_managed_info = Box::into_raw(Box::new(FFIManagedWalletInfo::new(managed_info))); @@ -385,7 +385,7 @@ mod utxo_tests { fn test_managed_wallet_get_utxos() { use crate::managed_wallet::FFIManagedWalletInfo; use key_wallet::account::account_type::StandardAccountType; - use key_wallet::managed_account::ManagedAccount; + use key_wallet::managed_account::ManagedCoreAccount; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet::Network; @@ -398,7 +398,7 @@ mod utxo_tests { let mut managed_info = ManagedWalletInfo::new(Network::Testnet, [3u8; 32]); // Add a UTXO to Testnet account - let mut testnet_account = ManagedAccount::new( + let mut testnet_account = ManagedCoreAccount::new( ManagedAccountType::Standard { index: 0, standard_account_type: StandardAccountType::BIP44Account, @@ -421,7 +421,7 @@ mod utxo_tests { for utxo in utxos { testnet_account.utxos.insert(utxo.outpoint, utxo); } - managed_info.accounts.insert(testnet_account); + managed_info.accounts.insert(testnet_account).unwrap(); let ffi_managed_info = Box::into_raw(Box::new(FFIManagedWalletInfo::new(managed_info))); diff --git a/key-wallet-ffi/src/wallet.rs b/key-wallet-ffi/src/wallet.rs index bef129711..5c082e470 100644 --- a/key-wallet-ffi/src/wallet.rs +++ b/key-wallet-ffi/src/wallet.rs @@ -464,12 +464,21 @@ pub unsafe extern "C" fn wallet_free_const(wallet: *const FFIWallet) { /// The caller must ensure that: /// - The wallet pointer is either null or points to a valid FFIWallet /// - The FFIWallet remains valid for the duration of this call +/// +/// # Note +/// +/// This function does NOT support the following account types: +/// - `PlatformPayment`: Use `wallet_add_platform_payment_account()` instead +/// - `DashpayReceivingFunds`: Use `wallet_add_dashpay_receiving_account()` instead +/// - `DashpayExternalAccount`: Use `wallet_add_dashpay_external_account_with_xpub_bytes()` instead #[no_mangle] pub unsafe extern "C" fn wallet_add_account( wallet: *mut FFIWallet, account_type: crate::types::FFIAccountType, account_index: c_uint, ) -> crate::types::FFIAccountResult { + use crate::types::FFIAccountType; + if wallet.is_null() { return crate::types::FFIAccountResult::error( FFIErrorCode::InvalidInput, @@ -477,6 +486,35 @@ pub unsafe extern "C" fn wallet_add_account( ); } + // Check for account types that require special handling + match account_type { + FFIAccountType::PlatformPayment => { + return crate::types::FFIAccountResult::error( + FFIErrorCode::InvalidInput, + "PlatformPayment accounts require account and key_class indices. \ + Use wallet_add_platform_payment_account() instead." + .to_string(), + ); + } + FFIAccountType::DashpayReceivingFunds => { + return crate::types::FFIAccountResult::error( + FFIErrorCode::InvalidInput, + "DashpayReceivingFunds accounts require identity IDs. \ + Use wallet_add_dashpay_receiving_account() instead." + .to_string(), + ); + } + FFIAccountType::DashpayExternalAccount => { + return crate::types::FFIAccountResult::error( + FFIErrorCode::InvalidInput, + "DashpayExternalAccount accounts require identity IDs. \ + Use wallet_add_dashpay_external_account_with_xpub_bytes() instead." + .to_string(), + ); + } + _ => {} // Other types are supported + } + let wallet = &mut *wallet; let account_type_rust = account_type.to_account_type(account_index); @@ -644,6 +682,13 @@ pub unsafe extern "C" fn wallet_add_dashpay_external_account_with_xpub_bytes( /// - The wallet pointer is either null or points to a valid FFIWallet /// - The xpub_bytes pointer is either null or points to at least xpub_len bytes /// - The FFIWallet remains valid for the duration of this call +/// +/// # Note +/// +/// This function does NOT support the following account types: +/// - `PlatformPayment`: Use `wallet_add_platform_payment_account()` instead +/// - `DashpayReceivingFunds`: Use `wallet_add_dashpay_receiving_account()` instead +/// - `DashpayExternalAccount`: Use `wallet_add_dashpay_external_account_with_xpub_bytes()` instead #[no_mangle] pub unsafe extern "C" fn wallet_add_account_with_xpub_bytes( wallet: *mut FFIWallet, @@ -652,6 +697,8 @@ pub unsafe extern "C" fn wallet_add_account_with_xpub_bytes( xpub_bytes: *const u8, xpub_len: usize, ) -> crate::types::FFIAccountResult { + use crate::types::FFIAccountType; + if wallet.is_null() { return crate::types::FFIAccountResult::error( FFIErrorCode::InvalidInput, @@ -666,6 +713,35 @@ pub unsafe extern "C" fn wallet_add_account_with_xpub_bytes( ); } + // Check for account types that require special handling + match account_type { + FFIAccountType::PlatformPayment => { + return crate::types::FFIAccountResult::error( + FFIErrorCode::InvalidInput, + "PlatformPayment accounts require account and key_class indices. \ + Use wallet_add_platform_payment_account() instead." + .to_string(), + ); + } + FFIAccountType::DashpayReceivingFunds => { + return crate::types::FFIAccountResult::error( + FFIErrorCode::InvalidInput, + "DashpayReceivingFunds accounts require identity IDs. \ + Use wallet_add_dashpay_receiving_account() instead." + .to_string(), + ); + } + FFIAccountType::DashpayExternalAccount => { + return crate::types::FFIAccountResult::error( + FFIErrorCode::InvalidInput, + "DashpayExternalAccount accounts require identity IDs. \ + Use wallet_add_dashpay_external_account_with_xpub_bytes() instead." + .to_string(), + ); + } + _ => {} // Other types are supported + } + let wallet = &mut *wallet; use key_wallet::ExtendedPubKey; @@ -730,6 +806,13 @@ pub unsafe extern "C" fn wallet_add_account_with_xpub_bytes( /// - The wallet pointer is either null or points to a valid FFIWallet /// - The xpub_string pointer is either null or points to a valid null-terminated C string /// - The FFIWallet remains valid for the duration of this call +/// +/// # Note +/// +/// This function does NOT support the following account types: +/// - `PlatformPayment`: Use `wallet_add_platform_payment_account()` instead +/// - `DashpayReceivingFunds`: Use `wallet_add_dashpay_receiving_account()` instead +/// - `DashpayExternalAccount`: Use `wallet_add_dashpay_external_account_with_xpub_bytes()` instead #[no_mangle] pub unsafe extern "C" fn wallet_add_account_with_string_xpub( wallet: *mut FFIWallet, @@ -737,6 +820,8 @@ pub unsafe extern "C" fn wallet_add_account_with_string_xpub( account_index: c_uint, xpub_string: *const c_char, ) -> crate::types::FFIAccountResult { + use crate::types::FFIAccountType; + if wallet.is_null() { return crate::types::FFIAccountResult::error( FFIErrorCode::InvalidInput, @@ -751,6 +836,35 @@ pub unsafe extern "C" fn wallet_add_account_with_string_xpub( ); } + // Check for account types that require special handling + match account_type { + FFIAccountType::PlatformPayment => { + return crate::types::FFIAccountResult::error( + FFIErrorCode::InvalidInput, + "PlatformPayment accounts require account and key_class indices. \ + Use wallet_add_platform_payment_account() instead." + .to_string(), + ); + } + FFIAccountType::DashpayReceivingFunds => { + return crate::types::FFIAccountResult::error( + FFIErrorCode::InvalidInput, + "DashpayReceivingFunds accounts require identity IDs. \ + Use wallet_add_dashpay_receiving_account() instead." + .to_string(), + ); + } + FFIAccountType::DashpayExternalAccount => { + return crate::types::FFIAccountResult::error( + FFIErrorCode::InvalidInput, + "DashpayExternalAccount accounts require identity IDs. \ + Use wallet_add_dashpay_external_account_with_xpub_bytes() instead." + .to_string(), + ); + } + _ => {} // Other types are supported + } + let wallet = &mut *wallet; use key_wallet::ExtendedPubKey; @@ -804,3 +918,71 @@ pub unsafe extern "C" fn wallet_add_account_with_string_xpub( ), } } + +/// Add a Platform Payment account (DIP-17) to the wallet +/// +/// Platform Payment accounts use the derivation path: +/// `m/9'/coin_type'/17'/account'/key_class'/index` +/// +/// # Arguments +/// * `wallet` - Pointer to the wallet +/// * `account_index` - The account index (hardened) in the derivation path +/// * `key_class` - The key class (hardened) - typically 0' for main addresses +/// +/// # Safety +/// +/// This function dereferences a raw pointer to FFIWallet. +/// The caller must ensure that: +/// - The wallet pointer is either null or points to a valid FFIWallet +/// - The FFIWallet remains valid for the duration of this call +#[no_mangle] +pub unsafe extern "C" fn wallet_add_platform_payment_account( + wallet: *mut FFIWallet, + account_index: c_uint, + key_class: c_uint, +) -> crate::types::FFIAccountResult { + use key_wallet::account::AccountType; + + if wallet.is_null() { + return crate::types::FFIAccountResult::error( + FFIErrorCode::InvalidInput, + "Wallet is null".to_string(), + ); + } + + let wallet = &mut *wallet; + + let account_type = AccountType::PlatformPayment { + account: account_index, + key_class, + }; + + match wallet.inner_mut() { + Some(w) => { + // Use the proper add_account method + match w.add_account(account_type, None) { + Ok(()) => { + // Get the account we just added + if let Some(account) = w.accounts.account_of_type(account_type) { + let ffi_account = crate::types::FFIAccount::new(account); + return crate::types::FFIAccountResult::success(Box::into_raw(Box::new( + ffi_account, + ))); + } + crate::types::FFIAccountResult::error( + FFIErrorCode::WalletError, + "Failed to retrieve account after adding".to_string(), + ) + } + Err(e) => crate::types::FFIAccountResult::error( + FFIErrorCode::WalletError, + format!("Failed to add platform payment account: {}", e), + ), + } + } + None => crate::types::FFIAccountResult::error( + FFIErrorCode::InvalidState, + "Cannot modify wallet".to_string(), + ), + } +} diff --git a/key-wallet-ffi/tests/test_managed_account_collection.rs b/key-wallet-ffi/tests/test_managed_account_collection.rs index 16a228f89..44f3d9195 100644 --- a/key-wallet-ffi/tests/test_managed_account_collection.rs +++ b/key-wallet-ffi/tests/test_managed_account_collection.rs @@ -73,7 +73,7 @@ fn test_managed_account_collection_basic() { assert!(!account.is_null()); // Clean up - key_wallet_ffi::managed_account::managed_account_free(account); + key_wallet_ffi::managed_account::managed_core_account_free(account); if !indices.is_null() { key_wallet_ffi::account_collection::free_u32_array(indices, indices_count); } @@ -196,11 +196,11 @@ fn test_managed_account_collection_with_special_accounts() { // Get specific accounts let identity_reg = managed_account_collection_get_identity_registration(collection); assert!(!identity_reg.is_null()); - key_wallet_ffi::managed_account::managed_account_free(identity_reg); + key_wallet_ffi::managed_account::managed_core_account_free(identity_reg); let voting_keys = managed_account_collection_get_provider_voting_keys(collection); assert!(!voting_keys.is_null()); - key_wallet_ffi::managed_account::managed_account_free(voting_keys); + key_wallet_ffi::managed_account::managed_core_account_free(voting_keys); // Clean up managed_account_collection_free(collection); diff --git a/key-wallet-manager/src/wallet_manager/mod.rs b/key-wallet-manager/src/wallet_manager/mod.rs index 9a79831bb..6b02c7999 100644 --- a/key-wallet-manager/src/wallet_manager/mod.rs +++ b/key-wallet-manager/src/wallet_manager/mod.rs @@ -22,7 +22,7 @@ use key_wallet::wallet::managed_wallet_info::{ManagedWalletInfo, TransactionReco use key_wallet::wallet::WalletType; use key_wallet::Utxo; use key_wallet::{Account, AccountType, Address, ExtendedPrivKey, Mnemonic, Network, Wallet}; -use key_wallet::{ExtendedPubKey, WalletBalance}; +use key_wallet::{ExtendedPubKey, WalletCoreBalance}; use std::collections::BTreeSet; use std::str::FromStr; use zeroize::Zeroize; @@ -483,8 +483,9 @@ impl WalletManager { let wallet_info_opt = self.wallet_infos.get_mut(&wallet_id); if let (Some(wallet), Some(wallet_info)) = (wallet_opt, wallet_info_opt) { - let check_result = - wallet_info.check_transaction(tx, context, wallet, update_state_if_found).await; + let check_result = wallet_info + .check_core_transaction(tx, context, wallet, update_state_if_found) + .await; // If the transaction is relevant if check_result.is_relevant { @@ -904,7 +905,10 @@ impl WalletManager { } /// Get balance for a specific wallet - pub fn get_wallet_balance(&self, wallet_id: &WalletId) -> Result { + pub fn get_wallet_balance( + &self, + wallet_id: &WalletId, + ) -> Result { // Get the wallet info let wallet_info = self.wallet_infos.get(wallet_id).ok_or(WalletError::WalletNotFound(*wallet_id))?; diff --git a/key-wallet/src/account/account_type.rs b/key-wallet/src/account/account_type.rs index 5b317a9ab..c3e0db8b4 100644 --- a/key-wallet/src/account/account_type.rs +++ b/key-wallet/src/account/account_type.rs @@ -4,7 +4,9 @@ use crate::bip32::{ChildNumber, DerivationPath}; use crate::dip9::DerivationPathReference; -use crate::transaction_checking::transaction_router::AccountTypeToCheck; +use crate::transaction_checking::transaction_router::{ + AccountTypeToCheck, PlatformAccountConversionError, +}; use crate::Network; #[cfg(feature = "bincode")] use bincode_derive::{Decode, Encode}; @@ -94,40 +96,45 @@ pub enum AccountType { }, } -impl From for AccountTypeToCheck { - fn from(value: AccountType) -> Self { +impl TryFrom for AccountTypeToCheck { + type Error = PlatformAccountConversionError; + + fn try_from(value: AccountType) -> Result { match value { AccountType::Standard { standard_account_type, .. } => match standard_account_type { - StandardAccountType::BIP44Account => AccountTypeToCheck::StandardBIP44, - StandardAccountType::BIP32Account => AccountTypeToCheck::StandardBIP32, + StandardAccountType::BIP44Account => Ok(AccountTypeToCheck::StandardBIP44), + StandardAccountType::BIP32Account => Ok(AccountTypeToCheck::StandardBIP32), }, AccountType::CoinJoin { .. - } => AccountTypeToCheck::CoinJoin, - AccountType::IdentityRegistration => AccountTypeToCheck::IdentityRegistration, + } => Ok(AccountTypeToCheck::CoinJoin), + AccountType::IdentityRegistration => Ok(AccountTypeToCheck::IdentityRegistration), AccountType::IdentityTopUp { .. - } => AccountTypeToCheck::IdentityTopUp, + } => Ok(AccountTypeToCheck::IdentityTopUp), AccountType::IdentityTopUpNotBoundToIdentity => { - AccountTypeToCheck::IdentityTopUpNotBound + Ok(AccountTypeToCheck::IdentityTopUpNotBound) } - AccountType::IdentityInvitation => AccountTypeToCheck::IdentityInvitation, - AccountType::ProviderVotingKeys => AccountTypeToCheck::ProviderVotingKeys, - AccountType::ProviderOwnerKeys => AccountTypeToCheck::ProviderOwnerKeys, - AccountType::ProviderOperatorKeys => AccountTypeToCheck::ProviderOperatorKeys, - AccountType::ProviderPlatformKeys => AccountTypeToCheck::ProviderPlatformKeys, + AccountType::IdentityInvitation => Ok(AccountTypeToCheck::IdentityInvitation), + AccountType::ProviderVotingKeys => Ok(AccountTypeToCheck::ProviderVotingKeys), + AccountType::ProviderOwnerKeys => Ok(AccountTypeToCheck::ProviderOwnerKeys), + AccountType::ProviderOperatorKeys => Ok(AccountTypeToCheck::ProviderOperatorKeys), + AccountType::ProviderPlatformKeys => Ok(AccountTypeToCheck::ProviderPlatformKeys), AccountType::DashpayReceivingFunds { .. - } => AccountTypeToCheck::DashpayReceivingFunds, + } => Ok(AccountTypeToCheck::DashpayReceivingFunds), AccountType::DashpayExternalAccount { .. - } => AccountTypeToCheck::DashpayExternalAccount, + } => Ok(AccountTypeToCheck::DashpayExternalAccount), AccountType::PlatformPayment { .. - } => AccountTypeToCheck::PlatformPayment, + } => { + // Platform Payment accounts (DIP-17) operate on Dash Platform, not Core chain. + Err(PlatformAccountConversionError) + } } } } diff --git a/key-wallet/src/account/mod.rs b/key-wallet/src/account/mod.rs index 4b56c80ca..704a2cd3a 100644 --- a/key-wallet/src/account/mod.rs +++ b/key-wallet/src/account/mod.rs @@ -36,7 +36,7 @@ pub use crate::managed_account::managed_account_trait::ManagedAccountTrait; pub use crate::managed_account::managed_account_type::ManagedAccountType; pub use crate::managed_account::metadata::AccountMetadata; pub use crate::managed_account::transaction_record::TransactionRecord; -pub use crate::managed_account::ManagedAccount; +pub use crate::managed_account::ManagedCoreAccount; pub use account_collection::AccountCollection; pub use account_trait::AccountTrait; pub use account_type::{AccountType, StandardAccountType}; diff --git a/key-wallet/src/lib.rs b/key-wallet/src/lib.rs index 2fbebb8d4..38b4e3cd8 100644 --- a/key-wallet/src/lib.rs +++ b/key-wallet/src/lib.rs @@ -65,10 +65,12 @@ pub use error::{Error, Result}; pub use gap_limit::{GapLimit, GapLimitManager, GapLimitStage}; pub use managed_account::address_pool::{AddressInfo, AddressPool, KeySource, PoolStats}; pub use managed_account::managed_account_type::ManagedAccountType; +pub use managed_account::managed_platform_account::ManagedPlatformAccount; +pub use managed_account::platform_address::PlatformP2PKHAddress; pub use mnemonic::Mnemonic; pub use seed::Seed; pub use utxo::{Utxo, UtxoSet}; -pub use wallet::{balance::WalletBalance, Wallet}; +pub use wallet::{balance::WalletCoreBalance, Wallet}; /// Re-export commonly used types pub mod prelude { diff --git a/key-wallet/src/managed_account/managed_account_collection.rs b/key-wallet/src/managed_account/managed_account_collection.rs index 2a1baabf4..d08ecde1d 100644 --- a/key-wallet/src/managed_account/managed_account_collection.rs +++ b/key-wallet/src/managed_account/managed_account_collection.rs @@ -11,8 +11,9 @@ use crate::gap_limit::{ }; use crate::managed_account::address_pool::{AddressPool, AddressPoolType}; use crate::managed_account::managed_account_type::ManagedAccountType; -use crate::managed_account::ManagedAccount; -use crate::transaction_checking::account_checker::AccountTypeMatch; +use crate::managed_account::managed_platform_account::ManagedPlatformAccount; +use crate::managed_account::ManagedCoreAccount; +use crate::transaction_checking::account_checker::CoreAccountTypeMatch; use crate::{Account, AccountCollection}; use crate::{KeySource, Network}; use alloc::collections::BTreeMap; @@ -25,33 +26,34 @@ use serde::{Deserialize, Serialize}; #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct ManagedAccountCollection { /// Standard BIP44 accounts by index - pub standard_bip44_accounts: BTreeMap, + pub standard_bip44_accounts: BTreeMap, /// Standard BIP32 accounts by index - pub standard_bip32_accounts: BTreeMap, + pub standard_bip32_accounts: BTreeMap, /// CoinJoin accounts by index - pub coinjoin_accounts: BTreeMap, + pub coinjoin_accounts: BTreeMap, /// Identity registration account (optional) - pub identity_registration: Option, + pub identity_registration: Option, /// Identity top-up accounts by registration index - pub identity_topup: BTreeMap, + pub identity_topup: BTreeMap, /// Identity top-up not bound to identity (optional) - pub identity_topup_not_bound: Option, + pub identity_topup_not_bound: Option, /// Identity invitation account (optional) - pub identity_invitation: Option, + pub identity_invitation: Option, /// Provider voting keys (optional) - pub provider_voting_keys: Option, + pub provider_voting_keys: Option, /// Provider owner keys (optional) - pub provider_owner_keys: Option, + pub provider_owner_keys: Option, /// Provider operator keys (optional) - pub provider_operator_keys: Option, + pub provider_operator_keys: Option, /// Provider platform keys (optional) - pub provider_platform_keys: Option, + pub provider_platform_keys: Option, /// DashPay receiving funds accounts keyed by (index, user_id, friend_id) - pub dashpay_receival_accounts: BTreeMap, + pub dashpay_receival_accounts: BTreeMap, /// DashPay external accounts keyed by (index, user_id, friend_id) - pub dashpay_external_accounts: BTreeMap, + pub dashpay_external_accounts: BTreeMap, /// Platform Payment accounts (DIP-17) - pub platform_payment_accounts: BTreeMap, + /// Uses ManagedPlatformAccount for simplified balance tracking without transactions/UTXOs + pub platform_payment_accounts: BTreeMap, } impl ManagedAccountCollection { @@ -162,7 +164,10 @@ impl ManagedAccountCollection { } /// Insert a managed account into the collection - pub fn insert(&mut self, account: ManagedAccount) { + /// + /// Returns an error if a PlatformPayment account type is passed, since those + /// should use `insert_platform_account()` with `ManagedPlatformAccount` instead. + pub fn insert(&mut self, account: ManagedCoreAccount) -> Result<(), crate::error::Error> { use crate::account::StandardAccountType; match &account.account_type { @@ -252,17 +257,25 @@ impl ManagedAccountCollection { self.dashpay_external_accounts.insert(key, account); } ManagedAccountType::PlatformPayment { - account: acc_index, - key_class, .. } => { - let key = PlatformPaymentAccountKey { - account: *acc_index, - key_class: *key_class, - }; - self.platform_payment_accounts.insert(key, account); + // Platform Payment accounts should use insert_platform_account() instead + // as they use ManagedPlatformAccount, not ManagedCoreAccount + return Err(crate::error::Error::InvalidParameter( + "Use insert_platform_account() for Platform Payment accounts".into(), + )); } } + Ok(()) + } + + /// Insert a managed platform account into the collection + pub fn insert_platform_account(&mut self, account: ManagedPlatformAccount) { + let key = PlatformPaymentAccountKey { + account: account.account, + key_class: account.key_class, + }; + self.platform_payment_accounts.insert(key, account); } /// Create a ManagedAccountCollection from an AccountCollection @@ -360,7 +373,9 @@ impl ManagedAccountCollection { // Convert Platform Payment accounts for (key, account) in &account_collection.platform_payment_accounts { - if let Ok(managed_account) = Self::create_managed_account_from_account(account) { + if let Ok(managed_account) = + Self::create_managed_platform_account_from_account(account, key) + { managed_collection.platform_payment_accounts.insert(*key, managed_account); } } @@ -371,7 +386,7 @@ impl ManagedAccountCollection { /// Create a ManagedAccount from an Account fn create_managed_account_from_account( account: &Account, - ) -> Result { + ) -> Result { // Use the account's existing public key let key_source = KeySource::Public(account.account_xpub); Self::create_managed_account_from_account_type( @@ -386,7 +401,7 @@ impl ManagedAccountCollection { #[cfg(feature = "bls")] fn create_managed_account_from_bls_account( account: &super::BLSAccount, - ) -> Result { + ) -> Result { let key_source = KeySource::BLSPublic(account.bls_public_key.clone()); Self::create_managed_account_from_account_type( account.account_type, @@ -401,7 +416,7 @@ impl ManagedAccountCollection { fn create_managed_account_from_eddsa_account( account: &super::EdDSAAccount, xpriv: Option, - ) -> Result { + ) -> Result { // EdDSA requires hardened derivation, so we need the private key to generate addresses let key_source = match xpriv { Some(priv_key) => KeySource::EdDSAPrivate(priv_key), @@ -421,7 +436,7 @@ impl ManagedAccountCollection { network: Network, is_watch_only: bool, key_source: &KeySource, - ) -> Result { + ) -> Result { // Get the derivation path for this account type let base_path = account_type .derivation_path(network) @@ -435,7 +450,7 @@ impl ManagedAccountCollection { } => { // For standard accounts, add the receive/change branch to the path let mut external_path = base_path.clone(); - external_path.push(crate::bip32::ChildNumber::from_normal_idx(0).unwrap()); // 0 for external + external_path.push(crate::bip32::ChildNumber::from_normal_idx(0)?); // 0 for external let external_pool = AddressPool::new( external_path, AddressPoolType::External, @@ -445,7 +460,7 @@ impl ManagedAccountCollection { )?; let mut internal_path = base_path; - internal_path.push(crate::bip32::ChildNumber::from_normal_idx(1).unwrap()); // 1 for internal + internal_path.push(crate::bip32::ChildNumber::from_normal_idx(1)?); // 1 for internal let internal_pool = AddressPool::new( internal_path, AddressPoolType::Internal, @@ -625,10 +640,41 @@ impl ManagedAccountCollection { } }; - Ok(ManagedAccount::new(managed_type, network, is_watch_only)) + Ok(ManagedCoreAccount::new(managed_type, network, is_watch_only)) } - pub fn get(&self, index: u32) -> Option<&ManagedAccount> { + /// Create a ManagedPlatformAccount from an Account for Platform Payment accounts + fn create_managed_platform_account_from_account( + account: &Account, + key: &PlatformPaymentAccountKey, + ) -> Result { + // Use the account's existing public key + let key_source = KeySource::Public(account.account_xpub); + + // Get the derivation path for this account type + let base_path = account + .account_type + .derivation_path(account.network) + .unwrap_or_else(|_| crate::bip32::DerivationPath::master()); + + // Create address pool for DIP-17 Platform Payment addresses + let addresses = AddressPool::new( + base_path, + AddressPoolType::Absent, + DIP17_GAP_LIMIT, + account.network, + &key_source, + )?; + + Ok(ManagedPlatformAccount::new( + key.account, + key.key_class, + addresses, + account.is_watch_only, + )) + } + + pub fn get(&self, index: u32) -> Option<&ManagedCoreAccount> { // Try standard BIP44 first if let Some(account) = self.standard_bip44_accounts.get(&index) { return Some(account); @@ -653,7 +699,7 @@ impl ManagedAccountCollection { } /// Get a mutable account by index - pub fn get_mut(&mut self, index: u32) -> Option<&mut ManagedAccount> { + pub fn get_mut(&mut self, index: u32) -> Option<&mut ManagedCoreAccount> { // Try standard BIP44 first if let Some(account) = self.standard_bip44_accounts.get_mut(&index) { return Some(account); @@ -677,50 +723,50 @@ impl ManagedAccountCollection { None } - /// Get an account reference by AccountTypeMatch + /// Get an account reference by CoreAccountTypeMatch pub fn get_by_account_type_match( &self, - account_type_match: &AccountTypeMatch, - ) -> Option<&ManagedAccount> { + account_type_match: &CoreAccountTypeMatch, + ) -> Option<&ManagedCoreAccount> { match account_type_match { - AccountTypeMatch::StandardBIP44 { + CoreAccountTypeMatch::StandardBIP44 { account_index, .. } => self.standard_bip44_accounts.get(account_index), - AccountTypeMatch::StandardBIP32 { + CoreAccountTypeMatch::StandardBIP32 { account_index, .. } => self.standard_bip32_accounts.get(account_index), - AccountTypeMatch::CoinJoin { + CoreAccountTypeMatch::CoinJoin { account_index, .. } => self.coinjoin_accounts.get(account_index), - AccountTypeMatch::IdentityRegistration { + CoreAccountTypeMatch::IdentityRegistration { .. } => self.identity_registration.as_ref(), - AccountTypeMatch::IdentityTopUp { + CoreAccountTypeMatch::IdentityTopUp { account_index, .. } => self.identity_topup.get(account_index), - AccountTypeMatch::IdentityTopUpNotBound { + CoreAccountTypeMatch::IdentityTopUpNotBound { .. } => self.identity_topup_not_bound.as_ref(), - AccountTypeMatch::IdentityInvitation { + CoreAccountTypeMatch::IdentityInvitation { .. } => self.identity_invitation.as_ref(), - AccountTypeMatch::ProviderVotingKeys { + CoreAccountTypeMatch::ProviderVotingKeys { .. } => self.provider_voting_keys.as_ref(), - AccountTypeMatch::ProviderOwnerKeys { + CoreAccountTypeMatch::ProviderOwnerKeys { .. } => self.provider_owner_keys.as_ref(), - AccountTypeMatch::ProviderOperatorKeys { + CoreAccountTypeMatch::ProviderOperatorKeys { .. } => self.provider_operator_keys.as_ref(), - AccountTypeMatch::ProviderPlatformKeys { + CoreAccountTypeMatch::ProviderPlatformKeys { .. } => self.provider_platform_keys.as_ref(), - AccountTypeMatch::DashpayReceivingFunds { + CoreAccountTypeMatch::DashpayReceivingFunds { account_index, involved_addresses, } => { @@ -741,7 +787,7 @@ impl ManagedAccountCollection { } }) } - AccountTypeMatch::DashpayExternalAccount { + CoreAccountTypeMatch::DashpayExternalAccount { account_index, involved_addresses, } => { @@ -762,17 +808,11 @@ impl ManagedAccountCollection { } }) } - AccountTypeMatch::PlatformPayment { - .. - } => { - // Platform Payment addresses are not used in Core chain transactions (DIP-17) - None - } } } /// Remove an account from the collection - pub fn remove(&mut self, index: u32) -> Option { + pub fn remove(&mut self, index: u32) -> Option { // Try standard BIP44 first if let Some(account) = self.standard_bip44_accounts.remove(&index) { return Some(account); @@ -822,7 +862,7 @@ impl ManagedAccountCollection { } /// Get all accounts - pub fn all_accounts(&self) -> Vec<&ManagedAccount> { + pub fn all_accounts(&self) -> Vec<&ManagedCoreAccount> { let mut accounts = Vec::new(); // Add standard BIP44 accounts @@ -873,7 +913,7 @@ impl ManagedAccountCollection { } /// Get all accounts mutably - pub fn all_accounts_mut(&mut self) -> Vec<&mut ManagedAccount> { + pub fn all_accounts_mut(&mut self) -> Vec<&mut ManagedCoreAccount> { let mut accounts = Vec::new(); // Add standard BIP44 accounts @@ -962,6 +1002,7 @@ impl ManagedAccountCollection { && self.provider_platform_keys.is_none() && self.dashpay_receival_accounts.is_empty() && self.dashpay_external_accounts.is_empty() + && self.platform_payment_accounts.is_empty() } /// Clear all accounts @@ -979,5 +1020,32 @@ impl ManagedAccountCollection { self.provider_platform_keys = None; self.dashpay_receival_accounts.clear(); self.dashpay_external_accounts.clear(); + self.platform_payment_accounts.clear(); + } + + /// Get all platform payment accounts + pub fn all_platform_accounts(&self) -> Vec<&ManagedPlatformAccount> { + self.platform_payment_accounts.values().collect() + } + + /// Get all platform payment accounts mutably + pub fn all_platform_accounts_mut(&mut self) -> Vec<&mut ManagedPlatformAccount> { + self.platform_payment_accounts.values_mut().collect() + } + + /// Get a platform payment account by key + pub fn get_platform_account( + &self, + key: &PlatformPaymentAccountKey, + ) -> Option<&ManagedPlatformAccount> { + self.platform_payment_accounts.get(key) + } + + /// Get a mutable platform payment account by key + pub fn get_platform_account_mut( + &mut self, + key: &PlatformPaymentAccountKey, + ) -> Option<&mut ManagedPlatformAccount> { + self.platform_payment_accounts.get_mut(key) } } diff --git a/key-wallet/src/managed_account/managed_account_trait.rs b/key-wallet/src/managed_account/managed_account_trait.rs index 5e9b41acc..505f959be 100644 --- a/key-wallet/src/managed_account/managed_account_trait.rs +++ b/key-wallet/src/managed_account/managed_account_trait.rs @@ -6,7 +6,7 @@ use crate::account::AccountMetadata; use crate::account::TransactionRecord; use crate::managed_account::managed_account_type::ManagedAccountType; use crate::utxo::Utxo; -use crate::wallet::balance::WalletBalance; +use crate::wallet::balance::WalletCoreBalance; use crate::Network; use alloc::collections::BTreeMap; use dashcore::blockdata::transaction::OutPoint; @@ -33,10 +33,10 @@ pub trait ManagedAccountTrait { fn is_watch_only(&self) -> bool; /// Get balance - fn balance(&self) -> &WalletBalance; + fn balance(&self) -> &WalletCoreBalance; /// Get mutable balance - fn balance_mut(&mut self) -> &mut WalletBalance; + fn balance_mut(&mut self) -> &mut WalletCoreBalance; /// Get transactions fn transactions(&self) -> &BTreeMap; diff --git a/key-wallet/src/managed_account/managed_platform_account.rs b/key-wallet/src/managed_account/managed_platform_account.rs new file mode 100644 index 000000000..a803feb0e --- /dev/null +++ b/key-wallet/src/managed_account/managed_platform_account.rs @@ -0,0 +1,585 @@ +//! Managed Platform Account for DIP-17 Platform Payment addresses +//! +//! This module provides the `ManagedPlatformAccount` type which is a simplified +//! account structure for Platform Payment accounts. Unlike `ManagedCoreAccount`, +//! this type: +//! - Uses a simple `u64` balance instead of `WalletCoreBalance` +//! - Tracks per-address balances directly +//! - Does NOT track transactions or UTXOs (Platform handles these) +//! +//! The derivation path follows DIP-17: `m/9'/coin_type'/17'/account'/key_class'/index` + +use super::address_pool::{AddressPool, KeySource}; +use super::metadata::AccountMetadata; +use super::platform_address::PlatformP2PKHAddress; +use crate::error::{Error, Result}; +use crate::Network; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; +use dashcore::Address; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Managed Platform Account for DIP-17 Platform Payment addresses +/// +/// This is a simplified account structure designed specifically for Platform +/// Payment accounts (DIP-17). It differs from `ManagedCoreAccount` in that: +/// +/// - **Balance**: Simple `u64` credit balance (1000 credits = 1 duff) +/// - **Address Balances**: Direct mapping of addresses to their credit balances +/// - **No Transactions**: Platform handles transaction tracking +/// - **No UTXOs**: Platform uses a different model for funds +/// +/// The address pool is kept for key derivation and address generation following +/// the DIP-17 derivation path: `m/9'/coin_type'/17'/account'/key_class'/index` +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct ManagedPlatformAccount { + /// Account index (hardened) - DIP-17 `account'` level + pub account: u32, + /// Key class (hardened) - DIP-17 `key_class'` level + /// 0' is default, 1' is reserved for change-like segregation + pub key_class: u32, + /// Network this account belongs to + pub network: Network, + /// Total balance in credits (1000 credits = 1 duff) + pub credit_balance: u64, + /// Per-address balances: PlatformP2PKHAddress -> balance in credits + pub address_balances: BTreeMap, + /// Address pool for key derivation and address generation + pub addresses: AddressPool, + /// Account metadata + pub metadata: AccountMetadata, + /// Whether this is a watch-only account + pub is_watch_only: bool, +} + +impl ManagedPlatformAccount { + /// Create a new managed platform account + /// + /// The network is derived from the AddressPool to ensure consistency. + pub fn new(account: u32, key_class: u32, addresses: AddressPool, is_watch_only: bool) -> Self { + let network = addresses.network; + Self { + account, + key_class, + network, + credit_balance: 0, + address_balances: BTreeMap::new(), + addresses, + metadata: AccountMetadata::default(), + is_watch_only, + } + } + + /// Get the total credit balance across all addresses + pub fn total_credit_balance(&self) -> u64 { + self.credit_balance + } + + /// Get the total balance in duffs (credit_balance / 1000) + pub fn duff_balance(&self) -> u64 { + self.credit_balance / 1000 + } + + /// Set the total credit balance + pub fn set_credit_balance(&mut self, credit_balance: u64) { + self.credit_balance = credit_balance; + self.metadata.last_used = Some(Self::current_timestamp()); + } + + /// Get the credit balance for a specific address + pub fn address_credit_balance(&self, address: &PlatformP2PKHAddress) -> u64 { + self.address_balances.get(address).copied().unwrap_or(0) + } + + /// Set the credit balance for a specific address + /// + /// This also updates the total balance by applying the delta. + /// If the address was previously unfunded (balance 0) and becomes funded, + /// and a `KeySource` is provided, the address will be marked as used + /// and the gap limit will be maintained. + pub fn set_address_credit_balance( + &mut self, + address: PlatformP2PKHAddress, + credit_balance: u64, + key_source: Option<&KeySource>, + ) { + let old_balance = self.address_balances.get(&address).copied().unwrap_or(0); + let was_unfunded = old_balance == 0; + let is_now_funded = credit_balance > 0; + + self.address_balances.insert(address, credit_balance); + // Apply delta to total: subtract old, add new + self.credit_balance = + self.credit_balance.saturating_sub(old_balance).saturating_add(credit_balance); + self.metadata.last_used = Some(Self::current_timestamp()); + + // If address became funded and we have a key source, update address pool + if was_unfunded && is_now_funded { + if let Some(ks) = key_source { + self.mark_and_maintain_gap_limit(&address, ks); + } + } + } + + /// Add credits to a specific address balance + /// + /// Returns the new credit balance for the address. + /// If the address was previously unfunded (balance 0) and becomes funded, + /// and a `KeySource` is provided, the address will be marked as used + /// and the gap limit will be maintained. + pub fn add_address_credit_balance( + &mut self, + address: PlatformP2PKHAddress, + amount: u64, + key_source: Option<&KeySource>, + ) -> u64 { + let current = self.address_balances.get(&address).copied().unwrap_or(0); + let was_unfunded = current == 0; + let new_balance = current.saturating_add(amount); + let is_now_funded = new_balance > 0; + + self.address_balances.insert(address, new_balance); + // Add the amount to the total (saturating to handle overflow) + self.credit_balance = self.credit_balance.saturating_add(amount); + self.metadata.last_used = Some(Self::current_timestamp()); + + // If address became funded and we have a key source, update address pool + if was_unfunded && is_now_funded { + if let Some(ks) = key_source { + self.mark_and_maintain_gap_limit(&address, ks); + } + } + + new_balance + } + + /// Remove credits from a specific address balance + /// + /// Uses saturating subtraction - balance will not go below zero. + /// Returns the new credit balance for the address. + pub fn remove_address_credit_balance( + &mut self, + address: PlatformP2PKHAddress, + amount: u64, + ) -> u64 { + let current = self.address_balances.get(&address).copied().unwrap_or(0); + // Only subtract what was actually removed (may be less due to saturating) + let actual_removed = current.min(amount); + let new_balance = current.saturating_sub(amount); + self.address_balances.insert(address, new_balance); + // Subtract only what was actually removed from the total + self.credit_balance = self.credit_balance.saturating_sub(actual_removed); + self.metadata.last_used = Some(Self::current_timestamp()); + new_balance + } + + /// Recalculate total credit balance from address balances + pub fn recalculate_credit_balance(&mut self) { + self.credit_balance = self.address_balances.values().sum(); + } + + /// Clear all address balances and reset total balance + pub fn clear_balances(&mut self) { + self.address_balances.clear(); + self.credit_balance = 0; + } + + /// Get all addresses with non-zero balances + pub fn funded_addresses(&self) -> Vec<&PlatformP2PKHAddress> { + self.address_balances + .iter() + .filter(|(_, &balance)| balance > 0) + .map(|(addr, _)| addr) + .collect() + } + + /// Get the number of addresses with funds + pub fn funded_address_count(&self) -> usize { + self.address_balances.values().filter(|&&b| b > 0).count() + } + + /// Check if an address belongs to this account + pub fn contains_address(&self, address: &Address) -> bool { + self.addresses.contains_address(address) + } + + /// Check if a platform address belongs to this account + pub fn contains_platform_address(&self, address: &PlatformP2PKHAddress) -> bool { + // Check if we have it in address_balances + if self.address_balances.contains_key(address) { + return true; + } + + // Check if the equivalent dashcore::Address is in the pool + let dashcore_addr = address.to_address(self.network); + self.addresses.contains_address(&dashcore_addr) + } + + /// Get all addresses in the pool as PlatformP2PKHAddress + pub fn all_platform_addresses(&self) -> Vec { + self.addresses + .all_addresses() + .iter() + .filter_map(|addr| PlatformP2PKHAddress::from_address(addr).ok()) + .collect() + } + + /// Get all addresses in the pool + pub fn all_addresses(&self) -> Vec
{ + self.addresses.all_addresses() + } + + /// Get the next unused address from the pool + pub fn next_unused_address( + &mut self, + key_source: &super::address_pool::KeySource, + add_to_state: bool, + ) -> Result
{ + self.addresses + .next_unused(key_source, add_to_state) + .map_err(|e| Error::InvalidParameter(format!("Failed to get next address: {}", e))) + } + + /// Get the next unused platform address + pub fn next_unused_platform_address( + &mut self, + key_source: &super::address_pool::KeySource, + add_to_state: bool, + ) -> Result { + let addr = self.next_unused_address(key_source, add_to_state)?; + PlatformP2PKHAddress::from_address(&addr) + } + + /// Mark an address as used + pub fn mark_address_used(&mut self, address: &Address) -> bool { + let result = self.addresses.mark_used(address); + if result { + self.metadata.last_used = Some(Self::current_timestamp()); + } + result + } + + /// Mark a platform address as used + pub fn mark_platform_address_used(&mut self, address: &PlatformP2PKHAddress) -> bool { + let dashcore_addr = address.to_address(self.network); + self.mark_address_used(&dashcore_addr) + } + + /// Mark a platform address as used and maintain the gap limit + /// + /// This is called internally when an address receives funds for the first time. + /// It marks the address as used in the address pool and generates new addresses + /// if needed to maintain the gap limit. + fn mark_and_maintain_gap_limit( + &mut self, + address: &PlatformP2PKHAddress, + key_source: &KeySource, + ) { + // Mark the address as used + self.mark_platform_address_used(address); + + // Maintain gap limit - generate new addresses if needed + // We ignore errors here since this is a best-effort operation + let _ = self.addresses.maintain_gap_limit(key_source); + } + + /// Maintain the gap limit for the address pool + /// + /// This generates new addresses if needed to maintain the gap limit. + /// Returns the newly generated addresses. + pub fn maintain_gap_limit(&mut self, key_source: &KeySource) -> Result> { + self.addresses + .maintain_gap_limit(key_source) + .map_err(|e| Error::InvalidParameter(format!("Failed to maintain gap limit: {}", e))) + } + + /// Get address info for a given address + pub fn get_address_info(&self, address: &Address) -> Option { + self.addresses.address_info(address).cloned() + } + + /// Get the current timestamp + fn current_timestamp() -> u64 { + #[cfg(feature = "std")] + { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() + } + #[cfg(not(feature = "std"))] + { + 0 // In no_std environments, timestamp must be provided externally + } + } + + /// Get pool statistics + pub fn address_pool_stats(&self) -> super::address_pool::PoolStats { + self.addresses.stats() + } + + /// Get total address count in the pool + pub fn total_address_count(&self) -> usize { + self.addresses.stats().total_generated as usize + } + + /// Get used address count in the pool + pub fn used_address_count(&self) -> usize { + self.addresses.stats().used_count as usize + } + + /// Get the gap limit for the address pool + pub fn gap_limit(&self) -> u32 { + self.addresses.gap_limit + } +} + +#[cfg(feature = "bincode")] +impl bincode::Encode for ManagedPlatformAccount { + fn encode( + &self, + encoder: &mut E, + ) -> core::result::Result<(), bincode::error::EncodeError> { + // Encode each field + bincode::Encode::encode(&self.account, encoder)?; + bincode::Encode::encode(&self.key_class, encoder)?; + bincode::Encode::encode(&self.network, encoder)?; + bincode::Encode::encode(&self.credit_balance, encoder)?; + + // Encode address_balances as a vec of tuples + let address_balances_vec: Vec<(PlatformP2PKHAddress, u64)> = + self.address_balances.iter().map(|(k, v)| (*k, *v)).collect(); + bincode::Encode::encode(&address_balances_vec, encoder)?; + + bincode::Encode::encode(&self.addresses, encoder)?; + bincode::Encode::encode(&self.metadata, encoder)?; + bincode::Encode::encode(&self.is_watch_only, encoder)?; + Ok(()) + } +} + +#[cfg(feature = "bincode")] +impl bincode::Decode for ManagedPlatformAccount { + fn decode>( + decoder: &mut D, + ) -> core::result::Result { + let account = bincode::Decode::decode(decoder)?; + let key_class = bincode::Decode::decode(decoder)?; + let network = bincode::Decode::decode(decoder)?; + let credit_balance = bincode::Decode::decode(decoder)?; + + // Decode address_balances from vec of tuples + let address_balances_vec: Vec<(PlatformP2PKHAddress, u64)> = + bincode::Decode::decode(decoder)?; + let address_balances: BTreeMap = + address_balances_vec.into_iter().collect(); + + let addresses = bincode::Decode::decode(decoder)?; + let metadata = bincode::Decode::decode(decoder)?; + let is_watch_only = bincode::Decode::decode(decoder)?; + + Ok(Self { + account, + key_class, + network, + credit_balance, + address_balances, + addresses, + metadata, + is_watch_only, + }) + } +} + +#[cfg(feature = "bincode")] +impl<'de, Context> bincode::BorrowDecode<'de, Context> for ManagedPlatformAccount { + fn borrow_decode>( + decoder: &mut D, + ) -> core::result::Result { + // Use the regular decode implementation + >::decode(decoder) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::bip32::{ChildNumber, DerivationPath}; + use crate::managed_account::address_pool::{AddressPool, AddressPoolType}; + + fn create_test_pool() -> AddressPool { + let base_path = DerivationPath::from(vec![ + ChildNumber::from_hardened_idx(9).unwrap(), + ChildNumber::from_hardened_idx(1).unwrap(), + ChildNumber::from_hardened_idx(17).unwrap(), + ChildNumber::from_hardened_idx(0).unwrap(), + ChildNumber::from_hardened_idx(0).unwrap(), + ]); + AddressPool::new_without_generation( + base_path, + AddressPoolType::Absent, + 10, + Network::Testnet, + ) + } + + #[test] + fn test_new_account() { + let pool = create_test_pool(); + let account = ManagedPlatformAccount::new(0, 0, pool, false); + + assert_eq!(account.account, 0); + assert_eq!(account.key_class, 0); + assert_eq!(account.network, Network::Testnet); + assert_eq!(account.credit_balance, 0); + assert!(account.address_balances.is_empty()); + assert!(!account.is_watch_only); + } + + #[test] + fn test_balance_operations() { + let pool = create_test_pool(); + let mut account = ManagedPlatformAccount::new(0, 0, pool, false); + + // Set address credit balance (this also updates total) + let addr = PlatformP2PKHAddress::new([0x11; 20]); + account.set_address_credit_balance(addr, 500, None); + assert_eq!(account.address_credit_balance(&addr), 500); + assert_eq!(account.total_credit_balance(), 500); + assert_eq!(account.duff_balance(), 0); // 500 credits = 0 duffs (integer division) + + // Add to address credit balance + let new_balance = account.add_address_credit_balance(addr, 500, None); + assert_eq!(new_balance, 1000); + assert_eq!(account.total_credit_balance(), 1000); + assert_eq!(account.duff_balance(), 1); // 1000 credits = 1 duff + + // Add more + let new_balance = account.add_address_credit_balance(addr, 200, None); + assert_eq!(new_balance, 1200); + assert_eq!(account.total_credit_balance(), 1200); + + // Remove from address credit balance + let new_balance = account.remove_address_credit_balance(addr, 100); + assert_eq!(new_balance, 1100); + assert_eq!(account.total_credit_balance(), 1100); + + // Update address balance directly (replacing existing) + account.set_address_credit_balance(addr, 600, None); + assert_eq!(account.address_credit_balance(&addr), 600); + assert_eq!(account.total_credit_balance(), 600); + } + + #[test] + fn test_set_credit_balance_directly() { + let pool = create_test_pool(); + let mut account = ManagedPlatformAccount::new(0, 0, pool, false); + + // set_credit_balance is for direct manipulation (e.g., deserialization) + // When used alone (no address balances), it just sets the total + account.set_credit_balance(1000); + assert_eq!(account.total_credit_balance(), 1000); + assert_eq!(account.duff_balance(), 1); + + account.set_credit_balance(2500); + assert_eq!(account.total_credit_balance(), 2500); + assert_eq!(account.duff_balance(), 2); + } + + #[test] + fn test_duff_balance_conversion() { + let pool = create_test_pool(); + let mut account = ManagedPlatformAccount::new(0, 0, pool, false); + + // Test various credit to duff conversions + account.set_credit_balance(0); + assert_eq!(account.duff_balance(), 0); + + account.set_credit_balance(999); + assert_eq!(account.duff_balance(), 0); // 999 credits = 0 duffs (integer division) + + account.set_credit_balance(1000); + assert_eq!(account.duff_balance(), 1); // 1000 credits = 1 duff + + account.set_credit_balance(1500); + assert_eq!(account.duff_balance(), 1); // 1500 credits = 1 duff + + account.set_credit_balance(5000); + assert_eq!(account.duff_balance(), 5); // 5000 credits = 5 duffs + + account.set_credit_balance(1_000_000); + assert_eq!(account.duff_balance(), 1000); // 1M credits = 1000 duffs + } + + #[test] + fn test_multiple_address_balances() { + let pool = create_test_pool(); + let mut account = ManagedPlatformAccount::new(0, 0, pool, false); + + let addr1 = PlatformP2PKHAddress::new([0x11; 20]); + let addr2 = PlatformP2PKHAddress::new([0x22; 20]); + let addr3 = PlatformP2PKHAddress::new([0x33; 20]); + + account.set_address_credit_balance(addr1, 100, None); + account.set_address_credit_balance(addr2, 200, None); + account.set_address_credit_balance(addr3, 300, None); + + assert_eq!(account.total_credit_balance(), 600); + assert_eq!(account.funded_address_count(), 3); + + // Set one to zero + account.set_address_credit_balance(addr2, 0, None); + assert_eq!(account.total_credit_balance(), 400); + assert_eq!(account.funded_address_count(), 2); + } + + #[test] + fn test_clear_balances() { + let pool = create_test_pool(); + let mut account = ManagedPlatformAccount::new(0, 0, pool, false); + + let addr = PlatformP2PKHAddress::new([0x11; 20]); + account.set_address_credit_balance(addr, 1000, None); + + account.clear_balances(); + assert_eq!(account.total_credit_balance(), 0); + assert!(account.address_balances.is_empty()); + } + + #[test] + fn test_funded_addresses() { + let pool = create_test_pool(); + let mut account = ManagedPlatformAccount::new(0, 0, pool, false); + + let addr1 = PlatformP2PKHAddress::new([0x11; 20]); + let addr2 = PlatformP2PKHAddress::new([0x22; 20]); + let addr3 = PlatformP2PKHAddress::new([0x33; 20]); + + account.set_address_credit_balance(addr1, 100, None); + account.set_address_credit_balance(addr2, 0, None); // Zero balance + account.set_address_credit_balance(addr3, 300, None); + + let funded = account.funded_addresses(); + assert_eq!(funded.len(), 2); + assert!(funded.contains(&&addr1)); + assert!(funded.contains(&&addr3)); + assert!(!funded.contains(&&addr2)); + } + + #[test] + fn test_contains_platform_address() { + let pool = create_test_pool(); + let mut account = ManagedPlatformAccount::new(0, 0, pool, false); + + let addr = PlatformP2PKHAddress::new([0x11; 20]); + + // Initially not in balances + assert!(!account.address_balances.contains_key(&addr)); + + // Add to balances + account.set_address_credit_balance(addr, 100, None); + assert!(account.contains_platform_address(&addr)); + } +} diff --git a/key-wallet/src/managed_account/mod.rs b/key-wallet/src/managed_account/mod.rs index 14be9a322..9f36b2c0e 100644 --- a/key-wallet/src/managed_account/mod.rs +++ b/key-wallet/src/managed_account/mod.rs @@ -15,7 +15,7 @@ use crate::derivation_bls_bip32::ExtendedBLSPubKey; #[cfg(any(feature = "bls", feature = "eddsa"))] use crate::managed_account::address_pool::PublicKeyType; use crate::utxo::Utxo; -use crate::wallet::balance::WalletBalance; +use crate::wallet::balance::WalletCoreBalance; #[cfg(feature = "eddsa")] use crate::AddressInfo; use crate::{ExtendedPubKey, Network}; @@ -31,7 +31,9 @@ pub mod address_pool; pub mod managed_account_collection; pub mod managed_account_trait; pub mod managed_account_type; +pub mod managed_platform_account; pub mod metadata; +pub mod platform_address; pub mod transaction_record; /// Managed account with mutable state @@ -41,7 +43,7 @@ pub mod transaction_record; /// the immutable Account structure. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct ManagedAccount { +pub struct ManagedCoreAccount { /// Account type with embedded address pools and index pub account_type: ManagedAccountType, /// Network this account belongs to @@ -51,14 +53,14 @@ pub struct ManagedAccount { /// Whether this is a watch-only account pub is_watch_only: bool, /// Account balance information - pub balance: WalletBalance, + pub balance: WalletCoreBalance, /// Transaction history for this account pub transactions: BTreeMap, /// UTXO set for this account pub utxos: BTreeMap, } -impl ManagedAccount { +impl ManagedCoreAccount { /// Create a new managed account pub fn new(account_type: ManagedAccountType, network: Network, is_watch_only: bool) -> Self { Self { @@ -66,7 +68,7 @@ impl ManagedAccount { network, metadata: AccountMetadata::default(), is_watch_only, - balance: WalletBalance::default(), + balance: WalletCoreBalance::default(), transactions: BTreeMap::new(), utxos: BTreeMap::new(), } @@ -282,7 +284,7 @@ impl ManagedAccount { unconfirmed += value; } } - self.balance = WalletBalance::new(spendable, unconfirmed, immature, locked); + self.balance = WalletCoreBalance::new(spendable, unconfirmed, immature, locked); self.metadata.last_used = Some(Self::current_timestamp()); } @@ -833,7 +835,7 @@ impl ManagedAccount { } } -impl ManagedAccountTrait for ManagedAccount { +impl ManagedAccountTrait for ManagedCoreAccount { fn account_type(&self) -> &ManagedAccountType { &self.account_type } @@ -858,11 +860,11 @@ impl ManagedAccountTrait for ManagedAccount { self.is_watch_only } - fn balance(&self) -> &WalletBalance { + fn balance(&self) -> &WalletCoreBalance { &self.balance } - fn balance_mut(&mut self) -> &mut WalletBalance { + fn balance_mut(&mut self) -> &mut WalletCoreBalance { &mut self.balance } diff --git a/key-wallet/src/managed_account/platform_address.rs b/key-wallet/src/managed_account/platform_address.rs new file mode 100644 index 000000000..31c15dc1c --- /dev/null +++ b/key-wallet/src/managed_account/platform_address.rs @@ -0,0 +1,214 @@ +//! Platform P2PKH address type for DIP-17/DIP-18 +//! +//! This module provides the `PlatformP2PKHAddress` type which represents +//! a 20-byte hash used in Platform Payment addresses. + +use crate::error::{Error, Result}; +use crate::Network; +use core::fmt; +use dashcore::address::Payload; +use dashcore::hashes::hash160::Hash as Hash160; +use dashcore::hashes::Hash as HashTrait; +use dashcore::Address; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Platform P2PKH address (DIP-17/DIP-18) +/// +/// This type stores the 20-byte hash portion of a P2PKH address used in +/// Platform Payment accounts. It provides methods for: +/// - Converting to/from `dashcore::Address` +/// - Extracting the raw hash bytes +/// +/// The derivation path for these addresses follows DIP-17: +/// `m/9'/coin_type'/17'/account'/key_class'/index` +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PlatformP2PKHAddress([u8; 20]); + +impl PlatformP2PKHAddress { + /// Create a new PlatformP2PKHAddress from a 20-byte hash + pub fn new(hash: [u8; 20]) -> Self { + Self(hash) + } + + /// Create from a byte slice + /// + /// Returns an error if the slice is not exactly 20 bytes + pub fn from_slice(slice: &[u8]) -> Result { + if slice.len() != 20 { + return Err(Error::InvalidAddress(format!("Expected 20 bytes, got {}", slice.len()))); + } + let mut bytes = [0u8; 20]; + bytes.copy_from_slice(slice); + Ok(Self(bytes)) + } + + /// Get the hash bytes as a reference + pub fn hash(&self) -> &[u8; 20] { + &self.0 + } + + /// Get the hash bytes as an owned array + pub fn to_bytes(&self) -> [u8; 20] { + self.0 + } + + /// Get the hash bytes as a slice + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + /// Convert to a dashcore::Address (P2PKH) + /// + /// This creates a standard P2PKH address from the hash. + /// Note: This is for interoperability - Platform addresses typically + /// use bech32m encoding rather than base58 P2PKH format. + pub fn to_address(&self, network: Network) -> Address { + let pubkey_hash = Hash160::from_slice(&self.0).expect("20 bytes is valid for Hash160"); + let payload = Payload::PubkeyHash(pubkey_hash.into()); + Address::new(network, payload) + } + + /// Create from a dashcore::Address + /// + /// Only P2PKH addresses are supported. Returns an error for other address types. + pub fn from_address(address: &Address) -> Result { + match address.payload() { + Payload::PubkeyHash(hash) => { + let bytes: [u8; 20] = *hash.as_byte_array(); + Ok(Self(bytes)) + } + _ => Err(Error::InvalidAddress( + "Only P2PKH addresses can be converted to PlatformP2PKHAddress".to_string(), + )), + } + } + + /// Create from an AddressInfo + /// + /// Extracts the P2PKH hash from the address in AddressInfo. + pub fn from_address_info(info: &super::address_pool::AddressInfo) -> Result { + Self::from_address(&info.address) + } +} + +impl fmt::Debug for PlatformP2PKHAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "PlatformP2PKHAddress({})", hex::encode(self.0)) + } +} + +impl fmt::Display for PlatformP2PKHAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Display as hex by default + write!(f, "{}", hex::encode(self.0)) + } +} + +impl From<[u8; 20]> for PlatformP2PKHAddress { + fn from(bytes: [u8; 20]) -> Self { + Self(bytes) + } +} + +impl From for [u8; 20] { + fn from(addr: PlatformP2PKHAddress) -> [u8; 20] { + addr.0 + } +} + +impl AsRef<[u8]> for PlatformP2PKHAddress { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +#[cfg(feature = "bincode")] +impl bincode::Encode for PlatformP2PKHAddress { + fn encode( + &self, + encoder: &mut E, + ) -> core::result::Result<(), bincode::error::EncodeError> { + bincode::Encode::encode(&self.0, encoder) + } +} + +#[cfg(feature = "bincode")] +impl bincode::Decode for PlatformP2PKHAddress { + fn decode>( + decoder: &mut D, + ) -> core::result::Result { + Ok(Self(<[u8; 20]>::decode(decoder)?)) + } +} + +#[cfg(feature = "bincode")] +impl<'de, Context> bincode::BorrowDecode<'de, Context> for PlatformP2PKHAddress { + fn borrow_decode>( + decoder: &mut D, + ) -> core::result::Result { + Ok(Self(<[u8; 20]>::borrow_decode(decoder)?)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_platform_address_creation() { + let hash = [0x12u8; 20]; + let addr = PlatformP2PKHAddress::new(hash); + assert_eq!(addr.hash(), &hash); + assert_eq!(addr.to_bytes(), hash); + } + + #[test] + fn test_from_slice() { + let hash = [0x34u8; 20]; + let addr = PlatformP2PKHAddress::from_slice(&hash).unwrap(); + assert_eq!(addr.to_bytes(), hash); + + // Wrong length should fail + let short = [0u8; 19]; + assert!(PlatformP2PKHAddress::from_slice(&short).is_err()); + } + + #[test] + fn test_to_from_dashcore_address() { + let hash = [ + 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, 0x45, 0xd1, 0xb3, + 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6, + ]; + let platform_addr = PlatformP2PKHAddress::new(hash); + + let dashcore_addr = platform_addr.to_address(Network::Testnet); + let roundtrip = PlatformP2PKHAddress::from_address(&dashcore_addr).unwrap(); + + assert_eq!(roundtrip, platform_addr); + } + + #[test] + fn test_display_and_debug() { + let hash = [0xab; 20]; + let addr = PlatformP2PKHAddress::new(hash); + + let display = format!("{}", addr); + assert_eq!(display, "abababababababababababababababababababab"); + + let debug = format!("{:?}", addr); + assert!(debug.contains("PlatformP2PKHAddress")); + assert!(debug.contains("abababababababababababababababababababab")); + } + + #[test] + fn test_from_array() { + let hash = [0x55u8; 20]; + let addr: PlatformP2PKHAddress = hash.into(); + assert_eq!(addr.to_bytes(), hash); + + let back: [u8; 20] = addr.into(); + assert_eq!(back, hash); + } +} diff --git a/key-wallet/src/test_utils/account.rs b/key-wallet/src/test_utils/account.rs index 06d62fadb..a4d8fa5ba 100644 --- a/key-wallet/src/test_utils/account.rs +++ b/key-wallet/src/test_utils/account.rs @@ -1,12 +1,10 @@ -use dashcore::Network; - use crate::account::StandardAccountType; use crate::managed_account::address_pool::{AddressPool, AddressPoolType, KeySource}; use crate::managed_account::managed_account_type::ManagedAccountType; -use crate::managed_account::ManagedAccount; -use crate::DerivationPath; +use crate::managed_account::ManagedCoreAccount; +use crate::{DerivationPath, Network}; -impl ManagedAccount { +impl ManagedCoreAccount { /// Create a test managed account with a standard BIP44 type and empty address pools pub fn dummy_bip44() -> Self { let base_path = DerivationPath::master(); @@ -36,6 +34,6 @@ impl ManagedAccount { internal_addresses: internal_pool, }; - ManagedAccount::new(account_type, Network::Regtest, false) + ManagedCoreAccount::new(account_type, Network::Regtest, false) } } diff --git a/key-wallet/src/tests/balance_tests.rs b/key-wallet/src/tests/balance_tests.rs index 59dcc653f..4d6ed746c 100644 --- a/key-wallet/src/tests/balance_tests.rs +++ b/key-wallet/src/tests/balance_tests.rs @@ -1,14 +1,14 @@ //! Tests for update_balance() UTXO categorization. -use crate::managed_account::ManagedAccount; +use crate::managed_account::ManagedCoreAccount; use crate::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; use crate::wallet::managed_wallet_info::ManagedWalletInfo; -use crate::{Utxo, WalletBalance}; +use crate::{Utxo, WalletCoreBalance}; #[test] fn test_balance_with_mixed_utxo_types() { let mut wallet_info = ManagedWalletInfo::dummy(1); - let mut account = ManagedAccount::dummy_bip44(); + let mut account = ManagedCoreAccount::dummy_bip44(); // Regular confirmed UTXO let utxo1 = Utxo::dummy(1, 100_000, 1000, false, true); @@ -19,63 +19,63 @@ fn test_balance_with_mixed_utxo_types() { // Immature coinbase (<100 confirmations at height 1100) let utxo3 = Utxo::dummy(3, 20_000_000, 1050, true, true); account.utxos.insert(utxo3.outpoint, utxo3); - wallet_info.accounts.insert(account); + wallet_info.accounts.insert(account).unwrap(); - assert_eq!(wallet_info.balance(), WalletBalance::default()); + assert_eq!(wallet_info.balance(), WalletCoreBalance::default()); wallet_info.update_synced_height(1100); - let expected = WalletBalance::new(10_100_000, 0, 20_000_000, 0); + let expected = WalletCoreBalance::new(10_100_000, 0, 20_000_000, 0); assert_eq!(wallet_info.balance(), expected); } #[test] fn test_coinbase_maturity_boundary() { let mut wallet_info = ManagedWalletInfo::dummy(2); - let mut account = ManagedAccount::dummy_bip44(); + let mut account = ManagedCoreAccount::dummy_bip44(); // Coinbase at height 1000 let utxo = Utxo::dummy(1, 50_000_000, 1000, true, true); account.utxos.insert(utxo.outpoint, utxo); - wallet_info.accounts.insert(account); + wallet_info.accounts.insert(account).unwrap(); - assert_eq!(wallet_info.balance(), WalletBalance::default()); + assert_eq!(wallet_info.balance(), WalletCoreBalance::default()); // 99 confirmations: immature wallet_info.update_synced_height(1099); - let expected_immature = WalletBalance::new(0, 0, 50_000_000, 0); + let expected_immature = WalletCoreBalance::new(0, 0, 50_000_000, 0); assert_eq!(wallet_info.balance(), expected_immature); // 100 confirmations: mature wallet_info.update_synced_height(1100); - let expected_mature = WalletBalance::new(50_000_000, 0, 0, 0); + let expected_mature = WalletCoreBalance::new(50_000_000, 0, 0, 0); assert_eq!(wallet_info.balance(), expected_mature); } #[test] fn test_locked_utxos_in_locked_balance() { let mut wallet_info = ManagedWalletInfo::dummy(3); - let mut account = ManagedAccount::dummy_bip44(); + let mut account = ManagedCoreAccount::dummy_bip44(); let mut utxo = Utxo::dummy(1, 100_000, 1000, false, true); utxo.is_locked = true; account.utxos.insert(utxo.outpoint, utxo); - wallet_info.accounts.insert(account); + wallet_info.accounts.insert(account).unwrap(); - assert_eq!(wallet_info.balance(), WalletBalance::default()); + assert_eq!(wallet_info.balance(), WalletCoreBalance::default()); wallet_info.update_synced_height(1100); - let expected = WalletBalance::new(0, 0, 0, 100_000); + let expected = WalletCoreBalance::new(0, 0, 0, 100_000); assert_eq!(wallet_info.balance(), expected); } #[test] fn test_unconfirmed_utxos_in_unconfirmed_balance() { let mut wallet_info = ManagedWalletInfo::dummy(4); - let mut account = ManagedAccount::dummy_bip44(); + let mut account = ManagedCoreAccount::dummy_bip44(); let utxo = Utxo::dummy(1, 100_000, 0, false, false); account.utxos.insert(utxo.outpoint, utxo); - wallet_info.accounts.insert(account); + wallet_info.accounts.insert(account).unwrap(); - assert_eq!(wallet_info.balance(), WalletBalance::default()); + assert_eq!(wallet_info.balance(), WalletCoreBalance::default()); wallet_info.update_synced_height(1100); - let expected = WalletBalance::new(0, 100_000, 0, 0); + let expected = WalletCoreBalance::new(0, 100_000, 0, 0); assert_eq!(wallet_info.balance(), expected); } diff --git a/key-wallet/src/transaction_checking/account_checker.rs b/key-wallet/src/transaction_checking/account_checker.rs index 78799db31..e63e769c3 100644 --- a/key-wallet/src/transaction_checking/account_checker.rs +++ b/key-wallet/src/transaction_checking/account_checker.rs @@ -4,7 +4,7 @@ //! specific accounts within a ManagedAccountCollection. use super::transaction_router::AccountTypeToCheck; -use crate::account::{ManagedAccount, ManagedAccountCollection}; +use crate::account::{ManagedAccountCollection, ManagedCoreAccount}; use crate::managed_account::address_pool::{AddressInfo, PublicKeyType}; use crate::managed_account::managed_account_type::ManagedAccountType; use crate::Address; @@ -44,9 +44,12 @@ pub struct TransactionCheckResult { pub new_addresses: Vec
, } -/// Enum representing the type of account that matched with embedded data +/// Enum representing the type of Core account that matched with embedded data +/// +/// Note: Platform Payment accounts (DIP-17) are NOT included here as they +/// operate on Dash Platform, not the Core chain. #[derive(Debug, Clone)] -pub enum AccountTypeMatch { +pub enum CoreAccountTypeMatch { /// Standard BIP44 account with index and involved addresses StandardBIP44 { account_index: u32, @@ -107,25 +110,18 @@ pub enum AccountTypeMatch { account_index: u32, involved_addresses: Vec, }, - /// Platform Payment account (DIP-17) - /// Note: Platform addresses are NOT used in Core chain transactions - PlatformPayment { - account_index: u32, - key_class: u32, - involved_addresses: Vec, - }, } -impl AccountTypeMatch { +impl CoreAccountTypeMatch { /// Get all involved addresses (both receive and change combined) pub fn all_involved_addresses(&self) -> Vec { match self { - AccountTypeMatch::StandardBIP44 { + CoreAccountTypeMatch::StandardBIP44 { involved_receive_addresses, involved_change_addresses, .. } - | AccountTypeMatch::StandardBIP32 { + | CoreAccountTypeMatch::StandardBIP32 { involved_receive_addresses, involved_change_addresses, .. @@ -134,44 +130,40 @@ impl AccountTypeMatch { all.extend(involved_change_addresses.clone()); all } - AccountTypeMatch::CoinJoin { + CoreAccountTypeMatch::CoinJoin { involved_addresses, .. } => involved_addresses.clone(), - AccountTypeMatch::IdentityRegistration { + CoreAccountTypeMatch::IdentityRegistration { involved_addresses, } - | AccountTypeMatch::IdentityTopUp { + | CoreAccountTypeMatch::IdentityTopUp { involved_addresses, .. } - | AccountTypeMatch::IdentityTopUpNotBound { + | CoreAccountTypeMatch::IdentityTopUpNotBound { involved_addresses, } - | AccountTypeMatch::IdentityInvitation { + | CoreAccountTypeMatch::IdentityInvitation { involved_addresses, } - | AccountTypeMatch::ProviderVotingKeys { + | CoreAccountTypeMatch::ProviderVotingKeys { involved_addresses, } - | AccountTypeMatch::ProviderOwnerKeys { + | CoreAccountTypeMatch::ProviderOwnerKeys { involved_addresses, } - | AccountTypeMatch::ProviderOperatorKeys { + | CoreAccountTypeMatch::ProviderOperatorKeys { involved_addresses, } - | AccountTypeMatch::ProviderPlatformKeys { + | CoreAccountTypeMatch::ProviderPlatformKeys { involved_addresses, } => involved_addresses.clone(), - AccountTypeMatch::DashpayReceivingFunds { - involved_addresses, - .. - } - | AccountTypeMatch::DashpayExternalAccount { + CoreAccountTypeMatch::DashpayReceivingFunds { involved_addresses, .. } - | AccountTypeMatch::PlatformPayment { + | CoreAccountTypeMatch::DashpayExternalAccount { involved_addresses, .. } => involved_addresses.clone(), @@ -181,31 +173,27 @@ impl AccountTypeMatch { /// Get the account index if applicable pub fn account_index(&self) -> Option { match self { - AccountTypeMatch::StandardBIP44 { + CoreAccountTypeMatch::StandardBIP44 { account_index, .. } - | AccountTypeMatch::StandardBIP32 { + | CoreAccountTypeMatch::StandardBIP32 { account_index, .. } - | AccountTypeMatch::CoinJoin { + | CoreAccountTypeMatch::CoinJoin { account_index, .. } - | AccountTypeMatch::IdentityTopUp { + | CoreAccountTypeMatch::IdentityTopUp { account_index, .. } => Some(*account_index), - AccountTypeMatch::DashpayReceivingFunds { - account_index, - .. - } - | AccountTypeMatch::DashpayExternalAccount { + CoreAccountTypeMatch::DashpayReceivingFunds { account_index, .. } - | AccountTypeMatch::PlatformPayment { + | CoreAccountTypeMatch::DashpayExternalAccount { account_index, .. } => Some(*account_index), @@ -216,48 +204,45 @@ impl AccountTypeMatch { /// Convert to AccountTypeToCheck for routing pub fn to_account_type_to_check(&self) -> AccountTypeToCheck { match self { - AccountTypeMatch::StandardBIP44 { + CoreAccountTypeMatch::StandardBIP44 { .. } => AccountTypeToCheck::StandardBIP44, - AccountTypeMatch::StandardBIP32 { + CoreAccountTypeMatch::StandardBIP32 { .. } => AccountTypeToCheck::StandardBIP32, - AccountTypeMatch::CoinJoin { + CoreAccountTypeMatch::CoinJoin { .. } => AccountTypeToCheck::CoinJoin, - AccountTypeMatch::IdentityRegistration { + CoreAccountTypeMatch::IdentityRegistration { .. } => AccountTypeToCheck::IdentityRegistration, - AccountTypeMatch::IdentityTopUp { + CoreAccountTypeMatch::IdentityTopUp { .. } => AccountTypeToCheck::IdentityTopUp, - AccountTypeMatch::IdentityTopUpNotBound { + CoreAccountTypeMatch::IdentityTopUpNotBound { .. } => AccountTypeToCheck::IdentityTopUpNotBound, - AccountTypeMatch::IdentityInvitation { + CoreAccountTypeMatch::IdentityInvitation { .. } => AccountTypeToCheck::IdentityInvitation, - AccountTypeMatch::ProviderVotingKeys { + CoreAccountTypeMatch::ProviderVotingKeys { .. } => AccountTypeToCheck::ProviderVotingKeys, - AccountTypeMatch::ProviderOwnerKeys { + CoreAccountTypeMatch::ProviderOwnerKeys { .. } => AccountTypeToCheck::ProviderOwnerKeys, - AccountTypeMatch::ProviderOperatorKeys { + CoreAccountTypeMatch::ProviderOperatorKeys { .. } => AccountTypeToCheck::ProviderOperatorKeys, - AccountTypeMatch::ProviderPlatformKeys { + CoreAccountTypeMatch::ProviderPlatformKeys { .. } => AccountTypeToCheck::ProviderPlatformKeys, - AccountTypeMatch::DashpayReceivingFunds { + CoreAccountTypeMatch::DashpayReceivingFunds { .. } => AccountTypeToCheck::DashpayReceivingFunds, - AccountTypeMatch::DashpayExternalAccount { + CoreAccountTypeMatch::DashpayExternalAccount { .. } => AccountTypeToCheck::DashpayExternalAccount, - AccountTypeMatch::PlatformPayment { - .. - } => AccountTypeToCheck::PlatformPayment, } } } @@ -266,7 +251,7 @@ impl AccountTypeMatch { #[derive(Debug, Clone)] pub struct AccountMatch { /// The type of account that matched with embedded data - pub account_type_match: AccountTypeMatch, + pub account_type_match: CoreAccountTypeMatch, /// Value received by this account pub received: u64, /// Value sent from this account @@ -390,18 +375,12 @@ impl ManagedAccountCollection { } matches } - AccountTypeToCheck::PlatformPayment => { - // Platform Payment addresses (DIP-17) are NOT used in Core chain transactions. - // They are only for Platform-side payments. This account type should never match - // any Core chain transaction by design. - Vec::new() - } } } /// Check indexed accounts (BTreeMap of accounts) fn check_indexed_accounts( - accounts: &alloc::collections::BTreeMap, + accounts: &alloc::collections::BTreeMap, tx: &Transaction, ) -> Vec { let mut matches = Vec::new(); @@ -414,7 +393,7 @@ impl ManagedAccountCollection { } } -impl ManagedAccount { +impl ManagedCoreAccount { /// Classify an address within this account pub fn classify_address(&self, address: &Address) -> AddressClassification { match &self.account_type { @@ -558,7 +537,7 @@ impl ManagedAccount { } } - // Create the appropriate AccountTypeMatch based on account type + // Create the appropriate CoreAccountTypeMatch based on account type let has_addresses = !involved_receive_addresses.is_empty() || !involved_change_addresses.is_empty() || !involved_other_addresses.is_empty() @@ -572,14 +551,14 @@ impl ManagedAccount { .. } => match standard_account_type { crate::account::account_type::StandardAccountType::BIP44Account => { - AccountTypeMatch::StandardBIP44 { + CoreAccountTypeMatch::StandardBIP44 { account_index: index.unwrap_or(0), involved_receive_addresses, involved_change_addresses, } } crate::account::account_type::StandardAccountType::BIP32Account => { - AccountTypeMatch::StandardBIP32 { + CoreAccountTypeMatch::StandardBIP32 { account_index: index.unwrap_or(0), involved_receive_addresses, involved_change_addresses, @@ -588,7 +567,7 @@ impl ManagedAccount { }, ManagedAccountType::CoinJoin { .. - } => AccountTypeMatch::CoinJoin { + } => CoreAccountTypeMatch::CoinJoin { account_index: index.unwrap_or(0), // For CoinJoin, use both receive addresses and other addresses // since CoinJoin addresses can be classified as either @@ -600,66 +579,64 @@ impl ManagedAccount { }, ManagedAccountType::IdentityRegistration { .. - } => AccountTypeMatch::IdentityRegistration { + } => CoreAccountTypeMatch::IdentityRegistration { involved_addresses: involved_other_addresses, }, ManagedAccountType::IdentityTopUp { .. - } => AccountTypeMatch::IdentityTopUp { + } => CoreAccountTypeMatch::IdentityTopUp { account_index: index.unwrap_or(0), involved_addresses: involved_other_addresses, }, ManagedAccountType::IdentityTopUpNotBoundToIdentity { .. - } => AccountTypeMatch::IdentityTopUpNotBound { + } => CoreAccountTypeMatch::IdentityTopUpNotBound { involved_addresses: involved_other_addresses, }, ManagedAccountType::IdentityInvitation { .. - } => AccountTypeMatch::IdentityInvitation { + } => CoreAccountTypeMatch::IdentityInvitation { involved_addresses: involved_other_addresses, }, ManagedAccountType::ProviderVotingKeys { .. - } => AccountTypeMatch::ProviderVotingKeys { + } => CoreAccountTypeMatch::ProviderVotingKeys { involved_addresses: involved_other_addresses, }, ManagedAccountType::ProviderOwnerKeys { .. - } => AccountTypeMatch::ProviderOwnerKeys { + } => CoreAccountTypeMatch::ProviderOwnerKeys { involved_addresses: involved_other_addresses, }, ManagedAccountType::ProviderOperatorKeys { .. - } => AccountTypeMatch::ProviderOperatorKeys { + } => CoreAccountTypeMatch::ProviderOperatorKeys { involved_addresses: involved_other_addresses, }, ManagedAccountType::ProviderPlatformKeys { .. - } => AccountTypeMatch::ProviderPlatformKeys { + } => CoreAccountTypeMatch::ProviderPlatformKeys { involved_addresses: involved_other_addresses, }, ManagedAccountType::DashpayReceivingFunds { .. - } => AccountTypeMatch::DashpayReceivingFunds { + } => CoreAccountTypeMatch::DashpayReceivingFunds { account_index: index.unwrap_or(0), involved_addresses: involved_other_addresses, }, ManagedAccountType::DashpayExternalAccount { .. - } => AccountTypeMatch::DashpayExternalAccount { + } => CoreAccountTypeMatch::DashpayExternalAccount { account_index: index.unwrap_or(0), involved_addresses: involved_other_addresses, }, ManagedAccountType::PlatformPayment { - account, - key_class, .. - } => AccountTypeMatch::PlatformPayment { - account_index: *account, - key_class: *key_class, - involved_addresses: involved_other_addresses, - }, + } => { + // Platform Payment accounts (DIP-17) operate on Dash Platform, not Core chain. + // They should never be checked for Core chain transactions. + return None; + } }; Some(AccountMatch { @@ -703,27 +680,27 @@ impl ManagedAccount { } if !involved_addresses.is_empty() { - // Create the appropriate AccountTypeMatch for identity accounts + // Create the appropriate CoreAccountTypeMatch for identity accounts let account_type_match = match &self.account_type { ManagedAccountType::IdentityRegistration { .. - } => AccountTypeMatch::IdentityRegistration { + } => CoreAccountTypeMatch::IdentityRegistration { involved_addresses, }, ManagedAccountType::IdentityTopUp { .. - } => AccountTypeMatch::IdentityTopUp { + } => CoreAccountTypeMatch::IdentityTopUp { account_index: index.unwrap_or(0), involved_addresses, }, ManagedAccountType::IdentityTopUpNotBoundToIdentity { .. - } => AccountTypeMatch::IdentityTopUpNotBound { + } => CoreAccountTypeMatch::IdentityTopUpNotBound { involved_addresses, }, ManagedAccountType::IdentityInvitation { .. - } => AccountTypeMatch::IdentityInvitation { + } => CoreAccountTypeMatch::IdentityInvitation { involved_addresses, }, _ => { @@ -772,7 +749,7 @@ impl ManagedAccount { // Get the address info if let Some(address_info) = addresses.addresses.get(&addr_index) { return Some(AccountMatch { - account_type_match: AccountTypeMatch::ProviderVotingKeys { + account_type_match: CoreAccountTypeMatch::ProviderVotingKeys { involved_addresses: vec![address_info.clone()], }, received: 0, @@ -812,7 +789,7 @@ impl ManagedAccount { // Get the address info if let Some(address_info) = addresses.addresses.get(&addr_index) { return Some(AccountMatch { - account_type_match: AccountTypeMatch::ProviderOwnerKeys { + account_type_match: CoreAccountTypeMatch::ProviderOwnerKeys { involved_addresses: vec![address_info.clone()], }, received: 0, @@ -857,7 +834,7 @@ impl ManagedAccount { let operator_key_bytes: &[u8; 48] = operator_public_key.as_ref(); if bls_key.len() == 48 && bls_key.as_slice() == operator_key_bytes { return Some(AccountMatch { - account_type_match: AccountTypeMatch::ProviderOperatorKeys { + account_type_match: CoreAccountTypeMatch::ProviderOperatorKeys { involved_addresses: vec![address_info.clone()], }, received: 0, @@ -902,9 +879,10 @@ impl ManagedAccount { // Get the address info if let Some(address_info) = addresses.addresses.get(&addr_index) { return Some(AccountMatch { - account_type_match: AccountTypeMatch::ProviderPlatformKeys { - involved_addresses: vec![address_info.clone()], - }, + account_type_match: + CoreAccountTypeMatch::ProviderPlatformKeys { + involved_addresses: vec![address_info.clone()], + }, received: 0, sent: 0, received_for_credit_conversion: 0, @@ -1008,8 +986,8 @@ mod tests { #[test] fn test_coinjoin_account_type_match_no_change_addresses() { - // Create a CoinJoin AccountTypeMatch - note it only has involved_addresses, no split - let coinjoin_match = AccountTypeMatch::CoinJoin { + // Create a CoinJoin CoreAccountTypeMatch - note it only has involved_addresses, no split + let coinjoin_match = CoreAccountTypeMatch::CoinJoin { account_index: 5, involved_addresses: vec![], // Empty for simplicity }; @@ -1028,7 +1006,7 @@ mod tests { #[test] fn test_standard_accounts_have_separate_receive_and_change() { // Test StandardBIP44 account has both receive and change addresses - let bip44_match = AccountTypeMatch::StandardBIP44 { + let bip44_match = CoreAccountTypeMatch::StandardBIP44 { account_index: 0, involved_receive_addresses: vec![], involved_change_addresses: vec![], @@ -1038,7 +1016,7 @@ mod tests { assert_eq!(bip44_match.account_index(), Some(0)); // Test StandardBIP32 account also has separate receive and change addresses - let bip32_match = AccountTypeMatch::StandardBIP32 { + let bip32_match = CoreAccountTypeMatch::StandardBIP32 { account_index: 1, involved_receive_addresses: vec![], involved_change_addresses: vec![], diff --git a/key-wallet/src/transaction_checking/mod.rs b/key-wallet/src/transaction_checking/mod.rs index 1229b903e..802c24b31 100644 --- a/key-wallet/src/transaction_checking/mod.rs +++ b/key-wallet/src/transaction_checking/mod.rs @@ -5,9 +5,11 @@ //! transaction types. pub mod account_checker; +pub mod platform_checker; pub mod transaction_router; pub mod wallet_checker; pub use account_checker::{AccountMatch, AddressClassification, TransactionCheckResult}; -pub use transaction_router::{TransactionRouter, TransactionType}; +pub use platform_checker::WalletPlatformChecker; +pub use transaction_router::{PlatformAccountConversionError, TransactionRouter, TransactionType}; pub use wallet_checker::{TransactionContext, WalletTransactionChecker}; diff --git a/key-wallet/src/transaction_checking/platform_checker.rs b/key-wallet/src/transaction_checking/platform_checker.rs new file mode 100644 index 000000000..d1edaadc4 --- /dev/null +++ b/key-wallet/src/transaction_checking/platform_checker.rs @@ -0,0 +1,436 @@ +//! Platform-level balance checking and management +//! +//! This module provides methods on ManagedWalletInfo for managing +//! Platform Payment account balances (DIP-17). + +use crate::account::account_collection::PlatformPaymentAccountKey; +use crate::managed_account::address_pool::KeySource; +use crate::managed_account::platform_address::PlatformP2PKHAddress; +use crate::wallet::managed_wallet_info::ManagedWalletInfo; + +/// Extension trait for ManagedWalletInfo to add Platform balance management capabilities +pub trait WalletPlatformChecker { + /// Get the total platform credit balance across all Platform Payment accounts + /// + /// This sums the credit_balance from all platform_payment_accounts. + fn platform_credit_balance(&self) -> u64; + + /// Get the total platform balance in duffs (credit_balance / 1000) + fn platform_duff_balance(&self) -> u64; + + /// Set the credit balance for a specific platform address + /// + /// The address must belong to one of the Platform Payment accounts. + /// If `key_source` is provided and the address becomes funded (0 → non-zero), + /// the address will be marked as used and gap limit will be maintained. + /// Returns true if the address was found and the balance was set. + fn set_platform_address_balance( + &mut self, + address: &PlatformP2PKHAddress, + credit_balance: u64, + key_source: Option<&KeySource>, + ) -> bool; + + /// Set the credit balance for a specific platform address by account key + /// + /// This is more efficient when you know which account the address belongs to. + /// If `key_source` is provided and the address becomes funded (0 → non-zero), + /// the address will be marked as used and gap limit will be maintained. + /// Returns true if the account was found and the balance was set. + fn set_platform_address_balance_for_account( + &mut self, + account_key: &PlatformPaymentAccountKey, + address: PlatformP2PKHAddress, + credit_balance: u64, + key_source: Option<&KeySource>, + ) -> bool; + + /// Increase the credit balance for a specific platform address + /// + /// The address must belong to one of the Platform Payment accounts. + /// If `key_source` is provided and the address becomes funded (0 → non-zero), + /// the address will be marked as used and gap limit will be maintained. + /// Returns the new balance if successful, None if the address was not found. + fn increase_platform_address_balance( + &mut self, + address: &PlatformP2PKHAddress, + amount: u64, + key_source: Option<&KeySource>, + ) -> Option; + + /// Increase the credit balance for a specific platform address by account key + /// + /// This is more efficient when you know which account the address belongs to. + /// If `key_source` is provided and the address becomes funded (0 → non-zero), + /// the address will be marked as used and gap limit will be maintained. + /// Returns the new balance if successful, None if the account was not found. + fn increase_platform_address_balance_for_account( + &mut self, + account_key: &PlatformPaymentAccountKey, + address: PlatformP2PKHAddress, + amount: u64, + key_source: Option<&KeySource>, + ) -> Option; + + /// Decrease the credit balance for a specific platform address + /// + /// The address must belong to one of the Platform Payment accounts. + /// The balance will not go below zero (saturating subtraction). + /// Returns the new balance if successful, None if the address was not found. + fn decrease_platform_address_balance( + &mut self, + address: &PlatformP2PKHAddress, + amount: u64, + ) -> Option; + + /// Get the credit balance for a specific platform address + /// + /// Returns the balance if the address was found, None otherwise. + fn get_platform_address_balance(&self, address: &PlatformP2PKHAddress) -> Option; +} + +impl WalletPlatformChecker for ManagedWalletInfo { + fn platform_credit_balance(&self) -> u64 { + self.accounts + .platform_payment_accounts + .values() + .map(|account| account.total_credit_balance()) + .sum() + } + + fn platform_duff_balance(&self) -> u64 { + self.platform_credit_balance() / 1000 + } + + fn set_platform_address_balance( + &mut self, + address: &PlatformP2PKHAddress, + credit_balance: u64, + key_source: Option<&KeySource>, + ) -> bool { + // Find the account that contains this address + for account in self.accounts.platform_payment_accounts.values_mut() { + if account.contains_platform_address(address) { + account.set_address_credit_balance(*address, credit_balance, key_source); + return true; + } + } + false + } + + fn set_platform_address_balance_for_account( + &mut self, + account_key: &PlatformPaymentAccountKey, + address: PlatformP2PKHAddress, + credit_balance: u64, + key_source: Option<&KeySource>, + ) -> bool { + if let Some(account) = self.accounts.platform_payment_accounts.get_mut(account_key) { + // Verify the address belongs to this account before modifying + if !account.contains_platform_address(&address) { + return false; + } + account.set_address_credit_balance(address, credit_balance, key_source); + true + } else { + false + } + } + + fn increase_platform_address_balance( + &mut self, + address: &PlatformP2PKHAddress, + amount: u64, + key_source: Option<&KeySource>, + ) -> Option { + // Find the account that contains this address + for account in self.accounts.platform_payment_accounts.values_mut() { + if account.contains_platform_address(address) { + let new_balance = account.add_address_credit_balance(*address, amount, key_source); + return Some(new_balance); + } + } + None + } + + fn increase_platform_address_balance_for_account( + &mut self, + account_key: &PlatformPaymentAccountKey, + address: PlatformP2PKHAddress, + amount: u64, + key_source: Option<&KeySource>, + ) -> Option { + if let Some(account) = self.accounts.platform_payment_accounts.get_mut(account_key) { + // Verify the address belongs to this account before modifying + if !account.contains_platform_address(&address) { + return None; + } + let new_balance = account.add_address_credit_balance(address, amount, key_source); + Some(new_balance) + } else { + None + } + } + + fn decrease_platform_address_balance( + &mut self, + address: &PlatformP2PKHAddress, + amount: u64, + ) -> Option { + // Find the account that contains this address + for account in self.accounts.platform_payment_accounts.values_mut() { + if account.contains_platform_address(address) { + let new_balance = account.remove_address_credit_balance(*address, amount); + return Some(new_balance); + } + } + None + } + + fn get_platform_address_balance(&self, address: &PlatformP2PKHAddress) -> Option { + // Find the account that contains this address + for account in self.accounts.platform_payment_accounts.values() { + if account.contains_platform_address(address) { + return Some(account.address_credit_balance(address)); + } + } + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::bip32::{ChildNumber, DerivationPath}; + use crate::managed_account::address_pool::{AddressPool, AddressPoolType}; + use crate::managed_account::managed_platform_account::ManagedPlatformAccount; + use crate::Network; + + fn create_test_pool() -> AddressPool { + let base_path = DerivationPath::from(vec![ + ChildNumber::from_hardened_idx(9).unwrap(), + ChildNumber::from_hardened_idx(1).unwrap(), + ChildNumber::from_hardened_idx(17).unwrap(), + ChildNumber::from_hardened_idx(0).unwrap(), + ChildNumber::from_hardened_idx(0).unwrap(), + ]); + AddressPool::new_without_generation( + base_path, + AddressPoolType::Absent, + 10, + Network::Testnet, + ) + } + + fn create_test_wallet_info() -> ManagedWalletInfo { + ManagedWalletInfo::new(Network::Testnet, [0u8; 32]) + } + + #[test] + fn test_platform_credit_balance_empty() { + let wallet_info = create_test_wallet_info(); + assert_eq!(wallet_info.platform_credit_balance(), 0); + assert_eq!(wallet_info.platform_duff_balance(), 0); + } + + #[test] + fn test_platform_credit_balance_with_accounts() { + let mut wallet_info = create_test_wallet_info(); + + // Create and add a platform account + let pool = create_test_pool(); + let mut account = ManagedPlatformAccount::new(0, 0, pool, false); + + // Add some balance + let addr = PlatformP2PKHAddress::new([0x11; 20]); + account.set_address_credit_balance(addr, 5000, None); + + let key = PlatformPaymentAccountKey { + account: 0, + key_class: 0, + }; + wallet_info.accounts.platform_payment_accounts.insert(key, account); + + assert_eq!(wallet_info.platform_credit_balance(), 5000); + assert_eq!(wallet_info.platform_duff_balance(), 5); // 5000 / 1000 = 5 + } + + #[test] + fn test_platform_credit_balance_multiple_accounts() { + let mut wallet_info = create_test_wallet_info(); + + // Create first platform account + let pool1 = create_test_pool(); + let mut account1 = ManagedPlatformAccount::new(0, 0, pool1, false); + let addr1 = PlatformP2PKHAddress::new([0x11; 20]); + account1.set_address_credit_balance(addr1, 3000, None); + + // Create second platform account + let pool2 = create_test_pool(); + let mut account2 = ManagedPlatformAccount::new(1, 0, pool2, false); + let addr2 = PlatformP2PKHAddress::new([0x22; 20]); + account2.set_address_credit_balance(addr2, 2000, None); + + wallet_info.accounts.platform_payment_accounts.insert( + PlatformPaymentAccountKey { + account: 0, + key_class: 0, + }, + account1, + ); + wallet_info.accounts.platform_payment_accounts.insert( + PlatformPaymentAccountKey { + account: 1, + key_class: 0, + }, + account2, + ); + + assert_eq!(wallet_info.platform_credit_balance(), 5000); + assert_eq!(wallet_info.platform_duff_balance(), 5); + } + + #[test] + fn test_set_platform_address_balance() { + let mut wallet_info = create_test_wallet_info(); + + // Create platform account + let pool = create_test_pool(); + let mut account = ManagedPlatformAccount::new(0, 0, pool, false); + let addr = PlatformP2PKHAddress::new([0x11; 20]); + account.set_address_credit_balance(addr, 1000, None); + + let key = PlatformPaymentAccountKey { + account: 0, + key_class: 0, + }; + wallet_info.accounts.platform_payment_accounts.insert(key, account); + + // Set balance for existing address + let result = wallet_info.set_platform_address_balance(&addr, 5000, None); + assert!(result); + assert_eq!(wallet_info.platform_credit_balance(), 5000); + + // Try to set balance for non-existent address + let unknown_addr = PlatformP2PKHAddress::new([0xFF; 20]); + let result = wallet_info.set_platform_address_balance(&unknown_addr, 1000, None); + assert!(!result); + } + + #[test] + fn test_increase_platform_address_balance() { + let mut wallet_info = create_test_wallet_info(); + + // Create platform account + let pool = create_test_pool(); + let mut account = ManagedPlatformAccount::new(0, 0, pool, false); + let addr = PlatformP2PKHAddress::new([0x11; 20]); + account.set_address_credit_balance(addr, 1000, None); + + let key = PlatformPaymentAccountKey { + account: 0, + key_class: 0, + }; + wallet_info.accounts.platform_payment_accounts.insert(key, account); + + // Increase balance + let new_balance = wallet_info.increase_platform_address_balance(&addr, 500, None); + assert_eq!(new_balance, Some(1500)); + assert_eq!(wallet_info.platform_credit_balance(), 1500); + + // Increase again + let new_balance = wallet_info.increase_platform_address_balance(&addr, 1000, None); + assert_eq!(new_balance, Some(2500)); + assert_eq!(wallet_info.platform_credit_balance(), 2500); + } + + #[test] + fn test_decrease_platform_address_balance() { + let mut wallet_info = create_test_wallet_info(); + + // Create platform account + let pool = create_test_pool(); + let mut account = ManagedPlatformAccount::new(0, 0, pool, false); + let addr = PlatformP2PKHAddress::new([0x11; 20]); + account.set_address_credit_balance(addr, 5000, None); + + let key = PlatformPaymentAccountKey { + account: 0, + key_class: 0, + }; + wallet_info.accounts.platform_payment_accounts.insert(key, account); + + // Decrease balance + let new_balance = wallet_info.decrease_platform_address_balance(&addr, 1000); + assert_eq!(new_balance, Some(4000)); + + // Decrease more than available (should saturate at 0) + let new_balance = wallet_info.decrease_platform_address_balance(&addr, 10000); + assert_eq!(new_balance, Some(0)); + } + + #[test] + fn test_get_platform_address_balance() { + let mut wallet_info = create_test_wallet_info(); + + // Create platform account + let pool = create_test_pool(); + let mut account = ManagedPlatformAccount::new(0, 0, pool, false); + let addr = PlatformP2PKHAddress::new([0x11; 20]); + account.set_address_credit_balance(addr, 3000, None); + + let key = PlatformPaymentAccountKey { + account: 0, + key_class: 0, + }; + wallet_info.accounts.platform_payment_accounts.insert(key, account); + + // Get balance for existing address + let balance = wallet_info.get_platform_address_balance(&addr); + assert_eq!(balance, Some(3000)); + + // Get balance for non-existent address + let unknown_addr = PlatformP2PKHAddress::new([0xFF; 20]); + let balance = wallet_info.get_platform_address_balance(&unknown_addr); + assert_eq!(balance, None); + } + + #[test] + fn test_set_platform_address_balance_for_account() { + let mut wallet_info = create_test_wallet_info(); + + // Create platform account with an address already having a balance + let pool = create_test_pool(); + let mut account = ManagedPlatformAccount::new(0, 0, pool, false); + let addr = PlatformP2PKHAddress::new([0x11; 20]); + // First set initial balance so the address is known to the account + account.set_address_credit_balance(addr, 1000, None); + + let key = PlatformPaymentAccountKey { + account: 0, + key_class: 0, + }; + wallet_info.accounts.platform_payment_accounts.insert(key, account); + + // Update balance using account key - should succeed since address is known + let result = wallet_info.set_platform_address_balance_for_account(&key, addr, 5000, None); + assert!(result); + assert_eq!(wallet_info.platform_credit_balance(), 5000); + + // Try with address not belonging to the account - should fail + let unknown_addr = PlatformP2PKHAddress::new([0xFF; 20]); + let result = + wallet_info.set_platform_address_balance_for_account(&key, unknown_addr, 2000, None); + assert!(!result); + assert_eq!(wallet_info.platform_credit_balance(), 5000); // Balance unchanged + + // Try with non-existent account + let bad_key = PlatformPaymentAccountKey { + account: 99, + key_class: 0, + }; + let result = + wallet_info.set_platform_address_balance_for_account(&bad_key, addr, 1000, None); + assert!(!result); + } +} diff --git a/key-wallet/src/transaction_checking/transaction_router/mod.rs b/key-wallet/src/transaction_checking/transaction_router/mod.rs index 36fde7583..6d8f6921c 100644 --- a/key-wallet/src/transaction_checking/transaction_router/mod.rs +++ b/key-wallet/src/transaction_checking/transaction_router/mod.rs @@ -162,7 +162,10 @@ impl TransactionRouter { } } -/// Account types that can be checked for transactions +/// Core account types that can be checked for transactions +/// +/// Note: Platform Payment accounts (DIP-17) are NOT included here as they +/// operate on Dash Platform, not the Core chain. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AccountTypeToCheck { StandardBIP44, @@ -178,115 +181,132 @@ pub enum AccountTypeToCheck { ProviderPlatformKeys, DashpayReceivingFunds, DashpayExternalAccount, - /// Platform Payment accounts (DIP-17). - /// Note: These are NOT checked for Core chain transactions as they operate on Dash Platform. - PlatformPayment, } -impl From for AccountTypeToCheck { - fn from(value: ManagedAccountType) -> Self { +/// Error returned when trying to convert a Platform Payment account to AccountTypeToCheck +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PlatformAccountConversionError; + +impl core::fmt::Display for PlatformAccountConversionError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "PlatformPayment accounts cannot be converted to AccountTypeToCheck") + } +} + +impl TryFrom for AccountTypeToCheck { + type Error = PlatformAccountConversionError; + + fn try_from(value: ManagedAccountType) -> Result { match value { ManagedAccountType::Standard { standard_account_type, .. } => match standard_account_type { crate::account::account_type::StandardAccountType::BIP44Account => { - AccountTypeToCheck::StandardBIP44 + Ok(AccountTypeToCheck::StandardBIP44) } crate::account::account_type::StandardAccountType::BIP32Account => { - AccountTypeToCheck::StandardBIP32 + Ok(AccountTypeToCheck::StandardBIP32) } }, ManagedAccountType::CoinJoin { .. - } => AccountTypeToCheck::CoinJoin, + } => Ok(AccountTypeToCheck::CoinJoin), ManagedAccountType::IdentityRegistration { .. - } => AccountTypeToCheck::IdentityRegistration, + } => Ok(AccountTypeToCheck::IdentityRegistration), ManagedAccountType::IdentityTopUp { .. - } => AccountTypeToCheck::IdentityTopUp, + } => Ok(AccountTypeToCheck::IdentityTopUp), ManagedAccountType::IdentityTopUpNotBoundToIdentity { .. - } => AccountTypeToCheck::IdentityTopUpNotBound, + } => Ok(AccountTypeToCheck::IdentityTopUpNotBound), ManagedAccountType::IdentityInvitation { .. - } => AccountTypeToCheck::IdentityInvitation, + } => Ok(AccountTypeToCheck::IdentityInvitation), ManagedAccountType::ProviderVotingKeys { .. - } => AccountTypeToCheck::ProviderVotingKeys, + } => Ok(AccountTypeToCheck::ProviderVotingKeys), ManagedAccountType::ProviderOwnerKeys { .. - } => AccountTypeToCheck::ProviderOwnerKeys, + } => Ok(AccountTypeToCheck::ProviderOwnerKeys), ManagedAccountType::ProviderOperatorKeys { .. - } => AccountTypeToCheck::ProviderOperatorKeys, + } => Ok(AccountTypeToCheck::ProviderOperatorKeys), ManagedAccountType::ProviderPlatformKeys { .. - } => AccountTypeToCheck::ProviderPlatformKeys, + } => Ok(AccountTypeToCheck::ProviderPlatformKeys), ManagedAccountType::DashpayReceivingFunds { .. - } => AccountTypeToCheck::DashpayReceivingFunds, + } => Ok(AccountTypeToCheck::DashpayReceivingFunds), ManagedAccountType::DashpayExternalAccount { .. - } => AccountTypeToCheck::DashpayExternalAccount, + } => Ok(AccountTypeToCheck::DashpayExternalAccount), ManagedAccountType::PlatformPayment { .. - } => AccountTypeToCheck::PlatformPayment, + } => { + // Platform Payment accounts (DIP-17) operate on Dash Platform, not the Core chain. + Err(PlatformAccountConversionError) + } } } } -impl From<&ManagedAccountType> for AccountTypeToCheck { - fn from(value: &ManagedAccountType) -> Self { +impl TryFrom<&ManagedAccountType> for AccountTypeToCheck { + type Error = PlatformAccountConversionError; + + fn try_from(value: &ManagedAccountType) -> Result { match value { ManagedAccountType::Standard { standard_account_type, .. } => match standard_account_type { crate::account::account_type::StandardAccountType::BIP44Account => { - AccountTypeToCheck::StandardBIP44 + Ok(AccountTypeToCheck::StandardBIP44) } crate::account::account_type::StandardAccountType::BIP32Account => { - AccountTypeToCheck::StandardBIP32 + Ok(AccountTypeToCheck::StandardBIP32) } }, ManagedAccountType::CoinJoin { .. - } => AccountTypeToCheck::CoinJoin, + } => Ok(AccountTypeToCheck::CoinJoin), ManagedAccountType::IdentityRegistration { .. - } => AccountTypeToCheck::IdentityRegistration, + } => Ok(AccountTypeToCheck::IdentityRegistration), ManagedAccountType::IdentityTopUp { .. - } => AccountTypeToCheck::IdentityTopUp, + } => Ok(AccountTypeToCheck::IdentityTopUp), ManagedAccountType::IdentityTopUpNotBoundToIdentity { .. - } => AccountTypeToCheck::IdentityTopUpNotBound, + } => Ok(AccountTypeToCheck::IdentityTopUpNotBound), ManagedAccountType::IdentityInvitation { .. - } => AccountTypeToCheck::IdentityInvitation, + } => Ok(AccountTypeToCheck::IdentityInvitation), ManagedAccountType::ProviderVotingKeys { .. - } => AccountTypeToCheck::ProviderVotingKeys, + } => Ok(AccountTypeToCheck::ProviderVotingKeys), ManagedAccountType::ProviderOwnerKeys { .. - } => AccountTypeToCheck::ProviderOwnerKeys, + } => Ok(AccountTypeToCheck::ProviderOwnerKeys), ManagedAccountType::ProviderOperatorKeys { .. - } => AccountTypeToCheck::ProviderOperatorKeys, + } => Ok(AccountTypeToCheck::ProviderOperatorKeys), ManagedAccountType::ProviderPlatformKeys { .. - } => AccountTypeToCheck::ProviderPlatformKeys, + } => Ok(AccountTypeToCheck::ProviderPlatformKeys), ManagedAccountType::DashpayReceivingFunds { .. - } => AccountTypeToCheck::DashpayReceivingFunds, + } => Ok(AccountTypeToCheck::DashpayReceivingFunds), ManagedAccountType::DashpayExternalAccount { .. - } => AccountTypeToCheck::DashpayExternalAccount, + } => Ok(AccountTypeToCheck::DashpayExternalAccount), ManagedAccountType::PlatformPayment { .. - } => AccountTypeToCheck::PlatformPayment, + } => { + // Platform Payment accounts (DIP-17) operate on Dash Platform, not the Core chain. + Err(PlatformAccountConversionError) + } } } } diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/asset_unlock.rs b/key-wallet/src/transaction_checking/transaction_router/tests/asset_unlock.rs index 6f7fce641..2f2ccf1fb 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/asset_unlock.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/asset_unlock.rs @@ -133,7 +133,7 @@ async fn test_asset_unlock_transaction_routing() { timestamp: Some(1234567890), }; - let result = managed_wallet_info.check_transaction(&tx, context, &mut wallet, true).await; + let result = managed_wallet_info.check_core_transaction(&tx, context, &mut wallet, true).await; // The transaction should be recognized as relevant assert!(result.is_relevant, "Asset unlock transaction should be recognized as relevant"); @@ -209,7 +209,7 @@ async fn test_asset_unlock_routing_to_bip32_account() { timestamp: Some(1234567890), }; - let result = managed_wallet_info.check_transaction(&tx, context, &mut wallet, true).await; + let result = managed_wallet_info.check_core_transaction(&tx, context, &mut wallet, true).await; // Should be recognized as relevant assert!(result.is_relevant, "Asset unlock transaction to BIP32 account should be relevant"); diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs b/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs index a969cfae2..acd2c81c1 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs @@ -107,7 +107,7 @@ async fn test_coinbase_transaction_routing_to_bip44_receive_address() { // Check the coinbase transaction let result = managed_wallet_info - .check_transaction( + .check_core_transaction( &coinbase_tx, context, &mut wallet, @@ -181,7 +181,7 @@ async fn test_coinbase_transaction_routing_to_bip44_change_address() { // Check the coinbase transaction let result = managed_wallet_info - .check_transaction( + .check_core_transaction( &coinbase_tx, context, &mut wallet, @@ -251,7 +251,8 @@ async fn test_update_state_flag_behavior() { }; // First check with update_state = false - let result1 = managed_wallet_info.check_transaction(&tx, context, &mut wallet, false).await; + let result1 = + managed_wallet_info.check_core_transaction(&tx, context, &mut wallet, false).await; assert!(result1.is_relevant); @@ -274,7 +275,7 @@ async fn test_update_state_flag_behavior() { // Now check with update_state = true let result2 = managed_wallet_info - .check_transaction( + .check_core_transaction( &tx, context, &mut wallet, @@ -395,7 +396,7 @@ async fn test_coinbase_transaction_with_payload_routing() { }; let result = - managed_wallet_info.check_transaction(&coinbase_tx, context, &mut wallet, true).await; + managed_wallet_info.check_core_transaction(&coinbase_tx, context, &mut wallet, true).await; assert!(result.is_relevant, "Coinbase with payload should be relevant"); assert_eq!(result.total_received, 5000000000, "Should have received block reward"); diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/conversions.rs b/key-wallet/src/transaction_checking/transaction_router/tests/conversions.rs index d499f7e8b..1620447ca 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/conversions.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/conversions.rs @@ -1,4 +1,4 @@ -//! Tests for From trait conversions from ManagedAccountType to AccountTypeToCheck +//! Tests for TryFrom trait conversions from ManagedAccountType to AccountTypeToCheck use crate::account::account_type::StandardAccountType; use crate::bip32::DerivationPath; @@ -23,7 +23,7 @@ fn test_single_pool() -> AddressPool { } #[test] -fn test_from_standard_bip44_account() { +fn test_try_from_standard_bip44_account() { let managed_account = ManagedAccountType::Standard { index: 0, standard_account_type: StandardAccountType::BIP44Account, @@ -31,12 +31,12 @@ fn test_from_standard_bip44_account() { internal_addresses: test_address_pool(AddressPoolType::Internal), }; - let check_type: AccountTypeToCheck = managed_account.into(); + let check_type: AccountTypeToCheck = managed_account.try_into().unwrap(); assert_eq!(check_type, AccountTypeToCheck::StandardBIP44); } #[test] -fn test_from_standard_bip32_account() { +fn test_try_from_standard_bip32_account() { let managed_account = ManagedAccountType::Standard { index: 0, standard_account_type: StandardAccountType::BIP32Account, @@ -44,104 +44,116 @@ fn test_from_standard_bip32_account() { internal_addresses: test_address_pool(AddressPoolType::Internal), }; - let check_type: AccountTypeToCheck = managed_account.into(); + let check_type: AccountTypeToCheck = managed_account.try_into().unwrap(); assert_eq!(check_type, AccountTypeToCheck::StandardBIP32); } #[test] -fn test_from_coinjoin_account() { +fn test_try_from_coinjoin_account() { let managed_account = ManagedAccountType::CoinJoin { index: 0, addresses: test_single_pool(), }; - let check_type: AccountTypeToCheck = managed_account.into(); + let check_type: AccountTypeToCheck = managed_account.try_into().unwrap(); assert_eq!(check_type, AccountTypeToCheck::CoinJoin); } #[test] -fn test_from_identity_registration() { +fn test_try_from_identity_registration() { let managed_account = ManagedAccountType::IdentityRegistration { addresses: test_single_pool(), }; - let check_type: AccountTypeToCheck = managed_account.into(); + let check_type: AccountTypeToCheck = managed_account.try_into().unwrap(); assert_eq!(check_type, AccountTypeToCheck::IdentityRegistration); } #[test] -fn test_from_identity_topup() { +fn test_try_from_identity_topup() { let managed_account = ManagedAccountType::IdentityTopUp { registration_index: 1, addresses: test_single_pool(), }; - let check_type: AccountTypeToCheck = managed_account.into(); + let check_type: AccountTypeToCheck = managed_account.try_into().unwrap(); assert_eq!(check_type, AccountTypeToCheck::IdentityTopUp); } #[test] -fn test_from_identity_topup_not_bound() { +fn test_try_from_identity_topup_not_bound() { let managed_account = ManagedAccountType::IdentityTopUpNotBoundToIdentity { addresses: test_single_pool(), }; - let check_type: AccountTypeToCheck = managed_account.into(); + let check_type: AccountTypeToCheck = managed_account.try_into().unwrap(); assert_eq!(check_type, AccountTypeToCheck::IdentityTopUpNotBound); } #[test] -fn test_from_identity_invitation() { +fn test_try_from_identity_invitation() { let managed_account = ManagedAccountType::IdentityInvitation { addresses: test_single_pool(), }; - let check_type: AccountTypeToCheck = managed_account.into(); + let check_type: AccountTypeToCheck = managed_account.try_into().unwrap(); assert_eq!(check_type, AccountTypeToCheck::IdentityInvitation); } #[test] -fn test_from_provider_voting_keys() { +fn test_try_from_provider_voting_keys() { let managed_account = ManagedAccountType::ProviderVotingKeys { addresses: test_single_pool(), }; - let check_type: AccountTypeToCheck = managed_account.into(); + let check_type: AccountTypeToCheck = managed_account.try_into().unwrap(); assert_eq!(check_type, AccountTypeToCheck::ProviderVotingKeys); } #[test] -fn test_from_provider_owner_keys() { +fn test_try_from_provider_owner_keys() { let managed_account = ManagedAccountType::ProviderOwnerKeys { addresses: test_single_pool(), }; - let check_type: AccountTypeToCheck = managed_account.into(); + let check_type: AccountTypeToCheck = managed_account.try_into().unwrap(); assert_eq!(check_type, AccountTypeToCheck::ProviderOwnerKeys); } #[test] -fn test_from_provider_operator_keys() { +fn test_try_from_provider_operator_keys() { let managed_account = ManagedAccountType::ProviderOperatorKeys { addresses: test_single_pool(), }; - let check_type: AccountTypeToCheck = managed_account.into(); + let check_type: AccountTypeToCheck = managed_account.try_into().unwrap(); assert_eq!(check_type, AccountTypeToCheck::ProviderOperatorKeys); } #[test] -fn test_from_provider_platform_keys() { +fn test_try_from_provider_platform_keys() { let managed_account = ManagedAccountType::ProviderPlatformKeys { addresses: test_single_pool(), }; - let check_type: AccountTypeToCheck = managed_account.into(); + let check_type: AccountTypeToCheck = managed_account.try_into().unwrap(); assert_eq!(check_type, AccountTypeToCheck::ProviderPlatformKeys); } #[test] -fn test_from_ref_standard_bip44_account() { +fn test_try_from_platform_payment_fails() { + let managed_account = ManagedAccountType::PlatformPayment { + account: 0, + key_class: 0, + addresses: test_single_pool(), + }; + + let result: Result = managed_account.try_into(); + assert!(result.is_err()); +} + +#[test] +fn test_try_from_ref_standard_bip44_account() { let managed_account = ManagedAccountType::Standard { index: 0, standard_account_type: StandardAccountType::BIP44Account, @@ -149,12 +161,12 @@ fn test_from_ref_standard_bip44_account() { internal_addresses: test_address_pool(AddressPoolType::Internal), }; - let check_type: AccountTypeToCheck = (&managed_account).into(); + let check_type: AccountTypeToCheck = (&managed_account).try_into().unwrap(); assert_eq!(check_type, AccountTypeToCheck::StandardBIP44); } #[test] -fn test_from_ref_all_account_types() { +fn test_try_from_ref_all_account_types() { // Test all account types using reference conversion let test_cases = vec![ ( @@ -225,7 +237,19 @@ fn test_from_ref_all_account_types() { ]; for (managed_account, expected) in test_cases { - let check_type: AccountTypeToCheck = (&managed_account).into(); + let check_type: AccountTypeToCheck = (&managed_account).try_into().unwrap(); assert_eq!(check_type, expected); } } + +#[test] +fn test_try_from_ref_platform_payment_fails() { + let managed_account = ManagedAccountType::PlatformPayment { + account: 0, + key_class: 0, + addresses: test_single_pool(), + }; + + let result: Result = (&managed_account).try_into(); + assert!(result.is_err()); +} diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/identity_transactions.rs b/key-wallet/src/transaction_checking/transaction_router/tests/identity_transactions.rs index 0c1fdb4ac..51fd92e5b 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/identity_transactions.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/identity_transactions.rs @@ -144,7 +144,7 @@ async fn test_identity_registration_account_routing() { }; // First check without updating state - let result = managed_wallet_info.check_transaction(&tx, context, &mut wallet, true).await; + let result = managed_wallet_info.check_core_transaction(&tx, context, &mut wallet, true).await; println!( "Identity registration transaction result: is_relevant={}, received={}, credit_conversion={}", @@ -220,7 +220,7 @@ async fn test_normal_payment_to_identity_address_not_detected() { }; let result = managed_wallet_info - .check_transaction( + .check_core_transaction( &normal_tx, context, &mut wallet, diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/provider.rs b/key-wallet/src/transaction_checking/transaction_router/tests/provider.rs index 6955067b2..b8a53ad49 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/provider.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/provider.rs @@ -229,7 +229,7 @@ async fn test_provider_registration_transaction_routing_check_owner_only() { timestamp: Some(1234567890), }; - let result = managed_wallet_info.check_transaction(&tx, context, &mut wallet, true).await; + let result = managed_wallet_info.check_core_transaction(&tx, context, &mut wallet, true).await; println!( "Provider registration transaction result: is_relevant={}, received={}", @@ -365,7 +365,7 @@ async fn test_provider_registration_transaction_routing_check_voting_only() { timestamp: Some(1234567890), }; - let result = managed_wallet_info.check_transaction(&tx, context, &mut wallet, true).await; + let result = managed_wallet_info.check_core_transaction(&tx, context, &mut wallet, true).await; println!( "Provider registration transaction result (voting): is_relevant={}, received={}", @@ -502,7 +502,7 @@ async fn test_provider_registration_transaction_routing_check_operator_only() { timestamp: Some(1234567890), }; - let result = managed_wallet_info.check_transaction(&tx, context, &mut wallet, true).await; + let result = managed_wallet_info.check_core_transaction(&tx, context, &mut wallet, true).await; println!( "Provider registration transaction result (operator): is_relevant={}, received={}", @@ -706,7 +706,7 @@ async fn test_provider_registration_transaction_routing_check_platform_only() { timestamp: Some(1234567890), }; - let result = managed_wallet_info.check_transaction(&tx, context, &mut wallet, true).await; + let result = managed_wallet_info.check_core_transaction(&tx, context, &mut wallet, true).await; println!( "Provider registration transaction result (platform): is_relevant={}, received={}", @@ -827,7 +827,7 @@ async fn test_provider_update_registrar_with_voting_and_operator() { timestamp: Some(1234567890), }; - let result = managed_wallet_info.check_transaction(&tx, context, &mut wallet, true).await; + let result = managed_wallet_info.check_core_transaction(&tx, context, &mut wallet, true).await; // Should be recognized as relevant due to voting and operator keys assert!(result.is_relevant, "Provider update registrar should be relevant"); @@ -920,7 +920,7 @@ async fn test_provider_revocation_classification_and_routing() { timestamp: Some(1234567890), }; - let result = managed_wallet_info.check_transaction(&tx, context, &mut wallet, true).await; + let result = managed_wallet_info.check_core_transaction(&tx, context, &mut wallet, true).await; // Should be recognized as relevant due to collateral return assert!(result.is_relevant, "Provider revocation with collateral return should be relevant"); diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/routing.rs b/key-wallet/src/transaction_checking/transaction_router/tests/routing.rs index 34f9b54f2..b583f7b5d 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/routing.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/routing.rs @@ -90,7 +90,7 @@ async fn test_transaction_routing_to_bip44_account() { // Check the transaction using the managed wallet info let result = managed_wallet_info - .check_transaction( + .check_core_transaction( &tx, context, &mut wallet, @@ -157,7 +157,7 @@ async fn test_transaction_routing_to_bip32_account() { }; // Check with update_state = false - let result = managed_wallet_info.check_transaction(&tx, context, &mut wallet, false).await; + let result = managed_wallet_info.check_core_transaction(&tx, context, &mut wallet, false).await; // The transaction should be recognized as relevant assert!(result.is_relevant, "Transaction should be relevant to the BIP32 account"); @@ -177,7 +177,7 @@ async fn test_transaction_routing_to_bip32_account() { // Now check with update_state = true let result = managed_wallet_info - .check_transaction( + .check_core_transaction( &tx, context, &mut wallet, @@ -275,7 +275,7 @@ async fn test_transaction_routing_to_coinjoin_account() { timestamp: Some(1234567890), }; - let result = managed_wallet_info.check_transaction(&tx, context, &mut wallet, true).await; + let result = managed_wallet_info.check_core_transaction(&tx, context, &mut wallet, true).await; // This test may fail if CoinJoin detection is not properly implemented println!( @@ -378,7 +378,7 @@ async fn test_transaction_affects_multiple_accounts() { // Check the transaction let result = managed_wallet_info - .check_transaction( + .check_core_transaction( &tx, context, &mut wallet, @@ -399,7 +399,8 @@ async fn test_transaction_affects_multiple_accounts() { println!("Multi-account transaction result: accounts_affected={:?}", result.affected_accounts); // Test with update_state = false to ensure state isn't modified - let result2 = managed_wallet_info.check_transaction(&tx, context, &mut wallet, false).await; + let result2 = + managed_wallet_info.check_core_transaction(&tx, context, &mut wallet, false).await; assert_eq!( result2.total_received, result.total_received, diff --git a/key-wallet/src/transaction_checking/wallet_checker.rs b/key-wallet/src/transaction_checking/wallet_checker.rs index df1db5ece..a0253b2d1 100644 --- a/key-wallet/src/transaction_checking/wallet_checker.rs +++ b/key-wallet/src/transaction_checking/wallet_checker.rs @@ -45,7 +45,7 @@ pub trait WalletTransactionChecker { /// /// The context parameter indicates where the transaction comes from (mempool, block, etc.) /// - async fn check_transaction( + async fn check_core_transaction( &mut self, tx: &Transaction, context: TransactionContext, @@ -56,7 +56,7 @@ pub trait WalletTransactionChecker { #[async_trait] impl WalletTransactionChecker for ManagedWalletInfo { - async fn check_transaction( + async fn check_core_transaction( &mut self, tx: &Transaction, context: TransactionContext, @@ -92,61 +92,54 @@ impl WalletTransactionChecker for ManagedWalletInfo { for account_match in &result.affected_accounts { // Find and update the specific account - use super::account_checker::AccountTypeMatch; + use super::account_checker::CoreAccountTypeMatch; let account = match &account_match.account_type_match { - AccountTypeMatch::StandardBIP44 { + CoreAccountTypeMatch::StandardBIP44 { account_index, .. } => self.accounts.standard_bip44_accounts.get_mut(account_index), - AccountTypeMatch::StandardBIP32 { + CoreAccountTypeMatch::StandardBIP32 { account_index, .. } => self.accounts.standard_bip32_accounts.get_mut(account_index), - AccountTypeMatch::CoinJoin { + CoreAccountTypeMatch::CoinJoin { account_index, .. } => self.accounts.coinjoin_accounts.get_mut(account_index), - AccountTypeMatch::IdentityRegistration { + CoreAccountTypeMatch::IdentityRegistration { .. } => self.accounts.identity_registration.as_mut(), - AccountTypeMatch::IdentityTopUp { + CoreAccountTypeMatch::IdentityTopUp { account_index, .. } => self.accounts.identity_topup.get_mut(account_index), - AccountTypeMatch::IdentityTopUpNotBound { + CoreAccountTypeMatch::IdentityTopUpNotBound { .. } => self.accounts.identity_topup_not_bound.as_mut(), - AccountTypeMatch::IdentityInvitation { + CoreAccountTypeMatch::IdentityInvitation { .. } => self.accounts.identity_invitation.as_mut(), - AccountTypeMatch::ProviderVotingKeys { + CoreAccountTypeMatch::ProviderVotingKeys { .. } => self.accounts.provider_voting_keys.as_mut(), - AccountTypeMatch::ProviderOwnerKeys { + CoreAccountTypeMatch::ProviderOwnerKeys { .. } => self.accounts.provider_owner_keys.as_mut(), - AccountTypeMatch::ProviderOperatorKeys { + CoreAccountTypeMatch::ProviderOperatorKeys { .. } => self.accounts.provider_operator_keys.as_mut(), - AccountTypeMatch::ProviderPlatformKeys { + CoreAccountTypeMatch::ProviderPlatformKeys { .. } => self.accounts.provider_platform_keys.as_mut(), - AccountTypeMatch::DashpayReceivingFunds { + CoreAccountTypeMatch::DashpayReceivingFunds { .. } - | AccountTypeMatch::DashpayExternalAccount { + | CoreAccountTypeMatch::DashpayExternalAccount { .. } => { // DashPay managed accounts are not persisted here yet None } - AccountTypeMatch::PlatformPayment { - .. - } => { - // Platform Payment addresses are NOT used in Core chain transactions. - // This branch should never be reached by design (per DIP-17). - None - } }; if let Some(account) = account { @@ -391,7 +384,8 @@ mod tests { let context = TransactionContext::Mempool; let mut wallet_mut = wallet; - let result = managed_wallet.check_transaction(&tx, context, &mut wallet_mut, true).await; + let result = + managed_wallet.check_core_transaction(&tx, context, &mut wallet_mut, true).await; // Should return default result with no relevance assert!(!result.is_relevant); @@ -471,7 +465,8 @@ mod tests { }; // This should exercise BIP32 account branch in the update logic - let result = managed_wallet.check_transaction(&tx, context, &mut wallet, true).await; + let result = + managed_wallet.check_core_transaction(&tx, context, &mut wallet, true).await; // Should be relevant since it's our address assert!(result.is_relevant); @@ -507,7 +502,8 @@ mod tests { }; // This should exercise CoinJoin account branch in the update logic - let result = managed_wallet.check_transaction(&tx, context, &mut wallet, true).await; + let result = + managed_wallet.check_core_transaction(&tx, context, &mut wallet, true).await; // Since this is not a coinjoin looking transaction, we should not pick up on it. assert!(!result.is_relevant); @@ -566,7 +562,7 @@ mod tests { }; let result = - managed_wallet.check_transaction(&coinbase_tx, context, &mut wallet, true).await; + managed_wallet.check_core_transaction(&coinbase_tx, context, &mut wallet, true).await; // Set synced_height to block where coinbase was received to trigger balance updates. managed_wallet.update_synced_height(block_height); @@ -630,8 +626,9 @@ mod tests { timestamp: Some(1_650_000_000), }; - let funding_result = - managed_wallet.check_transaction(&funding_tx, funding_context, &mut wallet, true).await; + let funding_result = managed_wallet + .check_core_transaction(&funding_tx, funding_context, &mut wallet, true) + .await; assert!(funding_result.is_relevant, "Funding transaction must be relevant"); assert_eq!(funding_result.total_received, funding_value); @@ -665,8 +662,9 @@ mod tests { timestamp: Some(1_650_000_100), }; - let spend_result = - managed_wallet.check_transaction(&spend_tx, spend_context, &mut wallet, true).await; + let spend_result = managed_wallet + .check_core_transaction(&spend_tx, spend_context, &mut wallet, true) + .await; assert!(spend_result.is_relevant, "Spend transaction should be detected"); assert_eq!(spend_result.total_received, 0); @@ -741,7 +739,7 @@ mod tests { // Process the coinbase transaction let result = - managed_wallet.check_transaction(&coinbase_tx, context, &mut wallet, true).await; + managed_wallet.check_core_transaction(&coinbase_tx, context, &mut wallet, true).await; // Set synced_height to block where coinbase was received to trigger balance updates. managed_wallet.update_synced_height(block_height); @@ -830,7 +828,7 @@ mod tests { // Test with Mempool context let context = TransactionContext::Mempool; - let result = managed_wallet.check_transaction(&tx, context, &mut wallet, true).await; + let result = managed_wallet.check_core_transaction(&tx, context, &mut wallet, true).await; // Should be relevant assert!(result.is_relevant); @@ -877,7 +875,7 @@ mod tests { }; // First processing - should be marked as new - let result1 = managed_wallet.check_transaction(&tx, context, &mut wallet, true).await; + let result1 = managed_wallet.check_core_transaction(&tx, context, &mut wallet, true).await; assert!(result1.is_relevant, "Transaction should be relevant"); assert!( @@ -901,7 +899,7 @@ mod tests { ); // Second processing (simulating rescan) - should be marked as existing - let result2 = managed_wallet.check_transaction(&tx, context, &mut wallet, true).await; + let result2 = managed_wallet.check_core_transaction(&tx, context, &mut wallet, true).await; assert!(result2.is_relevant, "Transaction should still be relevant on rescan"); assert!( diff --git a/key-wallet/src/wallet/balance.rs b/key-wallet/src/wallet/balance.rs index 55d402482..550155e06 100644 --- a/key-wallet/src/wallet/balance.rs +++ b/key-wallet/src/wallet/balance.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; /// Wallet balance breakdown #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct WalletBalance { +pub struct WalletCoreBalance { /// Confirmed and mature balance (UTXOs with enough confirmations to be spendable) spendable: u64, /// Unconfirmed balance (UTXOs without confirmations) @@ -21,7 +21,7 @@ pub struct WalletBalance { locked: u64, } -impl WalletBalance { +impl WalletCoreBalance { /// Create a new wallet balance pub fn new(spendable: u64, unconfirmed: u64, immature: u64, locked: u64) -> Self { Self { @@ -58,7 +58,7 @@ impl WalletBalance { } } -impl Display for WalletBalance { +impl Display for WalletCoreBalance { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { write!( f, @@ -72,7 +72,7 @@ impl Display for WalletBalance { } } -impl AddAssign for WalletBalance { +impl AddAssign for WalletCoreBalance { fn add_assign(&mut self, other: Self) { self.spendable += other.spendable; self.unconfirmed += other.unconfirmed; @@ -87,7 +87,7 @@ mod tests { #[test] fn test_balance_creation_and_getters() { - let balance = WalletBalance::new(1000, 500, 100, 200); + let balance = WalletCoreBalance::new(1000, 500, 100, 200); assert_eq!(balance.spendable(), 1000); assert_eq!(balance.unconfirmed(), 500); assert_eq!(balance.immature(), 100); @@ -98,19 +98,19 @@ mod tests { #[test] #[should_panic(expected = "attempt to add with overflow")] fn test_balance_overflow() { - let balance = WalletBalance::new(u64::MAX, u64::MAX, u64::MAX, u64::MAX); + let balance = WalletCoreBalance::new(u64::MAX, u64::MAX, u64::MAX, u64::MAX); balance.total(); } #[test] fn test_balance_display() { - let zero = WalletBalance::default(); + let zero = WalletCoreBalance::default(); assert_eq!( zero.to_string(), "Spendable: 0, Unconfirmed: 0, Immature: 0, Locked: 0, Total: 0" ); - let balance = WalletBalance::new(1000, 500, 100, 200); + let balance = WalletCoreBalance::new(1000, 500, 100, 200); let display = balance.to_string(); assert_eq!( display, @@ -120,8 +120,8 @@ mod tests { #[test] fn test_balance_add_assign() { - let mut balance = WalletBalance::new(1000, 500, 50, 200); - let balance_add = WalletBalance::new(300, 100, 100, 50); + let mut balance = WalletCoreBalance::new(1000, 500, 50, 200); + let balance_add = WalletCoreBalance::new(300, 100, 100, 50); // Test adding actual balances balance += balance_add; assert_eq!(balance.spendable(), 1300); @@ -131,7 +131,7 @@ mod tests { assert_eq!(balance.total(), 2300); // Test adding zero balances let balance_before = balance; - balance += WalletBalance::default(); + balance += WalletCoreBalance::default(); assert_eq!(balance_before, balance); } } diff --git a/key-wallet/src/wallet/helper.rs b/key-wallet/src/wallet/helper.rs index 53821d176..8b214298b 100644 --- a/key-wallet/src/wallet/helper.rs +++ b/key-wallet/src/wallet/helper.rs @@ -838,11 +838,6 @@ impl Wallet { // Currently not retrieved via this helper None } - crate::transaction_checking::transaction_router::AccountTypeToCheck::PlatformPayment => { - // Platform Payment addresses are not used in Core chain transactions - // and xpubs are not retrieved via this helper - None - } } } } diff --git a/key-wallet/src/wallet/managed_wallet_info/helpers.rs b/key-wallet/src/wallet/managed_wallet_info/helpers.rs index e524b6884..31194e0d4 100644 --- a/key-wallet/src/wallet/managed_wallet_info/helpers.rs +++ b/key-wallet/src/wallet/managed_wallet_info/helpers.rs @@ -1,24 +1,26 @@ //! Helper methods for ManagedWalletInfo use super::ManagedWalletInfo; -use crate::account::ManagedAccount; +use crate::account::account_collection::PlatformPaymentAccountKey; +use crate::account::ManagedCoreAccount; +use crate::managed_account::managed_platform_account::ManagedPlatformAccount; use alloc::vec::Vec; impl ManagedWalletInfo { // BIP44 Account Helpers /// Get the first BIP44 managed account - pub fn first_bip44_managed_account(&self) -> Option<&ManagedAccount> { + pub fn first_bip44_managed_account(&self) -> Option<&ManagedCoreAccount> { self.bip44_managed_account_at_index(0) } /// Get the first BIP44 managed account (mutable) - pub fn first_bip44_managed_account_mut(&mut self) -> Option<&mut ManagedAccount> { + pub fn first_bip44_managed_account_mut(&mut self) -> Option<&mut ManagedCoreAccount> { self.bip44_managed_account_at_index_mut(0) } /// Get a BIP44 managed account at a specific index - pub fn bip44_managed_account_at_index(&self, index: u32) -> Option<&ManagedAccount> { + pub fn bip44_managed_account_at_index(&self, index: u32) -> Option<&ManagedCoreAccount> { self.accounts.standard_bip44_accounts.get(&index) } @@ -26,24 +28,24 @@ impl ManagedWalletInfo { pub fn bip44_managed_account_at_index_mut( &mut self, index: u32, - ) -> Option<&mut ManagedAccount> { + ) -> Option<&mut ManagedCoreAccount> { self.accounts.standard_bip44_accounts.get_mut(&index) } // BIP32 Account Helpers /// Get the first BIP32 managed account - pub fn first_bip32_managed_account(&self) -> Option<&ManagedAccount> { + pub fn first_bip32_managed_account(&self) -> Option<&ManagedCoreAccount> { self.bip32_managed_account_at_index(0) } /// Get the first BIP32 managed account (mutable) - pub fn first_bip32_managed_account_mut(&mut self) -> Option<&mut ManagedAccount> { + pub fn first_bip32_managed_account_mut(&mut self) -> Option<&mut ManagedCoreAccount> { self.bip32_managed_account_at_index_mut(0) } /// Get a BIP32 managed account at a specific index - pub fn bip32_managed_account_at_index(&self, index: u32) -> Option<&ManagedAccount> { + pub fn bip32_managed_account_at_index(&self, index: u32) -> Option<&ManagedCoreAccount> { self.accounts.standard_bip32_accounts.get(&index) } @@ -51,24 +53,24 @@ impl ManagedWalletInfo { pub fn bip32_managed_account_at_index_mut( &mut self, index: u32, - ) -> Option<&mut ManagedAccount> { + ) -> Option<&mut ManagedCoreAccount> { self.accounts.standard_bip32_accounts.get_mut(&index) } // CoinJoin Account Helpers /// Get the first CoinJoin managed account - pub fn first_coinjoin_managed_account(&self) -> Option<&ManagedAccount> { + pub fn first_coinjoin_managed_account(&self) -> Option<&ManagedCoreAccount> { self.coinjoin_managed_account_at_index(0) } /// Get the first CoinJoin managed account (mutable) - pub fn first_coinjoin_managed_account_mut(&mut self) -> Option<&mut ManagedAccount> { + pub fn first_coinjoin_managed_account_mut(&mut self) -> Option<&mut ManagedCoreAccount> { self.coinjoin_managed_account_at_index_mut(0) } /// Get a CoinJoin managed account at a specific index - pub fn coinjoin_managed_account_at_index(&self, index: u32) -> Option<&ManagedAccount> { + pub fn coinjoin_managed_account_at_index(&self, index: u32) -> Option<&ManagedCoreAccount> { self.accounts.coinjoin_accounts.get(&index) } @@ -76,19 +78,19 @@ impl ManagedWalletInfo { pub fn coinjoin_managed_account_at_index_mut( &mut self, index: u32, - ) -> Option<&mut ManagedAccount> { + ) -> Option<&mut ManagedCoreAccount> { self.accounts.coinjoin_accounts.get_mut(&index) } // TopUp Account Helpers /// Get the first TopUp managed account - pub fn first_topup_managed_account(&self) -> Option<&ManagedAccount> { + pub fn first_topup_managed_account(&self) -> Option<&ManagedCoreAccount> { self.accounts.identity_topup.values().next() } /// Get the first TopUp managed account (mutable) - pub fn first_topup_managed_account_mut(&mut self) -> Option<&mut ManagedAccount> { + pub fn first_topup_managed_account_mut(&mut self) -> Option<&mut ManagedCoreAccount> { self.accounts.identity_topup.values_mut().next() } @@ -96,7 +98,7 @@ impl ManagedWalletInfo { pub fn topup_managed_account_at_registration_index( &self, registration_index: u32, - ) -> Option<&ManagedAccount> { + ) -> Option<&ManagedCoreAccount> { self.accounts.identity_topup.get(®istration_index) } @@ -104,94 +106,186 @@ impl ManagedWalletInfo { pub fn topup_managed_account_at_registration_index_mut( &mut self, registration_index: u32, - ) -> Option<&mut ManagedAccount> { + ) -> Option<&mut ManagedCoreAccount> { self.accounts.identity_topup.get_mut(®istration_index) } // Identity Registration Account Helper /// Get the identity registration managed account - pub fn identity_registration_managed_account(&self) -> Option<&ManagedAccount> { + pub fn identity_registration_managed_account(&self) -> Option<&ManagedCoreAccount> { self.accounts.identity_registration.as_ref() } /// Get the identity registration managed account (mutable) - pub fn identity_registration_managed_account_mut(&mut self) -> Option<&mut ManagedAccount> { + pub fn identity_registration_managed_account_mut(&mut self) -> Option<&mut ManagedCoreAccount> { self.accounts.identity_registration.as_mut() } // Identity TopUp Not Bound Account Helper /// Get the identity top-up not bound managed account - pub fn identity_topup_not_bound_managed_account(&self) -> Option<&ManagedAccount> { + pub fn identity_topup_not_bound_managed_account(&self) -> Option<&ManagedCoreAccount> { self.accounts.identity_topup_not_bound.as_ref() } /// Get the identity top-up not bound managed account (mutable) - pub fn identity_topup_not_bound_managed_account_mut(&mut self) -> Option<&mut ManagedAccount> { + pub fn identity_topup_not_bound_managed_account_mut( + &mut self, + ) -> Option<&mut ManagedCoreAccount> { self.accounts.identity_topup_not_bound.as_mut() } // Identity Invitation Account Helper /// Get the identity invitation managed account - pub fn identity_invitation_managed_account(&self) -> Option<&ManagedAccount> { + pub fn identity_invitation_managed_account(&self) -> Option<&ManagedCoreAccount> { self.accounts.identity_invitation.as_ref() } /// Get the identity invitation managed account (mutable) - pub fn identity_invitation_managed_account_mut(&mut self) -> Option<&mut ManagedAccount> { + pub fn identity_invitation_managed_account_mut(&mut self) -> Option<&mut ManagedCoreAccount> { self.accounts.identity_invitation.as_mut() } // Provider Voting Keys Account Helper /// Get the provider voting keys managed account - pub fn provider_voting_keys_managed_account(&self) -> Option<&ManagedAccount> { + pub fn provider_voting_keys_managed_account(&self) -> Option<&ManagedCoreAccount> { self.accounts.provider_voting_keys.as_ref() } /// Get the provider voting keys managed account (mutable) - pub fn provider_voting_keys_managed_account_mut(&mut self) -> Option<&mut ManagedAccount> { + pub fn provider_voting_keys_managed_account_mut(&mut self) -> Option<&mut ManagedCoreAccount> { self.accounts.provider_voting_keys.as_mut() } // Provider Owner Keys Account Helper /// Get the provider owner keys managed account - pub fn provider_owner_keys_managed_account(&self) -> Option<&ManagedAccount> { + pub fn provider_owner_keys_managed_account(&self) -> Option<&ManagedCoreAccount> { self.accounts.provider_owner_keys.as_ref() } /// Get the provider owner keys managed account (mutable) - pub fn provider_owner_keys_managed_account_mut(&mut self) -> Option<&mut ManagedAccount> { + pub fn provider_owner_keys_managed_account_mut(&mut self) -> Option<&mut ManagedCoreAccount> { self.accounts.provider_owner_keys.as_mut() } // Provider Operator Keys Account Helper /// Get the provider operator keys managed account - pub fn provider_operator_keys_managed_account(&self) -> Option<&ManagedAccount> { + pub fn provider_operator_keys_managed_account(&self) -> Option<&ManagedCoreAccount> { self.accounts.provider_operator_keys.as_ref() } /// Get the provider operator keys managed account (mutable) - pub fn provider_operator_keys_managed_account_mut(&mut self) -> Option<&mut ManagedAccount> { + pub fn provider_operator_keys_managed_account_mut( + &mut self, + ) -> Option<&mut ManagedCoreAccount> { self.accounts.provider_operator_keys.as_mut() } // Provider Platform Keys Account Helper /// Get the provider platform keys managed account - pub fn provider_platform_keys_managed_account(&self) -> Option<&ManagedAccount> { + pub fn provider_platform_keys_managed_account(&self) -> Option<&ManagedCoreAccount> { self.accounts.provider_platform_keys.as_ref() } /// Get the provider platform keys managed account (mutable) - pub fn provider_platform_keys_managed_account_mut(&mut self) -> Option<&mut ManagedAccount> { + pub fn provider_platform_keys_managed_account_mut( + &mut self, + ) -> Option<&mut ManagedCoreAccount> { self.accounts.provider_platform_keys.as_mut() } + // Platform Payment Account Helpers (DIP-17) + + /// Get the first platform payment managed account + /// + /// Returns the platform payment account with the lowest account index and key_class 0. + pub fn first_platform_payment_managed_account(&self) -> Option<&ManagedPlatformAccount> { + self.platform_payment_managed_account(0, 0) + } + + /// Get the first platform payment managed account (mutable) + /// + /// Returns the platform payment account with account index 0 and key_class 0. + pub fn first_platform_payment_managed_account_mut( + &mut self, + ) -> Option<&mut ManagedPlatformAccount> { + self.platform_payment_managed_account_mut(0, 0) + } + + /// Get a platform payment managed account by account index (with default key_class 0) + pub fn platform_payment_managed_account_at_index( + &self, + account_index: u32, + ) -> Option<&ManagedPlatformAccount> { + self.platform_payment_managed_account(account_index, 0) + } + + /// Get a platform payment managed account by account index (mutable, with default key_class 0) + pub fn platform_payment_managed_account_at_index_mut( + &mut self, + account_index: u32, + ) -> Option<&mut ManagedPlatformAccount> { + self.platform_payment_managed_account_mut(account_index, 0) + } + + /// Get a platform payment managed account by account index and key class + pub fn platform_payment_managed_account( + &self, + account_index: u32, + key_class: u32, + ) -> Option<&ManagedPlatformAccount> { + let key = PlatformPaymentAccountKey { + account: account_index, + key_class, + }; + self.accounts.platform_payment_accounts.get(&key) + } + + /// Get a platform payment managed account by account index and key class (mutable) + pub fn platform_payment_managed_account_mut( + &mut self, + account_index: u32, + key_class: u32, + ) -> Option<&mut ManagedPlatformAccount> { + let key = PlatformPaymentAccountKey { + account: account_index, + key_class, + }; + self.accounts.platform_payment_accounts.get_mut(&key) + } + + /// Get all platform payment managed accounts + pub fn all_platform_payment_managed_accounts(&self) -> Vec<&ManagedPlatformAccount> { + self.accounts.platform_payment_accounts.values().collect() + } + + /// Get all platform payment managed accounts (mutable) + pub fn all_platform_payment_managed_accounts_mut( + &mut self, + ) -> Vec<&mut ManagedPlatformAccount> { + self.accounts.platform_payment_accounts.values_mut().collect() + } + + /// Get the number of platform payment accounts + pub fn platform_payment_account_count(&self) -> usize { + self.accounts.platform_payment_accounts.len() + } + + /// Check if a platform payment account exists + pub fn has_platform_payment_account(&self, account_index: u32, key_class: u32) -> bool { + let key = PlatformPaymentAccountKey { + account: account_index, + key_class, + }; + self.accounts.platform_payment_accounts.contains_key(&key) + } + // General Helpers /// Check if the wallet has any accounts @@ -205,7 +299,7 @@ impl ManagedWalletInfo { } /// Get all accounts - pub fn all_managed_accounts(&self) -> Vec<&ManagedAccount> { + pub fn all_managed_accounts(&self) -> Vec<&ManagedCoreAccount> { self.accounts.all_accounts() } } diff --git a/key-wallet/src/wallet/managed_wallet_info/managed_accounts.rs b/key-wallet/src/wallet/managed_wallet_info/managed_accounts.rs index a90bb300d..390d7d400 100644 --- a/key-wallet/src/wallet/managed_wallet_info/managed_accounts.rs +++ b/key-wallet/src/wallet/managed_wallet_info/managed_accounts.rs @@ -7,7 +7,7 @@ use super::{managed_account_operations::ManagedAccountOperations, ManagedWalletI use crate::account::BLSAccount; #[cfg(feature = "eddsa")] use crate::account::EdDSAAccount; -use crate::account::{Account, AccountType, ManagedAccount}; +use crate::account::{Account, AccountType, ManagedCoreAccount}; use crate::bip32::ExtendedPubKey; use crate::error::{Error, Result}; use crate::wallet::{Wallet, WalletType}; @@ -42,7 +42,7 @@ impl ManagedAccountOperations for ManagedWalletInfo { })?; // Create the ManagedAccount from the Account - let managed_account = ManagedAccount::from_account(account); + let managed_account = ManagedCoreAccount::from_account(account); // Check if managed account already exists if self.accounts.contains_managed_account_type(managed_account.managed_type()) { @@ -53,7 +53,7 @@ impl ManagedAccountOperations for ManagedWalletInfo { } // Insert into the collection - self.accounts.insert(managed_account); + self.accounts.insert(managed_account)?; Ok(()) } @@ -118,7 +118,7 @@ impl ManagedAccountOperations for ManagedWalletInfo { let account = Account::new(None, account_type, account_xpub, self.network)?; // Create the ManagedAccount from the Account - let managed_account = ManagedAccount::from_account(&account); + let managed_account = ManagedCoreAccount::from_account(&account); // Check if managed account already exists if self.accounts.contains_managed_account_type(managed_account.managed_type()) { @@ -129,7 +129,7 @@ impl ManagedAccountOperations for ManagedWalletInfo { } // Insert into the collection - self.accounts.insert(managed_account); + self.accounts.insert(managed_account)?; Ok(()) } @@ -163,7 +163,7 @@ impl ManagedAccountOperations for ManagedWalletInfo { })?; // Create the ManagedAccount from the BLS Account - let managed_account = ManagedAccount::from_bls_account(bls_account); + let managed_account = ManagedCoreAccount::from_bls_account(bls_account); // Check if managed account already exists if self.accounts.contains_managed_account_type(managed_account.managed_type()) { @@ -174,7 +174,7 @@ impl ManagedAccountOperations for ManagedWalletInfo { } // Insert into the collection - self.accounts.insert(managed_account); + self.accounts.insert(managed_account)?; Ok(()) } @@ -236,7 +236,7 @@ impl ManagedAccountOperations for ManagedWalletInfo { BLSAccount::from_public_key_bytes(None, account_type, bls_public_key, self.network)?; // Create the ManagedAccount from the BLS Account - let managed_account = ManagedAccount::from_bls_account(&bls_account); + let managed_account = ManagedCoreAccount::from_bls_account(&bls_account); // Check if managed account already exists if self.accounts.contains_managed_account_type(managed_account.managed_type()) { @@ -247,7 +247,7 @@ impl ManagedAccountOperations for ManagedWalletInfo { } // Insert into the collection - self.accounts.insert(managed_account); + self.accounts.insert(managed_account)?; Ok(()) } @@ -282,7 +282,7 @@ impl ManagedAccountOperations for ManagedWalletInfo { })?; // Create the ManagedAccount from the EdDSA Account - let managed_account = ManagedAccount::from_eddsa_account(eddsa_account); + let managed_account = ManagedCoreAccount::from_eddsa_account(eddsa_account); // Check if managed account already exists if self.accounts.contains_managed_account_type(managed_account.managed_type()) { @@ -293,7 +293,7 @@ impl ManagedAccountOperations for ManagedWalletInfo { } // Insert into the collection - self.accounts.insert(managed_account); + self.accounts.insert(managed_account)?; Ok(()) } @@ -359,7 +359,7 @@ impl ManagedAccountOperations for ManagedWalletInfo { )?; // Create the ManagedAccount from the EdDSA Account - let managed_account = ManagedAccount::from_eddsa_account(&eddsa_account); + let managed_account = ManagedCoreAccount::from_eddsa_account(&eddsa_account); // Check if managed account already exists if self.accounts.contains_managed_account_type(managed_account.managed_type()) { @@ -370,7 +370,7 @@ impl ManagedAccountOperations for ManagedWalletInfo { } // Insert into the collection - self.accounts.insert(managed_account); + self.accounts.insert(managed_account)?; Ok(()) } } diff --git a/key-wallet/src/wallet/managed_wallet_info/mod.rs b/key-wallet/src/wallet/managed_wallet_info/mod.rs index 363b3d7e4..7c49fb021 100644 --- a/key-wallet/src/wallet/managed_wallet_info/mod.rs +++ b/key-wallet/src/wallet/managed_wallet_info/mod.rs @@ -14,7 +14,7 @@ pub mod wallet_info_interface; pub use managed_account_operations::ManagedAccountOperations; -use super::balance::WalletBalance; +use super::balance::WalletCoreBalance; use super::metadata::WalletMetadata; use crate::account::ManagedAccountCollection; use crate::Network; @@ -43,8 +43,8 @@ pub struct ManagedWalletInfo { pub metadata: WalletMetadata, /// All managed accounts pub accounts: ManagedAccountCollection, - /// Cached wallet balance - should be updated when accounts change - pub balance: WalletBalance, + /// Cached wallet core balance - should be updated when accounts change + pub balance: WalletCoreBalance, } impl ManagedWalletInfo { @@ -57,7 +57,7 @@ impl ManagedWalletInfo { description: None, metadata: WalletMetadata::default(), accounts: ManagedAccountCollection::new(), - balance: WalletBalance::default(), + balance: WalletCoreBalance::default(), } } @@ -70,7 +70,7 @@ impl ManagedWalletInfo { description: None, metadata: WalletMetadata::default(), accounts: ManagedAccountCollection::new(), - balance: WalletBalance::default(), + balance: WalletCoreBalance::default(), } } @@ -83,7 +83,7 @@ impl ManagedWalletInfo { description: None, metadata: WalletMetadata::default(), accounts: ManagedAccountCollection::from_account_collection(&wallet.accounts), - balance: WalletBalance::default(), + balance: WalletCoreBalance::default(), } } diff --git a/key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs b/key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs index 5f2a84955..98c9b3a0a 100644 --- a/key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs +++ b/key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs @@ -12,7 +12,7 @@ use crate::wallet::managed_wallet_info::transaction_building::{ }; use crate::wallet::managed_wallet_info::TransactionRecord; use crate::wallet::ManagedWalletInfo; -use crate::{Network, Utxo, Wallet, WalletBalance}; +use crate::{Network, Utxo, Wallet, WalletCoreBalance}; use alloc::collections::BTreeSet; use alloc::vec::Vec; use dashcore::prelude::CoreBlockHeight; @@ -71,7 +71,7 @@ pub trait WalletInfoInterface: Sized + WalletTransactionChecker + ManagedAccount fn get_spendable_utxos(&self) -> BTreeSet<&Utxo>; /// Get the wallet balance - fn balance(&self) -> WalletBalance; + fn balance(&self) -> WalletCoreBalance; /// Update the wallet balance fn update_balance(&mut self); @@ -185,12 +185,12 @@ impl WalletInfoInterface for ManagedWalletInfo { self.utxos().into_iter().filter(|utxo| utxo.is_spendable(self.synced_height())).collect() } - fn balance(&self) -> WalletBalance { + fn balance(&self) -> WalletCoreBalance { self.balance } fn update_balance(&mut self) { - let mut balance = WalletBalance::default(); + let mut balance = WalletCoreBalance::default(); let synced_height = self.synced_height(); for account in self.accounts.all_accounts_mut() { account.update_balance(synced_height); diff --git a/key-wallet/src/wallet/mod.rs b/key-wallet/src/wallet/mod.rs index 91226c98e..f7a30e81e 100644 --- a/key-wallet/src/wallet/mod.rs +++ b/key-wallet/src/wallet/mod.rs @@ -15,7 +15,7 @@ pub mod metadata; pub mod root_extended_keys; pub mod stats; -pub use self::balance::WalletBalance; +pub use self::balance::WalletCoreBalance; pub use self::managed_wallet_info::ManagedWalletInfo; use self::root_extended_keys::{RootExtendedPrivKey, RootExtendedPubKey}; use crate::account::account_collection::AccountCollection;