This commit is contained in:
jonesmarvin8 2026-05-06 20:35:46 -04:00
parent 36a1ff319c
commit d98f13adde
6 changed files with 151 additions and 95 deletions

View File

@ -178,6 +178,33 @@ Account owned by authenticated transfer program
| `wallet token burn` | ChainIndex (key path) for holding account | | `wallet token burn` | ChainIndex (key path) for holding account |
| `wallet token mint` | ChainIndices (key paths) for definition and holding public accounts | | `wallet token mint` | ChainIndices (key paths) for definition and holding public accounts |
These commands work as expected, but uses `key-path` features to use a public account via Keycard.
1. Initialize new Token
```bash
wallet token new \
--definition-key-path "m/44'/60'/0'/1/0" \
--supply-key-path "m/44'/60'/0'/1/1" \
--total-supply 100000 \
--name SNT
# Output:
Keycard PIN:
Transaction hash is 2f0ddd9ad46e1c8cde8dac4eb69ebb5d8fdf167647e421aa79900adaaa9b34d0
```
2. Initialize new token holding
```bash
wallet token init \
--definition-account-id "Public/$LEE_DEF_ID" \
--holder-key-path "m/44'/60'/0'/0/2"
# Output
Keycard PIN:
Transaction hash is d4442e32bf33efbac03672e3c5f6e181bc7e34f0911cf00ef915eeaee6787a5b
LEE holding initialized for keycard m/44'/60'/0'/0/2
```
### AMM program ### AMM program
| Command | Description | | Command | Description |
|--------------------------------|---------------------------------------------------------------------| |--------------------------------|---------------------------------------------------------------------|
@ -185,4 +212,6 @@ Account owned by authenticated transfer program
| `wallet amm swap-exact-input` | ChaindIndex (key path) to initialize Keycard public account | | `wallet amm swap-exact-input` | ChaindIndex (key path) to initialize Keycard public account |
| `wallet amm swap-exact-output` | ChainIndices (key paths) for `from` and `to` public accounts | | `wallet amm swap-exact-output` | ChainIndices (key paths) for `from` and `to` public accounts |
| `wallet amm add-liquidity` | ChainIndex (key path) for holding account | | `wallet amm add-liquidity` | ChainIndex (key path) for holding account |
| `wallet amm reemove-liquidity` | ChainIndices (key paths) for definition and holding public accounts | | `wallet amm remove-liquidity` | ChainIndices (key paths) for definition and holding public accounts |
These commands work as expected, but uses `key-path` features to use a public account via Keycard.

View File

@ -33,6 +33,8 @@ wallet account get --key-path "m/44'/60'/0'/0/0"
echo "=== Test: pinata claim path 0 ===" echo "=== Test: pinata claim path 0 ==="
wallet pinata claim --key-path "m/44'/60'/0'/0/0" wallet pinata claim --key-path "m/44'/60'/0'/0/0"
sleep 5
echo "=== Test: account get path 0 (after claim) ===" echo "=== Test: account get path 0 (after claim) ==="
wallet account get --key-path "m/44'/60'/0'/0/0" wallet account get --key-path "m/44'/60'/0'/0/0"
@ -48,13 +50,6 @@ echo "=== Test: account get path 0 ==="
wallet account get --key-path "m/44'/60'/0'/0/0" wallet account get --key-path "m/44'/60'/0'/0/0"
echo "=== Test: account get path 1 ===" echo "=== Test: account get path 1 ==="
wallet account get --key-path "m/44'/60'/0'/0/1" wallet account get --key-path "m/44'/60'/0'/0/1"
# =============================================================================
# (1) Shielded auth-transfer to an owned private account; verify decoded state.
#
# Use --to-label (ShieldedOwned path) so the wallet decodes the received note
# after sync and the balance is visible locally.
# =============================================================================
echo "" echo ""
echo "=== Test (1): Shielded auth-transfer to owned private account ===" echo "=== Test (1): Shielded auth-transfer to owned private account ==="
@ -64,6 +59,5 @@ wallet auth-transfer send --amount 2 \
--to-vpk "02a8626b0c0ad9383c5678dad48c3969b4174fb377cdb03a6259648032c774cec8" --to-vpk "02a8626b0c0ad9383c5678dad48c3969b4174fb377cdb03a6259648032c774cec8"
echo "Shielded auth-transfer sent" echo "Shielded auth-transfer sent"
# TODO: add a time delay here sleep 5
wallet account get --key-path "m/44'/60'/0'/0/0" wallet account get --key-path "m/44'/60'/0'/0/0"

