mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-08-04 12:03:16 +00:00
Merge pull request #511 from logos-blockchain/arjentix/add-vault-wallet-commands
feat(wallet): add vault cli commands
This commit is contained in:
commit
756e304cef
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -9819,7 +9819,6 @@ dependencies = [
|
|||||||
"testcontainers",
|
"testcontainers",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
"vault_core",
|
|
||||||
"wallet",
|
"wallet",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -10904,6 +10903,7 @@ dependencies = [
|
|||||||
"token_core",
|
"token_core",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
|
"vault_core",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
45
Justfile
45
Justfile
@ -8,7 +8,7 @@ METHODS_PATH := "program_methods"
|
|||||||
TEST_METHODS_PATH := "test_program_methods"
|
TEST_METHODS_PATH := "test_program_methods"
|
||||||
ARTIFACTS := "artifacts"
|
ARTIFACTS := "artifacts"
|
||||||
|
|
||||||
# Build risc0 program artifacts
|
# Build risc0 program artifacts.
|
||||||
build-artifacts:
|
build-artifacts:
|
||||||
@echo "🔨 Building artifacts"
|
@echo "🔨 Building artifacts"
|
||||||
@for methods_path in {{METHODS_PATH}} {{TEST_METHODS_PATH}}; do \
|
@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; \
|
cp target/$methods_path/riscv32im-risc0-zkvm-elf/docker/*.bin {{ARTIFACTS}}/$methods_path; \
|
||||||
done
|
done
|
||||||
|
|
||||||
# Run tests
|
# Format codebase.
|
||||||
|
fmt:
|
||||||
|
@echo "🎨 Formatting codebase"
|
||||||
|
cargo +nightly fmt
|
||||||
|
taplo fmt
|
||||||
|
|
||||||
|
# Run tests.
|
||||||
test:
|
test:
|
||||||
@echo "🧪 Running tests"
|
@echo "🧪 Running tests"
|
||||||
RISC0_DEV_MODE=1 cargo nextest run --no-fail-fast
|
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 crypto_primitives_bench --bench primitives
|
||||||
cargo bench -p cycle_bench --features ppe --bench verify
|
cargo bench -p cycle_bench --features ppe --bench verify
|
||||||
|
|
||||||
# Run Bedrock node in docker
|
# Run Bedrock node in docker.
|
||||||
[working-directory: 'bedrock']
|
[working-directory: 'bedrock']
|
||||||
run-bedrock:
|
run-bedrock:
|
||||||
@echo "⛓️ Running bedrock"
|
@echo "⛓️ Running bedrock"
|
||||||
docker compose up
|
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']
|
[working-directory: 'lez/sequencer/service']
|
||||||
run-sequencer:
|
run-sequencer standalone="":
|
||||||
@echo "🧠 Running sequencer"
|
@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']
|
[working-directory: 'lez/indexer/service']
|
||||||
run-indexer mock="":
|
run-indexer mock="":
|
||||||
@echo "🔍 Running indexer"
|
@echo "🔍 Running indexer"
|
||||||
@if [ "{{mock}}" = "mock" ]; then \
|
@if [ "{{mock}}" = "mock" ]; then \
|
||||||
echo "🧪 Using mock data"; \
|
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 \
|
else \
|
||||||
echo "🚀 Using real data"; \
|
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
|
fi
|
||||||
|
|
||||||
# Run Explorer
|
# Run Explorer.
|
||||||
[working-directory: 'lez/explorer_service']
|
[working-directory: 'lez/explorer_service']
|
||||||
run-explorer:
|
run-explorer:
|
||||||
@echo "🌐 Running explorer"
|
@echo "🌐 Running explorer"
|
||||||
RUST_LOG=info cargo leptos serve
|
RUST_LOG=info cargo leptos serve
|
||||||
|
|
||||||
# Run Wallet
|
# Run Wallet.
|
||||||
[working-directory: 'lez/wallet']
|
[working-directory: 'lez/wallet']
|
||||||
run-wallet +args:
|
run-wallet +args:
|
||||||
@echo "🔑 Running wallet"
|
@echo "🔑 Running wallet"
|
||||||
LEE_WALLET_HOME_DIR=$(pwd)/configs/debug cargo run --release -p wallet -- {{args}}
|
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 runtime data
|
||||||
clean:
|
clean:
|
||||||
@echo "🧹 Cleaning run artifacts"
|
@echo "🧹 Cleaning run artifacts"
|
||||||
|
|||||||
@ -46,7 +46,7 @@ _wallet() {
|
|||||||
cword=$COMP_CWORD
|
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.
|
# Find the main command and subcommand by scanning words before the cursor.
|
||||||
# Global options that take a value are skipped along with their argument.
|
# Global options that take a value are skipped along with their argument.
|
||||||
@ -535,6 +535,38 @@ _wallet() {
|
|||||||
esac
|
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)
|
config)
|
||||||
case "$subcmd" in
|
case "$subcmd" in
|
||||||
"")
|
"")
|
||||||
|
|||||||
@ -25,6 +25,7 @@ _wallet() {
|
|||||||
'token:Token program interaction subcommand'
|
'token:Token program interaction subcommand'
|
||||||
'amm:AMM program interaction subcommand'
|
'amm:AMM program interaction subcommand'
|
||||||
'ata:Associated Token Account 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'
|
'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'
|
'config:Command to setup config, get and set config fields'
|
||||||
'restore-keys:Restoring keys from given password at given depth'
|
'restore-keys:Restoring keys from given password at given depth'
|
||||||
@ -56,6 +57,9 @@ _wallet() {
|
|||||||
ata)
|
ata)
|
||||||
_wallet_ata
|
_wallet_ata
|
||||||
;;
|
;;
|
||||||
|
vault)
|
||||||
|
_wallet_vault
|
||||||
|
;;
|
||||||
config)
|
config)
|
||||||
_wallet_config
|
_wallet_config
|
||||||
;;
|
;;
|
||||||
@ -442,6 +446,41 @@ _wallet_ata() {
|
|||||||
esac
|
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
|
# config subcommand
|
||||||
_wallet_config() {
|
_wallet_config() {
|
||||||
local -a subcommands
|
local -a subcommands
|
||||||
@ -515,6 +554,7 @@ _wallet_help() {
|
|||||||
'token:Token program interaction subcommand'
|
'token:Token program interaction subcommand'
|
||||||
'amm:AMM program interaction subcommand'
|
'amm:AMM program interaction subcommand'
|
||||||
'ata:Associated Token Account 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'
|
'check-health:Check the wallet can connect to the node'
|
||||||
'config:Command to setup config, get and set config fields'
|
'config:Command to setup config, get and set config fields'
|
||||||
'restore-keys:Restoring keys from given password at given depth'
|
'restore-keys:Restoring keys from given password at given depth'
|
||||||
|
|||||||
208
integration_tests/tests/vault.rs
Normal file
208
integration_tests/tests/vault.rs
Normal 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(())
|
||||||
|
}
|
||||||
@ -18,6 +18,7 @@ token_core.workspace = true
|
|||||||
amm_core.workspace = true
|
amm_core.workspace = true
|
||||||
testnet_initial_state.workspace = true
|
testnet_initial_state.workspace = true
|
||||||
ata_core.workspace = true
|
ata_core.workspace = true
|
||||||
|
vault_core.workspace = true
|
||||||
bip39.workspace = true
|
bip39.workspace = true
|
||||||
pyo3.workspace = true
|
pyo3.workspace = true
|
||||||
rpassword = "7"
|
rpassword = "7"
|
||||||
|
|||||||
@ -22,7 +22,7 @@ use crate::{
|
|||||||
programs::{
|
programs::{
|
||||||
amm::AmmProgramAgnosticSubcommand, ata::AtaSubcommand,
|
amm::AmmProgramAgnosticSubcommand, ata::AtaSubcommand,
|
||||||
native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand,
|
native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand,
|
||||||
token::TokenProgramAgnosticSubcommand,
|
token::TokenProgramAgnosticSubcommand, vault::VaultSubcommand,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
storage::Storage,
|
storage::Storage,
|
||||||
@ -65,6 +65,9 @@ pub enum Command {
|
|||||||
/// Associated Token Account program interaction subcommand.
|
/// Associated Token Account program interaction subcommand.
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Ata(AtaSubcommand),
|
Ata(AtaSubcommand),
|
||||||
|
/// Vault program interaction subcommand.
|
||||||
|
#[command(subcommand)]
|
||||||
|
Vault(VaultSubcommand),
|
||||||
/// Group key management (create, invite, join, derive keys).
|
/// Group key management (create, invite, join, derive keys).
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Group(GroupSubcommand),
|
Group(GroupSubcommand),
|
||||||
@ -254,6 +257,7 @@ pub async fn execute_subcommand(
|
|||||||
Command::Token(token_subcommand) => token_subcommand.handle_subcommand(wallet_core).await?,
|
Command::Token(token_subcommand) => token_subcommand.handle_subcommand(wallet_core).await?,
|
||||||
Command::AMM(amm_subcommand) => amm_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::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::Group(group_subcommand) => group_subcommand.handle_subcommand(wallet_core).await?,
|
||||||
Command::Keycard(keycard_subcommand) => {
|
Command::Keycard(keycard_subcommand) => {
|
||||||
keycard_subcommand.handle_subcommand(wallet_core).await?
|
keycard_subcommand.handle_subcommand(wallet_core).await?
|
||||||
|
|||||||
@ -3,3 +3,4 @@ pub mod ata;
|
|||||||
pub mod native_token_transfer;
|
pub mod native_token_transfer;
|
||||||
pub mod pinata;
|
pub mod pinata;
|
||||||
pub mod token;
|
pub mod token;
|
||||||
|
pub mod vault;
|
||||||
|
|||||||
140
lez/wallet/src/cli/programs/vault.rs
Normal file
140
lez/wallet/src/cli/programs/vault.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,3 +6,4 @@ pub mod ata;
|
|||||||
pub mod native_token_transfer;
|
pub mod native_token_transfer;
|
||||||
pub mod pinata;
|
pub mod pinata;
|
||||||
pub mod token;
|
pub mod token;
|
||||||
|
pub mod vault;
|
||||||
|
|||||||
139
lez/wallet/src/program_facades/vault.rs
Normal file
139
lez/wallet/src/program_facades/vault.rs
Normal 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)
|
||||||
|
}
|
||||||
@ -17,7 +17,6 @@ lee_core = { workspace = true, features = ["host"] }
|
|||||||
sequencer_core = { workspace = true, features = ["default", "testnet"] }
|
sequencer_core = { workspace = true, features = ["default", "testnet"] }
|
||||||
sequencer_service.workspace = true
|
sequencer_service.workspace = true
|
||||||
sequencer_service_rpc = { workspace = true, features = ["client"] }
|
sequencer_service_rpc = { workspace = true, features = ["client"] }
|
||||||
vault_core.workspace = true
|
|
||||||
wallet.workspace = true
|
wallet.workspace = true
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
|||||||
@ -353,7 +353,7 @@ impl TestContextBuilder {
|
|||||||
)
|
)
|
||||||
.context("Failed to setup wallet")?;
|
.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
|
.await
|
||||||
.context("Failed to initialize public accounts in wallet")?;
|
.context("Failed to initialize public accounts in wallet")?;
|
||||||
|
|
||||||
|
|||||||
@ -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 anyhow::{Context as _, Result, bail};
|
||||||
use common::transaction::LeeTransaction;
|
|
||||||
use indexer_service::IndexerHandle;
|
use indexer_service::IndexerHandle;
|
||||||
use lee::{AccountId, PrivateKey, PublicKey, PublicTransaction, program::Program};
|
use lee::{AccountId, PrivateKey, PublicKey};
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use sequencer_service::{GenesisAction, SequencerHandle};
|
use sequencer_service::{GenesisAction, SequencerHandle};
|
||||||
use sequencer_service_rpc::RpcClient as _;
|
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use testcontainers::compose::DockerCompose;
|
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::{
|
use crate::{
|
||||||
BEDROCK_SERVICE_PORT, BEDROCK_SERVICE_WITH_OPEN_PORT,
|
BEDROCK_SERVICE_PORT, BEDROCK_SERVICE_WITH_OPEN_PORT,
|
||||||
config::{self, InitialPrivateAccountForWallet},
|
config::{self, InitialPrivateAccountForWallet},
|
||||||
|
private_mention, public_mention,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn setup_bedrock_node() -> Result<(DockerCompose, SocketAddr)> {
|
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(
|
pub async fn setup_public_accounts_with_initial_supply(
|
||||||
wallet: &WalletCore,
|
wallet: &mut WalletCore,
|
||||||
initial_public_accounts: &[(PrivateKey, u128)],
|
initial_public_accounts: &[(PrivateKey, u128)],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for (private_key, amount) in initial_public_accounts {
|
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(
|
async fn claim_funds_from_vault(
|
||||||
wallet: &WalletCore,
|
wallet: &mut WalletCore,
|
||||||
owner_id: AccountId,
|
owner_id: AccountId,
|
||||||
amount: u128,
|
amount: u128,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let vault_program_id = Program::vault().id();
|
let result = wallet::cli::execute_subcommand(
|
||||||
let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner_id);
|
wallet,
|
||||||
|
Command::Vault(VaultSubcommand::Claim {
|
||||||
let nonces = wallet
|
account_id: public_mention(owner_id),
|
||||||
.get_accounts_nonces(vec![owner_id])
|
amount,
|
||||||
.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 },
|
|
||||||
)
|
)
|
||||||
.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 SubcommandReturnValue::Empty = result else {
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
bail!("Expected Empty return value for public vault claim");
|
||||||
|
};
|
||||||
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")?;
|
|
||||||
|
|
||||||
Ok(())
|
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}");
|
bail!("Missing private account in wallet key chain for account {owner_id}");
|
||||||
};
|
};
|
||||||
|
|
||||||
let vault_program = Program::vault();
|
let result = wallet::cli::execute_subcommand(
|
||||||
let vault_program_id = vault_program.id();
|
wallet,
|
||||||
let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner_id);
|
Command::Vault(VaultSubcommand::Claim {
|
||||||
|
account_id: private_mention(owner_id),
|
||||||
|
amount,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("Failed to execute private vault claim command")?;
|
||||||
|
|
||||||
let instruction_data =
|
let SubcommandReturnValue::PrivacyPreservingTransfer { .. } = result else {
|
||||||
Program::serialize_instruction(vault_core::Instruction::Claim { amount })
|
bail!("Expected PrivacyPreservingTransfer return value for private vault claim");
|
||||||
.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");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user