Merge pull request #511 from logos-blockchain/arjentix/add-vault-wallet-commands

feat(wallet): add vault cli commands
This commit is contained in:
Daniil Polyakov 2026-06-15 22:14:09 +03:00 committed by GitHub
commit 756e304cef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 636 additions and 103 deletions

2
Cargo.lock generated
View File

@ -9819,7 +9819,6 @@ dependencies = [
"testcontainers",
"tokio",
"url",
"vault_core",
"wallet",
]
@ -10904,6 +10903,7 @@ dependencies = [
"token_core",
"tokio",
"url",
"vault_core",
"zeroize",
]

View File

@ -8,7 +8,7 @@ METHODS_PATH := "program_methods"
TEST_METHODS_PATH := "test_program_methods"
ARTIFACTS := "artifacts"
# Build risc0 program artifacts
# Build risc0 program artifacts.
build-artifacts:
@echo "🔨 Building artifacts"
@for methods_path in {{METHODS_PATH}} {{TEST_METHODS_PATH}}; do \
@ -18,7 +18,13 @@ build-artifacts:
cp target/$methods_path/riscv32im-risc0-zkvm-elf/docker/*.bin {{ARTIFACTS}}/$methods_path; \
done
# Run tests
# Format codebase.
fmt:
@echo "🎨 Formatting codebase"
cargo +nightly fmt
taplo fmt
# Run tests.
test:
@echo "🧪 Running tests"
RISC0_DEV_MODE=1 cargo nextest run --no-fail-fast
@ -29,42 +35,59 @@ bench:
cargo bench -p crypto_primitives_bench --bench primitives
cargo bench -p cycle_bench --features ppe --bench verify
# Run Bedrock node in docker
# Run Bedrock node in docker.
[working-directory: 'bedrock']
run-bedrock:
@echo "⛓️ Running bedrock"
docker compose up
# Run Sequencer
# Run Sequencer. Run with RISC0_DEV_MODE=1 to disable proof verification for faster iteration.
[working-directory: 'lez/sequencer/service']
run-sequencer:
run-sequencer standalone="":
@echo "🧠 Running sequencer"
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release -p sequencer_service configs/debug/sequencer_config.json
@if [ "{{standalone}}" = "standalone" ]; then \
echo "🧪 Running in standalone mode"; \
RUST_LOG=info cargo run --features standalone --release -p sequencer_service configs/debug/sequencer_config.json; \
else \
echo "🚀 Running in normal mode"; \
RUST_LOG=info cargo run --release -p sequencer_service configs/debug/sequencer_config.json; \
fi
# Run Indexer
# Run Indexer. Run with RISC0_DEV_MODE=1 to disable proof verification for faster iteration.
[working-directory: 'lez/indexer/service']
run-indexer mock="":
@echo "🔍 Running indexer"
@if [ "{{mock}}" = "mock" ]; then \
echo "🧪 Using mock data"; \
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release --features mock-responses -p indexer_service configs/indexer_config.json; \
RUST_LOG=info cargo run --release --features mock-responses -p indexer_service configs/indexer_config.json; \
else \
echo "🚀 Using real data"; \
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release -p indexer_service configs/indexer_config.json; \
RUST_LOG=info cargo run --release -p indexer_service configs/indexer_config.json; \
fi
# Run Explorer
# Run Explorer.
[working-directory: 'lez/explorer_service']
run-explorer:
@echo "🌐 Running explorer"
RUST_LOG=info cargo leptos serve
# Run Wallet
# Run Wallet.
[working-directory: 'lez/wallet']
run-wallet +args:
@echo "🔑 Running wallet"
LEE_WALLET_HOME_DIR=$(pwd)/configs/debug cargo run --release -p wallet -- {{args}}
# Import test accounts supplied in sequencer configuration.
wallet-import-test-accounts:
@echo "⚙️ Initializing accounts"
just run-wallet account import public --private-key 7f273098f25b71e6c005a9519f2678da8d1c7f01f6a27778e2d9948abdf901fb
just run-wallet vault claim --account-id Public/CbgR6tj5kWx5oziiFptM7jMvrQeYY3Mzaao6ciuhSr2r --amount 10000
just run-wallet account import public --private-key f434f8741720014586ae43356d2aec6257da086222f604ddb75d69733b86fc4c
just run-wallet vault claim --account-id Public/2RHZhw9h534Zr3eq2RGhQete2Hh667foECzXPmSkGni2 --amount 20000
just run-wallet account list
# Clean runtime data
clean:
@echo "🧹 Cleaning run artifacts"

View File

@ -46,7 +46,7 @@ _wallet() {
cword=$COMP_CWORD
}
local commands="auth-transfer chain-info account pinata token amm ata check-health config restore-keys deploy-program help"
local commands="auth-transfer chain-info account pinata token amm ata vault check-health config restore-keys deploy-program help"
# Find the main command and subcommand by scanning words before the cursor.
# Global options that take a value are skipped along with their argument.
@ -535,6 +535,38 @@ _wallet() {
esac
;;
vault)
case "$subcmd" in
"")
COMPREPLY=($(compgen -W "transfer claim help" -- "$cur"))
;;
transfer)
case "$prev" in
--from | --to)
_wallet_complete_account_id "$cur"
;;
--amount)
;; # no specific completion
*)
COMPREPLY=($(compgen -W "--from --to --amount" -- "$cur"))
;;
esac
;;
claim)
case "$prev" in
--account-id)
_wallet_complete_account_id "$cur"
;;
--amount)
;; # no specific completion
*)
COMPREPLY=($(compgen -W "--account-id --amount" -- "$cur"))
;;
esac
;;
esac
;;
config)
case "$subcmd" in
"")

View File

@ -25,6 +25,7 @@ _wallet() {
'token:Token program interaction subcommand'
'amm:AMM program interaction subcommand'
'ata:Associated Token Account program interaction subcommand'
'vault:Vault program interaction subcommand'
'check-health:Check the wallet can connect to the node and builtin local programs match the remote versions'
'config:Command to setup config, get and set config fields'
'restore-keys:Restoring keys from given password at given depth'
@ -56,6 +57,9 @@ _wallet() {
ata)
_wallet_ata
;;
vault)
_wallet_vault
;;
config)
_wallet_config
;;
@ -442,6 +446,41 @@ _wallet_ata() {
esac
}
# vault subcommand
_wallet_vault() {
local -a subcommands
_arguments -C \
'1: :->subcommand' \
'*:: :->args'
case $state in
subcommand)
subcommands=(
'transfer:Transfer native tokens from sender to recipient vault account'
'claim:Claim native tokens from account vault account'
'help:Print this message or the help of the given subcommand(s)'
)
_describe -t subcommands 'vault subcommands' subcommands
;;
args)
case $line[1] in
transfer)
_arguments \
'--from[Source account with privacy prefix or label]:from:_wallet_account_ids' \
'--to[Recipient account with privacy prefix or label]:to:_wallet_account_ids' \
'--amount[Amount of native tokens to transfer]:amount:'
;;
claim)
_arguments \
'--account-id[Account with privacy prefix or label; vault id is derived automatically]:account:_wallet_account_ids' \
'--amount[Amount of native tokens to claim]:amount:'
;;
esac
;;
esac
}
# config subcommand
_wallet_config() {
local -a subcommands
@ -515,6 +554,7 @@ _wallet_help() {
'token:Token program interaction subcommand'
'amm:AMM program interaction subcommand'
'ata:Associated Token Account program interaction subcommand'
'vault:Vault program interaction subcommand'
'check-health:Check the wallet can connect to the node'
'config:Command to setup config, get and set config fields'
'restore-keys:Restoring keys from given password at given depth'

View File

@ -0,0 +1,208 @@
#![expect(
clippy::tests_outside_test_module,
reason = "We don't care about these in tests"
)]
use anyhow::{Context as _, Result};
use integration_tests::{TestContext, private_mention, public_mention};
use lee::program::Program;
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::cli::{Command, SubcommandReturnValue, programs::vault::VaultSubcommand};
#[test]
async fn public_transfer_and_public_claim() -> Result<()> {
let mut ctx = TestContext::new().await?;
let amount: u128 = 100;
let sender = ctx.existing_public_accounts()[0];
let recipient = ctx.existing_public_accounts()[1];
let vault_program_id = Program::vault().id();
let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient);
let sender_balance_before = ctx.sequencer_client().get_account_balance(sender).await?;
let recipient_balance_before = ctx
.sequencer_client()
.get_account_balance(recipient)
.await?;
let recipient_vault_balance_before = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
let transfer_result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Vault(VaultSubcommand::Transfer {
from: public_mention(sender),
to: public_mention(recipient),
amount,
}),
)
.await?;
assert!(
matches!(transfer_result, SubcommandReturnValue::Empty),
"Expected Empty return value for public vault transfer"
);
let sender_balance_after_transfer = ctx.sequencer_client().get_account_balance(sender).await?;
let recipient_balance_after_transfer = ctx
.sequencer_client()
.get_account_balance(recipient)
.await?;
let recipient_vault_balance_after_transfer = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
assert_eq!(
sender_balance_after_transfer,
sender_balance_before - amount
);
assert_eq!(recipient_balance_after_transfer, recipient_balance_before);
assert_eq!(
recipient_vault_balance_after_transfer,
recipient_vault_balance_before + amount
);
let claim_result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Vault(VaultSubcommand::Claim {
account_id: public_mention(recipient),
amount,
}),
)
.await?;
assert!(
matches!(claim_result, SubcommandReturnValue::Empty),
"Expected Empty return value for public vault claim"
);
let sender_balance_after_claim = ctx.sequencer_client().get_account_balance(sender).await?;
let recipient_balance_after_claim = ctx
.sequencer_client()
.get_account_balance(recipient)
.await?;
let recipient_vault_balance_after_claim = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
assert_eq!(sender_balance_after_claim, sender_balance_before - amount);
assert_eq!(
recipient_balance_after_claim,
recipient_balance_before + amount
);
assert_eq!(
recipient_vault_balance_after_claim,
recipient_vault_balance_before
);
Ok(())
}
#[test]
async fn private_transfer_and_private_claim() -> Result<()> {
let mut ctx = TestContext::new().await?;
let amount: u128 = 100;
let sender = ctx.existing_private_accounts()[0];
let owner = ctx.existing_private_accounts()[1];
let vault_program_id = Program::vault().id();
let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner);
let sender_balance_before = ctx
.wallet()
.get_account_private(sender)
.context("Failed to load sender private account")?
.balance;
let owner_balance_before = ctx
.wallet()
.get_account_private(owner)
.context("Failed to load owner private account")?
.balance;
let owner_vault_balance_before = ctx
.sequencer_client()
.get_account_balance(owner_vault_id)
.await?;
let transfer_result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Vault(VaultSubcommand::Transfer {
from: private_mention(sender),
to: private_mention(owner),
amount,
}),
)
.await?;
assert!(
matches!(
transfer_result,
SubcommandReturnValue::PrivacyPreservingTransfer { .. }
),
"Expected PrivacyPreservingTransfer return value for private vault transfer"
);
let sender_balance_after_transfer = ctx
.wallet()
.get_account_private(sender)
.context("Failed to load sender private account after transfer")?
.balance;
let owner_balance_after_transfer = ctx
.wallet()
.get_account_private(owner)
.context("Failed to load owner private account after transfer")?
.balance;
let owner_vault_balance_after_transfer = ctx
.sequencer_client()
.get_account_balance(owner_vault_id)
.await?;
assert_eq!(
sender_balance_after_transfer,
sender_balance_before - amount
);
assert_eq!(owner_balance_after_transfer, owner_balance_before);
assert_eq!(
owner_vault_balance_after_transfer,
owner_vault_balance_before + amount
);
let claim_result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Vault(VaultSubcommand::Claim {
account_id: private_mention(owner),
amount,
}),
)
.await?;
assert!(
matches!(
claim_result,
SubcommandReturnValue::PrivacyPreservingTransfer { .. }
),
"Expected PrivacyPreservingTransfer return value for private vault claim"
);
let sender_balance_after_claim = ctx
.wallet()
.get_account_private(sender)
.context("Failed to load sender private account after claim")?
.balance;
let owner_balance_after_claim = ctx
.wallet()
.get_account_private(owner)
.context("Failed to load owner private account after claim")?
.balance;
let owner_vault_balance_after_claim = ctx
.sequencer_client()
.get_account_balance(owner_vault_id)
.await?;
assert_eq!(sender_balance_after_claim, sender_balance_before - amount);
assert_eq!(owner_balance_after_claim, owner_balance_before + amount);
assert_eq!(owner_vault_balance_after_claim, owner_vault_balance_before);
Ok(())
}

View File

@ -18,6 +18,7 @@ token_core.workspace = true
amm_core.workspace = true
testnet_initial_state.workspace = true
ata_core.workspace = true
vault_core.workspace = true
bip39.workspace = true
pyo3.workspace = true
rpassword = "7"

View File

@ -22,7 +22,7 @@ use crate::{
programs::{
amm::AmmProgramAgnosticSubcommand, ata::AtaSubcommand,
native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand,
token::TokenProgramAgnosticSubcommand,
token::TokenProgramAgnosticSubcommand, vault::VaultSubcommand,
},
},
storage::Storage,
@ -65,6 +65,9 @@ pub enum Command {
/// Associated Token Account program interaction subcommand.
#[command(subcommand)]
Ata(AtaSubcommand),
/// Vault program interaction subcommand.
#[command(subcommand)]
Vault(VaultSubcommand),
/// Group key management (create, invite, join, derive keys).
#[command(subcommand)]
Group(GroupSubcommand),
@ -254,6 +257,7 @@ pub async fn execute_subcommand(
Command::Token(token_subcommand) => token_subcommand.handle_subcommand(wallet_core).await?,
Command::AMM(amm_subcommand) => amm_subcommand.handle_subcommand(wallet_core).await?,
Command::Ata(ata_subcommand) => ata_subcommand.handle_subcommand(wallet_core).await?,
Command::Vault(vault_subcommand) => vault_subcommand.handle_subcommand(wallet_core).await?,
Command::Group(group_subcommand) => group_subcommand.handle_subcommand(wallet_core).await?,
Command::Keycard(keycard_subcommand) => {
keycard_subcommand.handle_subcommand(wallet_core).await?

View File

@ -3,3 +3,4 @@ pub mod ata;
pub mod native_token_transfer;
pub mod pinata;
pub mod token;
pub mod vault;

View File

@ -0,0 +1,140 @@
use anyhow::Result;
use clap::Subcommand;
use common::transaction::LeeTransaction;
use lee::AccountId;
use crate::{
AccDecodeData::Decode,
WalletCore,
account::AccountIdWithPrivacy,
cli::{CliAccountMention, SubcommandReturnValue, WalletSubcommand},
program_facades::vault::Vault,
};
/// Represents generic CLI subcommand for a wallet working with vault program.
#[derive(Subcommand, Debug, Clone)]
pub enum VaultSubcommand {
/// Transfer native tokens from sender to recipient's vault account.
Transfer {
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
from: CliAccountMention,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
to: CliAccountMention,
/// Amount of native tokens to transfer.
#[arg(long)]
amount: u128,
},
/// Claim native tokens from account's vault account.
Claim {
/// Either 32 byte base58 account id string with privacy prefix or a label.
///
/// The owner's vault account id is computed from this account id.
#[arg(long)]
account_id: CliAccountMention,
/// Amount of native tokens to claim.
#[arg(long)]
amount: u128,
},
}
impl WalletSubcommand for VaultSubcommand {
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
Self::Transfer { from, to, amount } => {
let from = from.resolve(wallet_core.storage())?;
let recipient = to.resolve(wallet_core.storage())?;
let recipient_id = account_id_without_privacy(recipient);
match from {
AccountIdWithPrivacy::Public(sender_id) => {
let tx_hash = Vault(wallet_core)
.send_transfer(sender_id, recipient_id, amount)
.await?;
println!("Transaction hash is {tx_hash}");
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
println!("Transaction data is {transfer_tx:?}");
Ok(SubcommandReturnValue::Empty)
}
AccountIdWithPrivacy::Private(sender_id) => {
let (tx_hash, secret_sender) = Vault(wallet_core)
.send_transfer_private_sender(sender_id, recipient_id, amount)
.await?;
println!("Transaction hash is {tx_hash}");
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
println!("Transaction data is {transfer_tx:?}");
if let LeeTransaction::PrivacyPreserving(tx) = transfer_tx {
wallet_core.decode_insert_privacy_preserving_transaction_results(
&tx,
&[Decode(secret_sender, sender_id)],
)?;
}
wallet_core.store_persistent_data()?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
}
}
Self::Claim { account_id, amount } => {
let account_id = account_id.resolve(wallet_core.storage())?;
match account_id {
AccountIdWithPrivacy::Public(owner_id) => {
let tx_hash = Vault(wallet_core).send_claim(owner_id, amount).await?;
println!("Transaction hash is {tx_hash}");
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
println!("Transaction data is {transfer_tx:?}");
Ok(SubcommandReturnValue::Empty)
}
AccountIdWithPrivacy::Private(owner_id) => {
let (tx_hash, secret_owner) = Vault(wallet_core)
.send_claim_private_owner(owner_id, amount)
.await?;
println!("Transaction hash is {tx_hash}");
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
println!("Transaction data is {transfer_tx:?}");
if let LeeTransaction::PrivacyPreserving(tx) = transfer_tx {
wallet_core.decode_insert_privacy_preserving_transaction_results(
&tx,
&[Decode(secret_owner, owner_id)],
)?;
}
wallet_core.store_persistent_data()?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
}
}
}
}
}
const fn account_id_without_privacy(account_id: AccountIdWithPrivacy) -> AccountId {
match account_id {
AccountIdWithPrivacy::Public(account_id) | AccountIdWithPrivacy::Private(account_id) => {
account_id
}
}
}

View File

@ -6,3 +6,4 @@ pub mod ata;
pub mod native_token_transfer;
pub mod pinata;
pub mod token;
pub mod vault;

View File

@ -0,0 +1,139 @@
use std::collections::HashMap;
use common::HashType;
use lee::{
AccountId, privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program,
};
use lee_core::SharedSecretKey;
use crate::{AccountIdentity, ExecutionFailureKind, WalletCore};
pub struct Vault<'wallet>(pub &'wallet WalletCore);
impl Vault<'_> {
pub async fn send_transfer(
&self,
sender_id: AccountId,
recipient_id: AccountId,
amount: u128,
) -> Result<HashType, ExecutionFailureKind> {
let program = Program::vault();
let vault_program_id = program.id();
let recipient_vault_id =
vault_core::compute_vault_account_id(vault_program_id, recipient_id);
let instruction = vault_core::Instruction::Transfer {
recipient_id,
amount,
};
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0
.send_pub_tx(
vec![
AccountIdentity::Public(sender_id),
AccountIdentity::PublicNoSign(recipient_vault_id),
],
instruction_data,
&program.into(),
)
.await
}
pub async fn send_transfer_private_sender(
&self,
sender_id: AccountId,
recipient_id: AccountId,
amount: u128,
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
let vault_program_id = Program::vault().id();
let recipient_vault_id =
vault_core::compute_vault_account_id(vault_program_id, recipient_id);
let instruction = vault_core::Instruction::Transfer {
recipient_id,
amount,
};
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0
.send_privacy_preserving_tx(
vec![
self.0
.resolve_private_account(sender_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
AccountIdentity::Public(recipient_vault_id),
],
instruction_data,
&vault_with_auth_dependency(),
)
.await
.map(|(hash, mut secrets)| {
let secret = secrets.pop().expect("expected sender's secret");
(hash, secret)
})
}
pub async fn send_claim(
&self,
owner_id: AccountId,
amount: u128,
) -> Result<HashType, ExecutionFailureKind> {
let program = Program::vault();
let vault_program_id = program.id();
let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner_id);
let instruction = vault_core::Instruction::Claim { amount };
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0
.send_pub_tx(
vec![
AccountIdentity::Public(owner_id),
AccountIdentity::PublicNoSign(owner_vault_id),
],
instruction_data,
&program.into(),
)
.await
}
pub async fn send_claim_private_owner(
&self,
owner_id: AccountId,
amount: u128,
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
let vault_program_id = Program::vault().id();
let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner_id);
let instruction = vault_core::Instruction::Claim { amount };
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0
.send_privacy_preserving_tx(
vec![
self.0
.resolve_private_account(owner_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
AccountIdentity::Public(owner_vault_id),
],
instruction_data,
&vault_with_auth_dependency(),
)
.await
.map(|(hash, mut secrets)| {
let secret = secrets.pop().expect("expected owner's secret");
(hash, secret)
})
}
}
fn vault_with_auth_dependency() -> ProgramWithDependencies {
let auth_transfer = Program::authenticated_transfer_program();
let mut deps = HashMap::new();
deps.insert(auth_transfer.id(), auth_transfer);
ProgramWithDependencies::new(Program::vault(), deps)
}

View File

@ -17,7 +17,6 @@ lee_core = { workspace = true, features = ["host"] }
sequencer_core = { workspace = true, features = ["default", "testnet"] }
sequencer_service.workspace = true
sequencer_service_rpc = { workspace = true, features = ["client"] }
vault_core.workspace = true
wallet.workspace = true
anyhow.workspace = true

View File

@ -353,7 +353,7 @@ impl TestContextBuilder {
)
.context("Failed to setup wallet")?;
setup_public_accounts_with_initial_supply(&wallet, &initial_public_accounts)
setup_public_accounts_with_initial_supply(&mut wallet, &initial_public_accounts)
.await
.context("Failed to initialize public accounts in wallet")?;

View File

@ -1,19 +1,22 @@
use std::{collections::HashMap, net::SocketAddr, path::PathBuf};
use std::{net::SocketAddr, path::PathBuf};
use anyhow::{Context as _, Result, bail};
use common::transaction::LeeTransaction;
use indexer_service::IndexerHandle;
use lee::{AccountId, PrivateKey, PublicKey, PublicTransaction, program::Program};
use lee::{AccountId, PrivateKey, PublicKey};
use log::{debug, warn};
use sequencer_service::{GenesisAction, SequencerHandle};
use sequencer_service_rpc::RpcClient as _;
use tempfile::TempDir;
use testcontainers::compose::DockerCompose;
use wallet::{AccDecodeData::Decode, AccountIdentity, WalletCore, config::WalletConfigOverrides};
use wallet::{
WalletCore,
cli::{Command, SubcommandReturnValue, programs::vault::VaultSubcommand},
config::WalletConfigOverrides,
};
use crate::{
BEDROCK_SERVICE_PORT, BEDROCK_SERVICE_WITH_OPEN_PORT,
config::{self, InitialPrivateAccountForWallet},
private_mention, public_mention,
};
pub async fn setup_bedrock_node() -> Result<(DockerCompose, SocketAddr)> {
@ -185,7 +188,7 @@ pub fn setup_wallet(
}
pub async fn setup_public_accounts_with_initial_supply(
wallet: &WalletCore,
wallet: &mut WalletCore,
initial_public_accounts: &[(PrivateKey, u128)],
) -> Result<()> {
for (private_key, amount) in initial_public_accounts {
@ -219,45 +222,23 @@ pub async fn setup_private_accounts_with_initial_supply(
}
async fn claim_funds_from_vault(
wallet: &WalletCore,
wallet: &mut WalletCore,
owner_id: AccountId,
amount: u128,
) -> Result<()> {
let vault_program_id = Program::vault().id();
let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner_id);
let nonces = wallet
.get_accounts_nonces(vec![owner_id])
.await
.context("Failed to fetch owner nonce")?;
let signing_key = wallet
.storage()
.key_chain()
.pub_account_signing_key(owner_id)
.with_context(|| format!("Missing signing key for public account {owner_id}"))?;
let message = lee::public_transaction::Message::try_new(
vault_program_id,
vec![owner_id, owner_vault_id],
nonces,
vault_core::Instruction::Claim { amount },
let result = wallet::cli::execute_subcommand(
wallet,
Command::Vault(VaultSubcommand::Claim {
account_id: public_mention(owner_id),
amount,
}),
)
.context("Failed to build vault claim message")?;
.await
.context("Failed to execute public vault claim command")?;
let witness_set = lee::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
let tx = PublicTransaction::new(message, witness_set);
let tx_hash = wallet
.sequencer_client
.send_transaction(LeeTransaction::Public(tx))
.await
.context("Failed to submit vault claim transaction")?;
wallet
.poll_native_token_transfer(tx_hash)
.await
.context("Failed to confirm vault claim transaction")?;
let SubcommandReturnValue::Empty = result else {
bail!("Expected Empty return value for public vault claim");
};
Ok(())
}
@ -271,55 +252,19 @@ async fn claim_funds_from_vault_to_private(
bail!("Missing private account in wallet key chain for account {owner_id}");
};
let vault_program = Program::vault();
let vault_program_id = vault_program.id();
let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner_id);
let result = wallet::cli::execute_subcommand(
wallet,
Command::Vault(VaultSubcommand::Claim {
account_id: private_mention(owner_id),
amount,
}),
)
.await
.context("Failed to execute private vault claim command")?;
let instruction_data =
Program::serialize_instruction(vault_core::Instruction::Claim { amount })
.context("Failed to serialize vault private claim instruction")?;
let program_with_dependencies =
lee::privacy_preserving_transaction::circuit::ProgramWithDependencies::new(
vault_program,
HashMap::from([(
Program::authenticated_transfer_program().id(),
Program::authenticated_transfer_program(),
)]),
);
let (tx_hash, mut secrets) = wallet
.send_privacy_preserving_tx(
vec![
AccountIdentity::PrivateOwned(owner_id),
AccountIdentity::Public(owner_vault_id),
],
instruction_data,
&program_with_dependencies,
)
.await
.context("Failed to submit private vault claim transaction")?;
let secret = secrets
.pop()
.context("Expected one private output secret for vault claim")?;
let transfer_tx = wallet
.poll_native_token_transfer(tx_hash)
.await
.context("Failed to confirm private vault claim transaction")?;
let LeeTransaction::PrivacyPreserving(tx) = transfer_tx else {
bail!("Expected privacy preserving transaction result for private vault claim");
let SubcommandReturnValue::PrivacyPreservingTransfer { .. } = result else {
bail!("Expected PrivacyPreservingTransfer return value for private vault claim");
};
wallet
.decode_insert_privacy_preserving_transaction_results(&tx, &[Decode(secret, owner_id)])
.context("Failed to decode private vault claim transaction")?;
wallet
.store_persistent_data()
.context("Failed to store wallet data after private vault claim")?;
Ok(())
}