View File

@ -1,17 +1,15 @@
source venv/bin/activate source venv/bin/activate
export KEYCARD_PIN=111111 export KEYCARD_PIN=111111
# =============================================================================
# (2) Initialize token definitions + initial supply holdings for LEZ and LEE.
# All without keycard.
# =============================================================================
echo "" echo ""
echo "=== Test (2): Create LEZ and LEE token definitions (without keycard) ===" echo "=== Create LEZ and LEE token definitions (without keycard) ==="
wallet account new public --label lez-def 2>/dev/null || true wallet account new public --label lez-def
wallet account new public --label lez-supply 2>/dev/null || true wallet account new public --label lez-supply
wallet account new public --label lee-def 2>/dev/null || true wallet account new public --label lee-def
wallet account new public --label lee-supply 2>/dev/null || true wallet account new public --label lee-supply
sleep 5
LEZ_DEF_ID=$(wallet account id --account-label lez-def) LEZ_DEF_ID=$(wallet account id --account-label lez-def)
LEE_DEF_ID=$(wallet account id --account-label lee-def) LEE_DEF_ID=$(wallet account id --account-label lee-def)
@ -31,14 +29,12 @@ wallet token new \
echo "LEE token created" echo "LEE token created"
# ============================================================================= # =============================================================================
# (3) Initialize LEE token holding accounts: # Initialize LEE token holding accounts:
# - two public keycard holders (paths 2 and 3) # - two public keycard holders (paths 2 and 3)
# - one private holder (without keycard) # - one private holder (without keycard)
#
# token init is idempotent: skips if the holder already has token data.
# ============================================================================= # =============================================================================
echo "" echo ""
echo "=== Test (3): Initialize LEE token holding accounts ===" echo "=== Initialize LEE/LEZ token holding accounts ==="
wallet token init \ wallet token init \
--definition-account-id "Public/$LEE_DEF_ID" \ --definition-account-id "Public/$LEE_DEF_ID" \
@ -50,6 +46,7 @@ wallet token init \
--holder-key-path "m/44'/60'/0'/0/3" --holder-key-path "m/44'/60'/0'/0/3"
echo "LEE holding initialized for keycard m/44'/60'/0'/0/3" echo "LEE holding initialized for keycard m/44'/60'/0'/0/3"
# For shielded transfer
wallet account new private --label lee-priv-holder 2>/dev/null || true wallet account new private --label lee-priv-holder 2>/dev/null || true
wallet token init \ wallet token init \
--definition-account-id "Public/$LEE_DEF_ID" \ --definition-account-id "Public/$LEE_DEF_ID" \
@ -71,17 +68,20 @@ wallet token send \
--amount 5000 --amount 5000
echo "Transferred 5000 LEE → keycard path 3" echo "Transferred 5000 LEE → keycard path 3"
sleep 5
echo "Keycard path 2 LEE state (balance should be 5000):" echo "Keycard path 2 LEE state (balance should be 5000):"
wallet account get --key-path "m/44'/60'/0'/0/2" wallet account get --key-path "m/44'/60'/0'/0/2"
echo "Keycard path 3 LEE state (balance should be 5000):" echo "Keycard path 3 LEE state (balance should be 5000):"
wallet account get --key-path "m/44'/60'/0'/0/3" wallet account get --key-path "m/44'/60'/0'/0/3"
# ============================================================================= # =============================================================================
# (4) Shielded (public → private) LEE transfer from keycard holding to the # Shielded (public → private) LEE transfer from keycard holding to the
# private LEE holding account. # private LEE holding account.
# ============================================================================= # =============================================================================
echo "" echo ""
echo "=== Test (4): Shielded transfer keycard LEE holding → private LEE holding ===" echo "=== Test Shielded transfer keycard LEE holding → private LEE holding ==="
wallet token send \ wallet token send \
--from-key-path "m/44'/60'/0'/0/2" \ --from-key-path "m/44'/60'/0'/0/2" \
@ -94,10 +94,10 @@ echo "Private LEE holder state (balance should be 500):"
wallet account get --account-label lee-priv-holder wallet account get --account-label lee-priv-holder
# ============================================================================= # =============================================================================
# (5) Create AMM pool for LEZ/LEE (without keycard) # Create AMM pool for LEZ/LEE (without keycard)
# ============================================================================= # =============================================================================
echo "" echo ""
echo "=== Test (5): Create AMM pool for LEZ/LEE (without keycard) ===" echo "=== Test Create AMM pool for LEZ/LEE (without keycard) ==="
wallet account new public --label amm-lp-lez-holding 2>/dev/null || true wallet account new public --label amm-lp-lez-holding 2>/dev/null || true
wallet account new public --label amm-lp-lee-holding 2>/dev/null || true wallet account new public --label amm-lp-lee-holding 2>/dev/null || true
@ -122,7 +122,7 @@ wallet amm new \
echo "AMM pool created for LEZ/LEE" echo "AMM pool created for LEZ/LEE"
# ============================================================================= # =============================================================================
# (6) Swaps, add liquidity, remove liquidity using keycard holding accounts. # Swaps, add liquidity, remove liquidity using keycard holding accounts.
# #
# Path layout: # Path layout:
# path 2 → LEE holding (4500 LEE after step 4) # path 2 → LEE holding (4500 LEE after step 4)
@ -130,7 +130,7 @@ echo "AMM pool created for LEZ/LEE"
# path 4 → fresh; initialized below as LEZ holding (receives swapped LEZ) # path 4 → fresh; initialized below as LEZ holding (receives swapped LEZ)
# ============================================================================= # =============================================================================
echo "" echo ""
echo "=== Test (6a): Initialize LEZ holding for keycard path 4 (swap output) ===" echo "=== Test Initialize LEZ holding for keycard path 4 (swap output) ==="
wallet token init \ wallet token init \
--definition-account-id "Public/$LEZ_DEF_ID" \ --definition-account-id "Public/$LEZ_DEF_ID" \
--holder-key-path "m/44'/60'/0'/0/4" --holder-key-path "m/44'/60'/0'/0/4"
@ -145,18 +145,16 @@ echo "Path 2: $PATH2_ID Path 3: $PATH3_ID Path 4: $PATH4_ID"
echo "LEE def ID: $LEE_DEF_ID" echo "LEE def ID: $LEE_DEF_ID"
echo "" echo ""
echo "=== Test (6b): Swap LEE → LEZ (path 2 sells LEE, path 4 receives LEZ) ===" echo "=== Swap LEE → LEZ (path 2 sells LEE, path 4 receives LEZ) ==="
# user-holding-b (path 2) is the input (LEE); user-holding-a (path 4) receives LEZ.
# --key-path signs for the input account (path 2).
wallet amm swap-exact-input \ wallet amm swap-exact-input \
--user-holding-a "Public/$PATH4_ID" \ --user-holding-a-key-path "m/44'/60'/0'/0/4" \
--user-holding-b "Public/$PATH2_ID" \ --user-holding-b-key-path "m/44'/60'/0'/0/2" \
--amount-in 500 \ --amount-in 100 \
--min-amount-out 1 \ --min-amount-out 1 \
--token-definition "$LEE_DEF_ID" \ --token-definition "$LEE_DEF_ID" \
--key-path "m/44'/60'/0'/0/2"
echo "Swap LEE→LEZ complete via keycard" echo "Swap LEE→LEZ complete via keycard"
echo "Path 4 (LEZ) state:" echo "Path 4 (LEZ) state:"
wallet account get --key-path "m/44'/60'/0'/0/4" wallet account get --key-path "m/44'/60'/0'/0/4"
echo "Path 2 (LEE) state:" echo "Path 2 (LEE) state:"
@ -185,6 +183,6 @@ wallet amm remove-liquidity \
echo "Remove liquidity complete" echo "Remove liquidity complete"
echo "" echo ""
echo "=== All keycard tests finished ===" echo "=== All keycard tests finished ==="

