refactored

This commit is contained in:
jonesmarvin8 2026-05-17 12:32:43 -04:00
parent c2dad4b602
commit a216234a95
17 changed files with 458 additions and 627 deletions

View File

@ -57,6 +57,7 @@ async fn fund_private_pda(
Program::serialize_instruction((seed, amount, auth_transfer_id, true))
.context("failed to serialize pda_fund_spend_proxy fund instruction")?,
proxy_program,
&None,
)
.await
.map_err(|e| anyhow::anyhow!("{e}"))?;
@ -93,6 +94,7 @@ async fn spend_private_pda(
Program::serialize_instruction((seed, amount, auth_transfer_id, false))
.context("failed to serialize pda_fund_spend_proxy instruction")?,
spend_program,
&None,
)
.await
.map_err(|e| anyhow::anyhow!("{e}"))?;

View File

@ -100,29 +100,38 @@ wallet token mint \
--amount 0
echo "LEZ holding initialized for keycard path 6"
# Keycard path 7: LEE holding
# Keycard path 7: LEE holding (different definition — safe to submit immediately)
wallet token mint \
--definition "m/44'/60'/0'/0/4" \
--holder "m/44'/60'/0'/0/7" \
--amount 0
echo "LEE holding initialized for keycard path 7"
# pub-receiver: public LEZ holding (for token transfer test)
# Wait for path2 (LEZ def) and path4 (LEE def) nonces to be confirmed before reusing them
sleep 15
# pub-receiver: public LEZ holding
wallet token mint \
--definition "m/44'/60'/0'/0/2" \
--holder pub-receiver \
--amount 0
echo "LEZ holding initialized for pub-receiver"
# AMM seed accounts
wallet token mint \
--definition "m/44'/60'/0'/0/2" \
--holder amm-lez-fund \
--amount 0
# amm-lee-fund: LEE holding (different definition — safe to submit with pub-receiver)
wallet token mint \
--definition "m/44'/60'/0'/0/4" \
--holder amm-lee-fund \
--amount 0
echo "LEE holding initialized for amm-lee-fund"
# Wait for path2 nonce to be confirmed before the third LEZ mint
sleep 15
# amm-lez-fund: LEZ holding
wallet token mint \
--definition "m/44'/60'/0'/0/2" \
--holder amm-lez-fund \
--amount 0
echo "AMM seed holdings initialized"
# =============================================================================
@ -143,6 +152,9 @@ wallet token send \
--amount 20000
echo "Transferred 20000 LEE → keycard path 7"
# Wait for path3 and path5 nonces to be confirmed before reusing them
sleep 15
wallet token send \
--from "m/44'/60'/0'/0/3" \
--to amm-lez-fund \
@ -292,19 +304,6 @@ wallet account get --account-id "m/44'/60'/0'/0/7"
# =============================================================================
# (9) Add liquidity — keycard accounts for holding A (path 6), B (path 7), LP (path 8)
# =============================================================================
echo ""
echo "=== (9) Initialize LP holding (keycard path 8) before add-liquidity ==="
wallet token mint \
--definition "Public/$LP_DEF_ID" \
--holder "m/44'/60'/0'/0/8" \
--amount 0
echo "Keycard path 8 (LP holding) initialized"
sleep 15
echo "Keycard path 8 (LP holding) state (after init):"
wallet account get --account-id "m/44'/60'/0'/0/8"
echo ""
echo "=== (9) Add liquidity (keycard path 6=LEZ, path 7=LEE, path 8=LP) ==="
wallet amm add-liquidity \

View File

