Skip to content
Merged
45 changes: 45 additions & 0 deletions key-wallet-ffi/include/key_wallet_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -3820,6 +3820,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
Comment on lines +4095 to +4101

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use the C enum identifiers in these notes to avoid FFI confusion.

The notes currently list Rust variant names, but C callers see FFIAccountType constants. Using the C identifiers makes the guidance unambiguous for FFI consumers.

📝 Suggested wording updates (apply to all three note blocks)
- * - `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
+ * - `PLATFORM_PAYMENT`: Use `wallet_add_platform_payment_account()` instead
+ * - `DASHPAY_RECEIVING_FUNDS`: Use `wallet_add_dashpay_receiving_account()` instead
+ * - `DASHPAY_EXTERNAL_ACCOUNT`: Use `wallet_add_dashpay_external_account_with_xpub_bytes()` instead

Also applies to: 3877-3883, 3903-3909

🤖 Prompt for AI Agents
In `@key-wallet-ffi/include/key_wallet_ffi.h` around lines 3823 - 3829, Update the
three NOTE comment blocks that currently reference Rust enum variant names and
replace those names with the corresponding C FFI enum identifiers
(FFIAccountType constants) so FFI consumers aren’t confused; specifically,
change the Rust variant mentions in the block that references
wallet_add_platform_payment_account(), wallet_add_dashpay_receiving_account(),
and wallet_add_dashpay_external_account_with_xpub_bytes() to use the appropriate
FFIAccountType constants (FFIAccountType::PlatformPayment,
FFIAccountType::DashpayReceivingFunds, FFIAccountType::DashpayExternalAccount or
the exact C-style names used in the header) and apply the same replacement to
the other two note blocks referenced in the comment.

*/

FFIAccountResult wallet_add_account(FFIWallet *wallet,
Expand Down Expand Up @@ -3867,6 +3874,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,
Expand All @@ -3886,6 +3900,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,
Expand All @@ -3894,6 +3915,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)
;
Comment on lines +4190 to +4212

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, find the Rust implementation of wallet_add_platform_payment_account
rg -n "wallet_add_platform_payment_account" -g '*.rs' -A 20

Repository: dashpay/rust-dashcore

Length of output: 10569


🏁 Script executed:

#!/bin/bash
# Find AccountType definition and PlatformPayment variant
rg -n "enum AccountType|impl.*AccountType" -g '*.rs' -A 30 | head -100

Repository: dashpay/rust-dashcore

Length of output: 9121


🏁 Script executed:

#!/bin/bash
# Find AccountType enum definition with PlatformPayment
rg -n "pub enum AccountType" -g '*.rs' -A 50 | grep -A 50 "key-wallet/src/account"

Repository: dashpay/rust-dashcore

Length of output: 8910


🏁 Script executed:

#!/bin/bash
# Search for PlatformPayment in account type file
rg -n "PlatformPayment" key-wallet/src/account/account_type.rs -B 2 -A 5

Repository: dashpay/rust-dashcore

Length of output: 1858


🏁 Script executed:

#!/bin/bash
# Find the derivation path implementation for PlatformPayment
rg -n "DIP-17: m/9'" -B 5 -A 20 key-wallet/src/account/account_type.rs

Repository: dashpay/rust-dashcore

Length of output: 1264


🏁 Script executed:

#!/bin/bash
# Check the bip32 library usage - from_hardened_idx converts raw to hardened
# Let's search for how ChildNumber is used in the codebase
rg -n "from_hardened_idx" -B 2 -A 2 key-wallet/src/ | head -40

Repository: dashpay/rust-dashcore

Length of output: 3599


Clarify that account_index and key_class expect raw (non-hardened) values.

The Rust implementation applies hardening internally via ChildNumber::from_hardened_idx(), so callers must pass raw indices (e.g., 0, 1, 2). The current FFI header documentation stating "(hardened)" is misleading and could cause callers to incorrectly apply hardening twice, deriving wrong paths. Update the docs to say "pass raw index; hardening applied internally" or similar.

🤖 Prompt for AI Agents
In `@key-wallet-ffi/include/key_wallet_ffi.h` around lines 3918 - 3940, Update the
documentation for wallet_add_platform_payment_account to clarify that
account_index and key_class are raw (non-hardened) indices and that hardening is
applied internally (via ChildNumber::from_hardened_idx in the Rust
implementation); specifically replace or augment the “(hardened)” text for
account_index and key_class with wording like “pass raw index (e.g., 0, 1, 2);
hardening is applied internally” so callers do not double-harden the derivation
path for FFIWallet -> wallet_add_platform_payment_account returning
FFIAccountResult.


/*
Describe the wallet manager for a given network and return a newly
allocated C string.
Expand Down
18 changes: 14 additions & 4 deletions key-wallet-ffi/src/address_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand Down
8 changes: 4 additions & 4 deletions key-wallet-ffi/src/managed_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,25 @@ use crate::wallet_manager::FFIWalletManager;
use crate::FFINetwork;
use key_wallet::account::account_collection::DashpayAccountKey;
use key_wallet::managed_account::address_pool::AddressPool;
use key_wallet::managed_account::ManagedAccount;
use key_wallet::managed_account::ManagedCoreAccount;
use key_wallet::AccountType;

/// Opaque managed account handle that wraps ManagedAccount
pub struct FFIManagedAccount {
/// The underlying managed account
pub(crate) account: Arc<ManagedAccount>,
pub(crate) account: Arc<ManagedCoreAccount>,
}

impl FFIManagedAccount {
/// Create a new FFI managed account handle
pub fn new(account: &ManagedAccount) -> Self {
pub fn new(account: &ManagedCoreAccount) -> Self {
FFIManagedAccount {
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()
}
}
Expand Down
10 changes: 6 additions & 4 deletions key-wallet-ffi/src/managed_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1023,7 +1025,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();

Expand All @@ -1046,7 +1048,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));
Expand Down
2 changes: 1 addition & 1 deletion key-wallet-ffi/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
51 changes: 15 additions & 36 deletions key-wallet-ffi/src/transaction_checking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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() {
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
} => {
Expand All @@ -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 {
Expand All @@ -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,
} => {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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,
} => {
Expand All @@ -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,
} => {
Expand All @@ -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;
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions key-wallet-ffi/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ pub struct FFIBalance {
pub total: u64,
}

impl From<key_wallet::WalletBalance> for FFIBalance {
fn from(balance: key_wallet::WalletBalance) -> Self {
impl From<key_wallet::WalletCoreBalance> for FFIBalance {
fn from(balance: key_wallet::WalletCoreBalance) -> Self {
FFIBalance {
confirmed: balance.spendable(),
unconfirmed: balance.unconfirmed(),
Expand Down
Loading
Loading