View File

@ -30,8 +30,7 @@ pub enum AmmProgramAgnosticSubcommand {
#[arg( #[arg(
long, long,
conflicts_with = "user_holding_a", conflicts_with = "user_holding_a",
conflicts_with = "user_holding_a_key_path", conflicts_with = "user_holding_a_key_path"
required_unless_present_any = ["user_holding_a_label", "user_holding_a_key_path"]
)] )]
user_holding_a_label: Option<String>, user_holding_a_label: Option<String>,
/// Key path for user holding A (uses Keycard, alternative to --user-holding-a/label). /// Key path for user holding A (uses Keycard, alternative to --user-holding-a/label).
@ -67,7 +66,8 @@ pub enum AmmProgramAgnosticSubcommand {
#[arg( #[arg(
long, long,
conflicts_with = "user_holding_lp_label", conflicts_with = "user_holding_lp_label",
conflicts_with = "user_holding_lp_key_path" conflicts_with = "user_holding_lp_key_path",
required_unless_present_any = ["user_holding_lp_label", "user_holding_lp_key_path"]
)] )]
user_holding_lp: Option<String>, user_holding_lp: Option<String>,
/// User holding LP account label (alternative to --user-holding-lp). /// User holding LP account label (alternative to --user-holding-lp).
@ -128,11 +128,19 @@ pub enum AmmProgramAgnosticSubcommand {
/// `token_definition` - valid 32 byte base58 string WITHOUT privacy prefix. /// `token_definition` - valid 32 byte base58 string WITHOUT privacy prefix.
#[arg(long)] #[arg(long)]
token_definition: String, token_definition: String,
/// Key path for the input token's holding account (uses Keycard). /// Key path for user token's holding account fpr Token A (uses Keycard).
#[arg(long, conflicts_with = "user_holding_a")] #[arg(
long,
conflicts_with = "user_holding_a",
conflicts_with = "user_holding_a_label"
)]
user_holding_a_key_path: Option<String>, user_holding_a_key_path: Option<String>,
/// Key path for the input token's holding account (uses Keycard). /// Key path for user token's holding account fpr Token B (uses Keycard).
#[arg(long, conflicts_with = "user_holding_b")] #[arg(
long,
conflicts_with = "user_holding_b",
conflicts_with = "user_holding_b_label"
)]
user_holding_b_key_path: Option<String>, user_holding_b_key_path: Option<String>,
}, },
/// Swap specifying exact output amount. /// Swap specifying exact output amount.