@ -3,11 +3,14 @@ use std::path::PathBuf;
use nssa::{AccountId, PublicKey, Signature};
use nssa_core::NullifierPublicKey;
use pyo3::{prelude::*, types::PyAny};
use zeroize::Zeroizing;
use serde::{Deserialize, Serialize};
use zeroize::Zeroizing;
pub mod python_path;
/// NSK and VSK as fixed-length zeroizing byte arrays.
type PrivateKeyPair = (Zeroizing<[u8; 32]>, Zeroizing<[u8; 32]>);
// TODO: encrypt at rest alongside broader wallet storage encryption work.
#[derive(Serialize, Deserialize)]
pub struct KeycardPairingData {
@ -140,6 +143,10 @@ impl KeycardWallet {
})
}
#[expect(
clippy::arithmetic_side_effects,
reason = "64 - s_stripped.len() is safe: s_stripped.len() ≤ 31 because py_signature.len() is in [32, 63]"
)]
pub fn sign_message_for_path(
&self,
py: Python,
@ -152,12 +159,19 @@ impl KeycardWallet {
.call_method1("sign_message_for_path", (message, path))?
.extract()?;
// The keycard Python library strips the leading zero from the S component when
// S < 2^248. Re-insert it so the slice is always the expected 64 bytes (R || S).
let py_signature = if py_signature.len() == 63 {
// The keycard Python library strips leading zeros from S when S < 2^(8k) for some k.
// Left-pad S back to 32 bytes so the full signature is always 64 bytes (R || S).
let py_signature = if py_signature.len() < 64 {
if py_signature.len() < 32 {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"signature from keycard too short: {} bytes",
py_signature.len()
)));
}
let s_stripped = &py_signature[32..];
let mut padded = [0_u8; 64];
padded[..32].copy_from_slice(&py_signature[..32]);
padded[33..].copy_from_slice(&py_signature[32..]);
padded[(64 - s_stripped.len())..].copy_from_slice(s_stripped);
padded.to_vec()
} else {
py_signature
@ -212,11 +226,7 @@ impl KeycardWallet {
Ok(format!("Public/{}", AccountId::from(&public_key)))
}
pub fn get_private_keys_for_path(
&self,
py: Python,
path: &str,
) -> PyResult<(Zeroizing<[u8; 32]>, Zeroizing<[u8; 32]>)> {
pub fn get_private_keys_for_path(&self, py: Python, path: &str) -> PyResult<PrivateKeyPair> {
let (raw_nsk, raw_vsk): (Vec<u8>, Vec<u8>) = self
.instance
.bind(py)
@ -233,7 +243,7 @@ impl KeycardWallet {
raw_nsk.len()
)));
}
let mut arr = Zeroizing::new([0u8; 32]);
let mut arr = Zeroizing::new([0_u8; 32]);
arr.copy_from_slice(&raw_nsk);
arr
};
@ -245,7 +255,7 @@ impl KeycardWallet {
raw_vsk.len()
)));
}
let mut arr = Zeroizing::new([0u8; 32]);
let mut arr = Zeroizing::new([0_u8; 32]);
arr.copy_from_slice(&raw_vsk);
arr
};
@ -256,7 +266,7 @@ impl KeycardWallet {
pub fn get_private_keys_for_path_with_connect(
pin: &str,
path: &str,
) -> PyResult<(Zeroizing<[u8; 32]>, Zeroizing<[u8; 32]>)> {
) -> PyResult<PrivateKeyPair> {
Python::with_gil(|py| {
python_path::add_python_path(py)?;

View File

@ -89,7 +89,7 @@ impl PrivateAccountKind {
/// Borsh layout (all integers little-endian, variant index is u8):
///
/// ```text
/// Regular(ident): 0x00 || ident (16 LE) || [0u8; 64]
/// Regular(ident): 0x00 || ident (16 LE) || [0_u8; 64]
/// Pda { program_id, seed, ident }: 0x01 || program_id (32) || seed (32) || ident (16 LE)
/// ```
///

View File

@ -208,7 +208,7 @@ pub mod tests {
let nonces_bytes: &[u8] = &[1, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
// all remaining vec fields are empty: u32 len=0
let empty_vec_bytes: &[u8] = &[0_u8; 4];
// validity windows: unbounded = {from: None (0u8), to: None (0u8)}
// validity windows: unbounded = {from: None (0_u8), to: None (0_u8)}
let unbounded_window_bytes: &[u8] = &[0_u8; 2];
let expected_borsh_vec: Vec<u8> = [

View File

@ -188,7 +188,10 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded(
};
let to_identifier = u128::from_le_bytes(unsafe { (*to_identifier).data });
let amount = u128::from_le_bytes(unsafe { *amount });
let key_path = optional_c_str(key_path);
let from_mention = optional_c_str(key_path).map_or_else(
|| CliAccountMention::Id(AccountIdWithPrivacy::Public(from_id)),
CliAccountMention::KeyPath,
);
let transfer = NativeTokenTransfer(&wallet);
@ -198,7 +201,7 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded(
to_vpk,
to_identifier,
amount,
&key_path,
&from_mention,
)) {
Ok((tx_hash, _shared_key)) => {
let tx_hash = CString::new(tx_hash.to_string())
@ -466,11 +469,14 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded_owned(
let from_id = AccountId::new(unsafe { (*from).data });
let to_id = AccountId::new(unsafe { (*to).data });
let amount = u128::from_le_bytes(unsafe { *amount });
let key_path = optional_c_str(key_path);
let from_mention = optional_c_str(key_path).map_or_else(
|| CliAccountMention::Id(AccountIdWithPrivacy::Public(from_id)),
CliAccountMention::KeyPath,
);
let transfer = NativeTokenTransfer(&wallet);
match block_on(transfer.send_shielded_transfer(from_id, to_id, amount, &key_path)) {
match block_on(transfer.send_shielded_transfer(from_id, to_id, amount, &from_mention)) {
Ok((tx_hash, _shared_key)) => {
let tx_hash = CString::new(tx_hash.to_string())
.map_or(ptr::null_mut(), std::ffi::CString::into_raw);

View File

@ -159,8 +159,8 @@ impl WalletSubcommand for KeycardSubcommand {
let (nsk, vsk) =
KeycardWallet::get_private_keys_for_path_with_connect(&pin, &key_path)
.map_err(anyhow::Error::from)?;
println!("NSK: {}", hex::encode(&*nsk));
println!("VSK: {}", hex::encode(&*vsk));
println!("NSK: {}", hex::encode(*nsk));
println!("VSK: {}", hex::encode(*vsk));
Ok(SubcommandReturnValue::Empty)
}
}

View File

@ -138,8 +138,10 @@ impl CliAccountMention {
Self::KeyPath(path) => {
let pin = read_pin()?;
let id_str =
keycard_wallet::KeycardWallet::get_public_account_id_for_path_with_connect(&pin, path)
.map_err(anyhow::Error::from)?;
keycard_wallet::KeycardWallet::get_public_account_id_for_path_with_connect(
&pin, path,
)
.map_err(anyhow::Error::from)?;
AccountIdWithPrivacy::from_str(&id_str)
.map_err(|e| anyhow::anyhow!("Invalid account id from keycard: {e}"))
}
@ -158,7 +160,6 @@ impl CliAccountMention {
Self::Id(_) | Self::Label(_) => None,
}
}
}
impl FromStr for CliAccountMention {

View File

@ -171,10 +171,7 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
let a_id = user_holding_a.resolve(wallet_core.storage())?;
let b_id = user_holding_b.resolve(wallet_core.storage())?;
match (a_id, b_id) {
(
AccountIdWithPrivacy::Public(a),
AccountIdWithPrivacy::Public(b),
) => {
(AccountIdWithPrivacy::Public(a), AccountIdWithPrivacy::Public(b)) => {
Amm(wallet_core)
.send_swap_exact_input(
a,
@ -205,10 +202,7 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
let a_id = user_holding_a.resolve(wallet_core.storage())?;
let b_id = user_holding_b.resolve(wallet_core.storage())?;
match (a_id, b_id) {
(
AccountIdWithPrivacy::Public(a),
AccountIdWithPrivacy::Public(b),
) => {
(AccountIdWithPrivacy::Public(a), AccountIdWithPrivacy::Public(b)) => {
Amm(wallet_core)
.send_swap_exact_output(
a,
@ -256,6 +250,7 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
max_amount_b,
&user_holding_a,
&user_holding_b,
&user_holding_lp,
)
.await?;

View File

@ -400,7 +400,12 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
Self::ShieldedOwned { from, to, amount, from_mention } => {
Self::ShieldedOwned {
from,
to,
amount,
from_mention,
} => {
let (tx_hash, secret) = NativeTokenTransfer(wallet_core)
.send_shielded_transfer(from, to, amount, &from_mention)
.await?;

View File

@ -119,17 +119,16 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
let definition_account_id = definition_account_id.resolve(wallet_core.storage())?;
let supply_account_id = supply_account_id.resolve(wallet_core.storage())?;
let underlying_subcommand = match (definition_account_id, supply_account_id) {
(
AccountIdWithPrivacy::Public(_),
AccountIdWithPrivacy::Public(_),
) => TokenProgramSubcommand::Create(
CreateNewTokenProgramSubcommand::NewPublicDefPublicSupp {
definition_account_id: def_mention,
supply_account_id: sup_mention,
name,
total_supply,
},
),
(AccountIdWithPrivacy::Public(_), AccountIdWithPrivacy::Public(_)) => {
TokenProgramSubcommand::Create(
CreateNewTokenProgramSubcommand::NewPublicDefPublicSupp {
definition_account_id: def_mention,
supply_account_id: sup_mention,
name,
total_supply,
},
)
}
(
AccountIdWithPrivacy::Public(definition_account_id),
AccountIdWithPrivacy::Private(supply_account_id),
@ -230,6 +229,7 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
sender_account_id: from,
recipient_account_id: to,
balance_to_move: amount,
sender_mention: from_mention,
},
)
}
@ -251,6 +251,7 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
recipient_vpk: to_vpk,
recipient_identifier: to_identifier,
balance_to_move: amount,
sender_mention: from_mention,
},
),
},
@ -267,14 +268,13 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
let definition = definition.resolve(wallet_core.storage())?;
let holder = holder.resolve(wallet_core.storage())?;
let underlying_subcommand = match (definition, holder) {
(
AccountIdWithPrivacy::Public(definition),
AccountIdWithPrivacy::Public(_),
) => TokenProgramSubcommand::Public(TokenProgramSubcommandPublic::BurnToken {
definition_account_id: definition,
holder_account_id: holder_mention,
amount,
}),
(AccountIdWithPrivacy::Public(definition), AccountIdWithPrivacy::Public(_)) => {
TokenProgramSubcommand::Public(TokenProgramSubcommandPublic::BurnToken {
definition_account_id: definition,
holder_account_id: holder_mention,
amount,
})
}
(
AccountIdWithPrivacy::Private(definition),
AccountIdWithPrivacy::Private(holder),
@ -338,16 +338,15 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
anyhow::bail!("List of public keys is uncomplete");
}
(Some(holder), None, None) => match (definition, holder) {
(
AccountIdWithPrivacy::Public(_),
AccountIdWithPrivacy::Public(_),
) => TokenProgramSubcommand::Public(
TokenProgramSubcommandPublic::MintToken {
definition_account_id: def_mention,
holder_account_id: hol_mention.expect("matched Some branch"),
amount,
},
),
(AccountIdWithPrivacy::Public(_), AccountIdWithPrivacy::Public(_)) => {
TokenProgramSubcommand::Public(
TokenProgramSubcommandPublic::MintToken {
definition_account_id: def_mention,
holder_account_id: hol_mention.expect("matched Some branch"),
amount,
},
)
}
(
AccountIdWithPrivacy::Private(definition),
AccountIdWithPrivacy::Private(holder),
@ -568,6 +567,8 @@ pub enum TokenProgramSubcommandShielded {
recipient_account_id: AccountId,
#[arg(short, long)]
balance_to_move: u128,
#[arg(skip)]
sender_mention: CliAccountMention,
},
// Transfer tokens using the token program
TransferTokenShieldedForeign {
@ -584,6 +585,8 @@ pub enum TokenProgramSubcommandShielded {
recipient_identifier: Option<u128>,
#[arg(short, long)]
balance_to_move: u128,
#[arg(skip)]
sender_mention: CliAccountMention,
},
// Burn tokens using the token program
BurnTokenShielded {
@ -689,7 +692,11 @@ impl WalletSubcommand for TokenProgramSubcommandPublic {
} => {
let sender = sender_account_id.resolve(wallet_core.storage())?;
let recipient = recipient_account_id.resolve(wallet_core.storage())?;
let (AccountIdWithPrivacy::Public(sender_id), AccountIdWithPrivacy::Public(recipient_id)) = (sender, recipient) else {
let (
AccountIdWithPrivacy::Public(sender_id),
AccountIdWithPrivacy::Public(recipient_id),
) = (sender, recipient)
else {
anyhow::bail!("Only public accounts supported for token transfer");
};
Token(wallet_core)
@ -713,7 +720,12 @@ impl WalletSubcommand for TokenProgramSubcommandPublic {
anyhow::bail!("Only public holder account supported for token burn");
};
Token(wallet_core)
.send_burn_transaction(definition_account_id, holder_id, amount, &holder_account_id)
.send_burn_transaction(
definition_account_id,
holder_id,
amount,
&holder_account_id,
)
.await?;
Ok(SubcommandReturnValue::Empty)
}
@ -724,11 +736,19 @@ impl WalletSubcommand for TokenProgramSubcommandPublic {
} => {
let definition = definition_account_id.resolve(wallet_core.storage())?;
let holder = holder_account_id.resolve(wallet_core.storage())?;
let (AccountIdWithPrivacy::Public(def_id), AccountIdWithPrivacy::Public(holder_id)) = (definition, holder) else {
let (AccountIdWithPrivacy::Public(def_id), AccountIdWithPrivacy::Public(holder_id)) =
(definition, holder)
else {
anyhow::bail!("Only public accounts supported for token mint");
};
Token(wallet_core)
.send_mint_transaction(def_id, holder_id, amount, &definition_account_id, &holder_account_id)
.send_mint_transaction(
def_id,
holder_id,
amount,
&definition_account_id,
&holder_account_id,
)
.await?;
Ok(SubcommandReturnValue::Empty)
}
@ -1049,6 +1069,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
recipient_vpk,
recipient_identifier,
balance_to_move,
sender_mention,
} => {
let recipient_npk_res = hex::decode(recipient_npk)?;
let mut recipient_npk = [0; 32];
@ -1069,6 +1090,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
recipient_vpk,
recipient_identifier.unwrap_or_else(rand::random),
balance_to_move,
&sender_mention,
)
.await?;
@ -1088,12 +1110,14 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
sender_account_id,
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,
recipient_account_id,
balance_to_move,
&sender_mention,
)
.await?;
@ -1332,7 +1356,9 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand {
} => {
let definition = definition_account_id.resolve(wallet_core.storage())?;
let supply = supply_account_id.resolve(wallet_core.storage())?;
let (AccountIdWithPrivacy::Public(def_id), AccountIdWithPrivacy::Public(sup_id)) = (definition, supply) else {
let (AccountIdWithPrivacy::Public(def_id), AccountIdWithPrivacy::Public(sup_id)) =
(definition, supply)
else {
anyhow::bail!("Only public accounts supported for new token definition");
};
Token(wallet_core)

View File

@ -1,5 +1,6 @@
#![expect(
clippy::print_stdout,
clippy::print_stderr,
reason = "This is a CLI application, printing to stdout and stderr is expected and convenient"
)]
#![expect(
@ -17,10 +18,12 @@ use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use keycard_wallet::KeycardWallet;
use log::info;
use nssa::{
Account, AccountId, PrivacyPreservingTransaction, PublicKey, Signature,
Account, AccountId, PrivacyPreservingTransaction, PublicKey, PublicTransaction, Signature,
privacy_preserving_transaction::{
circuit::ProgramWithDependencies, message::EncryptedAccountData,
},
program::Program,
public_transaction::WitnessSet as PublicWitnessSet,
};
use nssa_core::{
Commitment, MembershipProof, SharedSecretKey,
@ -36,6 +39,7 @@ use crate::{
account::{AccountIdWithPrivacy, Label},
config::WalletConfigOverrides,
poller::TxPoller,
signing::SigningGroups,
storage::key_chain::SharedAccountEntry,
};
@ -83,6 +87,20 @@ pub enum ExecutionFailureKind {
KeycardError(#[from] pyo3::PyErr),
}
impl ExecutionFailureKind {
/// Convert an [`anyhow::Error`] (e.g. from [`SigningGroups`]) 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::<pyo3::exceptions::PyRuntimeError, _>(
e.to_string(),
))
}
}
#[expect(clippy::partial_pub_fields, reason = "TODO: make all fields private")]
pub struct WalletCore {
config_path: PathBuf,
@ -545,6 +563,62 @@ impl WalletCore {
Ok(())
}
/// Send a public transaction, fetching nonces automatically from
/// [`SigningGroups::signing_ids`].
pub async fn send_public_tx<T: serde::Serialize>(
&self,
program: &Program,
account_ids: Vec<AccountId>,
instruction: T,
groups: SigningGroups,
) -> Result<HashType, ExecutionFailureKind> {
let nonces = self
.get_accounts_nonces(groups.signing_ids())
.await
.map_err(ExecutionFailureKind::SequencerError)?;
self.send_public_tx_with_nonces(program, account_ids, nonces, instruction, groups)
.await
}
/// Send a public transaction with caller-supplied nonces.
///
/// Use this when nonce fetching requires special handling (e.g. the AMM LP account
/// may not yet exist on-chain and needs a `Nonce(0)` fallback).
pub async fn send_public_tx_with_nonces<T: serde::Serialize>(
&self,
program: &Program,
account_ids: Vec<AccountId>,
nonces: Vec<Nonce>,
instruction: T,
groups: SigningGroups,
) -> Result<HashType, ExecutionFailureKind> {
let message = nssa::public_transaction::Message::try_new(
program.id(),
account_ids,
nonces,
instruction,
)?;
let pin = if groups.needs_pin() {
crate::helperfunctions::read_pin()
.map_err(ExecutionFailureKind::from_anyhow)?
.as_str()
.to_owned()
} else {
String::new()
};
let sigs = groups
.sign_all(&message.hash(), &pin)
.map_err(ExecutionFailureKind::from_anyhow)?;
let tx = PublicTransaction::new(message, PublicWitnessSet::from_raw_parts(sigs));
Ok(self
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?)
}
pub async fn send_privacy_preserving_tx(
&self,
accounts: Vec<PrivacyPreservingAccount>,
@ -574,36 +648,39 @@ impl WalletCore {
let mut pre_states = acc_manager.pre_states();
let (keycard_account, keycard_pin, keycard_path) = if let Some(key_path_str) = key_path.as_deref() {
let pin = crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<
pyo3::exceptions::PyRuntimeError,
_,
>(e.to_string()))
})?;
let account_id_str =
KeycardWallet::get_public_account_id_for_path_with_connect(&pin, key_path_str)?;
let account_id: AccountId =
match account_id_str.parse::<AccountIdWithPrivacy>().expect("Valid parsing of account id") {
let (keycard_account, keycard_pin, keycard_path) =
if let Some(key_path_str) = key_path.as_deref() {
let pin = crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<
pyo3::exceptions::PyRuntimeError,
_,
>(e.to_string()))
})?;
let account_id_str =
KeycardWallet::get_public_account_id_for_path_with_connect(&pin, key_path_str)?;
let account_id: AccountId = match account_id_str
.parse::<AccountIdWithPrivacy>()
.expect("Valid parsing of account id")
{
AccountIdWithPrivacy::Public(id) | AccountIdWithPrivacy::Private(id) => id,
};
let account = self
.get_account_public(account_id)
.await
.expect("Expect valid account");
let pin_str = pin.as_str().to_owned();
(
Some(AccountWithMetadata {
account,
is_authorized: true,
account_id,
}),
Some(pin_str),
Some(key_path_str.to_owned()),
)
} else {
(None, None, None)
};
let account = self
.get_account_public(account_id)
.await
.expect("Expect valid account");
let pin_str = pin.as_str().to_owned();
(
Some(AccountWithMetadata {
account,
is_authorized: true,
account_id,
}),
Some(pin_str),
Some(key_path_str.to_owned()),
)
} else {
(None, None, None)
};
let mut nonces: Vec<Nonce> = acc_manager.public_account_nonces().into_iter().collect();
@ -653,35 +730,39 @@ impl WalletCore {
)
.unwrap();
let witness_set = if let (Some(pin), Some(path)) =
(keycard_pin.as_deref(), keycard_path.as_deref())
{
let hash = message.hash();
let local_auth = acc_manager.public_account_auth();
let mut sigs: Vec<(Signature, PublicKey)> = local_auth
.iter()
.map(|&key| (Signature::new(key, &hash), PublicKey::new_from_private_key(key)))
.collect();
let keycard_sig = pyo3::Python::with_gil(|py| {
let mut ctx = crate::signing::KeycardSessionContext::new(pin);
let result = ctx
.get_or_connect(py)
.and_then(|w| w.sign_message_for_path(py, path, &hash));
ctx.close(py);
result
})
.map_err(ExecutionFailureKind::KeycardError)?;
sigs.push(keycard_sig);
nssa::privacy_preserving_transaction::witness_set::WitnessSet::from_raw_parts(
sigs, proof,
)
} else {
nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message(
&message,
proof,
&acc_manager.public_account_auth(),
)
};
let witness_set =
if let (Some(pin), Some(path)) = (keycard_pin.as_deref(), keycard_path.as_deref()) {
let hash = message.hash();
let local_auth = acc_manager.public_account_auth();
let mut sigs: Vec<(Signature, PublicKey)> = local_auth
.iter()
.map(|&key| {
(
Signature::new(key, &hash),
PublicKey::new_from_private_key(key),
)
})
.collect();
let keycard_sig = pyo3::Python::with_gil(|py| {
let mut ctx = crate::signing::KeycardSessionContext::new(pin);
let result = ctx
.get_or_connect(py)
.and_then(|w| w.sign_message_for_path(py, path, &hash));
ctx.close(py);
result
})
.map_err(ExecutionFailureKind::KeycardError)?;
sigs.push(keycard_sig);
nssa::privacy_preserving_transaction::witness_set::WitnessSet::from_raw_parts(
sigs, proof,
)
} else {
nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message(
&message,
proof,
&acc_manager.public_account_auth(),
)
};
let tx = PrivacyPreservingTransaction::new(message, witness_set);
let shared_secrets: Vec<_> = private_account_keys

View File

@ -1,16 +1,9 @@
use amm_core::{compute_liquidity_token_pda, compute_pool_pda, compute_vault_pda};
use common::{HashType, transaction::NSSATransaction};
use nssa::{AccountId, program::Program, public_transaction::WitnessSet};
use pyo3::exceptions::PyRuntimeError;
use sequencer_service_rpc::RpcClient as _;
use common::HashType;
use nssa::{AccountId, program::Program};
use token_core::TokenHolding;
use crate::{
ExecutionFailureKind, WalletCore,
cli::CliAccountMention,
helperfunctions::read_pin,
signing::SigningGroups,
};
use crate::{ExecutionFailureKind, WalletCore, cli::CliAccountMention, signing::SigningGroups};
pub struct Amm<'wallet>(pub &'wallet WalletCore);
impl Amm<'_> {
@ -73,41 +66,35 @@ impl Amm<'_> {
.add_sender(a_mention, user_holding_a, self.0)
.and_then(|()| groups.add_sender(b_mention, user_holding_b, self.0))
.and_then(|()| groups.add_recipient(lp_mention, user_holding_lp, self.0))
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
.map_err(ExecutionFailureKind::from_anyhow)?;
let mut nonces = self.0.get_accounts_nonces(vec![user_holding_a, user_holding_b]).await
let mut nonces = self
.0
.get_accounts_nonces(vec![user_holding_a, user_holding_b])
.await
.map_err(ExecutionFailureKind::SequencerError)?;
if groups.signing_ids().contains(&user_holding_lp) {
let lp_nonces = self.0.get_accounts_nonces(vec![user_holding_lp]).await
let lp_nonces = self
.0
.get_accounts_nonces(vec![user_holding_lp])
.await
.map_err(ExecutionFailureKind::SequencerError)?;
nonces.push(lp_nonces.into_iter().next().unwrap_or(nssa_core::account::Nonce(0)));
nonces.push(
lp_nonces
.into_iter()
.next()
.unwrap_or(nssa_core::account::Nonce(0)),
);
} else {
println!(
"Liquidity pool tokens receiver's account ({user_holding_lp}) private key not found in wallet. Proceeding with only liquidity provider's keys."
);
}
let message = nssa::public_transaction::Message::try_new(program.id(), account_ids, nonces, instruction).unwrap();
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?)
self.0
.send_public_tx_with_nonces(&program, account_ids, nonces, instruction, groups)
.await
}
#[expect(clippy::too_many_arguments, reason = "each parameter is distinct")]
@ -165,37 +152,18 @@ impl Amm<'_> {
} else if definition_token_b_id == token_definition_id_in {
(user_holding_b, b_mention)
} else {
return Err(ExecutionFailureKind::AccountDataError(token_definition_id_in));
return Err(ExecutionFailureKind::AccountDataError(
token_definition_id_in,
));
};
let mut groups = SigningGroups::new();
groups
.add_sender(seller_mention, account_id_auth, self.0)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(program.id(), account_ids, nonces, instruction).unwrap();
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?)
.map_err(ExecutionFailureKind::from_anyhow)?;
self.0
.send_public_tx(&program, account_ids, instruction, groups)
.await
}
#[expect(clippy::too_many_arguments, reason = "each parameter is distinct")]
@ -253,37 +221,18 @@ impl Amm<'_> {
} else if definition_token_b_id == token_definition_id_in {
(user_holding_b, b_mention)
} else {
return Err(ExecutionFailureKind::AccountDataError(token_definition_id_in));
return Err(ExecutionFailureKind::AccountDataError(
token_definition_id_in,
));
};
let mut groups = SigningGroups::new();
groups
.add_sender(seller_mention, account_id_auth, self.0)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(program.id(), account_ids, nonces, instruction).unwrap();
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?)
.map_err(ExecutionFailureKind::from_anyhow)?;
self.0
.send_public_tx(&program, account_ids, instruction, groups)
.await
}
#[expect(clippy::too_many_arguments, reason = "each parameter is distinct")]
@ -297,6 +246,7 @@ impl Amm<'_> {
max_amount_to_add_token_b: u128,
a_mention: &CliAccountMention,
b_mention: &CliAccountMention,
lp_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let instruction = amm_core::Instruction::AddLiquidity {
min_amount_liquidity,
@ -344,31 +294,36 @@ impl Amm<'_> {
groups
.add_sender(a_mention, user_holding_a, self.0)
.and_then(|()| groups.add_sender(b_mention, user_holding_b, self.0))
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
.and_then(|()| groups.add_recipient(lp_mention, user_holding_lp, self.0))
.map_err(ExecutionFailureKind::from_anyhow)?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
let mut nonces = self
.0
.get_accounts_nonces(vec![user_holding_a, user_holding_b])
.await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(program.id(), account_ids, nonces, instruction).unwrap();
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
if groups.signing_ids().contains(&user_holding_lp) {
let lp_nonces = self
.0
.get_accounts_nonces(vec![user_holding_lp])
.await
.map_err(ExecutionFailureKind::SequencerError)?;
nonces.push(
lp_nonces
.into_iter()
.next()
.unwrap_or(nssa_core::account::Nonce(0)),
);
} else {
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
println!(
"LP holder's account ({user_holding_lp}) private key not found in wallet. Proceeding with only liquidity providers' keys."
);
}
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?)
self.0
.send_public_tx_with_nonces(&program, account_ids, nonces, instruction, groups)
.await
}
#[expect(clippy::too_many_arguments, reason = "each parameter is distinct")]
@ -427,30 +382,9 @@ impl Amm<'_> {
let mut groups = SigningGroups::new();
groups
.add_sender(lp_mention, user_holding_lp, self.0)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(program.id(), account_ids, nonces, instruction).unwrap();
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?)
.map_err(ExecutionFailureKind::from_anyhow)?;
self.0
.send_public_tx(&program, account_ids, instruction, groups)
.await
}
}

View File

@ -1,19 +1,14 @@
use std::collections::HashMap;
use ata_core::{compute_ata_seed, get_associated_token_account_id};
use common::{HashType, transaction::NSSATransaction};
use common::HashType;
use nssa::{
AccountId, privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program,
public_transaction::WitnessSet,
};
use nssa_core::SharedSecretKey;
use pyo3::exceptions::PyRuntimeError;
use sequencer_service_rpc::RpcClient as _;
use crate::{
ExecutionFailureKind, PrivacyPreservingAccount, WalletCore,
cli::CliAccountMention,
helperfunctions::read_pin,
ExecutionFailureKind, PrivacyPreservingAccount, WalletCore, cli::CliAccountMention,
signing::SigningGroups,
};
@ -39,26 +34,10 @@ impl Ata<'_> {
let mut groups = SigningGroups::new();
groups
.add_sender(owner_mention, owner_id, self.0)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(program.id(), account_ids, nonces, instruction)?;
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self.0.sequencer_client.send_transaction(NSSATransaction::Public(tx)).await?)
.map_err(ExecutionFailureKind::from_anyhow)?;
self.0
.send_public_tx(&program, account_ids, instruction, groups)
.await
}
pub async fn send_transfer(
@ -77,31 +56,18 @@ impl Ata<'_> {
);
let account_ids = vec![owner_id, sender_ata_id, recipient_id];
let instruction = ata_core::Instruction::Transfer { ata_program_id, amount };
let instruction = ata_core::Instruction::Transfer {
ata_program_id,
amount,
};
let mut groups = SigningGroups::new();
groups
.add_sender(owner_mention, owner_id, self.0)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(program.id(), account_ids, nonces, instruction)?;
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self.0.sequencer_client.send_transaction(NSSATransaction::Public(tx)).await?)
.map_err(ExecutionFailureKind::from_anyhow)?;
self.0
.send_public_tx(&program, account_ids, instruction, groups)
.await
}
pub async fn send_burn(
@ -119,31 +85,18 @@ impl Ata<'_> {
);
let account_ids = vec![owner_id, holder_ata_id, definition_id];
let instruction = ata_core::Instruction::Burn { ata_program_id, amount };
let instruction = ata_core::Instruction::Burn {
ata_program_id,
amount,
};
let mut groups = SigningGroups::new();
groups
.add_sender(owner_mention, owner_id, self.0)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(program.id(), account_ids, nonces, instruction)?;
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self.0.sequencer_client.send_transaction(NSSATransaction::Public(tx)).await?)
.map_err(ExecutionFailureKind::from_anyhow)?;
self.0
.send_public_tx(&program, account_ids, instruction, groups)
.await
}
pub async fn send_create_private_owner(
@ -170,7 +123,12 @@ impl Ata<'_> {
];
self.0
.send_privacy_preserving_tx(accounts, instruction_data, &ata_with_token_dependency(), &None)
.send_privacy_preserving_tx(
accounts,
instruction_data,
&ata_with_token_dependency(),
&None,
)
.await
.map(|(hash, mut secrets)| {
let secret = secrets.pop().expect("expected owner's secret");
@ -207,7 +165,12 @@ impl Ata<'_> {
];
self.0
.send_privacy_preserving_tx(accounts, instruction_data, &ata_with_token_dependency(), &None)
.send_privacy_preserving_tx(
accounts,
instruction_data,
&ata_with_token_dependency(),
&None,
)
.await
.map(|(hash, mut secrets)| {
let secret = secrets.pop().expect("expected owner's secret");
@ -243,7 +206,12 @@ impl Ata<'_> {
];
self.0
.send_privacy_preserving_tx(accounts, instruction_data, &ata_with_token_dependency(), &None)
.send_privacy_preserving_tx(
accounts,
instruction_data,
&ata_with_token_dependency(),
&None,
)
.await
.map(|(hash, mut secrets)| {
let secret = secrets.pop().expect("expected owner's secret");

View File

@ -1,18 +1,9 @@
use authenticated_transfer_core::Instruction as AuthTransferInstruction;
use common::{HashType, transaction::NSSATransaction};
use nssa::{
AccountId, PublicTransaction,
program::Program,
public_transaction::{Message, WitnessSet},
};
use pyo3::exceptions::PyRuntimeError;
use sequencer_service_rpc::RpcClient as _;
use common::HashType;
use nssa::{AccountId, program::Program};
use super::NativeTokenTransfer;
use crate::{
ExecutionFailureKind, cli::CliAccountMention, helperfunctions::read_pin,
signing::SigningGroups,
};
use crate::{ExecutionFailureKind, cli::CliAccountMention, signing::SigningGroups};
impl NativeTokenTransfer<'_> {
pub async fn send_public_transfer(
@ -27,52 +18,18 @@ impl NativeTokenTransfer<'_> {
groups
.add_sender(from_mention, from, self.0)
.and_then(|()| groups.add_recipient(to_mention, to, self.0))
.map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
e.to_string(),
))
})?;
.map_err(ExecutionFailureKind::from_anyhow)?;
let program_id = Program::authenticated_transfer_program().id();
let nonces = self
.0
.get_accounts_nonces(groups.signing_ids())
self.0
.send_public_tx(
&Program::authenticated_transfer_program(),
vec![from, to],
AuthTransferInstruction::Transfer {
amount: balance_to_move,
},
groups,
)
.await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = Message::try_new(
program_id,
vec![from, to],
nonces,
AuthTransferInstruction::Transfer {
amount: balance_to_move,
},
)
.map_err(ExecutionFailureKind::TransactionBuildError)?;
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
e.to_string(),
))
})?
.as_str()
.to_owned()
} else {
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin).map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string()))
})?;
let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?)
}
pub async fn register_account(
@ -80,53 +37,18 @@ impl NativeTokenTransfer<'_> {
from: AccountId,
account_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let nonces = self
.0
.get_accounts_nonces(vec![from])
.await
.map_err(ExecutionFailureKind::SequencerError)?;
let account_ids = vec![from];
let program_id = Program::authenticated_transfer_program().id();
let message = Message::try_new(
program_id,
account_ids,
nonces,
AuthTransferInstruction::Initialize,
)
.map_err(ExecutionFailureKind::TransactionBuildError)?;
let mut groups = SigningGroups::new();
groups
.add_sender(account_mention, from, self.0)
.map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
e.to_string(),
))
})?;
.map_err(ExecutionFailureKind::from_anyhow)?;
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
e.to_string(),
))
})?
.as_str()
.to_owned()
} else {
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin).map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string()))
})?;
let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?)
self.0
.send_public_tx(
&Program::authenticated_transfer_program(),
vec![from],
AuthTransferInstruction::Initialize,
groups,
)
.await
}
}

