From be4b910a28fe1abfe2de8545702c9c8ff1e6e9b3 Mon Sep 17 00:00:00 2001 From: Marvin Jones Date: Thu, 28 May 2026 16:58:03 -0400 Subject: [PATCH] addressed comments --- .../pda_fund_spend_proxy.bin | Bin 404024 -> 403980 bytes wallet-ffi/src/transfer.rs | 24 +- wallet/Cargo.toml | 2 + wallet/src/account_manager.rs | 35 ++- wallet/src/cli/mod.rs | 11 + wallet/src/cli/programs/amm.rs | 33 +-- wallet/src/cli/programs/ata.rs | 15 +- .../src/cli/programs/native_token_transfer.rs | 75 +++---- wallet/src/cli/programs/token.rs | 67 +++--- wallet/src/lib.rs | 20 +- wallet/src/program_facades/amm.rs | 206 ++++++------------ wallet/src/program_facades/ata.rs | 53 ++--- .../native_token_transfer/public.rs | 46 +--- .../native_token_transfer/shielded.rs | 32 +-- wallet/src/program_facades/token.rs | 128 ++--------- wallet/src/signing.rs | 7 +- 16 files changed, 244 insertions(+), 510 deletions(-) diff --git a/artifacts/test_program_methods/pda_fund_spend_proxy.bin b/artifacts/test_program_methods/pda_fund_spend_proxy.bin index c649bb1fd8c6e9c137ccff3d9353a04c4435add0..9a04a29c5fb3a99a3aff52d8f9e734e28ccc5c39 100644 GIT binary patch delta 84587 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 { let tx_hash = CString::new(tx_hash.to_string()) @@ -196,12 +191,11 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded( let transfer = NativeTokenTransfer(&wallet); match block_on(transfer.send_shielded_transfer_to_outer_account( - from_id, + from_mention.into_public_identity(from_id), to_npk, to_vpk, to_identifier, amount, - &from_mention, )) { Ok((tx_hash, _shared_key)) => { let tx_hash = CString::new(tx_hash.to_string()) @@ -469,7 +463,11 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded_owned( let transfer = NativeTokenTransfer(&wallet); - match block_on(transfer.send_shielded_transfer(from_id, to_id, amount, &from_mention)) { + match block_on(transfer.send_shielded_transfer( + from_mention.into_public_identity(from_id), + to_id, + amount, + )) { Ok((tx_hash, _shared_key)) => { let tx_hash = CString::new(tx_hash.to_string()) .map_or(ptr::null_mut(), std::ffi::CString::into_raw); @@ -622,9 +620,7 @@ pub unsafe extern "C" fn wallet_ffi_register_public_account( let transfer = NativeTokenTransfer(&wallet); - let mention = CliAccountMention::Id(AccountIdWithPrivacy::Public(account_id)); - - match block_on(transfer.register_account(account_id, &mention)) { + match block_on(transfer.register_account(AccountIdentity::Public(account_id))) { Ok(tx_hash) => { let tx_hash = CString::new(tx_hash.to_string()) .map_or(ptr::null_mut(), std::ffi::CString::into_raw); diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index a09010ee..c8ccd3ef 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -47,6 +47,8 @@ url.workspace = true derive_more = { workspace = true, features = ["display"] } [features] +# Enables `keycard get-private-keys` command that prints sensitive secret keys to terminal. +# Never enable in production builds. keycard-debug = [] [dev-dependencies] diff --git a/wallet/src/account_manager.rs b/wallet/src/account_manager.rs index c3b23c34..a0825d04 100644 --- a/wallet/src/account_manager.rs +++ b/wallet/src/account_manager.rs @@ -11,7 +11,7 @@ use nssa_core::{ use crate::{ExecutionFailureKind, WalletCore}; -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum AccountIdentity { Public(AccountId), /// A public account without signing. Would not try to sign, even if account is owned. @@ -69,6 +69,22 @@ impl AccountIdentity { ) } + /// Returns the `AccountId` for public variants. Used by facades that need the raw ID + /// for derived-address computation alongside the identity. + #[must_use] + pub const fn public_account_id(&self) -> Option { + match self { + Self::Public(id) | Self::PublicNoSign(id) => Some(*id), + Self::PublicKeycard { account_id, .. } => Some(*account_id), + Self::PrivateOwned(_) + | Self::PrivateForeign { .. } + | Self::PrivatePdaOwned(_) + | Self::PrivatePdaForeign { .. } + | Self::PrivateShared { .. } + | Self::PrivatePdaShared { .. } => None, + } + } + #[must_use] pub const fn is_private(&self) -> bool { matches!( @@ -384,19 +400,14 @@ impl AccountManager { }) .collect(); - let keycard_paths = self + let keycard_paths: Vec<&str> = self .states .iter() - .fold(vec![], |mut acc, state| match state { - State::Private(_) | State::Public { .. } => acc, - State::PublicKeycard { - account: _, - key_path, - } => { - acc.push(key_path.as_str()); - acc - } - }); + .filter_map(|state| match state { + State::PublicKeycard { key_path, .. } => Some(key_path.as_str()), + State::Private(_) | State::Public { .. } => None, + }) + .collect(); if let Some(pin) = self.pin.clone() { pyo3::Python::with_gil(|py| -> pyo3::PyResult<()> { diff --git a/wallet/src/cli/mod.rs b/wallet/src/cli/mod.rs index 30443de9..5f0fcb44 100644 --- a/wallet/src/cli/mod.rs +++ b/wallet/src/cli/mod.rs @@ -155,6 +155,17 @@ impl CliAccountMention { Self::Id(_) | Self::Label(_) => None, } } + + #[must_use] + pub fn into_public_identity(self, account_id: nssa::AccountId) -> crate::AccountIdentity { + match self { + Self::KeyPath(key_path) => crate::AccountIdentity::PublicKeycard { + account_id, + key_path, + }, + Self::Id(_) | Self::Label(_) => crate::AccountIdentity::Public(account_id), + } + } } impl FromStr for CliAccountMention { diff --git a/wallet/src/cli/programs/amm.rs b/wallet/src/cli/programs/amm.rs index f2d546c6..1ef4fdba 100644 --- a/wallet/src/cli/programs/amm.rs +++ b/wallet/src/cli/programs/amm.rs @@ -142,14 +142,11 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand { ) => { let tx_hash = Amm(wallet_core) .send_new_definition( - a, - b, - lp, + user_holding_a.into_public_identity(a), + user_holding_b.into_public_identity(b), + user_holding_lp.into_public_identity(lp), balance_a, balance_b, - &user_holding_a, - &user_holding_b, - &user_holding_lp, ) .await?; println!("Transaction hash is {tx_hash}"); @@ -177,13 +174,11 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand { (AccountIdWithPrivacy::Public(a), AccountIdWithPrivacy::Public(b)) => { let tx_hash = Amm(wallet_core) .send_swap_exact_input( - a, - b, + user_holding_a.into_public_identity(a), + user_holding_b.into_public_identity(b), amount_in, min_amount_out, token_definition, - &user_holding_a, - &user_holding_b, ) .await?; println!("Transaction hash is {tx_hash}"); @@ -211,13 +206,11 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand { (AccountIdWithPrivacy::Public(a), AccountIdWithPrivacy::Public(b)) => { let tx_hash = Amm(wallet_core) .send_swap_exact_output( - a, - b, + user_holding_a.into_public_identity(a), + user_holding_b.into_public_identity(b), exact_amount_out, max_amount_in, token_definition, - &user_holding_a, - &user_holding_b, ) .await?; println!("Transaction hash is {tx_hash}"); @@ -251,15 +244,12 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand { ) => { let tx_hash = Amm(wallet_core) .send_add_liquidity( - a, - b, - lp, + user_holding_a.into_public_identity(a), + user_holding_b.into_public_identity(b), + user_holding_lp.into_public_identity(lp), min_amount_lp, max_amount_a, max_amount_b, - &user_holding_a, - &user_holding_b, - &user_holding_lp, ) .await?; println!("Transaction hash is {tx_hash}"); @@ -295,11 +285,10 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand { .send_remove_liquidity( a, b, - lp, + user_holding_lp.into_public_identity(lp), balance_lp, min_amount_a, min_amount_b, - &user_holding_lp, ) .await?; println!("Transaction hash is {tx_hash}"); diff --git a/wallet/src/cli/programs/ata.rs b/wallet/src/cli/programs/ata.rs index c2b96a85..1b9b9428 100644 --- a/wallet/src/cli/programs/ata.rs +++ b/wallet/src/cli/programs/ata.rs @@ -97,7 +97,7 @@ impl WalletSubcommand for AtaSubcommand { match owner_resolved { AccountIdWithPrivacy::Public(owner_id) => { let tx_hash = Ata(wallet_core) - .send_create(owner_id, definition_id, &owner) + .send_create(owner.into_public_identity(owner_id), definition_id) .await?; println!("Transaction hash is {tx_hash}"); let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?; @@ -138,7 +138,12 @@ impl WalletSubcommand for AtaSubcommand { match from_resolved { AccountIdWithPrivacy::Public(from_id) => { let tx_hash = Ata(wallet_core) - .send_transfer(from_id, definition_id, to_id, amount, &from) + .send_transfer( + from.into_public_identity(from_id), + definition_id, + to_id, + amount, + ) .await?; println!("Transaction hash is {tx_hash}"); let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?; @@ -177,7 +182,11 @@ impl WalletSubcommand for AtaSubcommand { match holder_resolved { AccountIdWithPrivacy::Public(holder_id) => { let tx_hash = Ata(wallet_core) - .send_burn(holder_id, definition_id, amount, &holder) + .send_burn( + holder.into_public_identity(holder_id), + definition_id, + amount, + ) .await?; println!("Transaction hash is {tx_hash}"); let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?; diff --git a/wallet/src/cli/programs/native_token_transfer.rs b/wallet/src/cli/programs/native_token_transfer.rs index 724bcca4..c20c2726 100644 --- a/wallet/src/cli/programs/native_token_transfer.rs +++ b/wallet/src/cli/programs/native_token_transfer.rs @@ -5,7 +5,7 @@ use nssa::AccountId; use crate::{ AccDecodeData::Decode, - WalletCore, + AccountIdentity, WalletCore, account::AccountIdWithPrivacy, cli::{CliAccountMention, SubcommandReturnValue, WalletSubcommand}, program_facades::native_token_transfer::NativeTokenTransfer, @@ -60,7 +60,7 @@ impl WalletSubcommand for AuthTransferSubcommand { match resolved { AccountIdWithPrivacy::Public(pub_account_id) => { let tx_hash = NativeTokenTransfer(wallet_core) - .register_account(pub_account_id, &account_id) + .register_account(account_id.into_public_identity(pub_account_id)) .await?; println!("Transaction hash is {tx_hash}"); @@ -124,12 +124,11 @@ impl WalletSubcommand for AuthTransferSubcommand { } (Some(to), None, None) => match (from, to) { (AccountIdWithPrivacy::Public(from), AccountIdWithPrivacy::Public(to)) => { + let to_mention = to_account.expect("matched Some branch"); NativeTokenTransferProgramSubcommand::Public { - from, - to, + from: Some(from_account.into_public_identity(from)), + to: Some(to_mention.into_public_identity(to)), amount, - from_mention: from_account, - to_mention: to_account.expect("matched Some branch"), } } ( @@ -148,10 +147,9 @@ impl WalletSubcommand for AuthTransferSubcommand { (AccountIdWithPrivacy::Public(from), AccountIdWithPrivacy::Private(to)) => { NativeTokenTransferProgramSubcommand::Shielded( NativeTokenTransferProgramSubcommandShielded::ShieldedOwned { - from, + from: Some(from_account.into_public_identity(from)), to, amount, - from_mention: from_account, }, ) } @@ -171,12 +169,11 @@ impl WalletSubcommand for AuthTransferSubcommand { AccountIdWithPrivacy::Public(from) => { NativeTokenTransferProgramSubcommand::Shielded( NativeTokenTransferProgramSubcommandShielded::ShieldedForeign { - from, + from: Some(from_account.into_public_identity(from)), to_npk, to_vpk, to_identifier, amount, - from_mention: from_account, }, ) } @@ -196,19 +193,13 @@ pub enum NativeTokenTransferProgramSubcommand { /// /// Public operation. Public { - /// from - valid 32 byte hex string. - #[arg(long)] - from: AccountId, - /// to - valid 32 byte hex string. - #[arg(long)] - to: AccountId, + #[arg(skip)] + from: Option, + #[arg(skip)] + to: Option, /// amount - amount of balance to move. #[arg(long)] amount: u128, - #[arg(skip)] - from_mention: CliAccountMention, - #[arg(skip)] - to_mention: CliAccountMention, }, /// Private execution. #[command(subcommand)] @@ -241,24 +232,21 @@ pub enum NativeTokenTransferProgramSubcommandShielded { /// Shielded operation. ShieldedOwned { /// from - valid 32 byte hex string. - #[arg(long)] - from: AccountId, + #[arg(skip)] + from: Option, /// to - valid 32 byte hex string. #[arg(long)] to: AccountId, /// amount - amount of balance to move. #[arg(long)] amount: u128, - #[arg(skip)] - from_mention: CliAccountMention, }, /// Send native token transfer from `from` to `to` for `amount`. /// /// Shielded operation. ShieldedForeign { - /// from - valid 32 byte hex string. - #[arg(long)] - from: AccountId, + #[arg(skip)] + from: Option, /// `to_npk` - valid 32 byte hex string. #[arg(long)] to_npk: String, @@ -271,8 +259,6 @@ pub enum NativeTokenTransferProgramSubcommandShielded { /// amount - amount of balance to move. #[arg(long)] amount: u128, - #[arg(skip)] - from_mention: CliAccountMention, }, } @@ -399,14 +385,13 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { wallet_core: &mut WalletCore, ) -> Result { match self { - Self::ShieldedOwned { - from, - to, - amount, - from_mention, - } => { + Self::ShieldedOwned { from, to, amount } => { let (tx_hash, secret) = NativeTokenTransfer(wallet_core) - .send_shielded_transfer(from, to, amount, &from_mention) + .send_shielded_transfer( + from.expect("from set during Send dispatch"), + to, + amount, + ) .await?; println!("Transaction hash is {tx_hash}"); @@ -432,7 +417,6 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { to_vpk, to_identifier, amount, - from_mention, } => { let to_npk_res = hex::decode(to_npk)?; let mut to_npk = [0; 32]; @@ -447,12 +431,11 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { let (tx_hash, _) = NativeTokenTransfer(wallet_core) .send_shielded_transfer_to_outer_account( - from, + from.expect("from set during Send dispatch"), to_npk, to_vpk, to_identifier.unwrap_or_else(rand::random), amount, - &from_mention, ) .await?; @@ -500,15 +483,13 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand { Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } - Self::Public { - from, - to, - amount, - from_mention, - to_mention, - } => { + Self::Public { from, to, amount } => { let tx_hash = NativeTokenTransfer(wallet_core) - .send_public_transfer(from, to, amount, &from_mention, &to_mention) + .send_public_transfer( + from.expect("from is set during Send dispatch"), + to.expect("to is set during Send dispatch"), + amount, + ) .await?; println!("Transaction hash is {tx_hash}"); diff --git a/wallet/src/cli/programs/token.rs b/wallet/src/cli/programs/token.rs index 6137812f..12abc309 100644 --- a/wallet/src/cli/programs/token.rs +++ b/wallet/src/cli/programs/token.rs @@ -5,7 +5,7 @@ use nssa::AccountId; use crate::{ AccDecodeData::Decode, - WalletCore, + AccountIdentity, WalletCore, account::AccountIdWithPrivacy, cli::{CliAccountMention, SubcommandReturnValue, WalletSubcommand}, program_facades::token::Token, @@ -226,10 +226,9 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand { (AccountIdWithPrivacy::Public(from), AccountIdWithPrivacy::Private(to)) => { TokenProgramSubcommand::Shielded( TokenProgramSubcommandShielded::TransferTokenShieldedOwned { - sender_account_id: from, + sender: Some(from_mention.into_public_identity(from)), recipient_account_id: to, balance_to_move: amount, - sender_mention: from_mention, }, ) } @@ -246,12 +245,11 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand { ), AccountIdWithPrivacy::Public(from) => TokenProgramSubcommand::Shielded( TokenProgramSubcommandShielded::TransferTokenShieldedForeign { - sender_account_id: from, + sender: Some(from_mention.into_public_identity(from)), recipient_npk: to_npk, recipient_vpk: to_vpk, recipient_identifier: to_identifier, balance_to_move: amount, - sender_mention: from_mention, }, ), }, @@ -561,19 +559,17 @@ pub enum TokenProgramSubcommandDeshielded { pub enum TokenProgramSubcommandShielded { // Transfer tokens using the token program TransferTokenShieldedOwned { - #[arg(short, long)] - sender_account_id: AccountId, + #[arg(skip)] + sender: Option, #[arg(short, long)] recipient_account_id: AccountId, #[arg(short, long)] balance_to_move: u128, - #[arg(skip)] - sender_mention: CliAccountMention, }, // Transfer tokens using the token program TransferTokenShieldedForeign { - #[arg(short, long)] - sender_account_id: AccountId, + #[arg(skip)] + sender: Option, /// `recipient_npk` - valid 32 byte hex string. #[arg(long)] recipient_npk: String, @@ -585,8 +581,6 @@ pub enum TokenProgramSubcommandShielded { recipient_identifier: Option, #[arg(short, long)] balance_to_move: u128, - #[arg(skip)] - sender_mention: CliAccountMention, }, // Burn tokens using the token program BurnTokenShielded { @@ -697,15 +691,15 @@ impl WalletSubcommand for TokenProgramSubcommandPublic { AccountIdWithPrivacy::Public(recipient_id), ) = (sender, recipient) else { - anyhow::bail!("Only public accounts supported for token transfer"); + anyhow::bail!( + "`TokenProgramSubcommandPublic::TransferToken`: Unexpected private account received." + ); }; let tx_hash = Token(wallet_core) .send_transfer_transaction( - sender_id, - recipient_id, + sender_account_id.into_public_identity(sender_id), + recipient_account_id.into_public_identity(recipient_id), balance_to_move, - &sender_account_id, - &recipient_account_id, ) .await?; println!("Transaction hash is {tx_hash}"); @@ -721,14 +715,15 @@ impl WalletSubcommand for TokenProgramSubcommandPublic { } => { let holder = holder_account_id.resolve(wallet_core.storage())?; let AccountIdWithPrivacy::Public(holder_id) = holder else { - anyhow::bail!("Only public holder account supported for token burn"); + anyhow::bail!( + "`TokenProgramSubcommandPublic::BurnToken`: holder account must be public." + ); }; let tx_hash = Token(wallet_core) .send_burn_transaction( definition_account_id, - holder_id, + holder_account_id.into_public_identity(holder_id), amount, - &holder_account_id, ) .await?; println!("Transaction hash is {tx_hash}"); @@ -747,15 +742,15 @@ impl WalletSubcommand for TokenProgramSubcommandPublic { let (AccountIdWithPrivacy::Public(def_id), AccountIdWithPrivacy::Public(holder_id)) = (definition, holder) else { - anyhow::bail!("Only public accounts supported for token mint"); + anyhow::bail!( + "`TokenProgramSubcommandPublic::MintToken`: holder account must be public." + ); }; let tx_hash = Token(wallet_core) .send_mint_transaction( - def_id, - holder_id, + definition_account_id.into_public_identity(def_id), + holder_account_id.into_public_identity(holder_id), amount, - &definition_account_id, - &holder_account_id, ) .await?; println!("Transaction hash is {tx_hash}"); @@ -1076,12 +1071,11 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { ) -> Result { match self { Self::TransferTokenShieldedForeign { - sender_account_id, + sender, recipient_npk, recipient_vpk, recipient_identifier, balance_to_move, - sender_mention, } => { let recipient_npk_res = hex::decode(recipient_npk)?; let mut recipient_npk = [0; 32]; @@ -1097,12 +1091,11 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { let (tx_hash, _) = Token(wallet_core) .send_transfer_transaction_shielded_foreign_account( - sender_account_id, + sender.expect("sender set during Send dispatch"), recipient_npk, recipient_vpk, recipient_identifier.unwrap_or_else(rand::random), balance_to_move, - &sender_mention, ) .await?; @@ -1119,17 +1112,15 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } Self::TransferTokenShieldedOwned { - sender_account_id, + sender, recipient_account_id, balance_to_move, - sender_mention, } => { let (tx_hash, secret_recipient) = Token(wallet_core) .send_transfer_transaction_shielded_owned_account( - sender_account_id, + sender.expect("sender set during Send dispatch"), recipient_account_id, balance_to_move, - &sender_mention, ) .await?; @@ -1371,16 +1362,14 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand { let (AccountIdWithPrivacy::Public(def_id), AccountIdWithPrivacy::Public(sup_id)) = (definition, supply) else { - anyhow::bail!("Only public accounts supported for new token definition"); + anyhow::bail!("`NewPublicDefPublicSupp`: unexpected private account received."); }; let tx_hash = Token(wallet_core) .send_new_definition( - def_id, - sup_id, + definition_account_id.into_public_identity(def_id), + supply_account_id.into_public_identity(sup_id), name, total_supply, - &definition_account_id, - &supply_account_id, ) .await?; println!("Transaction hash is {tx_hash}"); diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 4978c13b..6c89f3dc 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -77,24 +77,12 @@ pub enum ExecutionFailureKind { AccountDataError(AccountId), #[error("Failed to build transaction: {0}")] TransactionBuildError(#[from] nssa::error::NssaError), + #[error("Failed to sign transaction: {0}")] + SignError(anyhow::Error), #[error(transparent)] KeycardError(#[from] pyo3::PyErr), } -impl ExecutionFailureKind { - /// Convert an [`anyhow::Error`] (e.g. from [`SigningGroup`]) into a keycard error. - #[must_use] - #[expect( - clippy::needless_pass_by_value, - reason = "used as a method reference in map_err" - )] - pub fn from_anyhow(e: anyhow::Error) -> Self { - Self::KeycardError(pyo3::PyErr::new::( - e.to_string(), - )) - } -} - #[expect(clippy::partial_pub_fields, reason = "TODO: make all fields private")] pub struct WalletCore { config_path: PathBuf, @@ -606,7 +594,7 @@ impl WalletCore { let message_hash = message.hash(); let signatures_public_keys = acc_manager .sign_message(message_hash) - .map_err(ExecutionFailureKind::from_anyhow)?; + .map_err(ExecutionFailureKind::SignError)?; let witness_set = nssa::privacy_preserving_transaction::witness_set::WitnessSet::from_raw_parts( @@ -679,7 +667,7 @@ impl WalletCore { let message_hash = message.hash(); let signatures_public_keys = acc_manager .sign_message(message_hash) - .map_err(ExecutionFailureKind::from_anyhow)?; + .map_err(ExecutionFailureKind::SignError)?; let witness_set = nssa::public_transaction::WitnessSet::from_raw_parts(signatures_public_keys); diff --git a/wallet/src/program_facades/amm.rs b/wallet/src/program_facades/amm.rs index b47b8fdd..1676ee18 100644 --- a/wallet/src/program_facades/amm.rs +++ b/wallet/src/program_facades/amm.rs @@ -3,64 +3,43 @@ use common::HashType; use nssa::{AccountId, program::Program}; use token_core::TokenHolding; -use crate::{AccountIdentity, ExecutionFailureKind, WalletCore, cli::CliAccountMention}; +use crate::{AccountIdentity, ExecutionFailureKind, WalletCore}; pub struct Amm<'wallet>(pub &'wallet WalletCore); impl Amm<'_> { - #[expect(clippy::too_many_arguments, reason = "each parameter is distinct")] pub async fn send_new_definition( &self, - user_holding_a: AccountId, - user_holding_b: AccountId, - user_holding_lp: AccountId, + user_holding_a: AccountIdentity, + user_holding_b: AccountIdentity, + user_holding_lp: AccountIdentity, balance_a: u128, balance_b: u128, - user_holding_a_mention: &CliAccountMention, - user_holding_b_mention: &CliAccountMention, - user_holding_lp_mention: &CliAccountMention, ) -> Result { - let user_holding_a_identity = user_holding_a_mention.key_path().map_or( - AccountIdentity::Public(user_holding_a), - |key_path| AccountIdentity::PublicKeycard { - account_id: user_holding_a, - key_path: key_path.to_owned(), - }, - ); - - let user_holding_b_identity = user_holding_b_mention.key_path().map_or( - AccountIdentity::Public(user_holding_b), - |key_path| AccountIdentity::PublicKeycard { - account_id: user_holding_b, - key_path: key_path.to_owned(), - }, - ); - - let user_holding_lp_identity = user_holding_lp_mention.key_path().map_or( - AccountIdentity::Public(user_holding_lp), - |key_path| AccountIdentity::PublicKeycard { - account_id: user_holding_lp, - key_path: key_path.to_owned(), - }, - ); + let a_id = user_holding_a + .public_account_id() + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; + let b_id = user_holding_b + .public_account_id() + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; let program = Program::amm(); let amm_program_id = Program::amm().id(); let user_a_acc = self .0 - .get_account_public(user_holding_a) + .get_account_public(a_id) .await .map_err(ExecutionFailureKind::SequencerError)?; let user_b_acc = self .0 - .get_account_public(user_holding_b) + .get_account_public(b_id) .await .map_err(ExecutionFailureKind::SequencerError)?; let definition_token_a_id = TokenHolding::try_from(&user_a_acc.data) - .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_a))? + .map_err(|_err| ExecutionFailureKind::AccountDataError(a_id))? .definition_id(); let definition_token_b_id = TokenHolding::try_from(&user_b_acc.data) - .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_b))? + .map_err(|_err| ExecutionFailureKind::AccountDataError(b_id))? .definition_id(); let amm_pool = @@ -83,9 +62,9 @@ impl Amm<'_> { AccountIdentity::PublicNoSign(vault_holding_a), AccountIdentity::PublicNoSign(vault_holding_b), AccountIdentity::PublicNoSign(pool_lp), - user_holding_a_identity, - user_holding_b_identity, - user_holding_lp_identity, + user_holding_a, + user_holding_b, + user_holding_lp, ], instruction_data, &program.into(), @@ -93,35 +72,39 @@ impl Amm<'_> { .await } - #[expect(clippy::too_many_arguments, reason = "each parameter is distinct")] pub async fn send_swap_exact_input( &self, - user_holding_a: AccountId, - user_holding_b: AccountId, + user_holding_a: AccountIdentity, + user_holding_b: AccountIdentity, swap_amount_in: u128, min_amount_out: u128, token_definition_id_in: AccountId, - user_holding_a_mention: &CliAccountMention, - user_holding_b_mention: &CliAccountMention, ) -> Result { + let a_id = user_holding_a + .public_account_id() + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; + let b_id = user_holding_b + .public_account_id() + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; + let program = Program::amm(); let amm_program_id = Program::amm().id(); let user_a_acc = self .0 - .get_account_public(user_holding_a) + .get_account_public(a_id) .await .map_err(ExecutionFailureKind::SequencerError)?; let user_b_acc = self .0 - .get_account_public(user_holding_b) + .get_account_public(b_id) .await .map_err(ExecutionFailureKind::SequencerError)?; let definition_token_a_id = TokenHolding::try_from(&user_a_acc.data) - .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_a))? + .map_err(|_err| ExecutionFailureKind::AccountDataError(a_id))? .definition_id(); let definition_token_b_id = TokenHolding::try_from(&user_b_acc.data) - .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_b))? + .map_err(|_err| ExecutionFailureKind::AccountDataError(b_id))? .definition_id(); let amm_pool = @@ -145,27 +128,15 @@ impl Amm<'_> { } let user_a_signing_identity = if token_definition_id_in == definition_token_a_id { - user_holding_a_mention.key_path().map_or( - AccountIdentity::Public(user_holding_a), - |key_path| AccountIdentity::PublicKeycard { - account_id: user_holding_a, - key_path: key_path.to_owned(), - }, - ) + user_holding_a } else { - AccountIdentity::PublicNoSign(user_holding_a) + AccountIdentity::PublicNoSign(a_id) }; let user_b_signing_identity = if token_definition_id_in == definition_token_b_id { - user_holding_b_mention.key_path().map_or( - AccountIdentity::Public(user_holding_b), - |key_path| AccountIdentity::PublicKeycard { - account_id: user_holding_b, - key_path: key_path.to_owned(), - }, - ) + user_holding_b } else { - AccountIdentity::PublicNoSign(user_holding_b) + AccountIdentity::PublicNoSign(b_id) }; self.0 @@ -183,35 +154,39 @@ impl Amm<'_> { .await } - #[expect(clippy::too_many_arguments, reason = "each parameter is distinct")] pub async fn send_swap_exact_output( &self, - user_holding_a: AccountId, - user_holding_b: AccountId, + user_holding_a: AccountIdentity, + user_holding_b: AccountIdentity, exact_amount_out: u128, max_amount_in: u128, token_definition_id_in: AccountId, - user_holding_a_mention: &CliAccountMention, - user_holding_b_mention: &CliAccountMention, ) -> Result { + let a_id = user_holding_a + .public_account_id() + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; + let b_id = user_holding_b + .public_account_id() + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; + let program = Program::amm(); let amm_program_id = Program::amm().id(); let user_a_acc = self .0 - .get_account_public(user_holding_a) + .get_account_public(a_id) .await .map_err(ExecutionFailureKind::SequencerError)?; let user_b_acc = self .0 - .get_account_public(user_holding_b) + .get_account_public(b_id) .await .map_err(ExecutionFailureKind::SequencerError)?; let definition_token_a_id = TokenHolding::try_from(&user_a_acc.data) - .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_a))? + .map_err(|_err| ExecutionFailureKind::AccountDataError(a_id))? .definition_id(); let definition_token_b_id = TokenHolding::try_from(&user_b_acc.data) - .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_b))? + .map_err(|_err| ExecutionFailureKind::AccountDataError(b_id))? .definition_id(); let amm_pool = @@ -235,27 +210,15 @@ impl Amm<'_> { } let user_a_signing_identity = if token_definition_id_in == definition_token_a_id { - user_holding_a_mention.key_path().map_or( - AccountIdentity::Public(user_holding_a), - |key_path| AccountIdentity::PublicKeycard { - account_id: user_holding_a, - key_path: key_path.to_owned(), - }, - ) + user_holding_a } else { - AccountIdentity::PublicNoSign(user_holding_a) + AccountIdentity::PublicNoSign(a_id) }; let user_b_signing_identity = if token_definition_id_in == definition_token_b_id { - user_holding_b_mention.key_path().map_or( - AccountIdentity::Public(user_holding_b), - |key_path| AccountIdentity::PublicKeycard { - account_id: user_holding_b, - key_path: key_path.to_owned(), - }, - ) + user_holding_b } else { - AccountIdentity::PublicNoSign(user_holding_b) + AccountIdentity::PublicNoSign(b_id) }; self.0 @@ -273,61 +236,40 @@ impl Amm<'_> { .await } - #[expect(clippy::too_many_arguments, reason = "each parameter is distinct")] pub async fn send_add_liquidity( &self, - user_holding_a: AccountId, - user_holding_b: AccountId, - user_holding_lp: AccountId, + user_holding_a: AccountIdentity, + user_holding_b: AccountIdentity, + user_holding_lp: AccountIdentity, min_amount_liquidity: u128, max_amount_to_add_token_a: u128, max_amount_to_add_token_b: u128, - user_holding_a_mention: &CliAccountMention, - user_holding_b_mention: &CliAccountMention, - user_holding_lp_mention: &CliAccountMention, ) -> Result { - let user_holding_a_identity = user_holding_a_mention.key_path().map_or( - AccountIdentity::Public(user_holding_a), - |key_path| AccountIdentity::PublicKeycard { - account_id: user_holding_a, - key_path: key_path.to_owned(), - }, - ); - - let user_holding_b_identity = user_holding_b_mention.key_path().map_or( - AccountIdentity::Public(user_holding_b), - |key_path| AccountIdentity::PublicKeycard { - account_id: user_holding_b, - key_path: key_path.to_owned(), - }, - ); - - let user_holding_lp_identity = user_holding_lp_mention.key_path().map_or( - AccountIdentity::Public(user_holding_lp), - |key_path| AccountIdentity::PublicKeycard { - account_id: user_holding_lp, - key_path: key_path.to_owned(), - }, - ); + let a_id = user_holding_a + .public_account_id() + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; + let b_id = user_holding_b + .public_account_id() + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; let program = Program::amm(); let amm_program_id = Program::amm().id(); let user_a_acc = self .0 - .get_account_public(user_holding_a) + .get_account_public(a_id) .await .map_err(ExecutionFailureKind::SequencerError)?; let user_b_acc = self .0 - .get_account_public(user_holding_b) + .get_account_public(b_id) .await .map_err(ExecutionFailureKind::SequencerError)?; let definition_token_a_id = TokenHolding::try_from(&user_a_acc.data) - .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_a))? + .map_err(|_err| ExecutionFailureKind::AccountDataError(a_id))? .definition_id(); let definition_token_b_id = TokenHolding::try_from(&user_b_acc.data) - .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_b))? + .map_err(|_err| ExecutionFailureKind::AccountDataError(b_id))? .definition_id(); let amm_pool = @@ -350,9 +292,9 @@ impl Amm<'_> { AccountIdentity::PublicNoSign(vault_holding_a), AccountIdentity::PublicNoSign(vault_holding_b), AccountIdentity::PublicNoSign(pool_lp), - user_holding_a_identity, - user_holding_b_identity, - user_holding_lp_identity, + user_holding_a, + user_holding_b, + user_holding_lp, ], instruction_data, &program.into(), @@ -360,25 +302,15 @@ impl Amm<'_> { .await } - #[expect(clippy::too_many_arguments, reason = "each parameter is distinct")] pub async fn send_remove_liquidity( &self, user_holding_a: AccountId, user_holding_b: AccountId, - user_holding_lp: AccountId, + user_holding_lp: AccountIdentity, remove_liquidity_amount: u128, min_amount_to_remove_token_a: u128, min_amount_to_remove_token_b: u128, - user_holding_lp_mention: &CliAccountMention, ) -> Result { - let user_holding_lp_identity = user_holding_lp_mention.key_path().map_or( - AccountIdentity::Public(user_holding_lp), - |key_path| AccountIdentity::PublicKeycard { - account_id: user_holding_lp, - key_path: key_path.to_owned(), - }, - ); - let program = Program::amm(); let amm_program_id = Program::amm().id(); let user_a_acc = self @@ -421,7 +353,7 @@ impl Amm<'_> { AccountIdentity::PublicNoSign(pool_lp), AccountIdentity::PublicNoSign(user_holding_a), AccountIdentity::PublicNoSign(user_holding_b), - user_holding_lp_identity, + user_holding_lp, ], instruction_data, &program.into(), diff --git a/wallet/src/program_facades/ata.rs b/wallet/src/program_facades/ata.rs index d3a24fa3..6aba469c 100644 --- a/wallet/src/program_facades/ata.rs +++ b/wallet/src/program_facades/ata.rs @@ -7,26 +7,19 @@ use nssa::{ }; use nssa_core::SharedSecretKey; -use crate::{AccountIdentity, ExecutionFailureKind, WalletCore, cli::CliAccountMention}; +use crate::{AccountIdentity, ExecutionFailureKind, WalletCore}; pub struct Ata<'wallet>(pub &'wallet WalletCore); impl Ata<'_> { pub async fn send_create( &self, - owner_id: AccountId, + owner: AccountIdentity, definition_id: AccountId, - owner_mention: &CliAccountMention, ) -> Result { - let owner_identity = - owner_mention - .key_path() - .map_or(AccountIdentity::Public(owner_id), |key_path| { - AccountIdentity::PublicKeycard { - account_id: owner_id, - key_path: key_path.to_owned(), - } - }); + let owner_id = owner + .public_account_id() + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; let program = Program::ata(); let ata_program_id = program.id(); @@ -41,7 +34,7 @@ impl Ata<'_> { self.0 .send_pub_tx( vec![ - owner_identity, + owner, AccountIdentity::PublicNoSign(definition_id), AccountIdentity::PublicNoSign(ata_id), ], @@ -53,21 +46,14 @@ impl Ata<'_> { pub async fn send_transfer( &self, - owner_id: AccountId, + owner: AccountIdentity, definition_id: AccountId, recipient_id: AccountId, amount: u128, - owner_mention: &CliAccountMention, ) -> Result { - let owner_identity = - owner_mention - .key_path() - .map_or(AccountIdentity::Public(owner_id), |key_path| { - AccountIdentity::PublicKeycard { - account_id: owner_id, - key_path: key_path.to_owned(), - } - }); + let owner_id = owner + .public_account_id() + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; let program = Program::ata(); let ata_program_id = program.id(); @@ -85,7 +71,7 @@ impl Ata<'_> { self.0 .send_pub_tx( vec![ - owner_identity, + owner, AccountIdentity::PublicNoSign(sender_ata_id), AccountIdentity::PublicNoSign(recipient_id), ], @@ -97,20 +83,13 @@ impl Ata<'_> { pub async fn send_burn( &self, - owner_id: AccountId, + owner: AccountIdentity, definition_id: AccountId, amount: u128, - owner_mention: &CliAccountMention, ) -> Result { - let owner_identity = - owner_mention - .key_path() - .map_or(AccountIdentity::Public(owner_id), |key_path| { - AccountIdentity::PublicKeycard { - account_id: owner_id, - key_path: key_path.to_owned(), - } - }); + let owner_id = owner + .public_account_id() + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; let program = Program::ata(); let ata_program_id = program.id(); @@ -128,7 +107,7 @@ impl Ata<'_> { self.0 .send_pub_tx( vec![ - owner_identity, + owner, AccountIdentity::PublicNoSign(holder_ata_id), AccountIdentity::PublicNoSign(definition_id), ], diff --git a/wallet/src/program_facades/native_token_transfer/public.rs b/wallet/src/program_facades/native_token_transfer/public.rs index 8d441b2b..66930fef 100644 --- a/wallet/src/program_facades/native_token_transfer/public.rs +++ b/wallet/src/program_facades/native_token_transfer/public.rs @@ -1,46 +1,25 @@ use authenticated_transfer_core::Instruction as AuthTransferInstruction; use common::HashType; -use nssa::{AccountId, program::Program}; +use nssa::program::Program; use super::NativeTokenTransfer; use crate::{ - AccountIdentity, ExecutionFailureKind, cli::CliAccountMention, + AccountIdentity, ExecutionFailureKind, program_facades::native_token_transfer::auth_transfer_preparation, }; impl NativeTokenTransfer<'_> { pub async fn send_public_transfer( &self, - from: AccountId, - to: AccountId, + from: AccountIdentity, + to: AccountIdentity, balance_to_move: u128, - from_mention: &CliAccountMention, - to_mention: &CliAccountMention, ) -> Result { let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); - let from_identity = - from_mention - .key_path() - .map_or(AccountIdentity::Public(from), |key_path| { - AccountIdentity::PublicKeycard { - account_id: from, - key_path: key_path.to_owned(), - } - }); - - let to_identity = to_mention - .key_path() - .map_or(AccountIdentity::Public(to), |key_path| { - AccountIdentity::PublicKeycard { - account_id: to, - key_path: key_path.to_owned(), - } - }); - self.0 .send_pub_tx_with_pre_check( - vec![from_identity, to_identity], + vec![from, to], instruction_data, &program.into(), tx_pre_check, @@ -50,24 +29,13 @@ impl NativeTokenTransfer<'_> { pub async fn register_account( &self, - from: AccountId, - account_mention: &CliAccountMention, + account: AccountIdentity, ) -> Result { - let from_identity = - account_mention - .key_path() - .map_or(AccountIdentity::Public(from), |key_path| { - AccountIdentity::PublicKeycard { - account_id: from, - key_path: key_path.to_owned(), - } - }); - let program = Program::authenticated_transfer_program(); let instruction_data = Program::serialize_instruction(AuthTransferInstruction::Initialize)?; self.0 - .send_pub_tx(vec![from_identity], instruction_data, &program.into()) + .send_pub_tx(vec![account], instruction_data, &program.into()) .await } } diff --git a/wallet/src/program_facades/native_token_transfer/shielded.rs b/wallet/src/program_facades/native_token_transfer/shielded.rs index 339673fb..e745346a 100644 --- a/wallet/src/program_facades/native_token_transfer/shielded.rs +++ b/wallet/src/program_facades/native_token_transfer/shielded.rs @@ -3,31 +3,20 @@ use nssa::AccountId; use nssa_core::{Identifier, NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey}; use super::{NativeTokenTransfer, auth_transfer_preparation}; -use crate::{AccountIdentity, ExecutionFailureKind, cli::CliAccountMention}; +use crate::{AccountIdentity, ExecutionFailureKind}; impl NativeTokenTransfer<'_> { pub async fn send_shielded_transfer( &self, - from: AccountId, + from: AccountIdentity, to: AccountId, balance_to_move: u128, - from_mention: &CliAccountMention, ) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> { - let from_identity = - from_mention - .key_path() - .map_or(AccountIdentity::Public(from), |key_path| { - AccountIdentity::PublicKeycard { - account_id: from, - key_path: key_path.to_owned(), - } - }); - let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); self.0 .send_privacy_preserving_tx_with_pre_check( vec![ - from_identity, + from, self.0 .resolve_private_account(to) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, @@ -48,28 +37,17 @@ impl NativeTokenTransfer<'_> { pub async fn send_shielded_transfer_to_outer_account( &self, - from: AccountId, + from: AccountIdentity, to_npk: NullifierPublicKey, to_vpk: ViewingPublicKey, to_identifier: Identifier, balance_to_move: u128, - from_mention: &CliAccountMention, ) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> { - let from_identity = - from_mention - .key_path() - .map_or(AccountIdentity::Public(from), |key_path| { - AccountIdentity::PublicKeycard { - account_id: from, - key_path: key_path.to_owned(), - } - }); - let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); self.0 .send_privacy_preserving_tx_with_pre_check( vec![ - from_identity, + from, AccountIdentity::PrivateForeign { npk: to_npk, vpk: to_vpk, diff --git a/wallet/src/program_facades/token.rs b/wallet/src/program_facades/token.rs index 9d327f48..4d026a66 100644 --- a/wallet/src/program_facades/token.rs +++ b/wallet/src/program_facades/token.rs @@ -3,47 +3,25 @@ use nssa::{AccountId, program::Program}; use nssa_core::{Identifier, NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey}; use token_core::Instruction; -use crate::{AccountIdentity, ExecutionFailureKind, WalletCore, cli::CliAccountMention}; +use crate::{AccountIdentity, ExecutionFailureKind, WalletCore}; pub struct Token<'wallet>(pub &'wallet WalletCore); impl Token<'_> { pub async fn send_new_definition( &self, - definition_account_id: AccountId, - supply_account_id: AccountId, + definition: AccountIdentity, + supply: AccountIdentity, name: String, total_supply: u128, - definition_mention: &CliAccountMention, - supply_mention: &CliAccountMention, ) -> Result { - let definition_identity = definition_mention.key_path().map_or( - AccountIdentity::Public(definition_account_id), - |key_path| AccountIdentity::PublicKeycard { - account_id: definition_account_id, - key_path: key_path.to_owned(), - }, - ); - - let supply_identity = supply_mention.key_path().map_or( - AccountIdentity::Public(supply_account_id), - |key_path| AccountIdentity::PublicKeycard { - account_id: supply_account_id, - key_path: key_path.to_owned(), - }, - ); - let program = Program::token(); let instruction = Instruction::NewFungibleDefinition { name, total_supply }; let instruction_data = Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 - .send_pub_tx( - vec![definition_identity, supply_identity], - instruction_data, - &program.into(), - ) + .send_pub_tx(vec![definition, supply], instruction_data, &program.into()) .await } @@ -146,28 +124,10 @@ impl Token<'_> { pub async fn send_transfer_transaction( &self, - sender_account_id: AccountId, - recipient_account_id: AccountId, + sender: AccountIdentity, + recipient: AccountIdentity, amount: u128, - sender_mention: &CliAccountMention, - recipient_mention: &CliAccountMention, ) -> Result { - let sender_identity = sender_mention.key_path().map_or( - AccountIdentity::Public(sender_account_id), - |key_path| AccountIdentity::PublicKeycard { - account_id: sender_account_id, - key_path: key_path.to_owned(), - }, - ); - - let recipient_identity = recipient_mention.key_path().map_or( - AccountIdentity::Public(recipient_account_id), - |key_path| AccountIdentity::PublicKeycard { - account_id: recipient_account_id, - key_path: key_path.to_owned(), - }, - ); - let program = Program::token(); let instruction = Instruction::Transfer { amount_to_transfer: amount, @@ -176,11 +136,7 @@ impl Token<'_> { Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 - .send_pub_tx( - vec![sender_identity, recipient_identity], - instruction_data, - &program.into(), - ) + .send_pub_tx(vec![sender, recipient], instruction_data, &program.into()) .await } @@ -291,19 +247,10 @@ impl Token<'_> { pub async fn send_transfer_transaction_shielded_owned_account( &self, - sender_account_id: AccountId, + sender: AccountIdentity, recipient_account_id: AccountId, amount: u128, - sender_mention: &CliAccountMention, ) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> { - let sender_identity = sender_mention.key_path().map_or( - AccountIdentity::Public(sender_account_id), - |key_path| AccountIdentity::PublicKeycard { - account_id: sender_account_id, - key_path: key_path.to_owned(), - }, - ); - let instruction = Instruction::Transfer { amount_to_transfer: amount, }; @@ -312,7 +259,7 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - sender_identity, + sender, self.0 .resolve_private_account(recipient_account_id) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, @@ -332,21 +279,12 @@ impl Token<'_> { pub async fn send_transfer_transaction_shielded_foreign_account( &self, - sender_account_id: AccountId, + sender: AccountIdentity, recipient_npk: NullifierPublicKey, recipient_vpk: ViewingPublicKey, recipient_identifier: Identifier, amount: u128, - sender_mention: &CliAccountMention, ) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> { - let sender_identity = sender_mention.key_path().map_or( - AccountIdentity::Public(sender_account_id), - |key_path| AccountIdentity::PublicKeycard { - account_id: sender_account_id, - key_path: key_path.to_owned(), - }, - ); - let instruction = Instruction::Transfer { amount_to_transfer: amount, }; @@ -355,7 +293,7 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - sender_identity, + sender, AccountIdentity::PrivateForeign { npk: recipient_npk, vpk: recipient_vpk, @@ -378,18 +316,9 @@ impl Token<'_> { pub async fn send_burn_transaction( &self, definition_account_id: AccountId, - holder_account_id: AccountId, + holder: AccountIdentity, amount: u128, - holder_mention: &CliAccountMention, ) -> Result { - let holder_identity = holder_mention.key_path().map_or( - AccountIdentity::Public(holder_account_id), - |key_path| AccountIdentity::PublicKeycard { - account_id: holder_account_id, - key_path: key_path.to_owned(), - }, - ); - let program = Program::token(); let instruction = Instruction::Burn { amount_to_burn: amount, @@ -399,10 +328,7 @@ impl Token<'_> { self.0 .send_pub_tx( - vec![ - AccountIdentity::PublicNoSign(definition_account_id), - holder_identity, - ], + vec![AccountIdentity::PublicNoSign(definition_account_id), holder], instruction_data, &program.into(), ) @@ -511,28 +437,10 @@ impl Token<'_> { pub async fn send_mint_transaction( &self, - definition_account_id: AccountId, - holder_account_id: AccountId, + definition: AccountIdentity, + holder: AccountIdentity, amount: u128, - definition_mention: &CliAccountMention, - holder_mention: &CliAccountMention, ) -> Result { - let definition_identity = definition_mention.key_path().map_or( - AccountIdentity::Public(definition_account_id), - |key_path| AccountIdentity::PublicKeycard { - account_id: definition_account_id, - key_path: key_path.to_owned(), - }, - ); - - let holder_identity = holder_mention.key_path().map_or( - AccountIdentity::Public(holder_account_id), - |key_path| AccountIdentity::PublicKeycard { - account_id: holder_account_id, - key_path: key_path.to_owned(), - }, - ); - let program = Program::token(); let instruction = Instruction::Mint { amount_to_mint: amount, @@ -541,11 +449,7 @@ impl Token<'_> { Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 - .send_pub_tx( - vec![definition_identity, holder_identity], - instruction_data, - &program.into(), - ) + .send_pub_tx(vec![definition, holder], instruction_data, &program.into()) .await } diff --git a/wallet/src/signing.rs b/wallet/src/signing.rs index 661bbdb0..2ebf0e0b 100644 --- a/wallet/src/signing.rs +++ b/wallet/src/signing.rs @@ -15,17 +15,14 @@ impl KeycardSessionContext { } } - pub fn get_or_connect<'py>( - &'py mut self, - py: Python<'py>, - ) -> pyo3::PyResult<&'py KeycardWallet> { + pub fn get_or_connect(&mut self, py: Python<'_>) -> pyo3::PyResult<&KeycardWallet> { if self.wallet.is_none() { python_path::add_python_path(py)?; let wallet = KeycardWallet::new(py)?; wallet.connect(py, &self.pin)?; self.wallet = Some(wallet); } - Ok(self.wallet.as_ref().unwrap()) + Ok(self.wallet.as_ref().expect("wallet was just inserted")) } pub fn close(self, py: Python<'_>) {