View File

@ -117,7 +117,7 @@ pub enum TokenProgramAgnosticSubcommand {
#[arg(long, conflicts_with = "from", conflicts_with = "from_key_path")] #[arg(long, conflicts_with = "from", conflicts_with = "from_key_path")]
from_label: Option<String>, from_label: Option<String>,
/// to - valid 32 byte base58 string with privacy prefix. /// to - valid 32 byte base58 string with privacy prefix.
#[arg(long, conflicts_with = "to_label")] #[arg(long, conflicts_with = "to_label", conflicts_with = "to_key_path")]
to: Option<String>, to: Option<String>,
/// To account label (alternative to --to). /// To account label (alternative to --to).
#[arg(long, conflicts_with = "to")] #[arg(long, conflicts_with = "to")]
@ -136,20 +136,10 @@ pub enum TokenProgramAgnosticSubcommand {
#[arg(long)] #[arg(long)]
amount: u128, amount: u128,
/// `from_key_path` (alternative to --from) uses Keycard. /// `from_key_path` (alternative to --from) uses Keycard.
#[arg( #[arg(long, conflicts_with = "from", conflicts_with = "from_label")]
long,
conflicts_with = "from",
conflicts_with = "from",
conflicts_with = "from_label"
)]
from_key_path: Option<String>, from_key_path: Option<String>,
/// `to_key_path` (alternative to --to) uses Keycard. /// `to_key_path` (alternative to --to) uses Keycard.
#[arg( #[arg(long, conflicts_with = "to", conflicts_with = "to_label")]
long,
conflicts_with = "to",
conflicts_with = "to",
conflicts_with = "to_label"
)]
to_key_path: Option<String>, to_key_path: Option<String>,
}, },
/// Burn tokens on `holder`, modify `definition`. /// Burn tokens on `holder`, modify `definition`.

View File