View File

@ -1,21 +1,16 @@
use common::{HashType, transaction::NSSATransaction};
use nssa::{AccountId, program::Program, public_transaction::WitnessSet};
use common::HashType;
use nssa::{AccountId, program::Program};
use nssa_core::{Identifier, NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey};
use pyo3::exceptions::PyRuntimeError;
use sequencer_service_rpc::RpcClient as _;
use token_core::Instruction;
use crate::{
ExecutionFailureKind, PrivacyPreservingAccount, WalletCore,
cli::CliAccountMention,
helperfunctions::read_pin,
ExecutionFailureKind, PrivacyPreservingAccount, WalletCore, cli::CliAccountMention,
signing::SigningGroups,
};
pub struct Token<'wallet>(pub &'wallet WalletCore);
impl Token<'_> {
#[expect(clippy::too_many_arguments, reason = "each parameter is distinct")]
pub async fn send_new_definition(
&self,
definition_account_id: AccountId,
@ -26,37 +21,17 @@ impl Token<'_> {
supply_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let account_ids = vec![definition_account_id, supply_account_id];
let program_id = nssa::program::Program::token().id();
let instruction = Instruction::NewFungibleDefinition { name, total_supply };
let mut groups = SigningGroups::new();
groups
.add_sender(definition_mention, definition_account_id, self.0)
.and_then(|()| groups.add_sender(supply_mention, supply_account_id, self.0))
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
.map_err(ExecutionFailureKind::from_anyhow)?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(program_id, account_ids, nonces, instruction).unwrap();
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?)
self.0
.send_public_tx(&Program::token(), account_ids, instruction, groups)
.await
}
pub async fn send_new_definition_private_owned_supply(
@ -168,38 +143,19 @@ impl Token<'_> {
recipient_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let account_ids = vec![sender_account_id, recipient_account_id];
let program_id = nssa::program::Program::token().id();
let instruction = Instruction::Transfer { amount_to_transfer: amount };
let instruction = Instruction::Transfer {
amount_to_transfer: amount,
};
let mut groups = SigningGroups::new();
groups
.add_sender(sender_mention, sender_account_id, self.0)
.and_then(|()| groups.add_recipient(recipient_mention, recipient_account_id, self.0))
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
.map_err(ExecutionFailureKind::from_anyhow)?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(program_id, account_ids, nonces, instruction).unwrap();
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?)
self.0
.send_public_tx(&Program::token(), account_ids, instruction, groups)
.await
}
pub async fn send_transfer_transaction_private_owned_account(
@ -315,12 +271,14 @@ impl Token<'_> {
sender_account_id: AccountId,
recipient_account_id: AccountId,
amount: u128,
sender_mention: &CliAccountMention,
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
let instruction = Instruction::Transfer {
amount_to_transfer: amount,
};
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
let key_path = sender_mention.key_path().map(str::to_owned);
self.0
.send_privacy_preserving_tx(
@ -332,7 +290,7 @@ impl Token<'_> {
],
instruction_data,
&Program::token().into(),
&None,
&key_path,
)
.await
.map(|(resp, secrets)| {
@ -351,12 +309,14 @@ impl Token<'_> {
recipient_vpk: ViewingPublicKey,
recipient_identifier: Identifier,
amount: u128,
sender_mention: &CliAccountMention,
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
let instruction = Instruction::Transfer {
amount_to_transfer: amount,
};
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
let key_path = sender_mention.key_path().map(str::to_owned);
self.0
.send_privacy_preserving_tx(
@ -370,7 +330,7 @@ impl Token<'_> {
],
instruction_data,
&Program::token().into(),
&None,
&key_path,
)
.await
.map(|(resp, secrets)| {
@ -397,31 +357,11 @@ impl Token<'_> {
let mut groups = SigningGroups::new();
groups
.add_sender(holder_mention, holder_account_id, self.0)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
.map_err(ExecutionFailureKind::from_anyhow)?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(Program::token().id(), account_ids, nonces, instruction)
.expect("Instruction should serialize");
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?)
self.0
.send_public_tx(&Program::token(), account_ids, instruction, groups)
.await
}
pub async fn send_burn_transaction_private_owned_account(
@ -544,31 +484,11 @@ impl Token<'_> {
groups
.add_sender(definition_mention, definition_account_id, self.0)
.and_then(|()| groups.add_recipient(holder_mention, holder_account_id, self.0))
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
.map_err(ExecutionFailureKind::from_anyhow)?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(Program::token().id(), account_ids, nonces, instruction).unwrap();
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?)
self.0
.send_public_tx(&Program::token(), account_ids, instruction, groups)
.await
}
pub async fn send_mint_transaction_private_owned_account(
@ -750,5 +670,4 @@ impl Token<'_> {
(resp, first)
})
}
}

