diff --git a/key-wallet-ffi/include/key_wallet_ffi.h b/key-wallet-ffi/include/key_wallet_ffi.h index 2804cebb4..4353e5117 100644 --- a/key-wallet-ffi/include/key_wallet_ffi.h +++ b/key-wallet-ffi/include/key_wallet_ffi.h @@ -92,6 +92,14 @@ typedef enum { Platform Payment address (DIP-17) - Path: m/9'/5'/17'/account'/key_class'/index */ PLATFORM_PAYMENT = 13, + /* + Asset lock address top-up funding (subfeature 4) + */ + ASSET_LOCK_ADDRESS_TOP_UP = 14, + /* + Asset lock shielded address top-up funding (subfeature 5) + */ + ASSET_LOCK_SHIELDED_ADDRESS_TOP_UP = 15, } FFIAccountType; /* diff --git a/key-wallet-ffi/src/address_pool.rs b/key-wallet-ffi/src/address_pool.rs index 98ce14942..ef222a85e 100644 --- a/key-wallet-ffi/src/address_pool.rs +++ b/key-wallet-ffi/src/address_pool.rs @@ -45,6 +45,10 @@ fn get_managed_account_by_type<'a>( collection.identity_topup_not_bound.as_ref() } AccountType::IdentityInvitation => collection.identity_invitation.as_ref(), + AccountType::AssetLockAddressTopUp => collection.asset_lock_address_topup.as_ref(), + AccountType::AssetLockShieldedAddressTopUp => { + collection.asset_lock_shielded_address_topup.as_ref() + } AccountType::ProviderVotingKeys => collection.provider_voting_keys.as_ref(), AccountType::ProviderOwnerKeys => collection.provider_owner_keys.as_ref(), AccountType::ProviderOperatorKeys => collection.provider_operator_keys.as_ref(), @@ -94,6 +98,10 @@ fn get_managed_account_by_type_mut<'a>( collection.identity_topup_not_bound.as_mut() } AccountType::IdentityInvitation => collection.identity_invitation.as_mut(), + AccountType::AssetLockAddressTopUp => collection.asset_lock_address_topup.as_mut(), + AccountType::AssetLockShieldedAddressTopUp => { + collection.asset_lock_shielded_address_topup.as_mut() + } AccountType::ProviderVotingKeys => collection.provider_voting_keys.as_mut(), AccountType::ProviderOwnerKeys => collection.provider_owner_keys.as_mut(), AccountType::ProviderOperatorKeys => collection.provider_operator_keys.as_mut(), @@ -738,6 +746,20 @@ pub unsafe extern "C" fn managed_wallet_mark_address_used( } } } + if !found { + if let Some(account) = &mut collection.asset_lock_address_topup { + if account.mark_address_used(&address) { + found = true; + } + } + } + if !found { + if let Some(account) = &mut collection.asset_lock_shielded_address_topup { + if account.mark_address_used(&address) { + found = true; + } + } + } if !found { if let Some(account) = &mut collection.provider_voting_keys { if account.mark_address_used(&address) { diff --git a/key-wallet-ffi/src/managed_account.rs b/key-wallet-ffi/src/managed_account.rs index dafd3f907..5dcb0267f 100644 --- a/key-wallet-ffi/src/managed_account.rs +++ b/key-wallet-ffi/src/managed_account.rs @@ -244,6 +244,12 @@ pub unsafe extern "C" fn managed_wallet_get_account( managed_collection.identity_topup_not_bound.as_ref() } AccountType::IdentityInvitation => managed_collection.identity_invitation.as_ref(), + AccountType::AssetLockAddressTopUp => { + managed_collection.asset_lock_address_topup.as_ref() + } + AccountType::AssetLockShieldedAddressTopUp => { + managed_collection.asset_lock_shielded_address_topup.as_ref() + } AccountType::ProviderVotingKeys => managed_collection.provider_voting_keys.as_ref(), AccountType::ProviderOwnerKeys => managed_collection.provider_owner_keys.as_ref(), AccountType::ProviderOperatorKeys => managed_collection.provider_operator_keys.as_ref(), @@ -555,6 +561,8 @@ pub unsafe extern "C" fn managed_core_account_get_account_type( FFIAccountType::IdentityTopUpNotBoundToIdentity } AccountType::IdentityInvitation => FFIAccountType::IdentityInvitation, + AccountType::AssetLockAddressTopUp => FFIAccountType::AssetLockAddressTopUp, + AccountType::AssetLockShieldedAddressTopUp => FFIAccountType::AssetLockShieldedAddressTopUp, AccountType::ProviderVotingKeys => FFIAccountType::ProviderVotingKeys, AccountType::ProviderOwnerKeys => FFIAccountType::ProviderOwnerKeys, AccountType::ProviderOperatorKeys => FFIAccountType::ProviderOperatorKeys, @@ -1025,6 +1033,12 @@ pub unsafe extern "C" fn managed_core_account_get_address_pool( ManagedAccountType::IdentityInvitation { addresses, } => addresses, + ManagedAccountType::AssetLockAddressTopUp { + addresses, + } => addresses, + ManagedAccountType::AssetLockShieldedAddressTopUp { + addresses, + } => addresses, ManagedAccountType::ProviderVotingKeys { addresses, } => addresses, diff --git a/key-wallet-ffi/src/transaction_checking.rs b/key-wallet-ffi/src/transaction_checking.rs index af1b6b21b..4c7ccc1c8 100644 --- a/key-wallet-ffi/src/transaction_checking.rs +++ b/key-wallet-ffi/src/transaction_checking.rs @@ -348,6 +348,40 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ffi_accounts.push(ffi_match); continue; } + CoreAccountTypeMatch::AssetLockAddressTopUp { + involved_addresses, + } => { + let ffi_match = FFIAccountMatch { + account_type: 14, // AssetLockAddressTopUp + account_index: 0, + 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; + } + CoreAccountTypeMatch::AssetLockShieldedAddressTopUp { + involved_addresses, + } => { + let ffi_match = FFIAccountMatch { + account_type: 15, // AssetLockShieldedAddressTopUp + account_index: 0, + 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; + } CoreAccountTypeMatch::ProviderVotingKeys { involved_addresses, } => { diff --git a/key-wallet-ffi/src/types.rs b/key-wallet-ffi/src/types.rs index bb85cda0d..a3007b156 100644 --- a/key-wallet-ffi/src/types.rs +++ b/key-wallet-ffi/src/types.rs @@ -190,6 +190,10 @@ pub enum FFIAccountType { DashpayExternalAccount = 12, /// Platform Payment address (DIP-17) - Path: m/9'/5'/17'/account'/key_class'/index PlatformPayment = 13, + /// Asset lock address top-up funding (subfeature 4) + AssetLockAddressTopUp = 14, + /// Asset lock shielded address top-up funding (subfeature 5) + AssetLockShieldedAddressTopUp = 15, } impl FFIAccountType { @@ -220,6 +224,10 @@ impl FFIAccountType { key_wallet::AccountType::IdentityTopUpNotBoundToIdentity } FFIAccountType::IdentityInvitation => key_wallet::AccountType::IdentityInvitation, + FFIAccountType::AssetLockAddressTopUp => key_wallet::AccountType::AssetLockAddressTopUp, + FFIAccountType::AssetLockShieldedAddressTopUp => { + key_wallet::AccountType::AssetLockShieldedAddressTopUp + } FFIAccountType::ProviderVotingKeys => key_wallet::AccountType::ProviderVotingKeys, FFIAccountType::ProviderOwnerKeys => key_wallet::AccountType::ProviderOwnerKeys, FFIAccountType::ProviderOperatorKeys => key_wallet::AccountType::ProviderOperatorKeys, @@ -299,6 +307,12 @@ impl FFIAccountType { key_wallet::AccountType::IdentityInvitation => { (FFIAccountType::IdentityInvitation, 0, None) } + key_wallet::AccountType::AssetLockAddressTopUp => { + (FFIAccountType::AssetLockAddressTopUp, 0, None) + } + key_wallet::AccountType::AssetLockShieldedAddressTopUp => { + (FFIAccountType::AssetLockShieldedAddressTopUp, 0, None) + } key_wallet::AccountType::ProviderVotingKeys => { (FFIAccountType::ProviderVotingKeys, 0, None) } diff --git a/key-wallet-manager/tests/integration_test.rs b/key-wallet-manager/tests/integration_test.rs index 8a9a7014f..3c1700405 100644 --- a/key-wallet-manager/tests/integration_test.rs +++ b/key-wallet-manager/tests/integration_test.rs @@ -60,10 +60,10 @@ fn test_account_management() { ); assert!(result.is_ok()); - // Get accounts from wallet - Default creates 9 accounts (including PlatformPayment), plus the one we added + // Get accounts from wallet - Default creates 11 accounts (including PlatformPayment), plus the one we added let accounts = manager.get_accounts(&wallet_id); assert!(accounts.is_ok()); - assert_eq!(accounts.unwrap().len(), 10); // 9 from Default + 1 we added + assert_eq!(accounts.unwrap().len(), 12); // 11 from Default + 1 we added } #[test] diff --git a/key-wallet/src/account/account_collection.rs b/key-wallet/src/account/account_collection.rs index eb9dbaaad..5a2a2c566 100644 --- a/key-wallet/src/account/account_collection.rs +++ b/key-wallet/src/account/account_collection.rs @@ -58,6 +58,10 @@ pub struct AccountCollection { pub identity_topup_not_bound: Option, /// Identity invitation account (optional) pub identity_invitation: Option, + /// Asset lock address top-up account (optional) + pub asset_lock_address_topup: Option, + /// Asset lock shielded address top-up account (optional) + pub asset_lock_shielded_address_topup: Option, /// Provider voting keys (optional) pub provider_voting_keys: Option, /// Provider owner keys (optional) @@ -87,6 +91,8 @@ impl AccountCollection { identity_topup: BTreeMap::new(), identity_topup_not_bound: None, identity_invitation: None, + asset_lock_address_topup: None, + asset_lock_shielded_address_topup: None, provider_voting_keys: None, provider_owner_keys: None, #[cfg(feature = "bls")] @@ -135,6 +141,12 @@ impl AccountCollection { AccountType::IdentityInvitation => { self.identity_invitation = Some(account); } + AccountType::AssetLockAddressTopUp => { + self.asset_lock_address_topup = Some(account); + } + AccountType::AssetLockShieldedAddressTopUp => { + self.asset_lock_shielded_address_topup = Some(account); + } AccountType::ProviderVotingKeys => { self.provider_voting_keys = Some(account); } @@ -230,6 +242,10 @@ impl AccountCollection { } => self.identity_topup.contains_key(registration_index), AccountType::IdentityTopUpNotBoundToIdentity => self.identity_topup_not_bound.is_some(), AccountType::IdentityInvitation => self.identity_invitation.is_some(), + AccountType::AssetLockAddressTopUp => self.asset_lock_address_topup.is_some(), + AccountType::AssetLockShieldedAddressTopUp => { + self.asset_lock_shielded_address_topup.is_some() + } AccountType::ProviderVotingKeys => self.provider_voting_keys.is_some(), AccountType::ProviderOwnerKeys => self.provider_owner_keys.is_some(), #[cfg(feature = "bls")] @@ -299,6 +315,10 @@ impl AccountCollection { } => self.identity_topup.get(®istration_index), AccountType::IdentityTopUpNotBoundToIdentity => self.identity_topup_not_bound.as_ref(), AccountType::IdentityInvitation => self.identity_invitation.as_ref(), + AccountType::AssetLockAddressTopUp => self.asset_lock_address_topup.as_ref(), + AccountType::AssetLockShieldedAddressTopUp => { + self.asset_lock_shielded_address_topup.as_ref() + } AccountType::ProviderVotingKeys => self.provider_voting_keys.as_ref(), AccountType::ProviderOwnerKeys => self.provider_owner_keys.as_ref(), AccountType::ProviderOperatorKeys => None, // BLSAccount, use bls_account_of_type @@ -362,6 +382,10 @@ impl AccountCollection { } => self.identity_topup.get_mut(®istration_index), AccountType::IdentityTopUpNotBoundToIdentity => self.identity_topup_not_bound.as_mut(), AccountType::IdentityInvitation => self.identity_invitation.as_mut(), + AccountType::AssetLockAddressTopUp => self.asset_lock_address_topup.as_mut(), + AccountType::AssetLockShieldedAddressTopUp => { + self.asset_lock_shielded_address_topup.as_mut() + } AccountType::ProviderVotingKeys => self.provider_voting_keys.as_mut(), AccountType::ProviderOwnerKeys => self.provider_owner_keys.as_mut(), AccountType::ProviderOperatorKeys => None, // BLSAccount, use bls_account_of_type_mut @@ -425,6 +449,14 @@ impl AccountCollection { accounts.push(account); } + if let Some(account) = &self.asset_lock_address_topup { + accounts.push(account); + } + + if let Some(account) = &self.asset_lock_shielded_address_topup { + accounts.push(account); + } + if let Some(account) = &self.provider_voting_keys { accounts.push(account); } @@ -465,6 +497,14 @@ impl AccountCollection { accounts.push(account); } + if let Some(account) = &mut self.asset_lock_address_topup { + accounts.push(account); + } + + if let Some(account) = &mut self.asset_lock_shielded_address_topup { + accounts.push(account); + } + if let Some(account) = &mut self.provider_voting_keys { accounts.push(account); } @@ -565,6 +605,8 @@ impl AccountCollection { && self.identity_topup.is_empty() && self.identity_topup_not_bound.is_none() && self.identity_invitation.is_none() + && self.asset_lock_address_topup.is_none() + && self.asset_lock_shielded_address_topup.is_none() && self.provider_voting_keys.is_none() && self.provider_owner_keys.is_none(); @@ -590,6 +632,8 @@ impl AccountCollection { self.identity_topup.clear(); self.identity_topup_not_bound = None; self.identity_invitation = None; + self.asset_lock_address_topup = None; + self.asset_lock_shielded_address_topup = None; self.provider_voting_keys = None; self.provider_owner_keys = None; #[cfg(feature = "bls")] diff --git a/key-wallet/src/account/account_type.rs b/key-wallet/src/account/account_type.rs index c3e0db8b4..cd318ff88 100644 --- a/key-wallet/src/account/account_type.rs +++ b/key-wallet/src/account/account_type.rs @@ -53,6 +53,12 @@ pub enum AccountType { IdentityTopUpNotBoundToIdentity, /// Identity invitation funding IdentityInvitation, + /// Asset lock address top-up funding (subfeature 4) + /// Path: m/9'/coinType'/5'/4'/index' + AssetLockAddressTopUp, + /// Asset lock shielded address top-up funding (subfeature 5) + /// Path: m/9'/coinType'/5'/5'/index' + AssetLockShieldedAddressTopUp, /// Provider voting keys (DIP-3) /// Path: `m/9'/5'/3'/1'/[key_index]` ProviderVotingKeys, @@ -119,6 +125,10 @@ impl TryFrom for AccountTypeToCheck { Ok(AccountTypeToCheck::IdentityTopUpNotBound) } AccountType::IdentityInvitation => Ok(AccountTypeToCheck::IdentityInvitation), + AccountType::AssetLockAddressTopUp => Ok(AccountTypeToCheck::AssetLockAddressTopUp), + AccountType::AssetLockShieldedAddressTopUp => { + Ok(AccountTypeToCheck::AssetLockShieldedAddressTopUp) + } AccountType::ProviderVotingKeys => Ok(AccountTypeToCheck::ProviderVotingKeys), AccountType::ProviderOwnerKeys => Ok(AccountTypeToCheck::ProviderOwnerKeys), AccountType::ProviderOperatorKeys => Ok(AccountTypeToCheck::ProviderOperatorKeys), @@ -170,6 +180,8 @@ impl AccountType { } | Self::IdentityTopUpNotBoundToIdentity | Self::IdentityInvitation + | Self::AssetLockAddressTopUp + | Self::AssetLockShieldedAddressTopUp | Self::ProviderVotingKeys | Self::ProviderOwnerKeys | Self::ProviderOperatorKeys @@ -213,6 +225,12 @@ impl AccountType { Self::IdentityInvitation { .. } => DerivationPathReference::BlockchainIdentityCreditInvitationFunding, + Self::AssetLockAddressTopUp { + .. + } => DerivationPathReference::BlockchainAssetLockAddressTopupFunding, + Self::AssetLockShieldedAddressTopUp { + .. + } => DerivationPathReference::BlockchainAssetLockShieldedAddressTopupFunding, Self::ProviderVotingKeys { .. } => DerivationPathReference::ProviderVotingKeys, @@ -334,6 +352,32 @@ impl AccountType { _ => Err(crate::error::Error::InvalidNetwork), } } + Self::AssetLockAddressTopUp => { + // Base path without index - actual key index added when deriving + match network { + Network::Dash => { + Ok(DerivationPath::from(crate::dip9::ASSET_LOCK_ADDRESS_TOPUP_PATH_MAINNET)) + } + Network::Testnet | Network::Devnet | Network::Regtest => { + Ok(DerivationPath::from(crate::dip9::ASSET_LOCK_ADDRESS_TOPUP_PATH_TESTNET)) + } + _ => Err(crate::error::Error::InvalidNetwork), + } + } + Self::AssetLockShieldedAddressTopUp => { + // Base path without index - actual key index added when deriving + match network { + Network::Dash => Ok(DerivationPath::from( + crate::dip9::ASSET_LOCK_SHIELDED_ADDRESS_TOPUP_PATH_MAINNET, + )), + Network::Testnet | Network::Devnet | Network::Regtest => { + Ok(DerivationPath::from( + crate::dip9::ASSET_LOCK_SHIELDED_ADDRESS_TOPUP_PATH_TESTNET, + )) + } + _ => Err(crate::error::Error::InvalidNetwork), + } + } Self::ProviderVotingKeys => { // DIP-3: m/9'/5'/3'/1' (base path, actual key index added when deriving) Ok(DerivationPath::from(vec![ diff --git a/key-wallet/src/bip32.rs b/key-wallet/src/bip32.rs index 5bd805b7e..9c597587a 100644 --- a/key-wallet/src/bip32.rs +++ b/key-wallet/src/bip32.rs @@ -34,6 +34,8 @@ use secp256k1::{self, Secp256k1, XOnlyPublicKey}; use serde; use crate::dip9::{ + ASSET_LOCK_ADDRESS_TOPUP_PATH_MAINNET, ASSET_LOCK_ADDRESS_TOPUP_PATH_TESTNET, + ASSET_LOCK_SHIELDED_ADDRESS_TOPUP_PATH_MAINNET, ASSET_LOCK_SHIELDED_ADDRESS_TOPUP_PATH_TESTNET, COINJOIN_PATH_MAINNET, COINJOIN_PATH_TESTNET, DASH_BIP44_PATH_MAINNET, DASH_BIP44_PATH_TESTNET, IDENTITY_AUTHENTICATION_PATH_MAINNET, IDENTITY_AUTHENTICATION_PATH_TESTNET, IDENTITY_INVITATION_PATH_MAINNET, IDENTITY_INVITATION_PATH_TESTNET, @@ -1091,6 +1093,30 @@ impl DerivationPath { root_derivation_path } + pub fn asset_lock_address_top_up_path(network: Network, index: u32) -> Self { + let mut root_derivation_path: DerivationPath = match network { + Network::Dash => ASSET_LOCK_ADDRESS_TOPUP_PATH_MAINNET, + _ => ASSET_LOCK_ADDRESS_TOPUP_PATH_TESTNET, + } + .into(); + root_derivation_path.0.extend(&[ChildNumber::Hardened { + index, + }]); + root_derivation_path + } + + pub fn asset_lock_shielded_address_top_up_path(network: Network, index: u32) -> Self { + let mut root_derivation_path: DerivationPath = match network { + Network::Dash => ASSET_LOCK_SHIELDED_ADDRESS_TOPUP_PATH_MAINNET, + _ => ASSET_LOCK_SHIELDED_ADDRESS_TOPUP_PATH_TESTNET, + } + .into(); + root_derivation_path.0.extend(&[ChildNumber::Hardened { + index, + }]); + root_derivation_path + } + pub fn identity_authentication_path( network: Network, key_type: KeyDerivationType, @@ -2617,6 +2643,24 @@ mod tests { assert_eq!(path.to_string(), "m/9'/5'/5'/3'/15'"); } + #[test] + fn test_asset_lock_address_top_up_path() { + let path = DerivationPath::asset_lock_address_top_up_path(Network::Dash, 7); + assert_eq!(path.to_string(), "m/9'/5'/5'/4'/7'"); + + let path = DerivationPath::asset_lock_address_top_up_path(Network::Testnet, 0); + assert_eq!(path.to_string(), "m/9'/1'/5'/4'/0'"); + } + + #[test] + fn test_asset_lock_shielded_address_top_up_path() { + let path = DerivationPath::asset_lock_shielded_address_top_up_path(Network::Dash, 3); + assert_eq!(path.to_string(), "m/9'/5'/5'/5'/3'"); + + let path = DerivationPath::asset_lock_shielded_address_top_up_path(Network::Testnet, 1); + assert_eq!(path.to_string(), "m/9'/1'/5'/5'/1'"); + } + #[test] fn test_identity_authentication_path() { let path = DerivationPath::identity_authentication_path( diff --git a/key-wallet/src/dip9.rs b/key-wallet/src/dip9.rs index 76f6ca0e8..d4b094fc9 100644 --- a/key-wallet/src/dip9.rs +++ b/key-wallet/src/dip9.rs @@ -28,6 +28,8 @@ pub enum DerivationPathReference { ProviderPlatformNodeKeys = 14, CoinJoin = 15, PlatformPayment = 16, + BlockchainAssetLockAddressTopupFunding = 17, + BlockchainAssetLockShieldedAddressTopupFunding = 18, Root = 255, } @@ -131,6 +133,8 @@ pub const FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_AUTHENTICATION: u32 = 0; pub const FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_REGISTRATION: u32 = 1; pub const FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_TOPUP: u32 = 2; pub const FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_INVITATIONS: u32 = 3; +pub const FEATURE_PURPOSE_ASSET_LOCK_SUBFEATURE_ADDRESS_TOPUP: u32 = 4; +pub const FEATURE_PURPOSE_ASSET_LOCK_SUBFEATURE_SHIELDED_ADDRESS_TOPUP: u32 = 5; pub const FEATURE_PURPOSE_DASHPAY: u32 = 15; /// DIP-17: Platform Payment Addresses feature index pub const FEATURE_PURPOSE_PLATFORM_PAYMENT: u32 = 17; @@ -341,6 +345,84 @@ pub const IDENTITY_INVITATION_PATH_TESTNET: IndexConstPath<4> = IndexConstPath { path_type: DerivationPathType::CREDIT_FUNDING, }; +// Asset Lock Address Top-Up Paths +pub const ASSET_LOCK_ADDRESS_TOPUP_PATH_MAINNET: IndexConstPath<4> = IndexConstPath { + indexes: [ + ChildNumber::Hardened { + index: FEATURE_PURPOSE, + }, + ChildNumber::Hardened { + index: DASH_COIN_TYPE, + }, + ChildNumber::Hardened { + index: FEATURE_PURPOSE_IDENTITIES, + }, + ChildNumber::Hardened { + index: FEATURE_PURPOSE_ASSET_LOCK_SUBFEATURE_ADDRESS_TOPUP, + }, + ], + reference: DerivationPathReference::BlockchainAssetLockAddressTopupFunding, + path_type: DerivationPathType::CREDIT_FUNDING, +}; + +pub const ASSET_LOCK_ADDRESS_TOPUP_PATH_TESTNET: IndexConstPath<4> = IndexConstPath { + indexes: [ + ChildNumber::Hardened { + index: FEATURE_PURPOSE, + }, + ChildNumber::Hardened { + index: DASH_TESTNET_COIN_TYPE, + }, + ChildNumber::Hardened { + index: FEATURE_PURPOSE_IDENTITIES, + }, + ChildNumber::Hardened { + index: FEATURE_PURPOSE_ASSET_LOCK_SUBFEATURE_ADDRESS_TOPUP, + }, + ], + reference: DerivationPathReference::BlockchainAssetLockAddressTopupFunding, + path_type: DerivationPathType::CREDIT_FUNDING, +}; + +// Asset Lock Shielded Address Top-Up Paths +pub const ASSET_LOCK_SHIELDED_ADDRESS_TOPUP_PATH_MAINNET: IndexConstPath<4> = IndexConstPath { + indexes: [ + ChildNumber::Hardened { + index: FEATURE_PURPOSE, + }, + ChildNumber::Hardened { + index: DASH_COIN_TYPE, + }, + ChildNumber::Hardened { + index: FEATURE_PURPOSE_IDENTITIES, + }, + ChildNumber::Hardened { + index: FEATURE_PURPOSE_ASSET_LOCK_SUBFEATURE_SHIELDED_ADDRESS_TOPUP, + }, + ], + reference: DerivationPathReference::BlockchainAssetLockShieldedAddressTopupFunding, + path_type: DerivationPathType::CREDIT_FUNDING, +}; + +pub const ASSET_LOCK_SHIELDED_ADDRESS_TOPUP_PATH_TESTNET: IndexConstPath<4> = IndexConstPath { + indexes: [ + ChildNumber::Hardened { + index: FEATURE_PURPOSE, + }, + ChildNumber::Hardened { + index: DASH_TESTNET_COIN_TYPE, + }, + ChildNumber::Hardened { + index: FEATURE_PURPOSE_IDENTITIES, + }, + ChildNumber::Hardened { + index: FEATURE_PURPOSE_ASSET_LOCK_SUBFEATURE_SHIELDED_ADDRESS_TOPUP, + }, + ], + reference: DerivationPathReference::BlockchainAssetLockShieldedAddressTopupFunding, + path_type: DerivationPathType::CREDIT_FUNDING, +}; + // Authentication Keys Paths pub const IDENTITY_AUTHENTICATION_PATH_MAINNET: IndexConstPath<4> = IndexConstPath { indexes: [ diff --git a/key-wallet/src/managed_account/managed_account_collection.rs b/key-wallet/src/managed_account/managed_account_collection.rs index 5bc2ceaad..5fc272a03 100644 --- a/key-wallet/src/managed_account/managed_account_collection.rs +++ b/key-wallet/src/managed_account/managed_account_collection.rs @@ -50,6 +50,12 @@ macro_rules! get_by_account_type_match_impl { CoreAccountTypeMatch::IdentityInvitation { .. } => $self.identity_invitation.$as_opt(), + CoreAccountTypeMatch::AssetLockAddressTopUp { + .. + } => $self.asset_lock_address_topup.$as_opt(), + CoreAccountTypeMatch::AssetLockShieldedAddressTopUp { + .. + } => $self.asset_lock_shielded_address_topup.$as_opt(), CoreAccountTypeMatch::ProviderVotingKeys { .. } => $self.provider_voting_keys.$as_opt(), @@ -120,6 +126,10 @@ pub struct ManagedAccountCollection { pub identity_topup_not_bound: Option, /// Identity invitation account (optional) pub identity_invitation: Option, + /// Asset lock address top-up account (optional) + pub asset_lock_address_topup: Option, + /// Asset lock shielded address top-up account (optional) + pub asset_lock_shielded_address_topup: Option, /// Provider voting keys (optional) pub provider_voting_keys: Option, /// Provider owner keys (optional) @@ -148,6 +158,8 @@ impl ManagedAccountCollection { identity_topup: BTreeMap::new(), identity_topup_not_bound: None, identity_invitation: None, + asset_lock_address_topup: None, + asset_lock_shielded_address_topup: None, provider_voting_keys: None, provider_owner_keys: None, provider_operator_keys: None, @@ -192,6 +204,12 @@ impl ManagedAccountCollection { ManagedAccountType::IdentityInvitation { .. } => self.identity_invitation.is_some(), + ManagedAccountType::AssetLockAddressTopUp { + .. + } => self.asset_lock_address_topup.is_some(), + ManagedAccountType::AssetLockShieldedAddressTopUp { + .. + } => self.asset_lock_shielded_address_topup.is_some(), ManagedAccountType::ProviderVotingKeys { .. } => self.provider_voting_keys.is_some(), @@ -291,6 +309,16 @@ impl ManagedAccountCollection { } => { self.identity_invitation = Some(account); } + ManagedAccountType::AssetLockAddressTopUp { + .. + } => { + self.asset_lock_address_topup = Some(account); + } + ManagedAccountType::AssetLockShieldedAddressTopUp { + .. + } => { + self.asset_lock_shielded_address_topup = Some(account); + } ManagedAccountType::ProviderVotingKeys { .. } => { @@ -410,6 +438,18 @@ impl ManagedAccountCollection { } } + if let Some(account) = &account_collection.asset_lock_address_topup { + if let Ok(managed_account) = Self::create_managed_account_from_account(account) { + managed_collection.asset_lock_address_topup = Some(managed_account); + } + } + + if let Some(account) = &account_collection.asset_lock_shielded_address_topup { + if let Ok(managed_account) = Self::create_managed_account_from_account(account) { + managed_collection.asset_lock_shielded_address_topup = Some(managed_account); + } + } + if let Some(account) = &account_collection.provider_voting_keys { if let Ok(managed_account) = Self::create_managed_account_from_account(account) { managed_collection.provider_voting_keys = Some(managed_account); @@ -625,6 +665,30 @@ impl ManagedAccountCollection { addresses, } } + AccountType::AssetLockAddressTopUp => { + let addresses = AddressPool::new( + base_path, + AddressPoolType::Absent, + DEFAULT_SPECIAL_GAP_LIMIT, + network, + key_source, + )?; + ManagedAccountType::AssetLockAddressTopUp { + addresses, + } + } + AccountType::AssetLockShieldedAddressTopUp => { + let addresses = AddressPool::new( + base_path, + AddressPoolType::Absent, + DEFAULT_SPECIAL_GAP_LIMIT, + network, + key_source, + )?; + ManagedAccountType::AssetLockShieldedAddressTopUp { + addresses, + } + } AccountType::ProviderVotingKeys => { let addresses = AddressPool::new( base_path, @@ -898,6 +962,14 @@ impl ManagedAccountCollection { accounts.push(account); } + if let Some(account) = &self.asset_lock_address_topup { + accounts.push(account); + } + + if let Some(account) = &self.asset_lock_shielded_address_topup { + accounts.push(account); + } + if let Some(account) = &self.provider_voting_keys { accounts.push(account); } @@ -949,6 +1021,14 @@ impl ManagedAccountCollection { accounts.push(account); } + if let Some(account) = &mut self.asset_lock_address_topup { + accounts.push(account); + } + + if let Some(account) = &mut self.asset_lock_shielded_address_topup { + accounts.push(account); + } + if let Some(account) = &mut self.provider_voting_keys { accounts.push(account); } @@ -1005,6 +1085,8 @@ impl ManagedAccountCollection { && self.identity_topup.is_empty() && self.identity_topup_not_bound.is_none() && self.identity_invitation.is_none() + && self.asset_lock_address_topup.is_none() + && self.asset_lock_shielded_address_topup.is_none() && self.provider_voting_keys.is_none() && self.provider_owner_keys.is_none() && self.provider_operator_keys.is_none() @@ -1023,6 +1105,8 @@ impl ManagedAccountCollection { self.identity_topup.clear(); self.identity_topup_not_bound = None; self.identity_invitation = None; + self.asset_lock_address_topup = None; + self.asset_lock_shielded_address_topup = None; self.provider_voting_keys = None; self.provider_owner_keys = None; self.provider_operator_keys = None; diff --git a/key-wallet/src/managed_account/managed_account_type.rs b/key-wallet/src/managed_account/managed_account_type.rs index fa83e293f..d86c6812f 100644 --- a/key-wallet/src/managed_account/managed_account_type.rs +++ b/key-wallet/src/managed_account/managed_account_type.rs @@ -58,6 +58,18 @@ pub enum ManagedAccountType { /// Identity invitation address pool addresses: AddressPool, }, + /// Asset lock address top-up funding (subfeature 4) + /// Path: m/9'/coinType'/5'/4'/index' + AssetLockAddressTopUp { + /// Asset lock address top-up address pool + addresses: AddressPool, + }, + /// Asset lock shielded address top-up funding (subfeature 5) + /// Path: m/9'/coinType'/5'/5'/index' + AssetLockShieldedAddressTopUp { + /// Asset lock shielded address top-up address pool + addresses: AddressPool, + }, /// Provider voting keys (DIP-3) /// Path: `m/9'/5'/3'/1'/[key_index]` ProviderVotingKeys { @@ -143,6 +155,12 @@ impl ManagedAccountType { | Self::IdentityInvitation { .. } + | Self::AssetLockAddressTopUp { + .. + } + | Self::AssetLockShieldedAddressTopUp { + .. + } | Self::ProviderVotingKeys { .. } @@ -216,6 +234,14 @@ impl ManagedAccountType { addresses, .. } + | Self::AssetLockAddressTopUp { + addresses, + .. + } + | Self::AssetLockShieldedAddressTopUp { + addresses, + .. + } | Self::ProviderVotingKeys { addresses, .. @@ -279,6 +305,14 @@ impl ManagedAccountType { addresses, .. } + | Self::AssetLockAddressTopUp { + addresses, + .. + } + | Self::AssetLockShieldedAddressTopUp { + addresses, + .. + } | Self::ProviderVotingKeys { addresses, .. @@ -392,6 +426,12 @@ impl ManagedAccountType { Self::IdentityInvitation { .. } => AccountType::IdentityInvitation, + Self::AssetLockAddressTopUp { + .. + } => AccountType::AssetLockAddressTopUp, + Self::AssetLockShieldedAddressTopUp { + .. + } => AccountType::AssetLockShieldedAddressTopUp, Self::ProviderVotingKeys { .. } => AccountType::ProviderVotingKeys, @@ -567,6 +607,38 @@ impl ManagedAccountType { addresses: pool, }) } + AccountType::AssetLockAddressTopUp => { + let path = account_type + .derivation_path(network) + .unwrap_or_else(|_| DerivationPath::master()); + let pool = AddressPool::new( + path, + AddressPoolType::Absent, + DEFAULT_SPECIAL_GAP_LIMIT, + network, + key_source, + )?; + + Ok(Self::AssetLockAddressTopUp { + addresses: pool, + }) + } + AccountType::AssetLockShieldedAddressTopUp => { + let path = account_type + .derivation_path(network) + .unwrap_or_else(|_| DerivationPath::master()); + let pool = AddressPool::new( + path, + AddressPoolType::Absent, + DEFAULT_SPECIAL_GAP_LIMIT, + network, + key_source, + )?; + + Ok(Self::AssetLockShieldedAddressTopUp { + addresses: pool, + }) + } AccountType::ProviderVotingKeys => { let path = account_type .derivation_path(network) diff --git a/key-wallet/src/managed_account/mod.rs b/key-wallet/src/managed_account/mod.rs index 340fb6b3a..dba47ec19 100644 --- a/key-wallet/src/managed_account/mod.rs +++ b/key-wallet/src/managed_account/mod.rs @@ -235,6 +235,14 @@ impl ManagedCoreAccount { addresses, .. } + | ManagedAccountType::AssetLockAddressTopUp { + addresses, + .. + } + | ManagedAccountType::AssetLockShieldedAddressTopUp { + addresses, + .. + } | ManagedAccountType::ProviderVotingKeys { addresses, .. @@ -610,6 +618,14 @@ impl ManagedCoreAccount { addresses, .. } + | ManagedAccountType::AssetLockAddressTopUp { + addresses, + .. + } + | ManagedAccountType::AssetLockShieldedAddressTopUp { + addresses, + .. + } | ManagedAccountType::ProviderVotingKeys { addresses, .. @@ -699,6 +715,14 @@ impl ManagedCoreAccount { addresses, .. } + | ManagedAccountType::AssetLockAddressTopUp { + addresses, + .. + } + | ManagedAccountType::AssetLockShieldedAddressTopUp { + addresses, + .. + } | ManagedAccountType::ProviderVotingKeys { addresses, .. @@ -928,6 +952,14 @@ impl ManagedCoreAccount { addresses, .. } + | ManagedAccountType::AssetLockAddressTopUp { + addresses, + .. + } + | ManagedAccountType::AssetLockShieldedAddressTopUp { + addresses, + .. + } | ManagedAccountType::ProviderVotingKeys { addresses, .. diff --git a/key-wallet/src/transaction_checking/account_checker.rs b/key-wallet/src/transaction_checking/account_checker.rs index e63e769c3..fe96d0813 100644 --- a/key-wallet/src/transaction_checking/account_checker.rs +++ b/key-wallet/src/transaction_checking/account_checker.rs @@ -84,6 +84,14 @@ pub enum CoreAccountTypeMatch { IdentityInvitation { involved_addresses: Vec, }, + /// Asset lock address top-up account (no index) + AssetLockAddressTopUp { + involved_addresses: Vec, + }, + /// Asset lock shielded address top-up account (no index) + AssetLockShieldedAddressTopUp { + involved_addresses: Vec, + }, /// Provider voting keys account (no index) ProviderVotingKeys { involved_addresses: Vec, @@ -147,6 +155,12 @@ impl CoreAccountTypeMatch { | CoreAccountTypeMatch::IdentityInvitation { involved_addresses, } + | CoreAccountTypeMatch::AssetLockAddressTopUp { + involved_addresses, + } + | CoreAccountTypeMatch::AssetLockShieldedAddressTopUp { + involved_addresses, + } | CoreAccountTypeMatch::ProviderVotingKeys { involved_addresses, } @@ -225,6 +239,12 @@ impl CoreAccountTypeMatch { CoreAccountTypeMatch::IdentityInvitation { .. } => AccountTypeToCheck::IdentityInvitation, + CoreAccountTypeMatch::AssetLockAddressTopUp { + .. + } => AccountTypeToCheck::AssetLockAddressTopUp, + CoreAccountTypeMatch::AssetLockShieldedAddressTopUp { + .. + } => AccountTypeToCheck::AssetLockShieldedAddressTopUp, CoreAccountTypeMatch::ProviderVotingKeys { .. } => AccountTypeToCheck::ProviderVotingKeys, @@ -329,6 +349,18 @@ impl ManagedAccountCollection { .and_then(|account| account.check_asset_lock_transaction_for_match(tx, None)) .into_iter() .collect(), + AccountTypeToCheck::AssetLockAddressTopUp => self + .asset_lock_address_topup + .as_ref() + .and_then(|account| account.check_asset_lock_transaction_for_match(tx, None)) + .into_iter() + .collect(), + AccountTypeToCheck::AssetLockShieldedAddressTopUp => self + .asset_lock_shielded_address_topup + .as_ref() + .and_then(|account| account.check_asset_lock_transaction_for_match(tx, None)) + .into_iter() + .collect(), AccountTypeToCheck::ProviderVotingKeys => self .provider_voting_keys .as_ref() @@ -598,6 +630,16 @@ impl ManagedCoreAccount { } => CoreAccountTypeMatch::IdentityInvitation { involved_addresses: involved_other_addresses, }, + ManagedAccountType::AssetLockAddressTopUp { + .. + } => CoreAccountTypeMatch::AssetLockAddressTopUp { + involved_addresses: involved_other_addresses, + }, + ManagedAccountType::AssetLockShieldedAddressTopUp { + .. + } => CoreAccountTypeMatch::AssetLockShieldedAddressTopUp { + involved_addresses: involved_other_addresses, + }, ManagedAccountType::ProviderVotingKeys { .. } => CoreAccountTypeMatch::ProviderVotingKeys { @@ -703,6 +745,16 @@ impl ManagedCoreAccount { } => CoreAccountTypeMatch::IdentityInvitation { involved_addresses, }, + ManagedAccountType::AssetLockAddressTopUp { + .. + } => CoreAccountTypeMatch::AssetLockAddressTopUp { + involved_addresses, + }, + ManagedAccountType::AssetLockShieldedAddressTopUp { + .. + } => CoreAccountTypeMatch::AssetLockShieldedAddressTopUp { + involved_addresses, + }, _ => { // This shouldn't happen for AssetLock transactions return None; @@ -951,6 +1003,22 @@ impl ManagedCoreAccount { } } + // Check asset lock address top-up + if let Some(asset_lock_address_topup) = &collection.asset_lock_address_topup { + if asset_lock_address_topup.contains_address(address) { + return Some((AccountTypeToCheck::AssetLockAddressTopUp, None)); + } + } + + // Check asset lock shielded address top-up + if let Some(asset_lock_shielded_address_topup) = + &collection.asset_lock_shielded_address_topup + { + if asset_lock_shielded_address_topup.contains_address(address) { + return Some((AccountTypeToCheck::AssetLockShieldedAddressTopUp, None)); + } + } + // Check provider accounts if let Some(account) = &collection.provider_voting_keys { if account.contains_address(address) { diff --git a/key-wallet/src/transaction_checking/transaction_router/mod.rs b/key-wallet/src/transaction_checking/transaction_router/mod.rs index 6d8f6921c..fdaa556d7 100644 --- a/key-wallet/src/transaction_checking/transaction_router/mod.rs +++ b/key-wallet/src/transaction_checking/transaction_router/mod.rs @@ -115,6 +115,8 @@ impl TransactionRouter { AccountTypeToCheck::IdentityTopUp, AccountTypeToCheck::IdentityTopUpNotBound, AccountTypeToCheck::IdentityInvitation, + AccountTypeToCheck::AssetLockAddressTopUp, + AccountTypeToCheck::AssetLockShieldedAddressTopUp, ], TransactionType::AssetUnlock => { vec![AccountTypeToCheck::StandardBIP44, AccountTypeToCheck::StandardBIP32] @@ -175,6 +177,8 @@ pub enum AccountTypeToCheck { IdentityTopUp, IdentityTopUpNotBound, IdentityInvitation, + AssetLockAddressTopUp, + AssetLockShieldedAddressTopUp, ProviderVotingKeys, ProviderOwnerKeys, ProviderOperatorKeys, @@ -224,6 +228,12 @@ impl TryFrom for AccountTypeToCheck { ManagedAccountType::IdentityInvitation { .. } => Ok(AccountTypeToCheck::IdentityInvitation), + ManagedAccountType::AssetLockAddressTopUp { + .. + } => Ok(AccountTypeToCheck::AssetLockAddressTopUp), + ManagedAccountType::AssetLockShieldedAddressTopUp { + .. + } => Ok(AccountTypeToCheck::AssetLockShieldedAddressTopUp), ManagedAccountType::ProviderVotingKeys { .. } => Ok(AccountTypeToCheck::ProviderVotingKeys), @@ -283,6 +293,12 @@ impl TryFrom<&ManagedAccountType> for AccountTypeToCheck { ManagedAccountType::IdentityInvitation { .. } => Ok(AccountTypeToCheck::IdentityInvitation), + ManagedAccountType::AssetLockAddressTopUp { + .. + } => Ok(AccountTypeToCheck::AssetLockAddressTopUp), + ManagedAccountType::AssetLockShieldedAddressTopUp { + .. + } => Ok(AccountTypeToCheck::AssetLockShieldedAddressTopUp), ManagedAccountType::ProviderVotingKeys { .. } => Ok(AccountTypeToCheck::ProviderVotingKeys), diff --git a/key-wallet/src/wallet/helper.rs b/key-wallet/src/wallet/helper.rs index 8b214298b..82e693d0e 100644 --- a/key-wallet/src/wallet/helper.rs +++ b/key-wallet/src/wallet/helper.rs @@ -549,6 +549,12 @@ impl Wallet { // Identity top-up not bound to identity self.add_account(AccountType::IdentityTopUpNotBoundToIdentity, None)?; + // Asset lock address top-up + self.add_account(AccountType::AssetLockAddressTopUp, None)?; + + // Asset lock shielded address top-up + self.add_account(AccountType::AssetLockShieldedAddressTopUp, None)?; + // Provider keys accounts self.add_account(AccountType::ProviderVotingKeys, None)?; self.add_account(AccountType::ProviderOwnerKeys, None)?; @@ -571,6 +577,12 @@ impl Wallet { // Identity top-up not bound to identity self.add_account_with_passphrase(AccountType::IdentityTopUpNotBoundToIdentity, passphrase)?; + // Asset lock address top-up + self.add_account_with_passphrase(AccountType::AssetLockAddressTopUp, passphrase)?; + + // Asset lock shielded address top-up + self.add_account_with_passphrase(AccountType::AssetLockShieldedAddressTopUp, passphrase)?; + // Provider keys accounts self.add_account_with_passphrase(AccountType::ProviderVotingKeys, passphrase)?; self.add_account_with_passphrase(AccountType::ProviderOwnerKeys, passphrase)?; @@ -822,6 +834,12 @@ impl Wallet { crate::transaction_checking::transaction_router::AccountTypeToCheck::IdentityInvitation => { coll.identity_invitation.as_ref().map(|a| a.account_xpub) } + crate::transaction_checking::transaction_router::AccountTypeToCheck::AssetLockAddressTopUp => { + coll.asset_lock_address_topup.as_ref().map(|a| a.account_xpub) + } + crate::transaction_checking::transaction_router::AccountTypeToCheck::AssetLockShieldedAddressTopUp => { + coll.asset_lock_shielded_address_topup.as_ref().map(|a| a.account_xpub) + } crate::transaction_checking::transaction_router::AccountTypeToCheck::ProviderVotingKeys => { coll.provider_voting_keys.as_ref().map(|a| a.account_xpub) }