@ -197,14 +197,46 @@ impl Token<'_> {
let instruction = Instruction::Transfer { let instruction = Instruction::Transfer {
amount_to_transfer: amount, amount_to_transfer: amount,
}; };
// Only the sender authorises a token Transfer — the recipient holding must already be
// initialised (no recipient signature required, matching the burn pattern). let mut nonces = self
let nonces = self
.0 .0
.get_accounts_nonces(vec![sender_account_id]) .get_accounts_nonces(vec![sender_account_id])
.await .await
.map_err(ExecutionFailureKind::SequencerError)?; .map_err(ExecutionFailureKind::SequencerError)?;
let private_keys = if sender_key_path.is_none() {
let mut private_keys = Vec::new();
let sender_sk = self
.0
.storage
.user_data
.get_pub_account_signing_key(sender_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
private_keys.push(sender_sk);
if let Some(recipient_sk) = self
.0
.storage
.user_data
.get_pub_account_signing_key(recipient_account_id)
{
private_keys.push(recipient_sk);
let recipient_nonces = self
.0
.get_accounts_nonces(vec![recipient_account_id])
.await
.map_err(ExecutionFailureKind::SequencerError)?;
nonces.extend(recipient_nonces);
} else {
println!(
"Receiver's account ({recipient_account_id}) private key not found in wallet. Proceeding with only sender's key."
);
}
private_keys
} else {
Vec::new()
};
let message = nssa::public_transaction::Message::try_new( let message = nssa::public_transaction::Message::try_new(
program_id, program_id,
account_ids, account_ids,
@ -227,13 +259,7 @@ impl Token<'_> {
WitnessSet::from_list(&message, &[signature], &[public_key]) WitnessSet::from_list(&message, &[signature], &[public_key])
.map_err(ExecutionFailureKind::TransactionBuildError)? .map_err(ExecutionFailureKind::TransactionBuildError)?
} else { } else {
let sender_sk = self nssa::public_transaction::WitnessSet::for_message(&message, &private_keys)
.0
.storage
.user_data
.get_pub_account_signing_key(sender_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
nssa::public_transaction::WitnessSet::for_message(&message, &[sender_sk])
}; };
let tx = nssa::PublicTransaction::new(message, witness_set); let tx = nssa::PublicTransaction::new(message, witness_set);
@ -660,24 +686,38 @@ impl Token<'_> {
.await .await
.map_err(ExecutionFailureKind::SequencerError)?; .map_err(ExecutionFailureKind::SequencerError)?;
if self let private_keys = if definition_key_path.is_none() {
.0 let mut private_keys = Vec::new();
.storage let definition_sk = self
.user_data
.get_pub_account_signing_key(holder_account_id)
.is_some()
{
let recipient_nonces = self
.0 .0
.get_accounts_nonces(vec![holder_account_id]) .storage
.await .user_data
.map_err(ExecutionFailureKind::SequencerError)?; .get_pub_account_signing_key(definition_account_id)
nonces.extend(recipient_nonces); .ok_or(ExecutionFailureKind::KeyNotFoundError)?;
private_keys.push(definition_sk);
if let Some(holder_sk) = self
.0
.storage
.user_data
.get_pub_account_signing_key(holder_account_id)
{
private_keys.push(holder_sk);
let holder_nonce: Vec<nssa_core::account::Nonce> = self
.0
.get_accounts_nonces(vec![holder_account_id])
.await
.map_err(ExecutionFailureKind::SequencerError)?;
nonces.extend(holder_nonce);
} else {
println!(
"Holder's account ({holder_account_id}) private key not found in wallet. Proceeding with only definition's key."
);
}
private_keys
} else { } else {
println!( Vec::new()
"Holder's account ({holder_account_id}) private key not found in wallet. Proceeding with only definition's key." };
);
}
let message = nssa::public_transaction::Message::try_new( let message = nssa::public_transaction::Message::try_new(
Program::token().id(), Program::token().id(),
@ -687,24 +727,21 @@ impl Token<'_> {
) )
.unwrap(); .unwrap();
let msg_hash = message.hash(); let witness_set = if let Some(definition_key_path) = definition_key_path {
let witness_set = if let Some(kp) = definition_key_path {
let pin = crate::helperfunctions::read_pin().map_err(|e| { let pin = crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>( ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
e.to_string(), e.to_string(),
)) ))
})?; })?;
let (sig, pk) = KeycardWallet::sign_message_for_path_with_connect(&pin, kp, &msg_hash)?; let (signature, public_key) = KeycardWallet::sign_message_for_path_with_connect(
nssa::public_transaction::WitnessSet::from_list(&message, &[sig], &[pk]) &pin,
definition_key_path,
&message.hash(),
)?;
WitnessSet::from_list(&message, &[signature], &[public_key])
.map_err(ExecutionFailureKind::TransactionBuildError)? .map_err(ExecutionFailureKind::TransactionBuildError)?
} else { } else {
let signing_key = self nssa::public_transaction::WitnessSet::for_message(&message, &private_keys)
.0
.storage
.user_data
.get_pub_account_signing_key(definition_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key])
}; };
let tx = nssa::PublicTransaction::new(message, witness_set); let tx = nssa::PublicTransaction::new(message, witness_set);