View File

@ -112,75 +112,38 @@ impl SigningGroups {
Ok(sigs)
}
/// Add a recipient. Same as [`add_sender`] but silently skips accounts with no local
/// key and no keycard path — they are foreign and require neither a signature nor a nonce.
pub fn add_recipient(
&mut self,
mention: &CliAccountMention,
account_id: AccountId,
wallet_core: &WalletCore,
) -> Result<()> {
if let CliAccountMention::KeyPath(path) = mention {
self.keycard.push((account_id, path.clone()));
return Ok(());
}
if let Some(key) = wallet_core
.storage()
.key_chain()
.pub_account_signing_key(account_id)
{
self.local.push((account_id, key.clone()));
}
Ok(())
}
/// Returns `true` when a PIN is required (at least one keycard signer is present).
#[must_use]
pub const fn needs_pin(&self) -> bool {
!self.keycard.is_empty()
}
/// Account IDs that require a nonce (every non-foreign signer).
#[must_use]
pub fn signing_ids(&self) -> Vec<AccountId> {
self.local
.iter()
.map(|(id, _)| *id)
.chain(self.keycard.iter().map(|(id, _)| *id))
.collect()
}
/// Sign `hash` for every account in the group.
///
/// Local accounts are signed in pure Rust. Keycard accounts share one Python session.
pub fn sign_all(&self, hash: &[u8; 32], pin: &str) -> Result<Vec<(Signature, PublicKey)>> {
let mut sigs: Vec<(Signature, PublicKey)> = self
.local
.iter()
.map(|(_, key)| {
(
Signature::new(key, hash),
PublicKey::new_from_private_key(key),
)
})
.collect();
if !self.keycard.is_empty() {
pyo3::Python::with_gil(|py| -> pyo3::PyResult<()> {
python_path::add_python_path(py)?;
let wallet = KeycardWallet::new(py)?;
wallet.connect(py, pin)?;
for (_, path) in &self.keycard {
sigs.push(wallet.sign_message_for_path(py, path, hash)?);
}
drop(wallet.close_session(py));
Ok(())
})
.map_err(anyhow::Error::from)?;
}
Ok(sigs)
}
}
/// Lazily opens and reuses a single Keycard session for all keycard signers in one transaction.
pub struct KeycardSessionContext {
pin: String,
wallet: Option<KeycardWallet>,
}
impl KeycardSessionContext {
pub fn new(pin: impl Into<String>) -> Self {
Self {
pin: pin.into(),
wallet: None,
}
}
pub fn get_or_connect<'py>(
&'py mut self,
py: Python<'py>,
) -> pyo3::PyResult<&'py 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())
}
pub fn close(self, py: Python<'_>) {
if let Some(w) = self.wallet {
drop(w.close_session(py));
}
}
}