feat: remove initial accounts data from wallet, rely on import command instead

This commit is contained in:
Daniil Polyakov 2026-04-15 00:44:24 +03:00
parent f975b98d44
commit 0b070e5ad2
54 changed files with 2326 additions and 3163 deletions

13
Cargo.lock generated
View File

@ -1660,6 +1660,15 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "convert_case"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "convert_case"
version = "0.11.0"
@ -2062,6 +2071,7 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [
"convert_case 0.10.0",
"proc-macro2",
"quote",
"rustc_version",
@ -3988,6 +3998,7 @@ dependencies = [
"hmac-sha512",
"itertools 0.14.0",
"k256",
"log",
"nssa",
"nssa_core",
"rand 0.8.5",
@ -9000,6 +9011,7 @@ dependencies = [
"bip39",
"clap",
"common",
"derive_more",
"env_logger",
"futures",
"hex",
@ -9017,6 +9029,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"tempfile",
"testnet_initial_state",
"thiserror 2.0.18",
"token_core",

View File

@ -78,6 +78,7 @@ tokio-util = "0.7.18"
risc0-zkvm = { version = "3.0.5", features = ['std'] }
risc0-build = "3.0.5"
anyhow = "1.0.98"
derive_more = "2.1.1"
num_cpus = "1.13.1"
openssl = { version = "0.10", features = ["vendored"] }
openssl-probe = { version = "0.1.2" }

View File

@ -50,7 +50,7 @@ async fn main() {
// Load signing keys to provide authorization
let signing_key = wallet_core
.storage()
.user_data
.key_chain()
.get_pub_account_signing_key(account_id)
.expect("Input account should be a self owned public account");

View File

@ -7,12 +7,9 @@ use key_protocol::key_management::KeyChain;
use nssa::{Account, AccountId, PrivateKey, PublicKey};
use nssa_core::{account::Data, program::DEFAULT_PROGRAM_ID};
use sequencer_core::config::{BedrockConfig, SequencerConfig};
use testnet_initial_state::{
PrivateAccountPrivateInitialData, PrivateAccountPublicInitialData,
PublicAccountPrivateInitialData, PublicAccountPublicInitialData,
};
use testnet_initial_state::{PrivateAccountPublicInitialData, PublicAccountPublicInitialData};
use url::Url;
use wallet::config::{InitialAccountData, WalletConfig};
use wallet::config::WalletConfig;
/// Sequencer config options available for custom changes in integration tests.
#[derive(Debug, Clone, Copy)]
@ -126,28 +123,6 @@ impl InitialData {
})
.collect()
}
fn wallet_initial_accounts(&self) -> Vec<InitialAccountData> {
self.public_accounts
.iter()
.map(|(priv_key, _)| {
let pub_key = PublicKey::new_from_private_key(priv_key);
let account_id = AccountId::from(&pub_key);
InitialAccountData::Public(PublicAccountPrivateInitialData {
account_id,
pub_sign_key: priv_key.clone(),
})
})
.chain(self.private_accounts.iter().map(|(key_chain, account)| {
let account_id = AccountId::from(&key_chain.nullifier_public_key);
InitialAccountData::Private(Box::new(PrivateAccountPrivateInitialData {
account_id,
account: account.clone(),
key_chain: key_chain.clone(),
}))
}))
.collect()
}
}
#[derive(Debug, Clone, Copy)]
@ -223,10 +198,7 @@ pub fn sequencer_config(
})
}
pub fn wallet_config(
sequencer_addr: SocketAddr,
initial_data: &InitialData,
) -> Result<WalletConfig> {
pub fn wallet_config(sequencer_addr: SocketAddr) -> Result<WalletConfig> {
Ok(WalletConfig {
sequencer_addr: addr_to_url(UrlProtocol::Http, sequencer_addr)
.context("Failed to convert sequencer addr to URL")?,
@ -234,7 +206,6 @@ pub fn wallet_config(
seq_tx_poll_max_blocks: 15,
seq_poll_max_retries: 10,
seq_block_poll_max_amount: 100,
initial_accounts: Some(initial_data.wallet_initial_accounts()),
basic_auth: None,
})
}

View File

@ -14,7 +14,12 @@ use sequencer_service::SequencerHandle;
use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder};
use tempfile::TempDir;
use testcontainers::compose::DockerCompose;
use wallet::{WalletCore, config::WalletConfigOverrides};
use wallet::{
WalletCore,
account::{AccountIdWithPrivacy, Label},
cli::CliAccountMention,
config::WalletConfigOverrides,
};
pub mod config;
@ -83,8 +88,7 @@ impl TestContext {
.context("Failed to setup Sequencer")?;
let (wallet, temp_wallet_dir, wallet_password) =
Self::setup_wallet(sequencer_handle.addr(), &initial_data)
.await
Self::setup_wallet(sequencer_handle.addr(), initial_data)
.context("Failed to setup wallet")?;
let sequencer_url = config::addr_to_url(config::UrlProtocol::Http, sequencer_handle.addr())
@ -230,12 +234,12 @@ impl TestContext {
Ok((sequencer_handle, temp_sequencer_dir))
}
async fn setup_wallet(
fn setup_wallet(
sequencer_addr: SocketAddr,
initial_data: &config::InitialData,
initial_data: config::InitialData,
) -> Result<(WalletCore, TempDir, String)> {
let config = config::wallet_config(sequencer_addr, initial_data)
.context("Failed to create Wallet config")?;
let config =
config::wallet_config(sequencer_addr).context("Failed to create Wallet config")?;
let config_serialized =
serde_json::to_string_pretty(&config).context("Failed to serialize Wallet config")?;
@ -250,16 +254,30 @@ impl TestContext {
let config_overrides = WalletConfigOverrides::default();
let wallet_password = "test_pass".to_owned();
let (wallet, _mnemonic) = WalletCore::new_init_storage(
let (mut wallet, _mnemonic) = WalletCore::new_init_storage(
config_path,
storage_path,
Some(config_overrides),
&wallet_password,
)
.context("Failed to init wallet")?;
for (private_key, _balance) in initial_data.public_accounts {
wallet
.storage_mut()
.key_chain_mut()
.add_imported_public_account(private_key);
}
for (key_chain, account) in initial_data.private_accounts {
wallet
.storage_mut()
.key_chain_mut()
.add_imported_private_account(key_chain, account);
}
wallet
.store_persistent_data()
.await
.context("Failed to store wallet persistent data")?;
Ok((wallet, temp_wallet_dir, wallet_password))
@ -298,8 +316,9 @@ impl TestContext {
pub fn existing_public_accounts(&self) -> Vec<AccountId> {
self.wallet
.storage()
.user_data
.key_chain()
.public_account_ids()
.map(|(account_id, _idx)| account_id)
.collect()
}
@ -308,8 +327,9 @@ impl TestContext {
pub fn existing_private_accounts(&self) -> Vec<AccountId> {
self.wallet
.storage()
.user_data
.key_chain()
.private_account_ids()
.map(|(account_id, _idx)| account_id)
.collect()
}
}
@ -439,6 +459,21 @@ impl Drop for BlockingTestContext {
}
}
#[must_use]
pub const fn public_mention(account_id: AccountId) -> CliAccountMention {
CliAccountMention::Id(AccountIdWithPrivacy::Public(account_id))
}
#[must_use]
pub const fn private_mention(account_id: AccountId) -> CliAccountMention {
CliAccountMention::Id(AccountIdWithPrivacy::Private(account_id))
}
#[must_use]
pub fn label_mention(label: impl ToString) -> CliAccountMention {
CliAccountMention::Label(Label::new(label))
}
#[must_use]
pub fn format_public_account_id(account_id: AccountId) -> String {
format!("Public/{account_id}")

View File

@ -9,10 +9,13 @@ use log::info;
use nssa::program::Program;
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::cli::{
Command,
account::{AccountSubcommand, NewSubcommand},
execute_subcommand,
use wallet::{
account::{AccountIdWithPrivacy, Label},
cli::{
Command,
account::{AccountSubcommand, NewSubcommand},
execute_subcommand,
},
};
#[test]
@ -41,7 +44,7 @@ async fn get_existing_account() -> Result<()> {
async fn new_public_account_with_label() -> Result<()> {
let mut ctx = TestContext::new().await?;
let label = "my-test-public-account".to_owned();
let label = Label::new("my-test-public-account");
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: Some(label.clone()),
@ -55,14 +58,9 @@ async fn new_public_account_with_label() -> Result<()> {
};
// Verify the label was stored
let stored_label = ctx
.wallet()
.storage()
.labels
.get(&account_id.to_string())
.expect("Label should be stored for the new account");
let resolved = ctx.wallet().storage().resolve_label(&label);
assert_eq!(stored_label.to_string(), label);
assert_eq!(resolved, Some(AccountIdWithPrivacy::Public(account_id)));
info!("Successfully created public account with label");
@ -73,7 +71,7 @@ async fn new_public_account_with_label() -> Result<()> {
async fn new_private_account_with_label() -> Result<()> {
let mut ctx = TestContext::new().await?;
let label = "my-test-private-account".to_owned();
let label = Label::new("my-test-private-account");
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
cci: None,
label: Some(label.clone()),
@ -88,14 +86,9 @@ async fn new_private_account_with_label() -> Result<()> {
};
// Verify the label was stored
let stored_label = ctx
.wallet()
.storage()
.labels
.get(&account_id.to_string())
.expect("Label should be stored for the new account");
let resolved = ctx.wallet().storage().resolve_label(&label);
assert_eq!(stored_label.to_string(), label);
assert_eq!(resolved, Some(AccountIdWithPrivacy::Private(account_id)));
info!("Successfully created private account with label");
@ -119,12 +112,13 @@ async fn new_public_account_without_label() -> Result<()> {
panic!("Expected RegisterAccount return value")
};
// Verify no label was stored
// Verify no label was stored for the account id
assert!(
!ctx.wallet()
ctx.wallet()
.storage()
.labels
.contains_key(&account_id.to_string()),
.labels_for_account(AccountIdWithPrivacy::Public(account_id))
.next()
.is_none(),
"No label should be stored when not provided"
);

View File

@ -7,14 +7,19 @@
use std::time::Duration;
use anyhow::Result;
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id};
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, label_mention, public_mention,
};
use log::info;
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::{amm::AmmProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand},
use wallet::{
account::Label,
cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::{amm::AmmProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand},
},
};
#[test]
@ -113,10 +118,8 @@ async fn amm_public() -> Result<()> {
// Create new token
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id_1)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id_1)),
supply_account_label: None,
definition_account_id: public_mention(definition_account_id_1),
supply_account_id: public_mention(supply_account_id_1),
name: "A NAM1".to_owned(),
total_supply: 37,
@ -127,10 +130,8 @@ async fn amm_public() -> Result<()> {
// Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_1`
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: Some(format_public_account_id(supply_account_id_1)),
from_label: None,
to: Some(format_public_account_id(recipient_account_id_1)),
to_label: None,
from: public_mention(supply_account_id_1),
to: Some(public_mention(recipient_account_id_1)),
to_npk: None,
to_vpk: None,
amount: 7,
@ -142,10 +143,8 @@ async fn amm_public() -> Result<()> {
// Create new token
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id_2)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id_2)),
supply_account_label: None,
definition_account_id: public_mention(definition_account_id_2),
supply_account_id: public_mention(supply_account_id_2),
name: "A NAM2".to_owned(),
total_supply: 37,
@ -156,10 +155,8 @@ async fn amm_public() -> Result<()> {
// Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_2`
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: Some(format_public_account_id(supply_account_id_2)),
from_label: None,
to: Some(format_public_account_id(recipient_account_id_2)),
to_label: None,
from: public_mention(supply_account_id_2),
to: Some(public_mention(recipient_account_id_2)),
to_npk: None,
to_vpk: None,
amount: 7,
@ -191,12 +188,9 @@ async fn amm_public() -> Result<()> {
// Send creation tx
let subcommand = AmmProgramAgnosticSubcommand::New {
user_holding_a: Some(format_public_account_id(recipient_account_id_1)),
user_holding_a_label: None,
user_holding_b: Some(format_public_account_id(recipient_account_id_2)),
user_holding_b_label: None,
user_holding_lp: Some(format_public_account_id(user_holding_lp)),
user_holding_lp_label: None,
user_holding_a: public_mention(recipient_account_id_1),
user_holding_b: public_mention(recipient_account_id_2),
user_holding_lp: public_mention(user_holding_lp),
balance_a: 3,
balance_b: 3,
};
@ -237,13 +231,11 @@ async fn amm_public() -> Result<()> {
// Make swap
let subcommand = AmmProgramAgnosticSubcommand::SwapExactInput {
user_holding_a: Some(format_public_account_id(recipient_account_id_1)),
user_holding_a_label: None,
user_holding_b: Some(format_public_account_id(recipient_account_id_2)),
user_holding_b_label: None,
user_holding_a: public_mention(recipient_account_id_1),
user_holding_b: public_mention(recipient_account_id_2),
amount_in: 2,
min_amount_out: 1,
token_definition: definition_account_id_1.to_string(),
token_definition: definition_account_id_1,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?;
@ -282,13 +274,11 @@ async fn amm_public() -> Result<()> {
// Make swap
let subcommand = AmmProgramAgnosticSubcommand::SwapExactInput {
user_holding_a: Some(format_public_account_id(recipient_account_id_1)),
user_holding_a_label: None,
user_holding_b: Some(format_public_account_id(recipient_account_id_2)),
user_holding_b_label: None,
user_holding_a: public_mention(recipient_account_id_1),
user_holding_b: public_mention(recipient_account_id_2),
amount_in: 2,
min_amount_out: 1,
token_definition: definition_account_id_2.to_string(),
token_definition: definition_account_id_2,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?;
@ -327,12 +317,9 @@ async fn amm_public() -> Result<()> {
// Add liquidity
let subcommand = AmmProgramAgnosticSubcommand::AddLiquidity {
user_holding_a: Some(format_public_account_id(recipient_account_id_1)),
user_holding_a_label: None,
user_holding_b: Some(format_public_account_id(recipient_account_id_2)),
user_holding_b_label: None,
user_holding_lp: Some(format_public_account_id(user_holding_lp)),
user_holding_lp_label: None,
user_holding_a: public_mention(recipient_account_id_1),
user_holding_b: public_mention(recipient_account_id_2),
user_holding_lp: public_mention(user_holding_lp),
min_amount_lp: 1,
max_amount_a: 2,
max_amount_b: 2,
@ -374,12 +361,9 @@ async fn amm_public() -> Result<()> {
// Remove liquidity
let subcommand = AmmProgramAgnosticSubcommand::RemoveLiquidity {
user_holding_a: Some(format_public_account_id(recipient_account_id_1)),
user_holding_a_label: None,
user_holding_b: Some(format_public_account_id(recipient_account_id_2)),
user_holding_b_label: None,
user_holding_lp: Some(format_public_account_id(user_holding_lp)),
user_holding_lp_label: None,
user_holding_a: public_mention(recipient_account_id_1),
user_holding_b: public_mention(recipient_account_id_2),
user_holding_lp: public_mention(user_holding_lp),
balance_lp: 2,
min_amount_a: 1,
min_amount_b: 1,
@ -462,7 +446,7 @@ async fn amm_new_pool_using_labels() -> Result<()> {
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: Some(holding_a_label.clone()),
label: Some(Label::new(holding_a_label.clone())),
})),
)
.await?
@ -507,7 +491,7 @@ async fn amm_new_pool_using_labels() -> Result<()> {
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: Some(holding_b_label.clone()),
label: Some(Label::new(holding_b_label.clone())),
})),
)
.await?
@ -523,7 +507,7 @@ async fn amm_new_pool_using_labels() -> Result<()> {
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: Some(holding_lp_label.clone()),
label: Some(Label::new(holding_lp_label.clone())),
})),
)
.await?
@ -533,10 +517,8 @@ async fn amm_new_pool_using_labels() -> Result<()> {
// Create token 1 and distribute to holding_a
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id_1)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id_1)),
supply_account_label: None,
definition_account_id: public_mention(definition_account_id_1),
supply_account_id: public_mention(supply_account_id_1),
name: "TOKEN1".to_owned(),
total_supply: 10,
};
@ -544,10 +526,8 @@ async fn amm_new_pool_using_labels() -> Result<()> {
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: Some(format_public_account_id(supply_account_id_1)),
from_label: None,
to: Some(format_public_account_id(holding_a_id)),
to_label: None,
from: public_mention(supply_account_id_1),
to: Some(public_mention(holding_a_id)),
to_npk: None,
to_vpk: None,
amount: 5,
@ -557,10 +537,8 @@ async fn amm_new_pool_using_labels() -> Result<()> {
// Create token 2 and distribute to holding_b
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id_2)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id_2)),
supply_account_label: None,
definition_account_id: public_mention(definition_account_id_2),
supply_account_id: public_mention(supply_account_id_2),
name: "TOKEN2".to_owned(),
total_supply: 10,
};
@ -568,10 +546,8 @@ async fn amm_new_pool_using_labels() -> Result<()> {
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: Some(format_public_account_id(supply_account_id_2)),
from_label: None,
to: Some(format_public_account_id(holding_b_id)),
to_label: None,
from: public_mention(supply_account_id_2),
to: Some(public_mention(holding_b_id)),
to_npk: None,
to_vpk: None,
amount: 5,
@ -581,12 +557,9 @@ async fn amm_new_pool_using_labels() -> Result<()> {
// Create AMM pool using account labels instead of IDs
let subcommand = AmmProgramAgnosticSubcommand::New {
user_holding_a: None,
user_holding_a_label: Some(holding_a_label),
user_holding_b: None,
user_holding_b_label: Some(holding_b_label),
user_holding_lp: None,
user_holding_lp_label: Some(holding_lp_label),
user_holding_a: label_mention(holding_a_label),
user_holding_b: label_mention(holding_b_label),
user_holding_lp: label_mention(holding_lp_label),
balance_a: 3,
balance_b: 3,
};

View File

@ -9,8 +9,8 @@ use std::time::Duration;
use anyhow::{Context as _, Result};
use ata_core::{compute_ata_seed, get_associated_token_account_id};
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_private_account_id,
format_public_account_id, verify_commitment_is_in_state,
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, private_mention, public_mention,
verify_commitment_is_in_state,
};
use log::info;
use nssa::program::Program;
@ -68,10 +68,8 @@ async fn create_ata_initializes_holding_account() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
definition_account_id: public_mention(definition_account_id),
supply_account_id: public_mention(supply_account_id),
name: "TEST".to_owned(),
total_supply,
}),
@ -85,8 +83,8 @@ async fn create_ata_initializes_holding_account() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_public_account_id(owner_account_id),
token_definition: definition_account_id.to_string(),
owner: public_mention(owner_account_id),
token_definition: definition_account_id,
}),
)
.await?;
@ -132,10 +130,8 @@ async fn create_ata_is_idempotent() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
definition_account_id: public_mention(definition_account_id),
supply_account_id: public_mention(supply_account_id),
name: "TEST".to_owned(),
total_supply: 100,
}),
@ -149,8 +145,8 @@ async fn create_ata_is_idempotent() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_public_account_id(owner_account_id),
token_definition: definition_account_id.to_string(),
owner: public_mention(owner_account_id),
token_definition: definition_account_id,
}),
)
.await?;
@ -162,8 +158,8 @@ async fn create_ata_is_idempotent() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_public_account_id(owner_account_id),
token_definition: definition_account_id.to_string(),
owner: public_mention(owner_account_id),
token_definition: definition_account_id,
}),
)
.await?;
@ -212,10 +208,8 @@ async fn transfer_and_burn_via_ata() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
definition_account_id: public_mention(definition_account_id),
supply_account_id: public_mention(supply_account_id),
name: "TEST".to_owned(),
total_supply,
}),
@ -240,16 +234,16 @@ async fn transfer_and_burn_via_ata() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_public_account_id(sender_account_id),
token_definition: definition_account_id.to_string(),
owner: public_mention(sender_account_id),
token_definition: definition_account_id,
}),
)
.await?;
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_public_account_id(recipient_account_id),
token_definition: definition_account_id.to_string(),
owner: public_mention(recipient_account_id),
token_definition: definition_account_id,
}),
)
.await?;
@ -262,10 +256,8 @@ async fn transfer_and_burn_via_ata() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::Send {
from: Some(format_public_account_id(supply_account_id)),
from_label: None,
to: Some(format_public_account_id(sender_ata_id)),
to_label: None,
from: public_mention(supply_account_id),
to: Some(public_mention(sender_ata_id)),
to_npk: None,
to_vpk: None,
amount: fund_amount,
@ -281,9 +273,9 @@ async fn transfer_and_burn_via_ata() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Send {
from: format_public_account_id(sender_account_id),
token_definition: definition_account_id.to_string(),
to: recipient_ata_id.to_string(),
from: public_mention(sender_account_id),
token_definition: definition_account_id,
to: recipient_ata_id,
amount: transfer_amount,
}),
)
@ -319,8 +311,8 @@ async fn transfer_and_burn_via_ata() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Burn {
holder: format_public_account_id(sender_account_id),
token_definition: definition_account_id.to_string(),
holder: public_mention(sender_account_id),
token_definition: definition_account_id,
amount: burn_amount,
}),
)
@ -370,10 +362,8 @@ async fn create_ata_with_private_owner() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
definition_account_id: public_mention(definition_account_id),
supply_account_id: public_mention(supply_account_id),
name: "TEST".to_owned(),
total_supply: 100,
}),
@ -387,8 +377,8 @@ async fn create_ata_with_private_owner() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_private_account_id(owner_account_id),
token_definition: definition_account_id.to_string(),
owner: private_mention(owner_account_id),
token_definition: definition_account_id,
}),
)
.await?;
@ -444,10 +434,8 @@ async fn transfer_via_ata_private_owner() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
definition_account_id: public_mention(definition_account_id),
supply_account_id: public_mention(supply_account_id),
name: "TEST".to_owned(),
total_supply,
}),
@ -472,16 +460,16 @@ async fn transfer_via_ata_private_owner() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_private_account_id(sender_account_id),
token_definition: definition_account_id.to_string(),
owner: private_mention(sender_account_id),
token_definition: definition_account_id,
}),
)
.await?;
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_public_account_id(recipient_account_id),
token_definition: definition_account_id.to_string(),
owner: public_mention(recipient_account_id),
token_definition: definition_account_id,
}),
)
.await?;
@ -494,10 +482,8 @@ async fn transfer_via_ata_private_owner() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::Send {
from: Some(format_public_account_id(supply_account_id)),
from_label: None,
to: Some(format_public_account_id(sender_ata_id)),
to_label: None,
from: public_mention(supply_account_id),
to: Some(public_mention(sender_ata_id)),
to_npk: None,
to_vpk: None,
amount: fund_amount,
@ -513,9 +499,9 @@ async fn transfer_via_ata_private_owner() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Send {
from: format_private_account_id(sender_account_id),
token_definition: definition_account_id.to_string(),
to: recipient_ata_id.to_string(),
from: private_mention(sender_account_id),
token_definition: definition_account_id,
to: recipient_ata_id,
amount: transfer_amount,
}),
)
@ -570,10 +556,8 @@ async fn burn_via_ata_private_owner() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
definition_account_id: public_mention(definition_account_id),
supply_account_id: public_mention(supply_account_id),
name: "TEST".to_owned(),
total_supply,
}),
@ -594,8 +578,8 @@ async fn burn_via_ata_private_owner() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_private_account_id(holder_account_id),
token_definition: definition_account_id.to_string(),
owner: private_mention(holder_account_id),
token_definition: definition_account_id,
}),
)
.await?;
@ -608,10 +592,8 @@ async fn burn_via_ata_private_owner() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::Send {
from: Some(format_public_account_id(supply_account_id)),
from_label: None,
to: Some(format_public_account_id(holder_ata_id)),
to_label: None,
from: public_mention(supply_account_id),
to: Some(public_mention(holder_ata_id)),
to_npk: None,
to_vpk: None,
amount: fund_amount,
@ -627,8 +609,8 @@ async fn burn_via_ata_private_owner() -> Result<()> {
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Burn {
holder: format_private_account_id(holder_account_id),
token_definition: definition_account_id.to_string(),
holder: private_mention(holder_account_id),
token_definition: definition_account_id,
amount: burn_amount,
}),
)

View File

@ -2,18 +2,21 @@ use std::time::Duration;
use anyhow::{Context as _, Result};
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, fetch_privacy_preserving_tx,
format_private_account_id, format_public_account_id, verify_commitment_is_in_state,
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, fetch_privacy_preserving_tx, label_mention,
private_mention, public_mention, verify_commitment_is_in_state,
};
use log::info;
use nssa::{AccountId, program::Program};
use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point};
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::native_token_transfer::AuthTransferSubcommand,
use wallet::{
account::Label,
cli::{
CliAccountMention, Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::native_token_transfer::AuthTransferSubcommand,
},
};
#[test]
@ -24,10 +27,8 @@ async fn private_transfer_to_owned_account() -> Result<()> {
let to: AccountId = ctx.existing_private_accounts()[1];
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_private_account_id(from)),
from_label: None,
to: Some(format_private_account_id(to)),
to_label: None,
from: private_mention(from),
to: Some(private_mention(to)),
to_npk: None,
to_vpk: None,
amount: 100,
@ -65,10 +66,8 @@ async fn private_transfer_to_foreign_account() -> Result<()> {
let to_vpk = Secp256k1Point::from_scalar(to_npk.0);
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_private_account_id(from)),
from_label: None,
from: private_mention(from),
to: None,
to_label: None,
to_npk: Some(to_npk_string),
to_vpk: Some(hex::encode(to_vpk.0)),
amount: 100,
@ -115,10 +114,8 @@ async fn deshielded_transfer_to_public_account() -> Result<()> {
assert_eq!(from_acc.balance, 10000);
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_private_account_id(from)),
from_label: None,
to: Some(format_public_account_id(to)),
to_label: None,
from: private_mention(from),
to: Some(public_mention(to)),
to_npk: None,
to_vpk: None,
amount: 100,
@ -173,17 +170,15 @@ async fn private_transfer_to_owned_account_using_claiming_path() -> Result<()> {
let (to_keys, _) = ctx
.wallet()
.storage()
.user_data
.key_chain()
.get_private_account(to_account_id)
.cloned()
.context("Failed to get private account")?;
// Send to this account using claiming path (using npk and vpk instead of account ID)
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_private_account_id(from)),
from_label: None,
from: private_mention(from),
to: None,
to_label: None,
to_npk: Some(hex::encode(to_keys.nullifier_public_key.0)),
to_vpk: Some(hex::encode(to_keys.viewing_public_key.0)),
amount: 100,
@ -230,10 +225,8 @@ async fn shielded_transfer_to_owned_private_account() -> Result<()> {
let to: AccountId = ctx.existing_private_accounts()[1];
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_public_account_id(from)),
from_label: None,
to: Some(format_private_account_id(to)),
to_label: None,
from: public_mention(from),
to: Some(private_mention(to)),
to_npk: None,
to_vpk: None,
amount: 100,
@ -274,10 +267,8 @@ async fn shielded_transfer_to_foreign_account() -> Result<()> {
let from: AccountId = ctx.existing_public_accounts()[0];
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_public_account_id(from)),
from_label: None,
from: public_mention(from),
to: None,
to_label: None,
to_npk: Some(to_npk_string),
to_vpk: Some(hex::encode(to_vpk.0)),
amount: 100,
@ -339,17 +330,15 @@ async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> {
let (to_keys, _) = ctx
.wallet()
.storage()
.user_data
.key_chain()
.get_private_account(to_account_id)
.cloned()
.context("Failed to get private account")?;
// Send transfer using nullifier and viewing public keys
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_private_account_id(from)),
from_label: None,
from: private_mention(from),
to: None,
to_label: None,
to_npk: Some(hex::encode(to_keys.nullifier_public_key.0)),
to_vpk: Some(hex::encode(to_keys.viewing_public_key.0)),
amount: 100,
@ -397,8 +386,7 @@ async fn initialize_private_account() -> Result<()> {
};
let command = Command::AuthTransfer(AuthTransferSubcommand::Init {
account_id: Some(format_private_account_id(account_id)),
account_label: None,
account_id: private_mention(account_id),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
@ -439,20 +427,17 @@ async fn private_transfer_using_from_label() -> Result<()> {
let to: AccountId = ctx.existing_private_accounts()[1];
// Assign a label to the sender account
let label = "private-sender-label".to_owned();
let label = Label::new("private-sender-label");
let command = Command::Account(AccountSubcommand::Label {
account_id: Some(format_private_account_id(from)),
account_label: None,
account_id: private_mention(from),
label: label.clone(),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
// Send using the label instead of account ID
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: None,
from_label: Some(label),
to: Some(format_private_account_id(to)),
to_label: None,
from: CliAccountMention::Label(label),
to: Some(private_mention(to)),
to_npk: None,
to_vpk: None,
amount: 100,
@ -485,7 +470,7 @@ async fn initialize_private_account_using_label() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create a new private account with a label
let label = "init-private-label".to_owned();
let label = Label::new("init-private-label");
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
cci: None,
label: Some(label.clone()),
@ -497,8 +482,7 @@ async fn initialize_private_account_using_label() -> Result<()> {
// Initialize using the label instead of account ID
let command = Command::AuthTransfer(AuthTransferSubcommand::Init {
account_id: None,
account_label: Some(label),
account_id: label_mention(label),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;

View File

@ -1,15 +1,18 @@
use std::time::Duration;
use anyhow::Result;
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id};
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, public_mention};
use log::info;
use nssa::program::Program;
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::native_token_transfer::AuthTransferSubcommand,
use wallet::{
account::Label,
cli::{
CliAccountMention, Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::native_token_transfer::AuthTransferSubcommand,
},
};
#[test]
@ -17,10 +20,8 @@ async fn successful_transfer_to_existing_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
from_label: None,
to: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
to_label: None,
from: public_mention(ctx.existing_public_accounts()[0]),
to: Some(public_mention(ctx.existing_public_accounts()[1])),
to_npk: None,
to_vpk: None,
amount: 100,
@ -66,8 +67,9 @@ pub async fn successful_transfer_to_new_account() -> Result<()> {
let new_persistent_account_id = ctx
.wallet()
.storage()
.user_data
.account_ids()
.key_chain()
.public_account_ids()
.map(|(account_id, _)| account_id)
.find(|acc_id| {
*acc_id != ctx.existing_public_accounts()[0]
&& *acc_id != ctx.existing_public_accounts()[1]
@ -75,10 +77,8 @@ pub async fn successful_transfer_to_new_account() -> Result<()> {
.expect("Failed to find newly created account in the wallet storage");
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
from_label: None,
to: Some(format_public_account_id(new_persistent_account_id)),
to_label: None,
from: public_mention(ctx.existing_public_accounts()[0]),
to: Some(public_mention(new_persistent_account_id)),
to_npk: None,
to_vpk: None,
amount: 100,
@ -113,10 +113,8 @@ async fn failed_transfer_with_insufficient_balance() -> Result<()> {
let mut ctx = TestContext::new().await?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
from_label: None,
to: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
to_label: None,
from: public_mention(ctx.existing_public_accounts()[0]),
to: Some(public_mention(ctx.existing_public_accounts()[1])),
to_npk: None,
to_vpk: None,
amount: 1_000_000,
@ -153,10 +151,8 @@ async fn two_consecutive_successful_transfers() -> Result<()> {
// First transfer
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
from_label: None,
to: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
to_label: None,
from: public_mention(ctx.existing_public_accounts()[0]),
to: Some(public_mention(ctx.existing_public_accounts()[1])),
to_npk: None,
to_vpk: None,
amount: 100,
@ -187,10 +183,8 @@ async fn two_consecutive_successful_transfers() -> Result<()> {
// Second transfer
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
from_label: None,
to: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
to_label: None,
from: public_mention(ctx.existing_public_accounts()[0]),
to: Some(public_mention(ctx.existing_public_accounts()[1])),
to_npk: None,
to_vpk: None,
amount: 100,
@ -236,8 +230,7 @@ async fn initialize_public_account() -> Result<()> {
};
let command = Command::AuthTransfer(AuthTransferSubcommand::Init {
account_id: Some(format_public_account_id(account_id)),
account_label: None,
account_id: public_mention(account_id),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
@ -262,20 +255,17 @@ async fn successful_transfer_using_from_label() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Assign a label to the sender account
let label = "sender-label".to_owned();
let label = Label::new("sender-label");
let command = Command::Account(AccountSubcommand::Label {
account_id: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
account_label: None,
account_id: public_mention(ctx.existing_public_accounts()[0]),
label: label.clone(),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
// Send using the label instead of account ID
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: None,
from_label: Some(label),
to: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
to_label: None,
from: CliAccountMention::Label(label),
to: Some(public_mention(ctx.existing_public_accounts()[1])),
to_npk: None,
to_vpk: None,
amount: 100,
@ -309,20 +299,17 @@ async fn successful_transfer_using_to_label() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Assign a label to the receiver account
let label = "receiver-label".to_owned();
let label = Label::new("receiver-label");
let command = Command::Account(AccountSubcommand::Label {
account_id: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
account_label: None,
account_id: public_mention(ctx.existing_public_accounts()[1]),
label: label.clone(),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
// Send using the label for the recipient
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
from_label: None,
to: None,
to_label: Some(label),
from: public_mention(ctx.existing_public_accounts()[0]),
to: Some(CliAccountMention::Label(label)),
to_npk: None,
to_vpk: None,
amount: 100,

View File

@ -9,13 +9,16 @@ use std::time::Duration;
use anyhow::{Context as _, Result};
use indexer_service_rpc::RpcClient as _;
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_private_account_id,
format_public_account_id, verify_commitment_is_in_state,
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, private_mention, public_mention,
verify_commitment_is_in_state,
};
use log::info;
use nssa::AccountId;
use tokio::test;
use wallet::cli::{Command, programs::native_token_transfer::AuthTransferSubcommand};
use wallet::{
account::Label,
cli::{CliAccountMention, Command, programs::native_token_transfer::AuthTransferSubcommand},
};
/// Maximum time to wait for the indexer to catch up to the sequencer.
const L2_TO_L1_TIMEOUT_MILLIS: u64 = 180_000;
@ -108,10 +111,8 @@ async fn indexer_state_consistency() -> Result<()> {
let mut ctx = TestContext::new().await?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
from_label: None,
to: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
to_label: None,
from: public_mention(ctx.existing_public_accounts()[0]),
to: Some(public_mention(ctx.existing_public_accounts()[1])),
to_npk: None,
to_vpk: None,
amount: 100,
@ -144,10 +145,8 @@ async fn indexer_state_consistency() -> Result<()> {
let to: AccountId = ctx.existing_private_accounts()[1];
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_private_account_id(from)),
from_label: None,
to: Some(format_private_account_id(to)),
to_label: None,
from: private_mention(from),
to: Some(private_mention(to)),
to_npk: None,
to_vpk: None,
amount: 100,
@ -211,29 +210,25 @@ async fn indexer_state_consistency_with_labels() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Assign labels to both accounts
let from_label = "idx-sender-label".to_owned();
let to_label_str = "idx-receiver-label".to_owned();
let from_label = Label::new("idx-sender-label");
let to_label = Label::new("idx-receiver-label");
let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label {
account_id: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
account_label: None,
account_id: public_mention(ctx.existing_public_accounts()[0]),
label: from_label.clone(),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd).await?;
let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label {
account_id: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
account_label: None,
label: to_label_str.clone(),
account_id: public_mention(ctx.existing_public_accounts()[1]),
label: to_label.clone(),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd).await?;
// Send using labels instead of account IDs
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: None,
from_label: Some(from_label),
to: None,
to_label: Some(to_label_str),
from: CliAccountMention::Label(from_label),
to: Some(CliAccountMention::Label(to_label)),
to_npk: None,
to_vpk: None,
amount: 100,

View File

@ -8,18 +8,22 @@ use std::{str::FromStr as _, time::Duration};
use anyhow::{Context as _, Result};
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, fetch_privacy_preserving_tx,
format_private_account_id, format_public_account_id, verify_commitment_is_in_state,
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, fetch_privacy_preserving_tx, private_mention,
public_mention, verify_commitment_is_in_state,
};
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use key_protocol::key_management::{KeyChain, key_tree::chain_index::ChainIndex};
use log::info;
use nssa::{AccountId, program::Program};
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::native_token_transfer::AuthTransferSubcommand,
use wallet::{
account::HumanReadableAccount,
cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
import::ImportSubcommand,
programs::native_token_transfer::AuthTransferSubcommand,
},
};
#[test]
@ -62,17 +66,15 @@ async fn sync_private_account_with_non_zero_chain_index() -> Result<()> {
let (to_keys, _) = ctx
.wallet()
.storage()
.user_data
.key_chain()
.get_private_account(to_account_id)
.cloned()
.context("Failed to get private account")?;
// Send to this account using claiming path (using npk and vpk instead of account ID)
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_private_account_id(from)),
from_label: None,
from: private_mention(from),
to: None,
to_label: None,
to_npk: Some(hex::encode(to_keys.nullifier_public_key.0)),
to_vpk: Some(hex::encode(to_keys.viewing_public_key.0)),
amount: 100,
@ -145,10 +147,8 @@ async fn restore_keys_from_seed() -> Result<()> {
// Send to first private account
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_private_account_id(from)),
from_label: None,
to: Some(format_private_account_id(to_account_id1)),
to_label: None,
from: private_mention(from),
to: Some(private_mention(to_account_id1)),
to_npk: None,
to_vpk: None,
amount: 100,
@ -157,10 +157,8 @@ async fn restore_keys_from_seed() -> Result<()> {
// Send to second private account
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_private_account_id(from)),
from_label: None,
to: Some(format_private_account_id(to_account_id2)),
to_label: None,
from: private_mention(from),
to: Some(private_mention(to_account_id2)),
to_npk: None,
to_vpk: None,
amount: 101,
@ -197,10 +195,8 @@ async fn restore_keys_from_seed() -> Result<()> {
// Send to first public account
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_public_account_id(from)),
from_label: None,
to: Some(format_public_account_id(to_account_id3)),
to_label: None,
from: public_mention(from),
to: Some(public_mention(to_account_id3)),
to_npk: None,
to_vpk: None,
amount: 102,
@ -209,10 +205,8 @@ async fn restore_keys_from_seed() -> Result<()> {
// Send to second public account
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_public_account_id(from)),
from_label: None,
to: Some(format_public_account_id(to_account_id4)),
to_label: None,
from: public_mention(from),
to: Some(public_mention(to_account_id4)),
to_npk: None,
to_vpk: None,
amount: 103,
@ -228,56 +222,50 @@ async fn restore_keys_from_seed() -> Result<()> {
let acc1 = ctx
.wallet()
.storage()
.user_data
.private_key_tree
.get_node(to_account_id1)
.key_chain()
.get_private_account(to_account_id1)
.expect("Acc 1 should be restored");
let acc2 = ctx
.wallet()
.storage()
.user_data
.private_key_tree
.get_node(to_account_id2)
.key_chain()
.get_private_account(to_account_id2)
.expect("Acc 2 should be restored");
// Verify restored public accounts
let _acc3 = ctx
.wallet()
.storage()
.user_data
.public_key_tree
.get_node(to_account_id3)
.key_chain()
.get_pub_account_signing_key(to_account_id3)
.expect("Acc 3 should be restored");
let _acc4 = ctx
.wallet()
.storage()
.user_data
.public_key_tree
.get_node(to_account_id4)
.key_chain()
.get_pub_account_signing_key(to_account_id4)
.expect("Acc 4 should be restored");
assert_eq!(
acc1.value.1.program_owner,
acc1.1.program_owner,
Program::authenticated_transfer_program().id()
);
assert_eq!(
acc2.value.1.program_owner,
acc2.1.program_owner,
Program::authenticated_transfer_program().id()
);
assert_eq!(acc1.value.1.balance, 100);
assert_eq!(acc2.value.1.balance, 101);
assert_eq!(acc1.1.balance, 100);
assert_eq!(acc2.1.balance, 101);
info!("Tree checks passed, testing restored accounts can transact");
// Test that restored accounts can send transactions
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_private_account_id(to_account_id1)),
from_label: None,
to: Some(format_private_account_id(to_account_id2)),
to_label: None,
from: private_mention(to_account_id1),
to: Some(private_mention(to_account_id2)),
to_npk: None,
to_vpk: None,
amount: 10,
@ -285,10 +273,8 @@ async fn restore_keys_from_seed() -> Result<()> {
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_public_account_id(to_account_id3)),
from_label: None,
to: Some(format_public_account_id(to_account_id4)),
to_label: None,
from: public_mention(to_account_id3),
to: Some(public_mention(to_account_id4)),
to_npk: None,
to_vpk: None,
amount: 11,
@ -327,3 +313,129 @@ async fn restore_keys_from_seed() -> Result<()> {
Ok(())
}
#[test]
async fn import_public_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let private_key = nssa::PrivateKey::new_os_random();
let account_id = nssa::AccountId::from(&nssa::PublicKey::new_from_private_key(&private_key));
let command = Command::Import(ImportSubcommand::Public { private_key });
let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::Empty = sub_ret else {
anyhow::bail!("Expected Empty return value");
};
let imported_key = ctx
.wallet()
.storage()
.key_chain()
.get_pub_account_signing_key(account_id);
assert!(
imported_key.is_some(),
"Imported public account should be present"
);
Ok(())
}
#[test]
async fn import_private_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let key_chain = KeyChain::new_os_random();
let account_id = nssa::AccountId::from(&key_chain.nullifier_public_key);
let account = nssa::Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 777,
data: Default::default(),
nonce: Default::default(),
};
let key_chain_json = serde_json::to_string(&key_chain)
.context("Failed to serialize key chain for private import")?;
let account_state = HumanReadableAccount::from(account.clone());
let command = Command::Import(ImportSubcommand::Private {
key_chain_json,
account_state,
});
let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::Empty = sub_ret else {
anyhow::bail!("Expected Empty return value");
};
let imported = ctx
.wallet()
.storage()
.key_chain()
.get_private_account(account_id)
.context("Imported private account should be present")?;
assert_eq!(
imported.0.nullifier_public_key,
key_chain.nullifier_public_key
);
assert_eq!(imported.1, account);
Ok(())
}
#[test]
async fn import_private_account_second_time_overrides_account_data() -> Result<()> {
let mut ctx = TestContext::new().await?;
let key_chain = KeyChain::new_os_random();
let account_id = nssa::AccountId::from(&key_chain.nullifier_public_key);
let key_chain_json =
serde_json::to_string(&key_chain).context("Failed to serialize key chain")?;
let initial_account = nssa::Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 100,
data: Default::default(),
nonce: Default::default(),
};
// First import
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Import(ImportSubcommand::Private {
key_chain_json: key_chain_json.clone(),
account_state: HumanReadableAccount::from(initial_account),
}),
)
.await?;
let updated_account = nssa::Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 999,
data: Default::default(),
nonce: Default::default(),
};
// Second import with different account data (same key chain)
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Import(ImportSubcommand::Private {
key_chain_json,
account_state: HumanReadableAccount::from(updated_account.clone()),
}),
)
.await?;
let imported = ctx
.wallet()
.storage()
.key_chain()
.get_private_account(account_id)
.context("Imported private account should be present")?;
assert_eq!(
imported.1, updated_account,
"Second import should override account data"
);
Ok(())
}

View File

@ -9,8 +9,8 @@ use std::time::Duration;
use anyhow::{Context as _, Result};
use common::PINATA_BASE58;
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_private_account_id,
format_public_account_id, verify_commitment_is_in_state,
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, private_mention, public_mention,
verify_commitment_is_in_state,
};
use log::info;
use sequencer_service_rpc::RpcClient as _;
@ -42,8 +42,6 @@ async fn claim_pinata_to_uninitialized_public_account_fails_fast() -> Result<()>
anyhow::bail!("Expected RegisterAccount return value");
};
let winner_account_id_formatted = format_public_account_id(winner_account_id);
let pinata_balance_pre = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.parse().unwrap())
@ -52,8 +50,7 @@ async fn claim_pinata_to_uninitialized_public_account_fails_fast() -> Result<()>
let claim_result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: Some(winner_account_id_formatted),
to_label: None,
to: public_mention(winner_account_id),
}),
)
.await;
@ -97,8 +94,6 @@ async fn claim_pinata_to_uninitialized_private_account_fails_fast() -> Result<()
anyhow::bail!("Expected RegisterAccount return value");
};
let winner_account_id_formatted = format_private_account_id(winner_account_id);
let pinata_balance_pre = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.parse().unwrap())
@ -107,8 +102,7 @@ async fn claim_pinata_to_uninitialized_private_account_fails_fast() -> Result<()
let claim_result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: Some(winner_account_id_formatted),
to_label: None,
to: private_mention(winner_account_id),
}),
)
.await;
@ -139,8 +133,7 @@ async fn claim_pinata_to_existing_public_account() -> Result<()> {
let pinata_prize = 150;
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
to_label: None,
to: public_mention(ctx.existing_public_accounts()[0]),
});
let pinata_balance_pre = ctx
@ -178,10 +171,7 @@ async fn claim_pinata_to_existing_private_account() -> Result<()> {
let pinata_prize = 150;
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: Some(format_private_account_id(
ctx.existing_private_accounts()[0],
)),
to_label: None,
to: private_mention(ctx.existing_private_accounts()[0]),
});
let pinata_balance_pre = ctx
@ -241,12 +231,9 @@ async fn claim_pinata_to_new_private_account() -> Result<()> {
anyhow::bail!("Expected RegisterAccount return value");
};
let winner_account_id_formatted = format_private_account_id(winner_account_id);
// Initialize account under auth transfer program
let command = Command::AuthTransfer(AuthTransferSubcommand::Init {
account_id: Some(winner_account_id_formatted.clone()),
account_label: None,
account_id: private_mention(winner_account_id),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
@ -261,8 +248,7 @@ async fn claim_pinata_to_new_private_account() -> Result<()> {
// Claim pinata to the new private account
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: Some(winner_account_id_formatted),
to_label: None,
to: private_mention(winner_account_id),
});
let pinata_balance_pre = ctx

View File

@ -8,8 +8,8 @@ use std::time::Duration;
use anyhow::{Context as _, Result};
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_private_account_id,
format_public_account_id, verify_commitment_is_in_state,
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, label_mention, private_mention, public_mention,
verify_commitment_is_in_state,
};
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use log::info;
@ -17,10 +17,13 @@ use nssa::program::Program;
use sequencer_service_rpc::RpcClient as _;
use token_core::{TokenDefinition, TokenHolding};
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::token::TokenProgramAgnosticSubcommand,
use wallet::{
account::Label,
cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::token::TokenProgramAgnosticSubcommand,
},
};
#[test]
@ -79,10 +82,8 @@ async fn create_and_transfer_public_token() -> Result<()> {
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
definition_account_id: public_mention(definition_account_id),
supply_account_id: public_mention(supply_account_id),
name: name.clone(),
total_supply,
};
@ -128,10 +129,8 @@ async fn create_and_transfer_public_token() -> Result<()> {
// Transfer 7 tokens from supply_acc to recipient_account_id
let transfer_amount = 7;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: Some(format_public_account_id(supply_account_id)),
from_label: None,
to: Some(format_public_account_id(recipient_account_id)),
to_label: None,
from: public_mention(supply_account_id),
to: Some(public_mention(recipient_account_id)),
to_npk: None,
to_vpk: None,
amount: transfer_amount,
@ -175,10 +174,8 @@ async fn create_and_transfer_public_token() -> Result<()> {
// Burn 3 tokens from recipient_acc
let burn_amount = 3;
let subcommand = TokenProgramAgnosticSubcommand::Burn {
definition: Some(format_public_account_id(definition_account_id)),
definition_label: None,
holder: Some(format_public_account_id(recipient_account_id)),
holder_label: None,
definition: public_mention(definition_account_id),
holder: public_mention(recipient_account_id),
amount: burn_amount,
};
@ -221,10 +218,8 @@ async fn create_and_transfer_public_token() -> Result<()> {
// Mint 10 tokens at recipient_acc
let mint_amount = 10;
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: Some(format_public_account_id(definition_account_id)),
definition_label: None,
holder: Some(format_public_account_id(recipient_account_id)),
holder_label: None,
definition: public_mention(definition_account_id),
holder: Some(public_mention(recipient_account_id)),
holder_npk: None,
holder_vpk: None,
amount: mint_amount,
@ -327,10 +322,8 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_private_account_id(supply_account_id)),
supply_account_label: None,
definition_account_id: public_mention(definition_account_id),
supply_account_id: private_mention(supply_account_id),
name: name.clone(),
total_supply,
};
@ -366,10 +359,8 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
// Transfer 7 tokens from supply_acc to recipient_account_id
let transfer_amount = 7;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: Some(format_private_account_id(supply_account_id)),
from_label: None,
to: Some(format_private_account_id(recipient_account_id)),
to_label: None,
from: private_mention(supply_account_id),
to: Some(private_mention(recipient_account_id)),
to_npk: None,
to_vpk: None,
amount: transfer_amount,
@ -395,10 +386,8 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
// Burn 3 tokens from recipient_acc
let burn_amount = 3;
let subcommand = TokenProgramAgnosticSubcommand::Burn {
definition: Some(format_public_account_id(definition_account_id)),
definition_label: None,
holder: Some(format_private_account_id(recipient_account_id)),
holder_label: None,
definition: public_mention(definition_account_id),
holder: private_mention(recipient_account_id),
amount: burn_amount,
};
@ -489,10 +478,8 @@ async fn create_token_with_private_definition() -> Result<()> {
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_private_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
definition_account_id: private_mention(definition_account_id),
supply_account_id: public_mention(supply_account_id),
name: name.clone(),
total_supply,
};
@ -560,10 +547,8 @@ async fn create_token_with_private_definition() -> Result<()> {
// Mint to public account
let mint_amount_public = 10;
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: Some(format_private_account_id(definition_account_id)),
definition_label: None,
holder: Some(format_public_account_id(recipient_account_id_public)),
holder_label: None,
definition: private_mention(definition_account_id),
holder: Some(public_mention(recipient_account_id_public)),
holder_npk: None,
holder_vpk: None,
amount: mint_amount_public,
@ -608,10 +593,8 @@ async fn create_token_with_private_definition() -> Result<()> {
// Mint to private account
let mint_amount_private = 5;
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: Some(format_private_account_id(definition_account_id)),
definition_label: None,
holder: Some(format_private_account_id(recipient_account_id_private)),
holder_label: None,
definition: private_mention(definition_account_id),
holder: Some(private_mention(recipient_account_id_private)),
holder_npk: None,
holder_vpk: None,
amount: mint_amount_private,
@ -689,10 +672,8 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> {
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_private_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_private_account_id(supply_account_id)),
supply_account_label: None,
definition_account_id: private_mention(definition_account_id),
supply_account_id: private_mention(supply_account_id),
name,
total_supply,
};
@ -750,10 +731,8 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> {
// Transfer tokens
let transfer_amount = 7;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: Some(format_private_account_id(supply_account_id)),
from_label: None,
to: Some(format_private_account_id(recipient_account_id)),
to_label: None,
from: private_mention(supply_account_id),
to: Some(private_mention(recipient_account_id)),
to_npk: None,
to_vpk: None,
amount: transfer_amount,
@ -865,10 +844,8 @@ async fn shielded_token_transfer() -> Result<()> {
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
definition_account_id: public_mention(definition_account_id),
supply_account_id: public_mention(supply_account_id),
name,
total_supply,
};
@ -881,10 +858,8 @@ async fn shielded_token_transfer() -> Result<()> {
// Perform shielded transfer: public supply -> private recipient
let transfer_amount = 7;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: Some(format_public_account_id(supply_account_id)),
from_label: None,
to: Some(format_private_account_id(recipient_account_id)),
to_label: None,
from: public_mention(supply_account_id),
to: Some(private_mention(recipient_account_id)),
to_npk: None,
to_vpk: None,
amount: transfer_amount,
@ -991,10 +966,8 @@ async fn deshielded_token_transfer() -> Result<()> {
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_private_account_id(supply_account_id)),
supply_account_label: None,
definition_account_id: public_mention(definition_account_id),
supply_account_id: private_mention(supply_account_id),
name,
total_supply,
};
@ -1007,10 +980,8 @@ async fn deshielded_token_transfer() -> Result<()> {
// Perform deshielded transfer: private supply -> public recipient
let transfer_amount = 7;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: Some(format_private_account_id(supply_account_id)),
from_label: None,
to: Some(format_public_account_id(recipient_account_id)),
to_label: None,
from: private_mention(supply_account_id),
to: Some(public_mention(recipient_account_id)),
to_npk: None,
to_vpk: None,
amount: transfer_amount,
@ -1101,10 +1072,8 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_private_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_private_account_id(supply_account_id)),
supply_account_label: None,
definition_account_id: private_mention(definition_account_id),
supply_account_id: private_mention(supply_account_id),
name,
total_supply,
};
@ -1134,7 +1103,7 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
let (holder_keys, _) = ctx
.wallet()
.storage()
.user_data
.key_chain()
.get_private_account(recipient_account_id)
.cloned()
.context("Failed to get private account keys")?;
@ -1142,10 +1111,8 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
// Mint using claiming path (foreign account)
let mint_amount = 9;
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: Some(format_private_account_id(definition_account_id)),
definition_label: None,
definition: private_mention(definition_account_id),
holder: None,
holder_label: None,
holder_npk: Some(hex::encode(holder_keys.nullifier_public_key.0)),
holder_vpk: Some(hex::encode(holder_keys.viewing_public_key.0)),
amount: mint_amount,
@ -1198,7 +1165,7 @@ async fn create_token_using_labels() -> Result<()> {
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: Some(def_label.clone()),
label: Some(Label::new(def_label.clone())),
})),
)
.await?;
@ -1213,7 +1180,7 @@ async fn create_token_using_labels() -> Result<()> {
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: Some(supply_label.clone()),
label: Some(Label::new(supply_label.clone())),
})),
)
.await?;
@ -1228,10 +1195,8 @@ async fn create_token_using_labels() -> Result<()> {
let name = "LABELED TOKEN".to_owned();
let total_supply = 100;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: None,
definition_account_label: Some(def_label),
supply_account_id: None,
supply_account_label: Some(supply_label),
definition_account_id: label_mention(def_label),
supply_account_id: label_mention(supply_label),
name: name.clone(),
total_supply,
};
@ -1300,7 +1265,7 @@ async fn transfer_token_using_from_label() -> Result<()> {
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: Some(supply_label.clone()),
label: Some(Label::new(supply_label.clone())),
})),
)
.await?;
@ -1330,10 +1295,8 @@ async fn transfer_token_using_from_label() -> Result<()> {
// Create token
let total_supply = 50;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
definition_account_id: public_mention(definition_account_id),
supply_account_id: public_mention(supply_account_id),
name: "LABEL TEST TOKEN".to_owned(),
total_supply,
};
@ -1345,10 +1308,8 @@ async fn transfer_token_using_from_label() -> Result<()> {
// Transfer token using from_label instead of from
let transfer_amount = 20;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: None,
from_label: Some(supply_label),
to: Some(format_public_account_id(recipient_account_id)),
to_label: None,
from: label_mention(supply_label),
to: Some(public_mention(recipient_account_id)),
to_npk: None,
to_vpk: None,
amount: transfer_amount,

View File

@ -426,7 +426,7 @@ fn test_wallet_ffi_get_balance_public() -> Result<()> {
let balance = unsafe {
let mut out_balance: [u8; 16] = [0; 16];
let ffi_account_id = FfiBytes32::from(&account_id);
let ffi_account_id = FfiBytes32::from(account_id);
wallet_ffi_get_balance(
wallet_ffi_handle,
&raw const ffi_account_id,
@ -456,7 +456,7 @@ fn test_wallet_ffi_get_account_public() -> Result<()> {
let mut out_account = FfiAccount::default();
let account: Account = unsafe {
let ffi_account_id = FfiBytes32::from(&account_id);
let ffi_account_id = FfiBytes32::from(account_id);
wallet_ffi_get_account_public(
wallet_ffi_handle,
&raw const ffi_account_id,
@ -493,7 +493,7 @@ fn test_wallet_ffi_get_account_private() -> Result<()> {
let mut out_account = FfiAccount::default();
let account: Account = unsafe {
let ffi_account_id = FfiBytes32::from(&account_id);
let ffi_account_id = FfiBytes32::from(account_id);
wallet_ffi_get_account_private(
wallet_ffi_handle,
&raw const ffi_account_id,
@ -530,7 +530,7 @@ fn test_wallet_ffi_get_public_account_keys() -> Result<()> {
let mut out_key = FfiPublicAccountKey::default();
let key: PublicKey = unsafe {
let ffi_account_id = FfiBytes32::from(&account_id);
let ffi_account_id = FfiBytes32::from(account_id);
wallet_ffi_get_public_account_key(
wallet_ffi_handle,
&raw const ffi_account_id,
@ -569,7 +569,7 @@ fn test_wallet_ffi_get_private_account_keys() -> Result<()> {
let mut keys = FfiPrivateAccountKeys::default();
unsafe {
let ffi_account_id = FfiBytes32::from(&account_id);
let ffi_account_id = FfiBytes32::from(account_id);
wallet_ffi_get_private_account_keys(
wallet_ffi_handle,
&raw const ffi_account_id,
@ -582,7 +582,7 @@ fn test_wallet_ffi_get_private_account_keys() -> Result<()> {
.ctx()
.wallet()
.storage()
.user_data
.key_chain()
.get_private_account(account_id)
.unwrap()
.0;
@ -608,7 +608,7 @@ fn test_wallet_ffi_account_id_to_base58() -> Result<()> {
let private_key = PrivateKey::new_os_random();
let public_key = PublicKey::new_from_private_key(&private_key);
let account_id = AccountId::from(&public_key);
let ffi_bytes: FfiBytes32 = (&account_id).into();
let ffi_bytes: FfiBytes32 = account_id.into();
let ptr = unsafe { wallet_ffi_account_id_to_base58(&raw const ffi_bytes) };
let ffi_result = unsafe { CStr::from_ptr(ptr).to_str()? };
@ -777,8 +777,8 @@ fn test_wallet_ffi_transfer_public() -> Result<()> {
let ctx = BlockingTestContext::new()?;
let home = tempfile::tempdir()?;
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
let from: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[0]).into();
let to: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[1]).into();
let from: FfiBytes32 = ctx.ctx().existing_public_accounts()[0].into();
let to: FfiBytes32 = ctx.ctx().existing_public_accounts()[1].into();
let amount: [u8; 16] = 100_u128.to_le_bytes();
let mut transfer_result = FfiTransferResult::default();
@ -830,7 +830,7 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> {
let ctx = BlockingTestContext::new()?;
let home = tempfile::tempdir()?;
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
let from: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[0]).into();
let from: FfiBytes32 = ctx.ctx().existing_public_accounts()[0].into();
let (to, to_keys) = unsafe {
let mut out_account_id = FfiBytes32::default();
let mut out_keys = FfiPrivateAccountKeys::default();
@ -904,8 +904,8 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
let ctx = BlockingTestContext::new()?;
let home = tempfile::tempdir()?;
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
let from: FfiBytes32 = (&ctx.ctx().existing_private_accounts()[0]).into();
let to: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[0]).into();
let from: FfiBytes32 = ctx.ctx().existing_private_accounts()[0].into();
let to: FfiBytes32 = ctx.ctx().existing_public_accounts()[0].into();
let amount: [u8; 16] = 100_u128.to_le_bytes();
let mut transfer_result = FfiTransferResult::default();
@ -964,7 +964,7 @@ fn test_wallet_ffi_transfer_private() -> Result<()> {
let home = tempfile::tempdir()?;
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
let from: FfiBytes32 = (&ctx.ctx().existing_private_accounts()[0]).into();
let from: FfiBytes32 = ctx.ctx().existing_private_accounts()[0].into();
let (to, to_keys) = unsafe {
let mut out_account_id = FfiBytes32::default();
let mut out_keys = FfiPrivateAccountKeys::default();

View File

@ -7,6 +7,10 @@ license = { workspace = true }
[lints]
workspace = true
[features]
default = []
test_utils = []
[dependencies]
nssa.workspace = true
nssa_core.workspace = true
@ -14,6 +18,7 @@ common.workspace = true
anyhow.workspace = true
serde.workspace = true
log.workspace = true
k256.workspace = true
sha2.workspace = true
rand.workspace = true

View File

@ -9,6 +9,7 @@ use crate::key_management::{
};
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(any(test, feature = "test_utils"), derive(PartialEq, Eq))]
pub struct ChildKeysPrivate {
pub value: (KeyChain, nssa::Account),
pub ccc: [u8; 32],

View File

@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
use crate::key_management::key_tree::traits::KeyNode;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(any(test, feature = "test_utils"), derive(PartialEq, Eq))]
pub struct ChildKeysPublic {
pub csk: nssa::PrivateKey,
pub cpk: nssa::PublicKey,

View File

@ -20,6 +20,7 @@ pub mod traits;
pub const DEPTH_SOFT_CAP: u32 = 20;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(any(test, feature = "test_utils"), derive(PartialEq, Eq))]
pub struct KeyTree<N: KeyNode> {
pub key_map: BTreeMap<ChainIndex, N>,
pub account_id_map: BTreeMap<nssa::AccountId, ChainIndex>,

View File

@ -11,8 +11,9 @@ pub mod secret_holders;
pub type PublicAccountSigningKey = [u8; 32];
/// Private account keychain.
#[derive(Serialize, Deserialize, Clone, Debug)]
/// Entrypoint to key management.
#[cfg_attr(any(test, feature = "test_utils"), derive(PartialEq, Eq))]
pub struct KeyChain {
pub secret_spending_key: SecretSpendingKey,
pub private_key_holder: PrivateKeyHolder,

View File

@ -22,9 +22,10 @@ pub struct SecretSpendingKey(pub [u8; 32]);
pub type ViewingSecretKey = Scalar;
#[derive(Serialize, Deserialize, Debug, Clone)]
/// Private key holder. Produces public keys. Can produce `account_id`. Can produce shared secret
/// for recepient.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(any(test, feature = "test_utils"), derive(PartialEq, Eq))]
pub struct PrivateKeyHolder {
pub nullifier_secret_key: NullifierSecretKey,
pub viewing_secret_key: ViewingSecretKey,

View File

@ -1,215 +0,0 @@
use std::collections::BTreeMap;
use anyhow::Result;
use k256::AffinePoint;
use serde::{Deserialize, Serialize};
use crate::key_management::{
KeyChain,
key_tree::{KeyTreePrivate, KeyTreePublic, chain_index::ChainIndex},
secret_holders::SeedHolder,
};
pub type PublicKey = AffinePoint;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NSSAUserData {
/// Default public accounts.
pub default_pub_account_signing_keys: BTreeMap<nssa::AccountId, nssa::PrivateKey>,
/// Default private accounts.
pub default_user_private_accounts:
BTreeMap<nssa::AccountId, (KeyChain, nssa_core::account::Account)>,
/// Tree of public keys.
pub public_key_tree: KeyTreePublic,
/// Tree of private keys.
pub private_key_tree: KeyTreePrivate,
}
impl NSSAUserData {
fn valid_public_key_transaction_pairing_check(
accounts_keys_map: &BTreeMap<nssa::AccountId, nssa::PrivateKey>,
) -> bool {
let mut check_res = true;
for (account_id, key) in accounts_keys_map {
let expected_account_id =
nssa::AccountId::from(&nssa::PublicKey::new_from_private_key(key));
if &expected_account_id != account_id {
println!("{expected_account_id}, {account_id}");
check_res = false;
}
}
check_res
}
fn valid_private_key_transaction_pairing_check(
accounts_keys_map: &BTreeMap<nssa::AccountId, (KeyChain, nssa_core::account::Account)>,
) -> bool {
let mut check_res = true;
for (account_id, (key, _)) in accounts_keys_map {
let expected_account_id = nssa::AccountId::from(&key.nullifier_public_key);
if expected_account_id != *account_id {
println!("{expected_account_id}, {account_id}");
check_res = false;
}
}
check_res
}
pub fn new_with_accounts(
default_accounts_keys: BTreeMap<nssa::AccountId, nssa::PrivateKey>,
default_accounts_key_chains: BTreeMap<
nssa::AccountId,
(KeyChain, nssa_core::account::Account),
>,
public_key_tree: KeyTreePublic,
private_key_tree: KeyTreePrivate,
) -> Result<Self> {
if !Self::valid_public_key_transaction_pairing_check(&default_accounts_keys) {
anyhow::bail!(
"Key transaction pairing check not satisfied, there are public account_ids, which are not derived from keys"
);
}
if !Self::valid_private_key_transaction_pairing_check(&default_accounts_key_chains) {
anyhow::bail!(
"Key transaction pairing check not satisfied, there are private account_ids, which are not derived from keys"
);
}
Ok(Self {
default_pub_account_signing_keys: default_accounts_keys,
default_user_private_accounts: default_accounts_key_chains,
public_key_tree,
private_key_tree,
})
}
/// Generated new private key for public transaction signatures.
///
/// Returns the `account_id` of new account.
pub fn generate_new_public_transaction_private_key(
&mut self,
parent_cci: Option<ChainIndex>,
) -> (nssa::AccountId, ChainIndex) {
match parent_cci {
Some(parent_cci) => self
.public_key_tree
.generate_new_node(&parent_cci)
.expect("Parent must be present in a tree"),
None => self
.public_key_tree
.generate_new_node_layered()
.expect("Search for new node slot failed"),
}
}
/// Returns the signing key for public transaction signatures.
#[must_use]
pub fn get_pub_account_signing_key(
&self,
account_id: nssa::AccountId,
) -> Option<&nssa::PrivateKey> {
self.default_pub_account_signing_keys
.get(&account_id)
.or_else(|| self.public_key_tree.get_node(account_id).map(Into::into))
}
/// Generated new private key for privacy preserving transactions.
///
/// Returns the `account_id` of new account.
pub fn generate_new_privacy_preserving_transaction_key_chain(
&mut self,
parent_cci: Option<ChainIndex>,
) -> (nssa::AccountId, ChainIndex) {
match parent_cci {
Some(parent_cci) => self
.private_key_tree
.generate_new_node(&parent_cci)
.expect("Parent must be present in a tree"),
None => self
.private_key_tree
.generate_new_node_layered()
.expect("Search for new node slot failed"),
}
}
/// Returns the signing key for public transaction signatures.
#[must_use]
pub fn get_private_account(
&self,
account_id: nssa::AccountId,
) -> Option<&(KeyChain, nssa_core::account::Account)> {
self.default_user_private_accounts
.get(&account_id)
.or_else(|| self.private_key_tree.get_node(account_id).map(Into::into))
}
/// Returns the signing key for public transaction signatures.
pub fn get_private_account_mut(
&mut self,
account_id: &nssa::AccountId,
) -> Option<&mut (KeyChain, nssa_core::account::Account)> {
// First seek in defaults
if let Some(key) = self.default_user_private_accounts.get_mut(account_id) {
Some(key)
// Then seek in tree
} else {
self.private_key_tree
.get_node_mut(*account_id)
.map(Into::into)
}
}
pub fn account_ids(&self) -> impl Iterator<Item = nssa::AccountId> {
self.public_account_ids().chain(self.private_account_ids())
}
pub fn public_account_ids(&self) -> impl Iterator<Item = nssa::AccountId> {
self.default_pub_account_signing_keys
.keys()
.copied()
.chain(self.public_key_tree.account_id_map.keys().copied())
}
pub fn private_account_ids(&self) -> impl Iterator<Item = nssa::AccountId> {
self.default_user_private_accounts
.keys()
.copied()
.chain(self.private_key_tree.account_id_map.keys().copied())
}
}
impl Default for NSSAUserData {
fn default() -> Self {
let (seed_holder, _mnemonic) = SeedHolder::new_mnemonic("");
Self::new_with_accounts(
BTreeMap::new(),
BTreeMap::new(),
KeyTreePublic::new(&seed_holder),
KeyTreePrivate::new(&seed_holder),
)
.unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_account() {
let mut user_data = NSSAUserData::default();
let (account_id_private, _) = user_data
.generate_new_privacy_preserving_transaction_key_chain(Some(ChainIndex::root()));
let is_key_chain_generated = user_data.get_private_account(account_id_private).is_some();
assert!(is_key_chain_generated);
let account_id_private_str = account_id_private.to_string();
println!("{account_id_private_str:#?}");
let key_chain = &user_data.get_private_account(account_id_private).unwrap().0;
println!("{key_chain:#?}");
}
}

View File

@ -1,4 +1,3 @@
#![expect(clippy::print_stdout, reason = "TODO: fix later")]
pub mod key_management;
pub mod key_protocol_core;

View File

@ -3,6 +3,7 @@
use std::ptr;
use nssa::AccountId;
use wallet::account::AccountIdWithPrivacy;
use crate::{
block_on,
@ -148,40 +149,21 @@ pub unsafe extern "C" fn wallet_ffi_list_accounts(
}
};
let user_data = &wallet.storage().user_data;
let mut entries = Vec::new();
// Public accounts from default signing keys (preconfigured)
for account_id in user_data.default_pub_account_signing_keys.keys() {
entries.push(FfiAccountListEntry {
account_id: FfiBytes32::from_account_id(account_id),
is_public: true,
});
}
// Public accounts from key tree (generated)
for account_id in user_data.public_key_tree.account_id_map.keys() {
entries.push(FfiAccountListEntry {
account_id: FfiBytes32::from_account_id(account_id),
is_public: true,
});
}
// Private accounts from default accounts (preconfigured)
for account_id in user_data.default_user_private_accounts.keys() {
entries.push(FfiAccountListEntry {
account_id: FfiBytes32::from_account_id(account_id),
is_public: false,
});
}
// Private accounts from key tree (generated)
for account_id in user_data.private_key_tree.account_id_map.keys() {
entries.push(FfiAccountListEntry {
account_id: FfiBytes32::from_account_id(account_id),
is_public: false,
});
}
let entries = wallet
.storage()
.key_chain()
.account_ids()
.map(|(account_id, _idx)| match account_id {
AccountIdWithPrivacy::Public(account_id) => FfiAccountListEntry {
account_id: FfiBytes32::from_account_id(account_id),
is_public: true,
},
AccountIdWithPrivacy::Private(account_id) => FfiAccountListEntry {
account_id: FfiBytes32::from_account_id(account_id),
is_public: false,
},
})
.collect::<Vec<_>>();
let count = entries.len();

View File

@ -116,7 +116,7 @@ pub unsafe extern "C" fn wallet_ffi_get_private_account_keys(
let account_id = AccountId::new(unsafe { (*account_id).data });
let Some((key_chain, _account)) = wallet.storage().user_data.get_private_account(account_id)
let Some((key_chain, _account)) = wallet.storage().key_chain().get_private_account(account_id)
else {
print_error("Private account not found in wallet");
return WalletFfiError::AccountNotFound;

View File

@ -93,7 +93,7 @@ pub unsafe extern "C" fn wallet_ffi_get_last_synced_block(
};
unsafe {
*out_block_id = wallet.last_synced_block;
*out_block_id = wallet.storage().last_synced_block();
}
WalletFfiError::Success

View File

@ -149,7 +149,7 @@ impl FfiBytes32 {
/// Create from an `AccountId`.
#[must_use]
pub const fn from_account_id(id: &nssa::AccountId) -> Self {
pub const fn from_account_id(id: nssa::AccountId) -> Self {
Self { data: *id.value() }
}
}
@ -186,8 +186,8 @@ impl From<FfiU128> for u128 {
}
}
impl From<&nssa::AccountId> for FfiBytes32 {
fn from(id: &nssa::AccountId) -> Self {
impl From<nssa::AccountId> for FfiBytes32 {
fn from(id: nssa::AccountId) -> Self {
Self::from_account_id(id)
}
}

View File

@ -10,7 +10,6 @@ use std::{
use wallet::WalletCore;
use crate::{
block_on,
error::{print_error, WalletFfiError},
types::WalletHandle,
};
@ -212,7 +211,7 @@ pub unsafe extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfi
}
};
match block_on(wallet.store_persistent_data()) {
match wallet.store_persistent_data() {
Ok(()) => WalletFfiError::Success,
Err(e) => {
print_error(format!("Failed to save wallet: {e}"));

View File

@ -39,3 +39,8 @@ async-stream.workspace = true
indicatif = { version = "0.18.3", features = ["improved_unicode"] }
optfield = "0.4.0"
url.workspace = true
derive_more = { workspace = true, features = ["display"] }
[dev-dependencies]
tempfile.workspace = true
key_protocol = { workspace = true, features = ["test_utils"] }

View File

@ -3,409 +3,5 @@
"seq_poll_timeout": "30s",
"seq_tx_poll_max_blocks": 15,
"seq_poll_max_retries": 10,
"seq_block_poll_max_amount": 100,
"initial_accounts": [
{
"Public": {
"account_id": "CbgR6tj5kWx5oziiFptM7jMvrQeYY3Mzaao6ciuhSr2r",
"pub_sign_key": "7f273098f25b71e6c005a9519f2678da8d1c7f01f6a27778e2d9948abdf901fb"
}
},
{
"Public": {
"account_id": "2RHZhw9h534Zr3eq2RGhQete2Hh667foECzXPmSkGni2",
"pub_sign_key": "f434f8741720014586ae43356d2aec6257da086222f604ddb75d69733b86fc4c"
}
},
{
"Private": {
"account_id": "9DGDXnrNo4QhUUb2F8WDuDrPESja3eYDkZG5HkzvAvMC",
"account": {
"program_owner": [
0,
0,
0,
0,
0,
0,
0,
0
],
"balance": 10000,
"data": [],
"nonce": 0
},
"key_chain": {
"secret_spending_key": [
75,
231,
144,
165,
5,
36,
183,
237,
190,
227,
238,
13,
132,
39,
114,
228,
172,
82,
119,
164,
233,
132,
130,
224,
201,
90,
200,
156,
108,
199,
56,
22
],
"private_key_holder": {
"nullifier_secret_key": [
212,
34,
166,
184,
182,
77,
127,
176,
147,
68,
148,
190,
41,
244,
8,
202,
51,
10,
44,
43,
93,
41,
229,
130,
54,
96,
198,
242,
10,
227,
119,
1
],
"viewing_secret_key": [
205,
10,
5,
19,
148,
98,
49,
19,
251,
186,
247,
216,
75,
53,
184,
36,
84,
87,
236,
205,
105,
217,
213,
21,
61,
183,
133,
174,
121,
115,
51,
203
]
},
"nullifier_public_key": [
122,
213,
113,
8,
118,
179,
235,
94,
5,
219,
131,
106,
246,
253,
14,
204,
65,
93,
0,
198,
100,
108,
57,
48,
6,
65,
183,
31,
136,
86,
82,
165
],
"viewing_public_key": [
3,
165,
235,
215,
77,
4,
19,
45,
0,
27,
18,
26,
11,
226,
126,
174,
144,
167,
160,
199,
14,
23,
49,
163,
49,
138,
129,
229,
79,
9,
15,
234,
30
]
}
}
},
{
"Private": {
"account_id": "A6AT9UvsgitUi8w4BH43n6DyX1bK37DtSCfjEWXQQUrQ",
"account": {
"program_owner": [
0,
0,
0,
0,
0,
0,
0,
0
],
"balance": 20000,
"data": [],
"nonce": 0
},
"key_chain": {
"secret_spending_key": [
107,
49,
136,
174,
162,
107,
250,
105,
252,
146,
166,
197,
163,
132,
153,
222,
68,
17,
87,
101,
22,
113,
88,
97,
180,
203,
139,
18,
28,
62,
51,
149
],
"private_key_holder": {
"nullifier_secret_key": [
219,
5,
233,
185,
144,
150,
100,
58,
97,
5,
57,
163,
110,
46,
241,
216,
155,
217,
100,
51,
184,
21,
225,
148,
198,
9,
121,
239,
232,
98,
22,
218
],
"viewing_secret_key": [
35,
105,
230,
121,
218,
177,
21,
55,
83,
80,
95,
235,
161,
83,
11,
221,
67,
83,
1,
218,
49,
242,
53,
29,
26,
171,
170,
144,
49,
233,
159,
48
]
},
"nullifier_public_key": [
33,
68,
229,
154,
12,
235,
210,
229,
236,
144,
126,
122,
58,
107,
36,
58,
243,
128,
174,
197,
141,
137,
162,
190,
155,
234,
94,
156,
218,
34,
13,
221
],
"viewing_public_key": [
3,
122,
7,
137,
250,
84,
10,
85,
3,
15,
134,
250,
205,
40,
126,
211,
14,
120,
15,
55,
56,
214,
72,
243,
83,
17,
124,
242,
251,
184,
174,
150,
83
]
}
}
}
]
"seq_block_poll_max_amount": 100
}

137
wallet/src/account.rs Normal file
View File

@ -0,0 +1,137 @@
use std::str::FromStr;
use base58::{FromBase58 as _, ToBase58 as _};
use derive_more::Display;
use nssa::AccountId;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Display, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[display("{_0}")]
pub struct Label(String);
impl Label {
#[expect(
clippy::needless_pass_by_value,
reason = "Convenience for caller and negligible cost"
)]
#[must_use]
pub fn new(label: impl ToString) -> Self {
Self(label.to_string())
}
}
impl FromStr for Label {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(Self(s.to_owned()))
}
}
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum AccountIdWithPrivacy {
#[display("Public/{_0}")]
Public(AccountId),
#[display("Private/{_0}")]
Private(AccountId),
}
#[derive(Debug, Error)]
pub enum AccountIdWithPrivacyParseError {
#[error("Invalid format, expected 'Public/{{account_id}}' or 'Private/{{account_id}}'")]
InvalidFormat,
#[error("Invalid account id")]
InvalidAccountId(#[from] nssa_core::account::AccountIdError),
}
impl FromStr for AccountIdWithPrivacy {
type Err = AccountIdWithPrivacyParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(stripped) = s.strip_prefix("Public/") {
Ok(Self::Public(AccountId::from_str(stripped)?))
} else if let Some(stripped) = s.strip_prefix("Private/") {
Ok(Self::Private(AccountId::from_str(stripped)?))
} else {
Err(AccountIdWithPrivacyParseError::InvalidFormat)
}
}
}
/// Human-readable representation of an account.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HumanReadableAccount {
balance: u128,
program_owner: String,
data: String,
nonce: u128,
}
impl FromStr for HumanReadableAccount {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s).map_err(Into::into)
}
}
impl std::fmt::Display for HumanReadableAccount {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let json = serde_json::to_string_pretty(self).map_err(|_err| std::fmt::Error)?;
write!(f, "{json}")
}
}
impl From<nssa::Account> for HumanReadableAccount {
fn from(account: nssa::Account) -> Self {
let program_owner = account
.program_owner
.iter()
.flat_map(|n| n.to_le_bytes())
.collect::<Vec<u8>>()
.to_base58();
let data = hex::encode(account.data);
Self {
balance: account.balance,
program_owner,
data,
nonce: account.nonce.0,
}
}
}
impl From<HumanReadableAccount> for nssa::Account {
fn from(account: HumanReadableAccount) -> Self {
let mut program_owner_bytes = [0_u8; 32];
let decoded_program_owner = account
.program_owner
.from_base58()
.expect("Invalid base58 in HumanReadableAccount.program_owner");
assert!(
decoded_program_owner.len() == 32,
"HumanReadableAccount.program_owner must decode to exactly 32 bytes"
);
program_owner_bytes.copy_from_slice(&decoded_program_owner);
let mut program_owner = [0_u32; 8];
for (index, chunk) in program_owner_bytes.chunks_exact(4).enumerate() {
let chunk: [u8; 4] = chunk
.try_into()
.expect("chunk length is guaranteed to be 4");
program_owner[index] = u32::from_le_bytes(chunk);
}
let data = hex::decode(&account.data).expect("Invalid hex in HumanReadableAccount.data");
let data = data
.try_into()
.expect("Invalid account data: exceeds maximum allowed size");
Self {
balance: account.balance,
program_owner,
data,
nonce: nssa_core::account::Nonce(account.nonce),
}
}
}

View File

@ -1,245 +0,0 @@
use std::collections::{BTreeMap, HashMap, btree_map::Entry};
use anyhow::Result;
use bip39::Mnemonic;
use key_protocol::{
key_management::{
key_tree::{KeyTreePrivate, KeyTreePublic, chain_index::ChainIndex},
secret_holders::SeedHolder,
},
key_protocol_core::NSSAUserData,
};
use log::debug;
use nssa::program::Program;
use crate::config::{InitialAccountData, Label, PersistentAccountData, WalletConfig};
pub struct WalletChainStore {
pub user_data: NSSAUserData,
pub wallet_config: WalletConfig,
pub labels: HashMap<String, Label>,
}
impl WalletChainStore {
#[expect(
clippy::wildcard_enum_match_arm,
reason = "We perform search for specific variants only"
)]
pub fn new(
config: WalletConfig,
persistent_accounts: Vec<PersistentAccountData>,
labels: HashMap<String, Label>,
) -> Result<Self> {
if persistent_accounts.is_empty() {
anyhow::bail!("Roots not found; please run setup beforehand");
}
let mut public_init_acc_map = BTreeMap::new();
let mut private_init_acc_map = BTreeMap::new();
let public_root = persistent_accounts
.iter()
.find(|data| match data {
&PersistentAccountData::Public(data) => data.chain_index == ChainIndex::root(),
_ => false,
})
.cloned()
.expect("Malformed persistent account data, must have public root");
let private_root = persistent_accounts
.iter()
.find(|data| match data {
&PersistentAccountData::Private(data) => data.chain_index == ChainIndex::root(),
_ => false,
})
.cloned()
.expect("Malformed persistent account data, must have private root");
let mut public_tree = KeyTreePublic::new_from_root(match public_root {
PersistentAccountData::Public(data) => data.data,
_ => unreachable!(),
});
let mut private_tree = KeyTreePrivate::new_from_root(match private_root {
PersistentAccountData::Private(data) => data.data,
_ => unreachable!(),
});
for pers_acc_data in persistent_accounts {
match pers_acc_data {
PersistentAccountData::Public(data) => {
public_tree.insert(data.account_id, data.chain_index, data.data);
}
PersistentAccountData::Private(data) => {
private_tree.insert(data.account_id, data.chain_index, data.data);
}
PersistentAccountData::Preconfigured(acc_data) => match acc_data {
InitialAccountData::Public(data) => {
public_init_acc_map.insert(data.account_id, data.pub_sign_key);
}
InitialAccountData::Private(data) => {
private_init_acc_map
.insert(data.account_id, (data.key_chain, data.account));
}
},
}
}
Ok(Self {
user_data: NSSAUserData::new_with_accounts(
public_init_acc_map,
private_init_acc_map,
public_tree,
private_tree,
)?,
wallet_config: config,
labels,
})
}
pub fn new_storage(config: WalletConfig, password: &str) -> Result<(Self, Mnemonic)> {
let mut public_init_acc_map = BTreeMap::new();
let mut private_init_acc_map = BTreeMap::new();
let initial_accounts = config
.initial_accounts
.clone()
.unwrap_or_else(InitialAccountData::create_initial_accounts_data);
for init_acc_data in initial_accounts {
match init_acc_data {
InitialAccountData::Public(data) => {
public_init_acc_map.insert(data.account_id, data.pub_sign_key);
}
InitialAccountData::Private(data) => {
let mut account = data.account;
// TODO: Program owner is only known after code is compiled and can't be set
// in the config. Therefore we overwrite it here on
// startup. Fix this when program id can be fetched
// from the node and queried from the wallet.
account.program_owner = Program::authenticated_transfer_program().id();
private_init_acc_map.insert(data.account_id, (data.key_chain, account));
}
}
}
// TODO: Use password for storage encryption
let _ = password;
let (seed_holder, mnemonic) = SeedHolder::new_mnemonic("");
let public_tree = KeyTreePublic::new(&seed_holder);
let private_tree = KeyTreePrivate::new(&seed_holder);
Ok((
Self {
user_data: NSSAUserData::new_with_accounts(
public_init_acc_map,
private_init_acc_map,
public_tree,
private_tree,
)?,
wallet_config: config,
labels: HashMap::new(),
},
mnemonic,
))
}
/// Restore storage from an existing mnemonic phrase.
pub fn restore_storage(
config: WalletConfig,
mnemonic: &Mnemonic,
password: &str,
) -> Result<Self> {
// TODO: Use password for storage encryption
let _ = password;
let seed_holder = SeedHolder::from_mnemonic(mnemonic, "");
let public_tree = KeyTreePublic::new(&seed_holder);
let private_tree = KeyTreePrivate::new(&seed_holder);
Ok(Self {
user_data: NSSAUserData::new_with_accounts(
BTreeMap::new(),
BTreeMap::new(),
public_tree,
private_tree,
)?,
wallet_config: config,
labels: HashMap::new(),
})
}
pub fn insert_private_account_data(
&mut self,
account_id: nssa::AccountId,
account: nssa_core::account::Account,
) {
debug!("inserting at address {account_id}, this account {account:?}");
let entry = self
.user_data
.default_user_private_accounts
.entry(account_id)
.and_modify(|data| data.1 = account.clone());
if matches!(entry, Entry::Vacant(_)) {
self.user_data
.private_key_tree
.account_id_map
.get(&account_id)
.map(|chain_index| {
self.user_data
.private_key_tree
.key_map
.entry(chain_index.clone())
.and_modify(|data| data.value.1 = account)
});
}
}
}
#[cfg(test)]
mod tests {
use key_protocol::key_management::key_tree::{
keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic, traits::KeyNode as _,
};
use super::*;
use crate::config::{PersistentAccountDataPrivate, PersistentAccountDataPublic};
fn create_sample_wallet_config() -> WalletConfig {
WalletConfig {
sequencer_addr: "http://127.0.0.1".parse().unwrap(),
seq_poll_timeout: std::time::Duration::from_secs(12),
seq_tx_poll_max_blocks: 5,
seq_poll_max_retries: 10,
seq_block_poll_max_amount: 100,
basic_auth: None,
initial_accounts: None,
}
}
fn create_sample_persistent_accounts() -> Vec<PersistentAccountData> {
let public_data = ChildKeysPublic::root([42; 64]);
let private_data = ChildKeysPrivate::root([47; 64]);
vec![
PersistentAccountData::Public(PersistentAccountDataPublic {
account_id: public_data.account_id(),
chain_index: ChainIndex::root(),
data: public_data,
}),
PersistentAccountData::Private(Box::new(PersistentAccountDataPrivate {
account_id: private_data.account_id(),
chain_index: ChainIndex::root(),
data: private_data,
})),
]
}
#[test]
fn new_initializes_correctly() {
let config = create_sample_wallet_config();
let accs = create_sample_persistent_accounts();
let _ = WalletChainStore::new(config, accs, HashMap::new()).unwrap();
}
}

View File

@ -3,17 +3,12 @@ use clap::Subcommand;
use itertools::Itertools as _;
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use nssa::{Account, PublicKey, program::Program};
use sequencer_service_rpc::RpcClient as _;
use token_core::{TokenDefinition, TokenHolding};
use crate::{
WalletCore,
cli::{SubcommandReturnValue, WalletSubcommand},
config::Label,
helperfunctions::{
AccountPrivacyKind, HumanReadableAccount, parse_addr_with_privacy_prefix,
resolve_id_or_label,
},
account::{AccountIdWithPrivacy, HumanReadableAccount, Label},
cli::{CliAccountMention, SubcommandReturnValue, WalletSubcommand},
};
/// Represents generic chain CLI subcommand.
@ -27,17 +22,9 @@ pub enum AccountSubcommand {
/// Display keys (pk for public accounts, npk/vpk for private accounts).
#[arg(short, long)]
keys: bool,
/// Valid 32 byte base58 string with privacy prefix.
#[arg(
short,
long,
conflicts_with = "account_label",
required_unless_present = "account_label"
)]
account_id: Option<String>,
/// Account label (alternative to --account-id).
#[arg(long, conflicts_with = "account_id")]
account_label: Option<String>,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(short, long)]
account_id: CliAccountMention,
},
/// Produce new public or private account.
#[command(subcommand)]
@ -53,20 +40,12 @@ pub enum AccountSubcommand {
},
/// Set a label for an account.
Label {
/// Valid 32 byte base58 string with privacy prefix.
#[arg(
short,
long,
conflicts_with = "account_label",
required_unless_present = "account_label"
)]
account_id: Option<String>,
/// Account label (alternative to --account-id).
#[arg(long = "account-label", conflicts_with = "account_id")]
account_label: Option<String>,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(short, long)]
account_id: CliAccountMention,
/// The label to assign to the account.
#[arg(short, long)]
label: String,
label: Label,
},
}
@ -80,7 +59,7 @@ pub enum NewSubcommand {
cci: Option<ChainIndex>,
#[arg(short, long)]
/// Label to assign to the new account.
label: Option<String>,
label: Option<Label>,
},
/// Register new private account.
Private {
@ -89,7 +68,7 @@ pub enum NewSubcommand {
cci: Option<ChainIndex>,
#[arg(short, long)]
/// Label to assign to the new account.
label: Option<String>,
label: Option<Label>,
},
}
@ -100,21 +79,15 @@ impl WalletSubcommand for NewSubcommand {
) -> Result<SubcommandReturnValue> {
match self {
Self::Public { cci, label } => {
if let Some(label) = &label
&& wallet_core
.storage
.labels
.values()
.any(|l| l.to_string() == *label)
{
anyhow::bail!("Label '{label}' is already in use by another account");
if let Some(label) = &label {
wallet_core.storage().check_label_availability(label)?;
}
let (account_id, chain_index) = wallet_core.create_new_account_public(cci);
let private_key = wallet_core
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(account_id)
.unwrap();
@ -122,9 +95,8 @@ impl WalletSubcommand for NewSubcommand {
if let Some(label) = label {
wallet_core
.storage
.labels
.insert(account_id.to_string(), Label::new(label));
.storage_mut()
.add_label(label, AccountIdWithPrivacy::Public(account_id))?;
}
println!(
@ -132,33 +104,26 @@ impl WalletSubcommand for NewSubcommand {
);
println!("With pk {}", hex::encode(public_key.value()));
wallet_core.store_persistent_data().await?;
wallet_core.store_persistent_data()?;
Ok(SubcommandReturnValue::RegisterAccount { account_id })
}
Self::Private { cci, label } => {
if let Some(label) = &label
&& wallet_core
.storage
.labels
.values()
.any(|l| l.to_string() == *label)
{
anyhow::bail!("Label '{label}' is already in use by another account");
if let Some(label) = &label {
wallet_core.storage().check_label_availability(label)?;
}
let (account_id, chain_index) = wallet_core.create_new_account_private(cci);
if let Some(label) = label {
wallet_core
.storage
.labels
.insert(account_id.to_string(), Label::new(label));
.storage_mut()
.add_label(label, AccountIdWithPrivacy::Private(account_id))?;
}
let (key, _) = wallet_core
.storage
.user_data
.key_chain()
.get_private_account(account_id)
.unwrap();
@ -171,7 +136,7 @@ impl WalletSubcommand for NewSubcommand {
hex::encode(key.viewing_public_key.to_bytes())
);
wallet_core.store_persistent_data().await?;
wallet_core.store_persistent_data()?;
Ok(SubcommandReturnValue::RegisterAccount { account_id })
}
@ -180,7 +145,6 @@ impl WalletSubcommand for NewSubcommand {
}
impl WalletSubcommand for AccountSubcommand {
#[expect(clippy::cognitive_complexity, reason = "TODO: fix later")]
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
@ -190,48 +154,34 @@ impl WalletSubcommand for AccountSubcommand {
raw,
keys,
account_id,
account_label,
} => {
let resolved = resolve_id_or_label(
account_id,
account_label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?;
let (account_id_str, addr_kind) = parse_addr_with_privacy_prefix(&resolved)?;
let resolved = account_id.resolve(wallet_core.storage())?;
wallet_core
.storage()
.labels_for_account(resolved)
.for_each(|label| {
println!("Label: {label}");
});
let account_id: nssa::AccountId = account_id_str.parse()?;
if let Some(label) = wallet_core.storage.labels.get(&account_id_str) {
println!("Label: {label}");
}
let account = match addr_kind {
AccountPrivacyKind::Public => {
wallet_core.get_account_public(account_id).await?
}
AccountPrivacyKind::Private => wallet_core
.get_account_private(account_id)
.context("Private account not found in storage")?,
};
let account = wallet_core.get_account(resolved).await?;
// Helper closure to display keys for the account
let display_keys = |wallet_core: &WalletCore| -> Result<()> {
match addr_kind {
AccountPrivacyKind::Public => {
match resolved {
AccountIdWithPrivacy::Public(account_id) => {
let private_key = wallet_core
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(account_id)
.context("Public account not found in storage")?;
let public_key = PublicKey::new_from_private_key(private_key);
println!("pk {}", hex::encode(public_key.value()));
}
AccountPrivacyKind::Private => {
AccountIdWithPrivacy::Private(account_id) => {
let (key, _) = wallet_core
.storage
.user_data
.key_chain()
.get_private_account(account_id)
.context("Private account not found in storage")?;
@ -254,7 +204,7 @@ impl WalletSubcommand for AccountSubcommand {
if raw {
let account_hr: HumanReadableAccount = account.into();
println!("{}", serde_json::to_string(&account_hr).unwrap());
println!("{account_hr}");
return Ok(SubcommandReturnValue::Empty);
}
@ -271,67 +221,43 @@ impl WalletSubcommand for AccountSubcommand {
}
Self::New(new_subcommand) => new_subcommand.handle_subcommand(wallet_core).await,
Self::SyncPrivate => {
let curr_last_block = wallet_core.sequencer_client.get_last_block_id().await?;
if wallet_core
.storage
.user_data
.private_key_tree
.account_id_map
.is_empty()
{
wallet_core.last_synced_block = curr_last_block;
wallet_core.store_persistent_data().await?;
} else {
wallet_core.sync_to_block(curr_last_block).await?;
}
let curr_last_block = wallet_core.sync_to_latest_block().await?;
Ok(SubcommandReturnValue::SyncedToBlock(curr_last_block))
}
Self::List { long } => {
let user_data = &wallet_core.storage.user_data;
let labels = &wallet_core.storage.labels;
let key_chain = &wallet_core.storage.key_chain();
let storage = wallet_core.storage();
let format_with_label = |prefix: &str, id: nssa::AccountId| {
let id_str = id.to_string();
labels
.get(&id_str)
.map_or_else(|| prefix.to_owned(), |label| format!("{prefix} [{label}]"))
};
let format_with_label =
|id: AccountIdWithPrivacy, chain_index: Option<&ChainIndex>| {
let id_str =
chain_index.map_or_else(|| id.to_string(), |cci| format!("{cci} {id}"));
let labels = storage.labels_for_account(id).format(", ").to_string();
if labels.is_empty() {
id_str
} else {
format!("{id_str} [{labels}]")
}
};
if !long {
let accounts =
user_data
.default_pub_account_signing_keys
.keys()
.copied()
.map(|id| format_with_label(&format!("Preconfigured Public/{id}"), id))
.chain(user_data.default_user_private_accounts.keys().copied().map(
|id| format_with_label(&format!("Preconfigured Private/{id}"), id),
))
.chain(user_data.public_key_tree.account_id_map.iter().map(
|(id, chain_index)| {
format_with_label(&format!("{chain_index} Public/{id}"), *id)
},
))
.chain(user_data.private_key_tree.account_id_map.iter().map(
|(id, chain_index)| {
format_with_label(&format!("{chain_index} Private/{id}"), *id)
},
))
.format("\n");
let accounts = key_chain
.account_ids()
.map(|(id, idx)| format_with_label(id, idx))
.format("\n");
println!("{accounts}");
return Ok(SubcommandReturnValue::Empty);
}
// Detailed listing with --long flag
// Preconfigured public accounts
for id in user_data.default_pub_account_signing_keys.keys().copied() {
// Public key tree accounts
for (id, chain_index) in key_chain.public_account_ids() {
println!(
"{}",
format_with_label(&format!("Preconfigured Public/{id}"), id)
format_with_label(AccountIdWithPrivacy::Public(id), chain_index)
);
match wallet_core.get_account_public(id).await {
Ok(account) if account != Account::default() => {
@ -344,11 +270,11 @@ impl WalletSubcommand for AccountSubcommand {
}
}
// Preconfigured private accounts
for id in user_data.default_user_private_accounts.keys().copied() {
// Private key tree accounts
for (id, chain_index) in key_chain.private_account_ids() {
println!(
"{}",
format_with_label(&format!("Preconfigured Private/{id}"), id)
format_with_label(AccountIdWithPrivacy::Private(id), chain_index)
);
match wallet_core.get_account_private(id) {
Some(account) if account != Account::default() => {
@ -361,80 +287,18 @@ impl WalletSubcommand for AccountSubcommand {
}
}
// Public key tree accounts
for (id, chain_index) in &user_data.public_key_tree.account_id_map {
println!(
"{}",
format_with_label(&format!("{chain_index} Public/{id}"), *id)
);
match wallet_core.get_account_public(*id).await {
Ok(account) if account != Account::default() => {
let (description, json_view) = format_account_details(&account);
println!(" {description}");
println!(" {json_view}");
}
Ok(_) => println!(" Uninitialized"),
Err(e) => println!(" Error fetching account: {e}"),
}
}
// Private key tree accounts
for (id, chain_index) in &user_data.private_key_tree.account_id_map {
println!(
"{}",
format_with_label(&format!("{chain_index} Private/{id}"), *id)
);
match wallet_core.get_account_private(*id) {
Some(account) if account != Account::default() => {
let (description, json_view) = format_account_details(&account);
println!(" {description}");
println!(" {json_view}");
}
Some(_) => println!(" Uninitialized"),
None => println!(" Not found in local storage"),
}
}
Ok(SubcommandReturnValue::Empty)
}
Self::Label {
account_id,
account_label,
label,
} => {
let resolved = resolve_id_or_label(
account_id,
account_label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?;
let (account_id_str, _) = parse_addr_with_privacy_prefix(&resolved)?;
Self::Label { account_id, label } => {
let account_id = account_id.resolve(wallet_core.storage())?;
// Check if label is already used by a different account
if let Some(existing_account) = wallet_core
.storage
.labels
.iter()
.find(|(_, l)| l.to_string() == label)
.map(|(a, _)| a.clone())
&& existing_account != account_id_str
{
anyhow::bail!(
"Label '{label}' is already in use by account {existing_account}"
);
}
wallet_core
.storage_mut()
.add_label(label.clone(), account_id)?;
let old_label = wallet_core
.storage
.labels
.insert(account_id_str.clone(), Label::new(label.clone()));
wallet_core.store_persistent_data()?;
wallet_core.store_persistent_data().await?;
if let Some(old) = old_label {
eprintln!("Warning: overriding existing label '{old}'");
}
println!("Label '{label}' set for account {account_id_str}");
println!("Label '{label}' set for account {account_id}");
Ok(SubcommandReturnValue::Empty)
}

View File

@ -4,7 +4,6 @@ use clap::Subcommand;
use crate::{
WalletCore,
cli::{SubcommandReturnValue, WalletSubcommand},
config::InitialAccountData,
};
/// Represents generic config CLI subcommand.
@ -29,52 +28,32 @@ impl WalletSubcommand for ConfigSubcommand {
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
let config = wallet_core.config();
match self {
Self::Get { all, key } => {
if all {
let config_str =
serde_json::to_string_pretty(&wallet_core.storage.wallet_config)?;
let config_str = serde_json::to_string_pretty(&config)?;
println!("{config_str}");
} else if let Some(key) = key {
match key.as_str() {
"sequencer_addr" => {
println!("{}", wallet_core.storage.wallet_config.sequencer_addr);
println!("{}", config.sequencer_addr);
}
"seq_poll_timeout" => {
println!("{:?}", wallet_core.storage.wallet_config.seq_poll_timeout);
println!("{:?}", config.seq_poll_timeout);
}
"seq_tx_poll_max_blocks" => {
println!(
"{}",
wallet_core.storage.wallet_config.seq_tx_poll_max_blocks
);
println!("{}", config.seq_tx_poll_max_blocks);
}
"seq_poll_max_retries" => {
println!("{}", wallet_core.storage.wallet_config.seq_poll_max_retries);
println!("{}", config.seq_poll_max_retries);
}
"seq_block_poll_max_amount" => {
println!(
"{}",
wallet_core.storage.wallet_config.seq_block_poll_max_amount
);
}
"initial_accounts" => {
println!(
"{:#?}",
wallet_core
.storage
.wallet_config
.initial_accounts
.clone()
.unwrap_or_else(
InitialAccountData::create_initial_accounts_data
)
);
println!("{}", config.seq_block_poll_max_amount);
}
"basic_auth" => {
if let Some(basic_auth) = &wallet_core.storage.wallet_config.basic_auth
{
if let Some(basic_auth) = &config.basic_auth {
println!("{basic_auth}");
} else {
println!("Not set");
@ -89,27 +68,26 @@ impl WalletSubcommand for ConfigSubcommand {
}
}
Self::Set { key, value } => {
let mut config = config.clone();
match key.as_str() {
"sequencer_addr" => {
wallet_core.storage.wallet_config.sequencer_addr = value.parse()?;
config.sequencer_addr = value.parse()?;
}
"seq_poll_timeout" => {
wallet_core.storage.wallet_config.seq_poll_timeout =
humantime::parse_duration(&value)
.map_err(|e| anyhow::anyhow!("Invalid duration: {e}"))?;
config.seq_poll_timeout = humantime::parse_duration(&value)
.map_err(|e| anyhow::anyhow!("Invalid duration: {e}"))?;
}
"seq_tx_poll_max_blocks" => {
wallet_core.storage.wallet_config.seq_tx_poll_max_blocks = value.parse()?;
config.seq_tx_poll_max_blocks = value.parse()?;
}
"seq_poll_max_retries" => {
wallet_core.storage.wallet_config.seq_poll_max_retries = value.parse()?;
config.seq_poll_max_retries = value.parse()?;
}
"seq_block_poll_max_amount" => {
wallet_core.storage.wallet_config.seq_block_poll_max_amount =
value.parse()?;
config.seq_block_poll_max_amount = value.parse()?;
}
"basic_auth" => {
wallet_core.storage.wallet_config.basic_auth = Some(value.parse()?);
config.basic_auth = Some(value.parse()?);
}
"initial_accounts" => {
anyhow::bail!("Setting this field from wallet is not supported");
@ -119,6 +97,7 @@ impl WalletSubcommand for ConfigSubcommand {
}
}
wallet_core.set_config(config);
wallet_core.store_config_changes().await?;
}
Self::Description { key } => match key.as_str() {

73
wallet/src/cli/import.rs Normal file
View File

@ -0,0 +1,73 @@
use anyhow::Result;
use clap::Subcommand;
use key_protocol::key_management::KeyChain;
use crate::{
WalletCore,
account::HumanReadableAccount,
cli::{SubcommandReturnValue, WalletSubcommand},
};
#[derive(Subcommand, Debug, Clone)]
pub enum ImportSubcommand {
/// Import a public account signing key.
Public {
/// Private key in hex format.
#[arg(long)]
private_key: nssa::PrivateKey,
},
/// Import a private account keychain and account state.
Private {
/// Private account keychain JSON.
#[arg(long)]
key_chain_json: String,
/// Private account state JSON (`HumanReadableAccount`).
#[arg(long)]
account_state: HumanReadableAccount,
},
}
impl WalletSubcommand for ImportSubcommand {
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
Self::Public { private_key } => {
let account_id =
nssa::AccountId::from(&nssa::PublicKey::new_from_private_key(&private_key));
wallet_core
.storage_mut()
.key_chain_mut()
.add_imported_public_account(private_key);
wallet_core.store_persistent_data()?;
println!("Imported public account Public/{account_id}");
Ok(SubcommandReturnValue::Empty)
}
Self::Private {
key_chain_json,
account_state,
} => {
let key_chain: KeyChain = serde_json::from_str(&key_chain_json)
.map_err(|err| anyhow::anyhow!("Invalid key chain JSON: {err}"))?;
let account = nssa::Account::from(account_state);
let account_id = nssa::AccountId::from(&key_chain.nullifier_public_key);
wallet_core
.storage_mut()
.key_chain_mut()
.add_imported_private_account(key_chain, account);
wallet_core.store_persistent_data()?;
println!("Imported private account Private/{account_id}");
Ok(SubcommandReturnValue::Empty)
}
}
}
}

View File

@ -1,30 +1,35 @@
use std::{io::Write as _, path::PathBuf, str::FromStr as _};
use std::{io::Write as _, path::PathBuf, str::FromStr};
use anyhow::{Context as _, Result};
use bip39::Mnemonic;
use clap::{Parser, Subcommand};
use common::{HashType, transaction::NSSATransaction};
use derive_more::Display;
use futures::TryFutureExt as _;
use nssa::{ProgramDeploymentTransaction, program::Program};
use sequencer_service_rpc::RpcClient as _;
use crate::{
WalletCore,
account::{AccountIdWithPrivacy, Label},
cli::{
account::AccountSubcommand,
chain::ChainSubcommand,
config::ConfigSubcommand,
import::ImportSubcommand,
programs::{
amm::AmmProgramAgnosticSubcommand, ata::AtaSubcommand,
native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand,
token::TokenProgramAgnosticSubcommand,
},
},
storage::Storage,
};
pub mod account;
pub mod chain;
pub mod config;
pub mod import;
pub mod programs;
pub(crate) trait WalletSubcommand {
@ -63,6 +68,9 @@ pub enum Command {
/// Command to setup config, get and set config fields.
#[command(subcommand)]
Config(ConfigSubcommand),
/// Import external keys into wallet keychain.
#[command(subcommand)]
Import(ImportSubcommand),
/// Restoring keys from given password at given `depth`.
///
/// !!!WARNING!!! will rewrite current storage.
@ -104,6 +112,35 @@ pub enum SubcommandReturnValue {
SyncedToBlock(u64),
}
#[derive(Debug, Display, Clone, PartialEq, Eq, Hash)]
#[display("{_0}")]
pub enum CliAccountMention {
Id(AccountIdWithPrivacy),
Label(Label),
}
impl CliAccountMention {
pub fn resolve(&self, storage: &Storage) -> Result<AccountIdWithPrivacy> {
match self {
Self::Id(account_id) => Ok(*account_id),
Self::Label(label) => storage
.resolve_label(label)
.ok_or_else(|| anyhow::anyhow!("No account found for label `{label}`")),
}
}
}
impl FromStr for CliAccountMention {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
AccountIdWithPrivacy::from_str(s).map_or_else(
|_| Ok(Self::Label(Label::new(s.to_owned()))),
|account_id| Ok(Self::Id(account_id)),
)
}
}
pub async fn execute_subcommand(
wallet_core: &mut WalletCore,
command: Command,
@ -167,6 +204,9 @@ pub async fn execute_subcommand(
Command::Config(config_subcommand) => {
config_subcommand.handle_subcommand(wallet_core).await?
}
Command::Import(import_subcommand) => {
import_subcommand.handle_subcommand(wallet_core).await?
}
Command::RestoreKeys { depth } => {
let mnemonic = read_mnemonic_from_stdin()?;
let password = read_password_from_stdin()?;
@ -197,9 +237,7 @@ pub async fn execute_subcommand(
pub async fn execute_continuous_run(wallet_core: &mut WalletCore) -> Result<()> {
loop {
let latest_block_num = wallet_core.sequencer_client.get_last_block_id().await?;
wallet_core.sync_to_block(latest_block_num).await?;
wallet_core.sync_to_latest_block().await?;
tokio::time::sleep(wallet_core.config().seq_poll_timeout).await;
}
}
@ -227,25 +265,20 @@ pub fn read_mnemonic_from_stdin() -> Result<Mnemonic> {
pub async fn execute_keys_restoration(wallet_core: &mut WalletCore, depth: u32) -> Result<()> {
wallet_core
.storage
.user_data
.public_key_tree
.generate_tree_for_depth(depth);
.key_chain_mut()
.generate_trees_for_depth(depth);
println!("Public tree generated");
println!(
"Public tree generated\n\
Private tree generated"
);
wallet_core.sync_to_latest_block().await?;
wallet_core
.storage
.user_data
.private_key_tree
.generate_tree_for_depth(depth);
println!("Private tree generated");
wallet_core
.storage
.user_data
.public_key_tree
.cleanup_tree_remove_uninit_layered(depth, |account_id| {
.key_chain_mut()
.cleanup_trees_remove_uninit_layered(depth, |account_id| {
wallet_core
.sequencer_client
.get_account(account_id)
@ -253,25 +286,12 @@ pub async fn execute_keys_restoration(wallet_core: &mut WalletCore, depth: u32)
})
.await?;
println!("Public tree cleaned up");
println!(
"Public tree cleaned up\n\
Private tree cleaned up"
);
let last_block = wallet_core.sequencer_client.get_last_block_id().await?;
println!("Last block is {last_block}");
wallet_core.sync_to_block(last_block).await?;
println!("Private tree clean up start");
wallet_core
.storage
.user_data
.private_key_tree
.cleanup_tree_remove_uninit_layered(depth);
println!("Private tree cleaned up");
wallet_core.store_persistent_data().await?;
wallet_core.store_persistent_data()?;
Ok(())
}

View File

@ -4,8 +4,8 @@ use nssa::AccountId;
use crate::{
WalletCore,
cli::{SubcommandReturnValue, WalletSubcommand},
helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix, resolve_id_or_label},
account::AccountIdWithPrivacy,
cli::{CliAccountMention, SubcommandReturnValue, WalletSubcommand},
program_facades::amm::Amm,
};
@ -18,36 +18,15 @@ pub enum AmmProgramAgnosticSubcommand {
///
/// Only public execution allowed.
New {
/// `user_holding_a` - valid 32 byte base58 string with privacy prefix.
#[arg(
long,
conflicts_with = "user_holding_a_label",
required_unless_present = "user_holding_a_label"
)]
user_holding_a: Option<String>,
/// User holding A account label (alternative to --user-holding-a).
#[arg(long, conflicts_with = "user_holding_a")]
user_holding_a_label: Option<String>,
/// `user_holding_b` - valid 32 byte base58 string with privacy prefix.
#[arg(
long,
conflicts_with = "user_holding_b_label",
required_unless_present = "user_holding_b_label"
)]
user_holding_b: Option<String>,
/// User holding B account label (alternative to --user-holding-b).
#[arg(long, conflicts_with = "user_holding_b")]
user_holding_b_label: Option<String>,
/// `user_holding_lp` - valid 32 byte base58 string with privacy prefix.
#[arg(
long,
conflicts_with = "user_holding_lp_label",
required_unless_present = "user_holding_lp_label"
)]
user_holding_lp: Option<String>,
/// User holding LP account label (alternative to --user-holding-lp).
#[arg(long, conflicts_with = "user_holding_lp")]
user_holding_lp_label: Option<String>,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
user_holding_a: CliAccountMention,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
user_holding_b: CliAccountMention,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
user_holding_lp: CliAccountMention,
#[arg(long)]
balance_a: u128,
#[arg(long)]
@ -59,33 +38,19 @@ pub enum AmmProgramAgnosticSubcommand {
///
/// Only public execution allowed.
SwapExactInput {
/// `user_holding_a` - valid 32 byte base58 string with privacy prefix.
#[arg(
long,
conflicts_with = "user_holding_a_label",
required_unless_present = "user_holding_a_label"
)]
user_holding_a: Option<String>,
/// User holding A account label (alternative to --user-holding-a).
#[arg(long, conflicts_with = "user_holding_a")]
user_holding_a_label: Option<String>,
/// `user_holding_b` - valid 32 byte base58 string with privacy prefix.
#[arg(
long,
conflicts_with = "user_holding_b_label",
required_unless_present = "user_holding_b_label"
)]
user_holding_b: Option<String>,
/// User holding B account label (alternative to --user-holding-b).
#[arg(long, conflicts_with = "user_holding_b")]
user_holding_b_label: Option<String>,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
user_holding_a: CliAccountMention,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
user_holding_b: CliAccountMention,
#[arg(long)]
amount_in: u128,
#[arg(long)]
min_amount_out: u128,
/// `token_definition` - valid 32 byte base58 string WITHOUT privacy prefix.
#[arg(long)]
token_definition: String,
token_definition: AccountId,
},
/// Swap specifying exact output amount.
///
@ -93,19 +58,19 @@ pub enum AmmProgramAgnosticSubcommand {
///
/// Only public execution allowed.
SwapExactOutput {
/// `user_holding_a` - valid 32 byte base58 string with privacy prefix.
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
user_holding_a: String,
/// `user_holding_b` - valid 32 byte base58 string with privacy prefix.
user_holding_a: CliAccountMention,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
user_holding_b: String,
user_holding_b: CliAccountMention,
#[arg(long)]
exact_amount_out: u128,
#[arg(long)]
max_amount_in: u128,
/// `token_definition` - valid 32 byte base58 string WITHOUT privacy prefix.
#[arg(long)]
token_definition: String,
token_definition: AccountId,
},
/// Add liquidity.
///
@ -113,36 +78,15 @@ pub enum AmmProgramAgnosticSubcommand {
///
/// Only public execution allowed.
AddLiquidity {
/// `user_holding_a` - valid 32 byte base58 string with privacy prefix.
#[arg(
long,
conflicts_with = "user_holding_a_label",
required_unless_present = "user_holding_a_label"
)]
user_holding_a: Option<String>,
/// User holding A account label (alternative to --user-holding-a).
#[arg(long, conflicts_with = "user_holding_a")]
user_holding_a_label: Option<String>,
/// `user_holding_b` - valid 32 byte base58 string with privacy prefix.
#[arg(
long,
conflicts_with = "user_holding_b_label",
required_unless_present = "user_holding_b_label"
)]
user_holding_b: Option<String>,
/// User holding B account label (alternative to --user-holding-b).
#[arg(long, conflicts_with = "user_holding_b")]
user_holding_b_label: Option<String>,
/// `user_holding_lp` - valid 32 byte base58 string with privacy prefix.
#[arg(
long,
conflicts_with = "user_holding_lp_label",
required_unless_present = "user_holding_lp_label"
)]
user_holding_lp: Option<String>,
/// User holding LP account label (alternative to --user-holding-lp).
#[arg(long, conflicts_with = "user_holding_lp")]
user_holding_lp_label: Option<String>,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
user_holding_a: CliAccountMention,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
user_holding_b: CliAccountMention,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
user_holding_lp: CliAccountMention,
#[arg(long)]
min_amount_lp: u128,
#[arg(long)]
@ -156,36 +100,15 @@ pub enum AmmProgramAgnosticSubcommand {
///
/// Only public execution allowed.
RemoveLiquidity {
/// `user_holding_a` - valid 32 byte base58 string with privacy prefix.
#[arg(
long,
conflicts_with = "user_holding_a_label",
required_unless_present = "user_holding_a_label"
)]
user_holding_a: Option<String>,
/// User holding A account label (alternative to --user-holding-a).
#[arg(long, conflicts_with = "user_holding_a")]
user_holding_a_label: Option<String>,
/// `user_holding_b` - valid 32 byte base58 string with privacy prefix.
#[arg(
long,
conflicts_with = "user_holding_b_label",
required_unless_present = "user_holding_b_label"
)]
user_holding_b: Option<String>,
/// User holding B account label (alternative to --user-holding-b).
#[arg(long, conflicts_with = "user_holding_b")]
user_holding_b_label: Option<String>,
/// `user_holding_lp` - valid 32 byte base58 string with privacy prefix.
#[arg(
long,
conflicts_with = "user_holding_lp_label",
required_unless_present = "user_holding_lp_label"
)]
user_holding_lp: Option<String>,
/// User holding LP account label (alternative to --user-holding-lp).
#[arg(long, conflicts_with = "user_holding_lp")]
user_holding_lp_label: Option<String>,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
user_holding_a: CliAccountMention,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
user_holding_b: CliAccountMention,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
user_holding_lp: CliAccountMention,
#[arg(long)]
balance_lp: u128,
#[arg(long)]
@ -203,52 +126,19 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
match self {
Self::New {
user_holding_a,
user_holding_a_label,
user_holding_b,
user_holding_b_label,
user_holding_lp,
user_holding_lp_label,
balance_a,
balance_b,
} => {
let user_holding_a = resolve_id_or_label(
user_holding_a,
user_holding_a_label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?;
let user_holding_b = resolve_id_or_label(
user_holding_b,
user_holding_b_label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?;
let user_holding_lp = resolve_id_or_label(
user_holding_lp,
user_holding_lp_label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?;
let (user_holding_a, user_holding_a_privacy) =
parse_addr_with_privacy_prefix(&user_holding_a)?;
let (user_holding_b, user_holding_b_privacy) =
parse_addr_with_privacy_prefix(&user_holding_b)?;
let (user_holding_lp, user_holding_lp_privacy) =
parse_addr_with_privacy_prefix(&user_holding_lp)?;
let user_holding_a: AccountId = user_holding_a.parse()?;
let user_holding_b: AccountId = user_holding_b.parse()?;
let user_holding_lp: AccountId = user_holding_lp.parse()?;
match (
user_holding_a_privacy,
user_holding_b_privacy,
user_holding_lp_privacy,
) {
let user_holding_a = user_holding_a.resolve(wallet_core.storage())?;
let user_holding_b = user_holding_b.resolve(wallet_core.storage())?;
let user_holding_lp = user_holding_lp.resolve(wallet_core.storage())?;
match (user_holding_a, user_holding_b, user_holding_lp) {
(
AccountPrivacyKind::Public,
AccountPrivacyKind::Public,
AccountPrivacyKind::Public,
AccountIdWithPrivacy::Public(user_holding_a),
AccountIdWithPrivacy::Public(user_holding_b),
AccountIdWithPrivacy::Public(user_holding_lp),
) => {
Amm(wallet_core)
.send_new_definition(
@ -270,42 +160,25 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
}
Self::SwapExactInput {
user_holding_a,
user_holding_a_label,
user_holding_b,
user_holding_b_label,
amount_in,
min_amount_out,
token_definition,
} => {
let user_holding_a = resolve_id_or_label(
user_holding_a,
user_holding_a_label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?;
let user_holding_b = resolve_id_or_label(
user_holding_b,
user_holding_b_label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?;
let (user_holding_a, user_holding_a_privacy) =
parse_addr_with_privacy_prefix(&user_holding_a)?;
let (user_holding_b, user_holding_b_privacy) =
parse_addr_with_privacy_prefix(&user_holding_b)?;
let user_holding_a: AccountId = user_holding_a.parse()?;
let user_holding_b: AccountId = user_holding_b.parse()?;
match (user_holding_a_privacy, user_holding_b_privacy) {
(AccountPrivacyKind::Public, AccountPrivacyKind::Public) => {
let user_holding_a = user_holding_a.resolve(wallet_core.storage())?;
let user_holding_b = user_holding_b.resolve(wallet_core.storage())?;
match (user_holding_a, user_holding_b) {
(
AccountIdWithPrivacy::Public(user_holding_a),
AccountIdWithPrivacy::Public(user_holding_b),
) => {
Amm(wallet_core)
.send_swap_exact_input(
user_holding_a,
user_holding_b,
amount_in,
min_amount_out,
token_definition.parse()?,
token_definition,
)
.await?;
@ -324,23 +197,20 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
max_amount_in,
token_definition,
} => {
let (user_holding_a, user_holding_a_privacy) =
parse_addr_with_privacy_prefix(&user_holding_a)?;
let (user_holding_b, user_holding_b_privacy) =
parse_addr_with_privacy_prefix(&user_holding_b)?;
let user_holding_a: AccountId = user_holding_a.parse()?;
let user_holding_b: AccountId = user_holding_b.parse()?;
match (user_holding_a_privacy, user_holding_b_privacy) {
(AccountPrivacyKind::Public, AccountPrivacyKind::Public) => {
let user_holding_a = user_holding_a.resolve(wallet_core.storage())?;
let user_holding_b = user_holding_b.resolve(wallet_core.storage())?;
match (user_holding_a, user_holding_b) {
(
AccountIdWithPrivacy::Public(user_holding_a),
AccountIdWithPrivacy::Public(user_holding_b),
) => {
Amm(wallet_core)
.send_swap_exact_output(
user_holding_a,
user_holding_b,
exact_amount_out,
max_amount_in,
token_definition.parse()?,
token_definition,
)
.await?;
@ -354,53 +224,20 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
}
Self::AddLiquidity {
user_holding_a,
user_holding_a_label,
user_holding_b,
user_holding_b_label,
user_holding_lp,
user_holding_lp_label,
min_amount_lp,
max_amount_a,
max_amount_b,
} => {
let user_holding_a = resolve_id_or_label(
user_holding_a,
user_holding_a_label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?;
let user_holding_b = resolve_id_or_label(
user_holding_b,
user_holding_b_label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?;
let user_holding_lp = resolve_id_or_label(
user_holding_lp,
user_holding_lp_label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?;
let (user_holding_a, user_holding_a_privacy) =
parse_addr_with_privacy_prefix(&user_holding_a)?;
let (user_holding_b, user_holding_b_privacy) =
parse_addr_with_privacy_prefix(&user_holding_b)?;
let (user_holding_lp, user_holding_lp_privacy) =
parse_addr_with_privacy_prefix(&user_holding_lp)?;
let user_holding_a: AccountId = user_holding_a.parse()?;
let user_holding_b: AccountId = user_holding_b.parse()?;
let user_holding_lp: AccountId = user_holding_lp.parse()?;
match (
user_holding_a_privacy,
user_holding_b_privacy,
user_holding_lp_privacy,
) {
let user_holding_a = user_holding_a.resolve(wallet_core.storage())?;
let user_holding_b = user_holding_b.resolve(wallet_core.storage())?;
let user_holding_lp = user_holding_lp.resolve(wallet_core.storage())?;
match (user_holding_a, user_holding_b, user_holding_lp) {
(
AccountPrivacyKind::Public,
AccountPrivacyKind::Public,
AccountPrivacyKind::Public,
AccountIdWithPrivacy::Public(user_holding_a),
AccountIdWithPrivacy::Public(user_holding_b),
AccountIdWithPrivacy::Public(user_holding_lp),
) => {
Amm(wallet_core)
.send_add_liquidity(
@ -423,53 +260,20 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
}
Self::RemoveLiquidity {
user_holding_a,
user_holding_a_label,
user_holding_b,
user_holding_b_label,
user_holding_lp,
user_holding_lp_label,
balance_lp,
min_amount_a,
min_amount_b,
} => {
let user_holding_a = resolve_id_or_label(
user_holding_a,
user_holding_a_label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?;
let user_holding_b = resolve_id_or_label(
user_holding_b,
user_holding_b_label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?;
let user_holding_lp = resolve_id_or_label(
user_holding_lp,
user_holding_lp_label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?;
let (user_holding_a, user_holding_a_privacy) =
parse_addr_with_privacy_prefix(&user_holding_a)?;
let (user_holding_b, user_holding_b_privacy) =
parse_addr_with_privacy_prefix(&user_holding_b)?;
let (user_holding_lp, user_holding_lp_privacy) =
parse_addr_with_privacy_prefix(&user_holding_lp)?;
let user_holding_a: AccountId = user_holding_a.parse()?;
let user_holding_b: AccountId = user_holding_b.parse()?;
let user_holding_lp: AccountId = user_holding_lp.parse()?;
match (
user_holding_a_privacy,
user_holding_b_privacy,
user_holding_lp_privacy,
) {
let user_holding_a = user_holding_a.resolve(wallet_core.storage())?;
let user_holding_b = user_holding_b.resolve(wallet_core.storage())?;
let user_holding_lp = user_holding_lp.resolve(wallet_core.storage())?;
match (user_holding_a, user_holding_b, user_holding_lp) {
(
AccountPrivacyKind::Public,
AccountPrivacyKind::Public,
AccountPrivacyKind::Public,
AccountIdWithPrivacy::Public(user_holding_a),
AccountIdWithPrivacy::Public(user_holding_b),
AccountIdWithPrivacy::Public(user_holding_lp),
) => {
Amm(wallet_core)
.send_remove_liquidity(

View File

@ -7,8 +7,8 @@ use token_core::TokenHolding;
use crate::{
AccDecodeData::Decode,
WalletCore,
cli::{SubcommandReturnValue, WalletSubcommand},
helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix},
account::AccountIdWithPrivacy,
cli::{CliAccountMention, SubcommandReturnValue, WalletSubcommand},
program_facades::ata::Ata,
};
@ -19,42 +19,42 @@ pub enum AtaSubcommand {
Address {
/// Owner account - valid 32 byte base58 string (no privacy prefix).
#[arg(long)]
owner: String,
owner: AccountId,
/// Token definition account - valid 32 byte base58 string (no privacy prefix).
#[arg(long)]
token_definition: String,
token_definition: AccountId,
},
/// Create (or idempotently no-op) the Associated Token Account.
Create {
/// Owner account - valid 32 byte base58 string with privacy prefix.
/// Owner account mention - account id with privacy prefix or label.
#[arg(long)]
owner: String,
owner: CliAccountMention,
/// Token definition account - valid 32 byte base58 string WITHOUT privacy prefix.
#[arg(long)]
token_definition: String,
token_definition: AccountId,
},
/// Send tokens from owner's ATA to a recipient token holding account.
Send {
/// Sender account - valid 32 byte base58 string with privacy prefix.
/// Sender account mention - account id with privacy prefix or label.
#[arg(long)]
from: String,
from: CliAccountMention,
/// Token definition account - valid 32 byte base58 string WITHOUT privacy prefix.
#[arg(long)]
token_definition: String,
token_definition: AccountId,
/// Recipient account - valid 32 byte base58 string WITHOUT privacy prefix.
#[arg(long)]
to: String,
to: AccountId,
#[arg(long)]
amount: u128,
},
/// Burn tokens from holder's ATA.
Burn {
/// Holder account - valid 32 byte base58 string with privacy prefix.
/// Holder account mention - account id with privacy prefix or label.
#[arg(long)]
holder: String,
holder: CliAccountMention,
/// Token definition account - valid 32 byte base58 string WITHOUT privacy prefix.
#[arg(long)]
token_definition: String,
token_definition: AccountId,
#[arg(long)]
amount: u128,
},
@ -62,10 +62,10 @@ pub enum AtaSubcommand {
List {
/// Owner account - valid 32 byte base58 string (no privacy prefix).
#[arg(long)]
owner: String,
owner: AccountId,
/// Token definition accounts - valid 32 byte base58 strings (no privacy prefix).
#[arg(long, num_args = 1..)]
token_definition: Vec<String>,
token_definition: Vec<AccountId>,
},
}
@ -79,12 +79,10 @@ impl WalletSubcommand for AtaSubcommand {
owner,
token_definition,
} => {
let owner_id: AccountId = owner.parse()?;
let definition_id: AccountId = token_definition.parse()?;
let ata_program_id = Program::ata().id();
let ata_id = ata_core::get_associated_token_account_id(
&ata_program_id,
&ata_core::compute_ata_seed(owner_id, definition_id),
&ata_core::compute_ata_seed(owner, token_definition),
);
println!("{ata_id}");
Ok(SubcommandReturnValue::Empty)
@ -93,18 +91,17 @@ impl WalletSubcommand for AtaSubcommand {
owner,
token_definition,
} => {
let (owner_str, owner_privacy) = parse_addr_with_privacy_prefix(&owner)?;
let owner_id: AccountId = owner_str.parse()?;
let definition_id: AccountId = token_definition.parse()?;
let owner = owner.resolve(wallet_core.storage())?;
let definition_id = token_definition;
match owner_privacy {
AccountPrivacyKind::Public => {
match owner {
AccountIdWithPrivacy::Public(owner_id) => {
Ata(wallet_core)
.send_create(owner_id, definition_id)
.await?;
Ok(SubcommandReturnValue::Empty)
}
AccountPrivacyKind::Private => {
AccountIdWithPrivacy::Private(owner_id) => {
let (tx_hash, secret) = Ata(wallet_core)
.send_create_private_owner(owner_id, definition_id)
.await?;
@ -119,7 +116,7 @@ impl WalletSubcommand for AtaSubcommand {
)?;
}
wallet_core.store_persistent_data().await?;
wallet_core.store_persistent_data()?;
Ok(SubcommandReturnValue::Empty)
}
}
@ -130,19 +127,18 @@ impl WalletSubcommand for AtaSubcommand {
to,
amount,
} => {
let (from_str, from_privacy) = parse_addr_with_privacy_prefix(&from)?;
let from_id: AccountId = from_str.parse()?;
let definition_id: AccountId = token_definition.parse()?;
let to_id: AccountId = to.parse()?;
let from = from.resolve(wallet_core.storage())?;
let definition_id = token_definition;
let to_id = to;
match from_privacy {
AccountPrivacyKind::Public => {
match from {
AccountIdWithPrivacy::Public(from_id) => {
Ata(wallet_core)
.send_transfer(from_id, definition_id, to_id, amount)
.await?;
Ok(SubcommandReturnValue::Empty)
}
AccountPrivacyKind::Private => {
AccountIdWithPrivacy::Private(from_id) => {
let (tx_hash, secret) = Ata(wallet_core)
.send_transfer_private_owner(from_id, definition_id, to_id, amount)
.await?;
@ -157,7 +153,7 @@ impl WalletSubcommand for AtaSubcommand {
)?;
}
wallet_core.store_persistent_data().await?;
wallet_core.store_persistent_data()?;
Ok(SubcommandReturnValue::Empty)
}
}
@ -167,18 +163,17 @@ impl WalletSubcommand for AtaSubcommand {
token_definition,
amount,
} => {
let (holder_str, holder_privacy) = parse_addr_with_privacy_prefix(&holder)?;
let holder_id: AccountId = holder_str.parse()?;
let definition_id: AccountId = token_definition.parse()?;
let holder = holder.resolve(wallet_core.storage())?;
let definition_id = token_definition;
match holder_privacy {
AccountPrivacyKind::Public => {
match holder {
AccountIdWithPrivacy::Public(holder_id) => {
Ata(wallet_core)
.send_burn(holder_id, definition_id, amount)
.await?;
Ok(SubcommandReturnValue::Empty)
}
AccountPrivacyKind::Private => {
AccountIdWithPrivacy::Private(holder_id) => {
let (tx_hash, secret) = Ata(wallet_core)
.send_burn_private_owner(holder_id, definition_id, amount)
.await?;
@ -193,7 +188,7 @@ impl WalletSubcommand for AtaSubcommand {
)?;
}
wallet_core.store_persistent_data().await?;
wallet_core.store_persistent_data()?;
Ok(SubcommandReturnValue::Empty)
}
}
@ -202,32 +197,26 @@ impl WalletSubcommand for AtaSubcommand {
owner,
token_definition,
} => {
let owner_id: AccountId = owner.parse()?;
let ata_program_id = Program::ata().id();
for def in &token_definition {
let definition_id: AccountId = def.parse()?;
let ata_id = ata_core::get_associated_token_account_id(
&ata_program_id,
&ata_core::compute_ata_seed(owner_id, definition_id),
&ata_core::compute_ata_seed(owner, *def),
);
let account = wallet_core.get_account_public(ata_id).await?;
if account == Account::default() {
println!("No ATA for definition {definition_id}");
println!("No ATA for definition {def}");
} else {
let holding = TokenHolding::try_from(&account.data)?;
match holding {
TokenHolding::Fungible { balance, .. } => {
println!(
"ATA {ata_id} (definition {definition_id}): balance {balance}"
);
println!("ATA {ata_id} (definition {def}): balance {balance}");
}
TokenHolding::NftMaster { .. }
| TokenHolding::NftPrintedCopy { .. } => {
println!(
"ATA {ata_id} (definition {definition_id}): unsupported token type"
);
println!("ATA {ata_id} (definition {def}): unsupported token type");
}
}
}

View File

@ -6,11 +6,8 @@ use nssa::AccountId;
use crate::{
AccDecodeData::Decode,
WalletCore,
cli::{SubcommandReturnValue, WalletSubcommand},
helperfunctions::{
AccountPrivacyKind, parse_addr_with_privacy_prefix, resolve_account_label,
resolve_id_or_label,
},
account::AccountIdWithPrivacy,
cli::{CliAccountMention, SubcommandReturnValue, WalletSubcommand},
program_facades::native_token_transfer::NativeTokenTransfer,
};
@ -19,16 +16,9 @@ use crate::{
pub enum AuthTransferSubcommand {
/// Initialize account under authenticated transfer program.
Init {
/// `account_id` - valid 32 byte base58 string with privacy prefix.
#[arg(
long,
conflicts_with = "account_label",
required_unless_present = "account_label"
)]
account_id: Option<String>,
/// Account label (alternative to --account-id).
#[arg(long, conflicts_with = "account_id")]
account_label: Option<String>,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
account_id: CliAccountMention,
},
/// Send native tokens from one account to another with variable privacy.
///
@ -37,22 +27,12 @@ pub enum AuthTransferSubcommand {
///
/// First is used for owned accounts, second otherwise.
Send {
/// from - valid 32 byte base58 string with privacy prefix.
#[arg(
long,
conflicts_with = "from_label",
required_unless_present = "from_label"
)]
from: Option<String>,
/// From account label (alternative to --from).
#[arg(long, conflicts_with = "from")]
from_label: Option<String>,
/// to - valid 32 byte base58 string with privacy prefix.
#[arg(long, conflicts_with = "to_label")]
to: Option<String>,
/// To account label (alternative to --to).
#[arg(long, conflicts_with = "to")]
to_label: Option<String>,
/// 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: Option<CliAccountMention>,
/// `to_npk` - valid 32 byte hex string.
#[arg(long)]
to_npk: Option<String>,
@ -71,22 +51,10 @@ impl WalletSubcommand for AuthTransferSubcommand {
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
Self::Init {
account_id,
account_label,
} => {
let resolved = resolve_id_or_label(
account_id,
account_label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?;
let (account_id, addr_privacy) = parse_addr_with_privacy_prefix(&resolved)?;
match addr_privacy {
AccountPrivacyKind::Public => {
let account_id = account_id.parse()?;
Self::Init { account_id } => {
let resolved = account_id.resolve(wallet_core.storage())?;
match resolved {
AccountIdWithPrivacy::Public(account_id) => {
let tx_hash = NativeTokenTransfer(wallet_core)
.register_account(account_id)
.await?;
@ -97,11 +65,9 @@ impl WalletSubcommand for AuthTransferSubcommand {
println!("Transaction data is {transfer_tx:?}");
wallet_core.store_persistent_data().await?;
wallet_core.store_persistent_data()?;
}
AccountPrivacyKind::Private => {
let account_id = account_id.parse()?;
AccountIdWithPrivacy::Private(account_id) => {
let (tx_hash, secret) = NativeTokenTransfer(wallet_core)
.register_account_private(account_id)
.await?;
@ -119,7 +85,7 @@ impl WalletSubcommand for AuthTransferSubcommand {
)?;
}
wallet_core.store_persistent_data().await?;
wallet_core.store_persistent_data()?;
}
}
@ -127,30 +93,15 @@ impl WalletSubcommand for AuthTransferSubcommand {
}
Self::Send {
from,
from_label,
to,
to_label,
to_npk,
to_vpk,
amount,
} => {
let from = resolve_id_or_label(
from,
from_label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?;
let to = match (to, to_label) {
(v, None) => v,
(None, Some(label)) => Some(resolve_account_label(
&label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?),
(Some(_), Some(_)) => {
anyhow::bail!("Provide only one of --to or --to-label")
}
};
let from = from.resolve(wallet_core.storage())?;
let to = to
.map(|account_mention| account_mention.resolve(wallet_core.storage()))
.transpose()?;
let underlying_subcommand = match (to, to_npk, to_vpk) {
(None, None, None) => {
anyhow::bail!(
@ -165,67 +116,55 @@ impl WalletSubcommand for AuthTransferSubcommand {
(_, Some(_), None) | (_, None, Some(_)) => {
anyhow::bail!("List of public keys is uncomplete");
}
(Some(to), None, None) => {
let (from, from_privacy) = parse_addr_with_privacy_prefix(&from)?;
let (to, to_privacy) = parse_addr_with_privacy_prefix(&to)?;
match (from_privacy, to_privacy) {
(AccountPrivacyKind::Public, AccountPrivacyKind::Public) => {
NativeTokenTransferProgramSubcommand::Public { from, to, amount }
}
(AccountPrivacyKind::Private, AccountPrivacyKind::Private) => {
NativeTokenTransferProgramSubcommand::Private(
NativeTokenTransferProgramSubcommandPrivate::PrivateOwned {
from,
to,
amount,
},
)
}
(AccountPrivacyKind::Private, AccountPrivacyKind::Public) => {
NativeTokenTransferProgramSubcommand::Deshielded {
(Some(to), None, None) => match (from, to) {
(AccountIdWithPrivacy::Public(from), AccountIdWithPrivacy::Public(to)) => {
NativeTokenTransferProgramSubcommand::Public { from, to, amount }
}
(
AccountIdWithPrivacy::Private(from),
AccountIdWithPrivacy::Private(to),
) => NativeTokenTransferProgramSubcommand::Private(
NativeTokenTransferProgramSubcommandPrivate::PrivateOwned {
from,
to,
amount,
},
),
(AccountIdWithPrivacy::Private(from), AccountIdWithPrivacy::Public(to)) => {
NativeTokenTransferProgramSubcommand::Deshielded { from, to, amount }
}
(AccountIdWithPrivacy::Public(from), AccountIdWithPrivacy::Private(to)) => {
NativeTokenTransferProgramSubcommand::Shielded(
NativeTokenTransferProgramSubcommandShielded::ShieldedOwned {
from,
to,
amount,
}
}
(AccountPrivacyKind::Public, AccountPrivacyKind::Private) => {
NativeTokenTransferProgramSubcommand::Shielded(
NativeTokenTransferProgramSubcommandShielded::ShieldedOwned {
from,
to,
amount,
},
)
}
},
)
}
}
(None, Some(to_npk), Some(to_vpk)) => {
let (from, from_privacy) = parse_addr_with_privacy_prefix(&from)?;
match from_privacy {
AccountPrivacyKind::Private => {
NativeTokenTransferProgramSubcommand::Private(
NativeTokenTransferProgramSubcommandPrivate::PrivateForeign {
from,
to_npk,
to_vpk,
amount,
},
)
}
AccountPrivacyKind::Public => {
NativeTokenTransferProgramSubcommand::Shielded(
NativeTokenTransferProgramSubcommandShielded::ShieldedForeign {
from,
to_npk,
to_vpk,
amount,
},
)
}
},
(None, Some(to_npk), Some(to_vpk)) => match from {
AccountIdWithPrivacy::Private(from) => {
NativeTokenTransferProgramSubcommand::Private(
NativeTokenTransferProgramSubcommandPrivate::PrivateForeign {
from,
to_npk,
to_vpk,
amount,
},
)
}
}
AccountIdWithPrivacy::Public(from) => {
NativeTokenTransferProgramSubcommand::Shielded(
NativeTokenTransferProgramSubcommandShielded::ShieldedForeign {
from,
to_npk,
to_vpk,
amount,
},
)
}
},
};
underlying_subcommand.handle_subcommand(wallet_core).await
@ -243,10 +182,10 @@ pub enum NativeTokenTransferProgramSubcommand {
Public {
/// from - valid 32 byte hex string.
#[arg(long)]
from: String,
from: AccountId,
/// to - valid 32 byte hex string.
#[arg(long)]
to: String,
to: AccountId,
/// amount - amount of balance to move.
#[arg(long)]
amount: u128,
@ -260,10 +199,10 @@ pub enum NativeTokenTransferProgramSubcommand {
Deshielded {
/// from - valid 32 byte hex string.
#[arg(long)]
from: String,
from: AccountId,
/// to - valid 32 byte hex string.
#[arg(long)]
to: String,
to: AccountId,
/// amount - amount of balance to move.
#[arg(long)]
amount: u128,
@ -283,10 +222,10 @@ pub enum NativeTokenTransferProgramSubcommandShielded {
ShieldedOwned {
/// from - valid 32 byte hex string.
#[arg(long)]
from: String,
from: AccountId,
/// to - valid 32 byte hex string.
#[arg(long)]
to: String,
to: AccountId,
/// amount - amount of balance to move.
#[arg(long)]
amount: u128,
@ -297,7 +236,7 @@ pub enum NativeTokenTransferProgramSubcommandShielded {
ShieldedForeign {
/// from - valid 32 byte hex string.
#[arg(long)]
from: String,
from: AccountId,
/// `to_npk` - valid 32 byte hex string.
#[arg(long)]
to_npk: String,
@ -320,10 +259,10 @@ pub enum NativeTokenTransferProgramSubcommandPrivate {
PrivateOwned {
/// from - valid 32 byte hex string.
#[arg(long)]
from: String,
from: AccountId,
/// to - valid 32 byte hex string.
#[arg(long)]
to: String,
to: AccountId,
/// amount - amount of balance to move.
#[arg(long)]
amount: u128,
@ -334,7 +273,7 @@ pub enum NativeTokenTransferProgramSubcommandPrivate {
PrivateForeign {
/// from - valid 32 byte hex string.
#[arg(long)]
from: String,
from: AccountId,
/// `to_npk` - valid 32 byte hex string.
#[arg(long)]
to_npk: String,
@ -354,9 +293,6 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate {
) -> Result<SubcommandReturnValue> {
match self {
Self::PrivateOwned { from, to, amount } => {
let from: AccountId = from.parse().unwrap();
let to: AccountId = to.parse().unwrap();
let (tx_hash, [secret_from, secret_to]) = NativeTokenTransfer(wallet_core)
.send_private_transfer_to_owned_account(from, to, amount)
.await?;
@ -374,7 +310,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate {
)?;
}
wallet_core.store_persistent_data().await?;
wallet_core.store_persistent_data()?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -384,7 +320,6 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate {
to_vpk,
amount,
} => {
let from: AccountId = from.parse().unwrap();
let to_npk_res = hex::decode(to_npk)?;
let mut to_npk = [0; 32];
to_npk.copy_from_slice(&to_npk_res);
@ -413,7 +348,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate {
)?;
}
wallet_core.store_persistent_data().await?;
wallet_core.store_persistent_data()?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -428,9 +363,6 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
) -> Result<SubcommandReturnValue> {
match self {
Self::ShieldedOwned { from, to, amount } => {
let from: AccountId = from.parse().unwrap();
let to: AccountId = to.parse().unwrap();
let (tx_hash, secret) = NativeTokenTransfer(wallet_core)
.send_shielded_transfer(from, to, amount)
.await?;
@ -448,7 +380,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
)?;
}
wallet_core.store_persistent_data().await?;
wallet_core.store_persistent_data()?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -458,8 +390,6 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
to_vpk,
amount,
} => {
let from: AccountId = from.parse().unwrap();
let to_npk_res = hex::decode(to_npk)?;
let mut to_npk = [0; 32];
to_npk.copy_from_slice(&to_npk_res);
@ -477,7 +407,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
println!("Transaction hash is {tx_hash}");
wallet_core.store_persistent_data().await?;
wallet_core.store_persistent_data()?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -498,9 +428,6 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand {
shielded_subcommand.handle_subcommand(wallet_core).await
}
Self::Deshielded { from, to, amount } => {
let from: AccountId = from.parse().unwrap();
let to: AccountId = to.parse().unwrap();
let (tx_hash, secret) = NativeTokenTransfer(wallet_core)
.send_deshielded_transfer(from, to, amount)
.await?;
@ -518,14 +445,11 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand {
)?;
}
wallet_core.store_persistent_data().await?;
wallet_core.store_persistent_data()?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
Self::Public { from, to, amount } => {
let from: AccountId = from.parse().unwrap();
let to: AccountId = to.parse().unwrap();
let tx_hash = NativeTokenTransfer(wallet_core)
.send_public_transfer(from, to, amount)
.await?;
@ -536,7 +460,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand {
println!("Transaction data is {transfer_tx:?}");
wallet_core.store_persistent_data().await?;
wallet_core.store_persistent_data()?;
Ok(SubcommandReturnValue::Empty)
}

View File

@ -6,8 +6,8 @@ use nssa::{Account, AccountId};
use crate::{
AccDecodeData::Decode,
WalletCore,
cli::{SubcommandReturnValue, WalletSubcommand},
helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix, resolve_id_or_label},
account::AccountIdWithPrivacy,
cli::{CliAccountMention, SubcommandReturnValue, WalletSubcommand},
program_facades::pinata::Pinata,
};
@ -16,16 +16,9 @@ use crate::{
pub enum PinataProgramAgnosticSubcommand {
/// Claim pinata.
Claim {
/// to - valid 32 byte base58 string with privacy prefix.
#[arg(
long,
conflicts_with = "to_label",
required_unless_present = "to_label"
)]
to: Option<String>,
/// To account label (alternative to --to).
#[arg(long, conflicts_with = "to")]
to_label: Option<String>,
/// Either 32 byte base58 account id string with privacy prefix or a label.
#[arg(long)]
to: CliAccountMention,
},
}
@ -35,25 +28,18 @@ impl WalletSubcommand for PinataProgramAgnosticSubcommand {
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
let underlying_subcommand = match self {
Self::Claim { to, to_label } => {
let to = resolve_id_or_label(
to,
to_label,
&wallet_core.storage.labels,
&wallet_core.storage.user_data,
)?;
let (to, to_addr_privacy) = parse_addr_with_privacy_prefix(&to)?;
match to_addr_privacy {
AccountPrivacyKind::Public => {
Self::Claim { to } => {
let to = to.resolve(wallet_core.storage())?;
match to {
AccountIdWithPrivacy::Public(to) => {
PinataProgramSubcommand::Public(PinataProgramSubcommandPublic::Claim {
pinata_account_id: PINATA_BASE58.to_owned(),
pinata_account_id: PINATA_BASE58.parse()?,
winner_account_id: to,
})
}
AccountPrivacyKind::Private => PinataProgramSubcommand::Private(
AccountIdWithPrivacy::Private(to) => PinataProgramSubcommand::Private(
PinataProgramSubcommandPrivate::ClaimPrivateOwned {
pinata_account_id: PINATA_BASE58.to_owned(),
pinata_account_id: PINATA_BASE58.parse()?,
winner_account_id: to,
},
),
@ -84,10 +70,10 @@ pub enum PinataProgramSubcommandPublic {
Claim {
/// `pinata_account_id` - valid 32 byte hex string.
#[arg(long)]
pinata_account_id: String,
pinata_account_id: AccountId,
/// `winner_account_id` - valid 32 byte hex string.
#[arg(long)]
winner_account_id: String,
winner_account_id: AccountId,
},
}
@ -99,10 +85,10 @@ pub enum PinataProgramSubcommandPrivate {
ClaimPrivateOwned {
/// `pinata_account_id` - valid 32 byte hex string.
#[arg(long)]
pinata_account_id: String,
pinata_account_id: AccountId,
/// `winner_account_id` - valid 32 byte hex string.
#[arg(long)]
winner_account_id: String,
winner_account_id: AccountId,
},
}
@ -116,9 +102,6 @@ impl WalletSubcommand for PinataProgramSubcommandPublic {
pinata_account_id,
winner_account_id,
} => {
let pinata_account_id = pinata_account_id.parse()?;
let winner_account_id: AccountId = winner_account_id.parse()?;
ensure_public_recipient_initialized(wallet_core, winner_account_id).await?;
let solution = find_solution(wallet_core, pinata_account_id)
@ -151,9 +134,6 @@ impl WalletSubcommand for PinataProgramSubcommandPrivate {
pinata_account_id,
winner_account_id,
} => {
let pinata_account_id = pinata_account_id.parse()?;
let winner_account_id: AccountId = winner_account_id.parse()?;
ensure_private_owned_recipient_initialized(wallet_core, winner_account_id)?;
let solution = find_solution(wallet_core, pinata_account_id)
@ -179,7 +159,7 @@ impl WalletSubcommand for PinataProgramSubcommandPrivate {
)?;
}
wallet_core.store_persistent_data().await?;
wallet_core.store_persistent_data()?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}

File diff suppressed because it is too large Load Diff

View File

@ -1,169 +1,12 @@
use std::{
collections::HashMap,
io::{BufReader, Write as _},
path::Path,
time::Duration,
};
use std::{io::Write as _, path::Path, time::Duration};
use anyhow::{Context as _, Result};
use common::config::BasicAuth;
use humantime_serde;
use key_protocol::key_management::key_tree::{
chain_index::ChainIndex, keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic,
};
use log::warn;
use serde::{Deserialize, Serialize};
use testnet_initial_state::{
PrivateAccountPrivateInitialData, PublicAccountPrivateInitialData,
initial_priv_accounts_private_keys, initial_pub_accounts_private_keys,
};
use url::Url;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistentAccountDataPublic {
pub account_id: nssa::AccountId,
pub chain_index: ChainIndex,
pub data: ChildKeysPublic,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistentAccountDataPrivate {
pub account_id: nssa::AccountId,
pub chain_index: ChainIndex,
pub data: ChildKeysPrivate,
}
// Big difference in enum variants sizes
// however it is improbable, that we will have that much accounts, that it will substantialy affect
// memory
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum InitialAccountData {
Public(PublicAccountPrivateInitialData),
Private(Box<PrivateAccountPrivateInitialData>),
}
impl InitialAccountData {
#[must_use]
pub const fn account_id(&self) -> nssa::AccountId {
match &self {
Self::Public(acc) => acc.account_id,
Self::Private(acc) => acc.account_id,
}
}
pub(crate) fn create_initial_accounts_data() -> Vec<Self> {
let pub_data = initial_pub_accounts_private_keys();
let priv_data = initial_priv_accounts_private_keys();
pub_data
.into_iter()
.map(Into::into)
.chain(priv_data.into_iter().map(Into::into))
.collect()
}
}
// Big difference in enum variants sizes
// however it is improbable, that we will have that much accounts, that it will substantialy affect
// memory
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PersistentAccountData {
Public(PersistentAccountDataPublic),
Private(Box<PersistentAccountDataPrivate>),
Preconfigured(InitialAccountData),
}
/// A human-readable label for an account.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Label(String);
impl Label {
#[must_use]
pub const fn new(label: String) -> Self {
Self(label)
}
}
impl std::fmt::Display for Label {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistentStorage {
pub accounts: Vec<PersistentAccountData>,
pub last_synced_block: u64,
/// Account labels keyed by account ID string (e.g.,
/// "2rnKprXqWGWJTkDZKsQbFXa4ctKRbapsdoTKQFnaVGG8").
#[serde(default)]
pub labels: HashMap<String, Label>,
}
impl PersistentStorage {
pub fn from_path(path: &Path) -> Result<Self> {
#[expect(
clippy::wildcard_enum_match_arm,
reason = "We want to provide a specific error message for not found case"
)]
match std::fs::File::open(path) {
Ok(file) => {
let storage_content = BufReader::new(file);
Ok(serde_json::from_reader(storage_content)?)
}
Err(err) => match err.kind() {
std::io::ErrorKind::NotFound => {
anyhow::bail!("Not found, please setup roots from config command beforehand");
}
_ => {
anyhow::bail!("IO error {err:#?}");
}
},
}
}
}
impl PersistentAccountData {
#[must_use]
pub fn account_id(&self) -> nssa::AccountId {
match &self {
Self::Public(acc) => acc.account_id,
Self::Private(acc) => acc.account_id,
Self::Preconfigured(acc) => acc.account_id(),
}
}
}
impl From<PublicAccountPrivateInitialData> for InitialAccountData {
fn from(value: PublicAccountPrivateInitialData) -> Self {
Self::Public(value)
}
}
impl From<PrivateAccountPrivateInitialData> for InitialAccountData {
fn from(value: PrivateAccountPrivateInitialData) -> Self {
Self::Private(Box::new(value))
}
}
impl From<PersistentAccountDataPublic> for PersistentAccountData {
fn from(value: PersistentAccountDataPublic) -> Self {
Self::Public(value)
}
}
impl From<PersistentAccountDataPrivate> for PersistentAccountData {
fn from(value: PersistentAccountDataPrivate) -> Self {
Self::Private(Box::new(value))
}
}
impl From<InitialAccountData> for PersistentAccountData {
fn from(value: InitialAccountData) -> Self {
Self::Preconfigured(value)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GasConfig {
/// Gas spent per deploying one byte of data.
@ -199,8 +42,6 @@ pub struct WalletConfig {
/// Basic authentication credentials
#[serde(skip_serializing_if = "Option::is_none")]
pub basic_auth: Option<BasicAuth>,
#[serde(skip_serializing_if = "Option::is_none")]
pub initial_accounts: Option<Vec<InitialAccountData>>,
}
impl Default for WalletConfig {
@ -212,7 +53,6 @@ impl Default for WalletConfig {
seq_poll_max_retries: 5,
seq_block_poll_max_amount: 100,
basic_auth: None,
initial_accounts: None,
}
}
}
@ -263,7 +103,6 @@ impl WalletConfig {
seq_poll_max_retries,
seq_block_poll_max_amount,
basic_auth,
initial_accounts,
} = self;
let WalletConfigOverrides {
@ -273,7 +112,6 @@ impl WalletConfig {
seq_poll_max_retries: o_seq_poll_max_retries,
seq_block_poll_max_amount: o_seq_block_poll_max_amount,
basic_auth: o_basic_auth,
initial_accounts: o_initial_accounts,
} = overrides;
if let Some(v) = o_sequencer_addr {
@ -300,9 +138,5 @@ impl WalletConfig {
warn!("Overriding wallet config 'basic_auth' to {v:#?}");
*basic_auth = v;
}
if let Some(v) = o_initial_accounts {
warn!("Overriding wallet config 'initial_accounts' to {v:#?}");
*initial_accounts = v;
}
}
}

View File

@ -1,114 +1,10 @@
use std::{collections::HashMap, path::PathBuf, str::FromStr as _};
use std::{path::PathBuf, str::FromStr as _};
use anyhow::{Context as _, Result};
use base58::ToBase58 as _;
use key_protocol::key_protocol_core::NSSAUserData;
use nssa::Account;
use nssa_core::account::Nonce;
use rand::{RngCore as _, rngs::OsRng};
use serde::Serialize;
use testnet_initial_state::{PrivateAccountPrivateInitialData, PublicAccountPrivateInitialData};
use crate::{
HOME_DIR_ENV_VAR,
config::{
InitialAccountData, Label, PersistentAccountDataPrivate, PersistentAccountDataPublic,
PersistentStorage,
},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccountPrivacyKind {
Public,
Private,
}
/// Human-readable representation of an account.
#[derive(Serialize)]
pub(crate) struct HumanReadableAccount {
balance: u128,
program_owner: String,
data: String,
nonce: u128,
}
impl From<Account> for HumanReadableAccount {
fn from(account: Account) -> Self {
let program_owner = account
.program_owner
.iter()
.flat_map(|n| n.to_le_bytes())
.collect::<Vec<u8>>()
.to_base58();
let data = hex::encode(account.data);
Self {
balance: account.balance,
program_owner,
data,
nonce: account.nonce.0,
}
}
}
/// Resolve an account id-or-label pair to a `Privacy/id` string.
///
/// Exactly one of `id` or `label` must be `Some`. If `id` is provided it is
/// returned as-is; if `label` is provided it is resolved via
/// [`resolve_account_label`]. Any other combination returns an error.
pub fn resolve_id_or_label(
id: Option<String>,
label: Option<String>,
labels: &HashMap<String, Label>,
user_data: &NSSAUserData,
) -> Result<String> {
match (id, label) {
(Some(id), None) => Ok(id),
(None, Some(label)) => resolve_account_label(&label, labels, user_data),
_ => anyhow::bail!("provide exactly one of account id or account label"),
}
}
/// Resolve an account label to its full `Privacy/id` string representation.
///
/// Looks up the label in the labels map and determines whether the account is
/// public or private by checking the user data key trees.
pub fn resolve_account_label(
label: &str,
labels: &HashMap<String, Label>,
user_data: &NSSAUserData,
) -> Result<String> {
let account_id_str = labels
.iter()
.find(|(_, l)| l.to_string() == label)
.map(|(k, _)| k.clone())
.ok_or_else(|| anyhow::anyhow!("No account found with label '{label}'"))?;
let account_id: nssa::AccountId = account_id_str.parse()?;
let privacy = if user_data
.public_key_tree
.account_id_map
.contains_key(&account_id)
|| user_data
.default_pub_account_signing_keys
.contains_key(&account_id)
{
"Public"
} else if user_data
.private_key_tree
.account_id_map
.contains_key(&account_id)
|| user_data
.default_user_private_accounts
.contains_key(&account_id)
{
"Private"
} else {
anyhow::bail!("Account with label '{label}' not found in wallet");
};
Ok(format!("{privacy}/{account_id_str}"))
}
use crate::HOME_DIR_ENV_VAR;
/// Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed.
fn get_home_nssa_var() -> Result<PathBuf> {
@ -143,69 +39,6 @@ pub fn fetch_persistent_storage_path() -> Result<PathBuf> {
Ok(accs_path)
}
/// Produces data for storage.
#[must_use]
pub fn produce_data_for_storage(
user_data: &NSSAUserData,
last_synced_block: u64,
labels: HashMap<String, Label>,
) -> PersistentStorage {
let mut vec_for_storage = vec![];
for (account_id, key) in &user_data.public_key_tree.account_id_map {
if let Some(data) = user_data.public_key_tree.key_map.get(key) {
vec_for_storage.push(
PersistentAccountDataPublic {
account_id: *account_id,
chain_index: key.clone(),
data: data.clone(),
}
.into(),
);
}
}
for (account_id, key) in &user_data.private_key_tree.account_id_map {
if let Some(data) = user_data.private_key_tree.key_map.get(key) {
vec_for_storage.push(
PersistentAccountDataPrivate {
account_id: *account_id,
chain_index: key.clone(),
data: data.clone(),
}
.into(),
);
}
}
for (account_id, key) in &user_data.default_pub_account_signing_keys {
vec_for_storage.push(
InitialAccountData::Public(PublicAccountPrivateInitialData {
account_id: *account_id,
pub_sign_key: key.clone(),
})
.into(),
);
}
for (account_id, (key_chain, account)) in &user_data.default_user_private_accounts {
vec_for_storage.push(
InitialAccountData::Private(Box::new(PrivateAccountPrivateInitialData {
account_id: *account_id,
account: account.clone(),
key_chain: key_chain.clone(),
}))
.into(),
);
}
PersistentStorage {
accounts: vec_for_storage,
last_synced_block,
labels,
}
}
#[expect(dead_code, reason = "Maybe used later")]
pub(crate) fn produce_random_nonces(size: usize) -> Vec<Nonce> {
let mut result = vec![[0; 16]; size];
@ -217,42 +50,3 @@ pub(crate) fn produce_random_nonces(size: usize) -> Vec<Nonce> {
.map(|x| Nonce(u128::from_le_bytes(x)))
.collect()
}
pub(crate) fn parse_addr_with_privacy_prefix(
account_base58: &str,
) -> Result<(String, AccountPrivacyKind)> {
if account_base58.starts_with("Public/") {
Ok((
account_base58.strip_prefix("Public/").unwrap().to_owned(),
AccountPrivacyKind::Public,
))
} else if account_base58.starts_with("Private/") {
Ok((
account_base58.strip_prefix("Private/").unwrap().to_owned(),
AccountPrivacyKind::Private,
))
} else {
anyhow::bail!("Unsupported privacy kind, available variants is Public/ and Private/");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn addr_parse_with_privacy() {
let addr_base58 = "Public/BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy";
let (_, addr_kind) = parse_addr_with_privacy_prefix(addr_base58).unwrap();
assert_eq!(addr_kind, AccountPrivacyKind::Public);
let addr_base58 = "Private/BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy";
let (_, addr_kind) = parse_addr_with_privacy_prefix(addr_base58).unwrap();
assert_eq!(addr_kind, AccountPrivacyKind::Private);
let addr_base58 = "asdsada/BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy";
assert!(parse_addr_with_privacy_prefix(addr_base58).is_err());
}
}

View File

@ -1,6 +1,5 @@
#![expect(
clippy::print_stdout,
clippy::print_stderr,
reason = "This is a CLI application, printing to stdout and stderr is expected and convenient"
)]
#![expect(
@ -12,10 +11,9 @@ use std::path::PathBuf;
use anyhow::{Context as _, Result};
use bip39::Mnemonic;
use chain_storage::WalletChainStore;
use common::{HashType, transaction::NSSATransaction};
use config::WalletConfig;
use key_protocol::key_management::key_tree::{chain_index::ChainIndex, traits::KeyNode as _};
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use log::info;
use nssa::{
Account, AccountId, PrivacyPreservingTransaction,
@ -28,21 +26,19 @@ use nssa_core::{
};
pub use privacy_preserving_tx::PrivacyPreservingAccount;
use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder};
use storage::Storage;
use tokio::io::AsyncWriteExt as _;
use crate::{
config::{PersistentStorage, WalletConfigOverrides},
helperfunctions::produce_data_for_storage,
poller::TxPoller,
};
use crate::{account::AccountIdWithPrivacy, config::WalletConfigOverrides, poller::TxPoller};
pub mod chain_storage;
pub mod account;
pub mod cli;
pub mod config;
pub mod helperfunctions;
pub mod poller;
mod privacy_preserving_tx;
pub mod program_facades;
pub mod storage;
pub const HOME_DIR_ENV_VAR: &str = "NSSA_WALLET_HOME_DIR";
@ -73,11 +69,13 @@ pub enum ExecutionFailureKind {
pub struct WalletCore {
config_path: PathBuf,
config_overrides: Option<WalletConfigOverrides>,
storage: WalletChainStore,
config: WalletConfig,
storage: Storage,
storage_path: PathBuf,
poller: TxPoller,
pub sequencer_client: SequencerClient,
pub last_synced_block: u64,
}
impl WalletCore {
@ -94,24 +92,10 @@ impl WalletCore {
storage_path: PathBuf,
config_overrides: Option<WalletConfigOverrides>,
) -> Result<Self> {
let PersistentStorage {
accounts: persistent_accounts,
last_synced_block,
labels,
} = PersistentStorage::from_path(&storage_path).with_context(|| {
format!(
"Failed to read persistent storage at {}",
storage_path.display()
)
})?;
let storage = Storage::from_path(&storage_path)
.with_context(|| format!("Failed to load storage from {}", storage_path.display()))?;
Self::new(
config_path,
storage_path,
config_overrides,
|config| WalletChainStore::new(config, persistent_accounts, labels),
last_synced_block,
)
Self::new(config_path, storage_path, config_overrides, storage)
}
pub fn new_init_storage(
@ -120,30 +104,17 @@ impl WalletCore {
config_overrides: Option<WalletConfigOverrides>,
password: &str,
) -> Result<(Self, Mnemonic)> {
let mut mnemonic_out = None;
let wallet = Self::new(
config_path,
storage_path,
config_overrides,
|config| {
let (storage, mnemonic) = WalletChainStore::new_storage(config, password)?;
mnemonic_out = Some(mnemonic);
Ok(storage)
},
0,
)?;
Ok((
wallet,
mnemonic_out.expect("mnemonic should be set after new_storage"),
))
let (storage, mnemonic) = Storage::new(password).context("Failed to create storage")?;
let wallet = Self::new(config_path, storage_path, config_overrides, storage)?;
Ok((wallet, mnemonic))
}
fn new(
config_path: PathBuf,
storage_path: PathBuf,
config_overrides: Option<WalletConfigOverrides>,
storage_ctor: impl FnOnce(WalletConfig) -> Result<WalletChainStore>,
last_synced_block: u64,
storage: Storage,
) -> Result<Self> {
let mut config =
WalletConfig::from_path_or_initialize_default(&config_path).with_context(|| {
@ -176,54 +147,54 @@ impl WalletCore {
let tx_poller = TxPoller::new(&config, sequencer_client.clone());
let storage = storage_ctor(config)?;
Ok(Self {
config_path,
config_overrides,
config,
storage_path,
storage,
poller: tx_poller,
sequencer_client,
last_synced_block,
config_overrides,
})
}
/// Get configuration with applied overrides.
#[must_use]
pub const fn config(&self) -> &WalletConfig {
&self.storage.wallet_config
&self.config
}
pub fn set_config(&mut self, config: WalletConfig) {
self.config = config;
}
/// Get storage.
#[must_use]
pub const fn storage(&self) -> &WalletChainStore {
pub const fn storage(&self) -> &Storage {
&self.storage
}
/// Get mutable reference to storage.
#[must_use]
pub const fn storage_mut(&mut self) -> &mut Storage {
&mut self.storage
}
/// Restore storage from an existing mnemonic phrase.
pub fn restore_storage(&mut self, mnemonic: &Mnemonic, password: &str) -> Result<()> {
self.storage = WalletChainStore::restore_storage(
self.storage.wallet_config.clone(),
mnemonic,
password,
)?;
Ok(())
self.storage.restore(mnemonic, password)
}
/// Store persistent data at home.
pub async fn store_persistent_data(&self) -> Result<()> {
let data = produce_data_for_storage(
&self.storage.user_data,
self.last_synced_block,
self.storage.labels.clone(),
);
let storage = serde_json::to_vec_pretty(&data)?;
let mut storage_file = tokio::fs::File::create(&self.storage_path).await?;
storage_file.write_all(&storage).await?;
// Ensure data is flushed to disk before returning to prevent race conditions
storage_file.sync_all().await?;
pub fn store_persistent_data(&self) -> Result<()> {
self.storage
.save_to_path(&self.storage_path)
.with_context(|| {
format!(
"Failed to store persistent accounts at {}",
self.storage_path.display()
)
})?;
println!(
"Stored persistent accounts at {}",
@ -235,7 +206,7 @@ impl WalletCore {
/// Store persistent data at home.
pub async fn store_config_changes(&self) -> Result<()> {
let config = serde_json::to_vec_pretty(&self.storage.wallet_config)?;
let config = serde_json::to_vec_pretty(&self.config)?;
let mut config_file = tokio::fs::File::create(&self.config_path).await?;
config_file.write_all(&config).await?;
@ -252,7 +223,7 @@ impl WalletCore {
chain_index: Option<ChainIndex>,
) -> (AccountId, ChainIndex) {
self.storage
.user_data
.key_chain_mut()
.generate_new_public_transaction_private_key(chain_index)
}
@ -261,7 +232,7 @@ impl WalletCore {
chain_index: Option<ChainIndex>,
) -> (AccountId, ChainIndex) {
self.storage
.user_data
.key_chain_mut()
.generate_new_privacy_preserving_transaction_key_chain(chain_index)
}
@ -275,7 +246,20 @@ impl WalletCore {
Ok(self.sequencer_client.get_accounts_nonces(accs).await?)
}
/// Get account.
pub async fn get_account(&self, account_id: AccountIdWithPrivacy) -> Result<Account> {
match account_id {
AccountIdWithPrivacy::Public(acc_id) => self.get_account_public(acc_id).await,
AccountIdWithPrivacy::Private(acc_id) => {
if let Some(account) = self.get_account_private(acc_id) {
Ok(account)
} else {
anyhow::bail!("Private account with id {acc_id} not found in storage")
}
}
}
}
/// Get public account.
pub async fn get_account_public(&self, account_id: AccountId) -> Result<Account> {
Ok(self.sequencer_client.get_account(account_id).await?)
}
@ -286,21 +270,21 @@ impl WalletCore {
account_id: AccountId,
) -> Option<&nssa::PrivateKey> {
self.storage
.user_data
.key_chain()
.get_pub_account_signing_key(account_id)
}
#[must_use]
pub fn get_account_private(&self, account_id: AccountId) -> Option<Account> {
self.storage
.user_data
.key_chain()
.get_private_account(account_id)
.map(|value| value.1.clone())
}
#[must_use]
pub fn get_private_account_commitment(&self, account_id: AccountId) -> Option<Commitment> {
let (keys, account) = self.storage.user_data.get_private_account(account_id)?;
let (keys, account) = self.storage.key_chain().get_private_account(account_id)?;
Some(Commitment::new(&keys.nullifier_public_key, account))
}
@ -347,7 +331,9 @@ impl WalletCore {
println!("Received new acc {res_acc:#?}");
self.storage
.insert_private_account_data(*acc_account_id, res_acc);
.key_chain_mut()
.insert_private_account(*acc_account_id, res_acc)
.expect("Account Id should exist");
}
AccDecodeData::Skip => {}
}
@ -434,15 +420,23 @@ impl WalletCore {
))
}
pub async fn sync_to_latest_block(&mut self) -> Result<u64> {
let latest_block_id = self.sequencer_client.get_last_block_id().await?;
println!("Latest block is {latest_block_id}");
self.sync_to_block(latest_block_id).await?;
Ok(latest_block_id)
}
pub async fn sync_to_block(&mut self, block_id: u64) -> Result<()> {
use futures::TryStreamExt as _;
if self.last_synced_block >= block_id {
let last_synced_block = self.storage.last_synced_block();
if last_synced_block >= block_id {
return Ok(());
}
let before_polling = std::time::Instant::now();
let num_of_blocks = block_id.saturating_sub(self.last_synced_block);
let num_of_blocks = block_id.saturating_sub(last_synced_block);
if num_of_blocks == 0 {
return Ok(());
}
@ -450,9 +444,8 @@ impl WalletCore {
println!("Syncing to block {block_id}. Blocks to sync: {num_of_blocks}");
let poller = self.poller.clone();
let mut blocks = std::pin::pin!(
poller.poll_block_range(self.last_synced_block.saturating_add(1)..=block_id)
);
let mut blocks =
std::pin::pin!(poller.poll_block_range(last_synced_block.saturating_add(1)..=block_id));
let bar = indicatif::ProgressBar::new(num_of_blocks);
while let Some(block) = blocks.try_next().await? {
@ -460,8 +453,8 @@ impl WalletCore {
self.sync_private_accounts_with_tx(tx);
}
self.last_synced_block = block.header.block_id;
self.store_persistent_data().await?;
self.storage.set_last_synced_block(block.header.block_id);
self.store_persistent_data()?;
bar.inc(1);
}
bar.finish();
@ -479,23 +472,10 @@ impl WalletCore {
return;
};
let private_account_key_chains = self
let affected_accounts = self
.storage
.user_data
.default_user_private_accounts
.iter()
.map(|(acc_account_id, (key_chain, _))| (*acc_account_id, key_chain, None))
.chain(self.storage.user_data.private_key_tree.key_map.iter().map(
|(chain_index, keys_node)| {
(
keys_node.account_id(),
&keys_node.value.0,
chain_index.index(),
)
},
));
let affected_accounts = private_account_key_chains
.key_chain()
.private_account_key_chains()
.flat_map(|(acc_account_id, key_chain, index)| {
let view_tag = EncryptedAccountData::compute_view_tag(
&key_chain.nullifier_public_key,
@ -532,7 +512,9 @@ impl WalletCore {
"Received new account for account_id {affected_account_id:#?} with account object {new_acc:#?}"
);
self.storage
.insert_private_account_data(affected_account_id, new_acc);
.key_chain_mut()
.insert_private_account(affected_account_id, new_acc)
.expect("Account Id should exist");
}
}

View File

@ -61,7 +61,7 @@ async fn main() -> Result<()> {
println!(" {mnemonic}");
println!();
wallet.store_persistent_data().await?;
wallet.store_persistent_data()?;
wallet
};
let _output = execute_subcommand(&mut wallet, command).await?;

View File

@ -204,7 +204,7 @@ async fn private_acc_preparation(
) -> Result<AccountPreparedData, ExecutionFailureKind> {
let Some((from_keys, from_acc)) = wallet
.storage
.user_data
.key_chain()
.get_private_account(account_id)
.cloned()
else {

View File

@ -69,7 +69,7 @@ impl Amm<'_> {
let signing_key_a = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(user_holding_a)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
private_keys.push(signing_key_a);
@ -77,7 +77,7 @@ impl Amm<'_> {
let signing_key_b = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(user_holding_b)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
private_keys.push(signing_key_b);
@ -85,7 +85,7 @@ impl Amm<'_> {
if let Some(signing_key_lp) = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(user_holding_lp)
{
private_keys.push(signing_key_lp);
@ -187,7 +187,7 @@ impl Amm<'_> {
let signing_key = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(account_id_auth)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
@ -277,7 +277,7 @@ impl Amm<'_> {
let signing_key = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(account_id_auth)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
@ -361,14 +361,14 @@ impl Amm<'_> {
let signing_key_a = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(user_holding_a)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
let signing_key_b = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(user_holding_b)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
@ -454,7 +454,7 @@ impl Amm<'_> {
let signing_key_lp = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(user_holding_lp)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;

View File

@ -36,7 +36,7 @@ impl Ata<'_> {
let Some(signing_key) = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(owner_id)
else {
return Err(ExecutionFailureKind::KeyNotFoundError);
@ -88,7 +88,7 @@ impl Ata<'_> {
let Some(signing_key) = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(owner_id)
else {
return Err(ExecutionFailureKind::KeyNotFoundError);
@ -142,7 +142,7 @@ impl Ata<'_> {
let Some(signing_key) = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(owner_id)
else {
return Err(ExecutionFailureKind::KeyNotFoundError);

View File

@ -33,13 +33,13 @@ impl NativeTokenTransfer<'_> {
.map_err(ExecutionFailureKind::SequencerError)?;
let mut private_keys = Vec::new();
let from_signing_key = self.0.storage.user_data.get_pub_account_signing_key(from);
let from_signing_key = self.0.storage.key_chain().get_pub_account_signing_key(from);
let Some(from_signing_key) = from_signing_key else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
private_keys.push(from_signing_key);
let to_signing_key = self.0.storage.user_data.get_pub_account_signing_key(to);
let to_signing_key = self.0.storage.key_chain().get_pub_account_signing_key(to);
if let Some(to_signing_key) = to_signing_key {
private_keys.push(to_signing_key);
let to_nonces = self
@ -85,7 +85,7 @@ impl NativeTokenTransfer<'_> {
let program_id = Program::authenticated_transfer_program().id();
let message = Message::try_new(program_id, account_ids, nonces, instruction).unwrap();
let signing_key = self.0.storage.user_data.get_pub_account_signing_key(from);
let signing_key = self.0.storage.key_chain().get_pub_account_signing_key(from);
let Some(signing_key) = signing_key else {
return Err(ExecutionFailureKind::KeyNotFoundError);

View File

@ -35,13 +35,13 @@ impl Token<'_> {
let def_private_key = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(definition_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
let supply_private_key = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(supply_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
@ -169,7 +169,7 @@ impl Token<'_> {
let sender_sk = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(sender_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
private_keys.push(sender_sk);
@ -177,7 +177,7 @@ impl Token<'_> {
if let Some(recipient_sk) = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(recipient_account_id)
{
private_keys.push(recipient_sk);
@ -400,7 +400,7 @@ impl Token<'_> {
let signing_key = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(holder_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
let witness_set =
@ -528,7 +528,7 @@ impl Token<'_> {
let definition_sk = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(definition_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
private_keys.push(definition_sk);
@ -536,7 +536,7 @@ impl Token<'_> {
if let Some(holder_sk) = self
.0
.storage
.user_data
.key_chain()
.get_pub_account_signing_key(holder_account_id)
{
private_keys.push(holder_sk);

266
wallet/src/storage.rs Normal file
View File

@ -0,0 +1,266 @@
use std::{
collections::{BTreeMap, btree_map::Entry},
io::{BufReader, Write as _},
path::Path,
};
use anyhow::{Context as _, Result};
use bip39::Mnemonic;
use key_chain::UserKeyChain;
use key_protocol::key_management::{
key_tree::{KeyTreePrivate, KeyTreePublic},
secret_holders::SeedHolder,
};
use nssa_core::BlockId;
use crate::{
account::{AccountIdWithPrivacy, Label},
storage::persistent::PersistentStorage,
};
pub mod key_chain;
mod persistent;
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
pub struct Storage {
key_chain: UserKeyChain,
labels: BTreeMap<Label, AccountIdWithPrivacy>,
last_synced_block: BlockId,
}
impl Storage {
pub fn new(password: &str) -> Result<(Self, Mnemonic)> {
// TODO: Use password for storage encryption
// Question by @Arjentix: We probably want to encrypt file, not in-memory data?
let _ = password;
let (seed_holder, mnemonic) = SeedHolder::new_mnemonic("");
let public_tree = KeyTreePublic::new(&seed_holder);
let private_tree = KeyTreePrivate::new(&seed_holder);
Ok((
Self {
key_chain: UserKeyChain::new_with_accounts(public_tree, private_tree),
labels: BTreeMap::new(),
last_synced_block: 0,
},
mnemonic,
))
}
pub fn from_path(path: &Path) -> Result<Self> {
#[expect(
clippy::wildcard_enum_match_arm,
reason = "We want to provide a specific error message for not found case"
)]
match std::fs::File::open(path) {
Ok(file) => {
let storage_content = BufReader::new(file);
let persistent: persistent::PersistentStorage =
serde_json::from_reader(storage_content)
.context("Failed to parse storage file")?;
Ok(Self::from_persistent(persistent))
}
Err(err) => match err.kind() {
std::io::ErrorKind::NotFound => {
anyhow::bail!(
"Storage not found, please setup roots from config command beforehand"
);
}
_ => {
anyhow::bail!("IO error {err:#?}");
}
},
}
}
pub fn save_to_path(&self, path: &Path) -> Result<()> {
let persistent = self.to_persistent();
let storage_serialized = serde_json::to_vec_pretty(&persistent)?;
let mut file = std::fs::File::create(path).context("Failed to create file")?;
file.write_all(&storage_serialized)
.context("Failed to write to file")?;
file.sync_all().context("Failed to sync file")?;
Ok(())
}
/// Restore storage from an existing mnemonic phrase.
pub fn restore(&mut self, mnemonic: &Mnemonic, password: &str) -> Result<()> {
// TODO: Use password for storage encryption
let _ = password;
let seed_holder = SeedHolder::from_mnemonic(mnemonic, "");
let public_tree = KeyTreePublic::new(&seed_holder);
let private_tree = KeyTreePrivate::new(&seed_holder);
self.key_chain = UserKeyChain::new_with_accounts(public_tree, private_tree);
self.labels = BTreeMap::new();
Ok(())
}
#[must_use]
pub const fn key_chain(&self) -> &UserKeyChain {
&self.key_chain
}
pub const fn key_chain_mut(&mut self) -> &mut UserKeyChain {
&mut self.key_chain
}
pub fn check_label_availability(&self, label: &Label) -> Result<()> {
if self.labels.contains_key(label) {
Err(anyhow::anyhow!("Label `{label}` is already in use"))
} else {
Ok(())
}
}
pub fn add_label(&mut self, label: Label, account_id: AccountIdWithPrivacy) -> Result<()> {
// Creating error beforehand to avoid cloning label.
let err = anyhow::anyhow!("Label `{label}` is already in use");
match self.labels.entry(label) {
Entry::Occupied(_) => Err(err),
Entry::Vacant(entry) => {
entry.insert(account_id);
Ok(())
}
}
}
#[must_use]
pub fn resolve_label(&self, label: &Label) -> Option<AccountIdWithPrivacy> {
self.labels.get(label).copied()
}
// TODO: Slow implementation, consider maintaining reverse mapping if needed.
pub fn labels_for_account(
&self,
account_id: AccountIdWithPrivacy,
) -> impl Iterator<Item = &Label> {
self.labels
.iter()
.filter(move |(_, id)| **id == account_id)
.map(|(label, _)| label)
}
pub const fn set_last_synced_block(&mut self, block_id: BlockId) {
self.last_synced_block = block_id;
}
#[must_use]
pub const fn last_synced_block(&self) -> BlockId {
self.last_synced_block
}
fn to_persistent(&self) -> PersistentStorage {
let Self {
key_chain,
last_synced_block,
labels,
} = self;
PersistentStorage {
accounts: key_chain.to_persistent(),
last_synced_block: *last_synced_block,
labels: labels.clone(),
}
}
fn from_persistent(persistent: PersistentStorage) -> Self {
let PersistentStorage {
accounts,
last_synced_block,
labels,
} = persistent;
Self {
key_chain: UserKeyChain::from_persistent(accounts),
last_synced_block,
labels,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn save_load_roundtrip() {
let (mut storage, _) = Storage::new("test_pass").unwrap();
let (account_id, _) = storage
.key_chain_mut()
.generate_new_public_transaction_private_key(None);
let label = Label::new("test_label");
storage
.add_label(label, AccountIdWithPrivacy::Public(account_id))
.unwrap();
let _ = storage
.key_chain_mut()
.generate_new_privacy_preserving_transaction_key_chain(None);
storage.set_last_synced_block(42);
let temp_dir = tempfile::tempdir().unwrap();
let storage_path = temp_dir.path().join("storage.json");
storage.save_to_path(&storage_path).unwrap();
let loaded_store = Storage::from_path(&storage_path).unwrap();
assert_eq!(loaded_store, storage);
}
#[test]
fn resolve_label_works() {
let (mut storage, _) = Storage::new("test_pass").unwrap();
let label = Label::new("test_label");
let account_id = AccountIdWithPrivacy::Public(nssa::AccountId::default());
storage.add_label(label.clone(), account_id).unwrap();
assert_eq!(storage.resolve_label(&label), Some(account_id));
}
#[test]
fn resolve_label_returns_none_for_unknown_label() {
let (storage, _) = Storage::new("test_pass").unwrap();
let label = Label::new("test_label");
assert_eq!(storage.resolve_label(&label), None);
}
#[test]
fn labels_for_account_works() {
let (mut storage, _) = Storage::new("test_pass").unwrap();
let label = Label::new("test_label");
let account_id = AccountIdWithPrivacy::Public(nssa::AccountId::default());
storage.add_label(label.clone(), account_id).unwrap();
let another_label = Label::new("another_label");
storage
.add_label(another_label.clone(), account_id)
.unwrap();
assert_eq!(
storage.labels_for_account(account_id).collect::<Vec<_>>(),
vec![&label, &another_label]
);
}
#[test]
fn check_label_availability_works() {
let (mut storage, _) = Storage::new("test_pass").unwrap();
let label = Label::new("test_label");
let account_id = AccountIdWithPrivacy::Public(nssa::AccountId::default());
assert!(storage.check_label_availability(&label).is_ok());
storage.add_label(label.clone(), account_id).unwrap();
assert!(storage.check_label_availability(&label).is_err());
}
}

View File

@ -0,0 +1,524 @@
use std::collections::{BTreeMap, btree_map::Entry};
use anyhow::{Result, anyhow};
use key_protocol::key_management::{
KeyChain,
key_tree::{KeyTreePrivate, KeyTreePublic, chain_index::ChainIndex, traits::KeyNode as _},
secret_holders::SeedHolder,
};
use log::{debug, warn};
use nssa::AccountId;
use testnet_initial_state::{PrivateAccountPrivateInitialData, PublicAccountPrivateInitialData};
use crate::{
account::AccountIdWithPrivacy,
storage::persistent::{
PersistentAccountData, PersistentAccountDataPrivate, PersistentAccountDataPublic,
},
};
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct UserKeyChain {
/// Tree of public account keys.
public_key_tree: KeyTreePublic,
/// Tree of private account keys.
private_key_tree: KeyTreePrivate,
/// Imported public accounts.
imported_public_accounts: BTreeMap<AccountId, nssa::PrivateKey>,
/// Imported private accounts.
imported_private_accounts: BTreeMap<AccountId, (KeyChain, nssa_core::account::Account)>,
}
impl UserKeyChain {
#[must_use]
pub const fn new_with_accounts(
public_key_tree: KeyTreePublic,
private_key_tree: KeyTreePrivate,
) -> Self {
Self {
public_key_tree,
private_key_tree,
imported_public_accounts: BTreeMap::new(),
imported_private_accounts: BTreeMap::new(),
}
}
/// Generate new trees for public and private keys up to given depth.
///
/// See [`key_protocol::key_management::key_tree::KeyTree::generate_tree_for_depth()`] for more
/// details.
pub fn generate_trees_for_depth(&mut self, depth: u32) {
self.public_key_tree.generate_tree_for_depth(depth);
self.private_key_tree.generate_tree_for_depth(depth);
}
/// Cleanup non-initialized accounts from the trees up to given depth.
///
/// For more details see
/// [`key_protocol::key_management::key_tree::KeyTreePublic::cleanup_tree_remove_uninit_layered()`]
/// and [`key_protocol::key_management::key_tree::KeyTreePrivate::cleanup_tree_remove_uninit_layered()`].
pub async fn cleanup_trees_remove_uninit_layered<F: Future<Output = Result<nssa::Account>>>(
&mut self,
depth: u32,
get_account: impl Fn(AccountId) -> F,
) -> Result<()> {
self.public_key_tree
.cleanup_tree_remove_uninit_layered(depth, get_account)
.await?;
self.private_key_tree
.cleanup_tree_remove_uninit_layered(depth);
Ok(())
}
/// Generated new private key for public transaction signatures.
///
/// Returns the `account_id` of new account.
pub fn generate_new_public_transaction_private_key(
&mut self,
parent_cci: Option<ChainIndex>,
) -> (AccountId, ChainIndex) {
match parent_cci {
Some(parent_cci) => self
.public_key_tree
.generate_new_node(&parent_cci)
.expect("Parent must be present in a tree"),
None => self
.public_key_tree
.generate_new_node_layered()
.expect("Search for new node slot failed"),
}
}
/// Returns the signing key for public transaction signatures.
#[must_use]
pub fn get_pub_account_signing_key(&self, account_id: AccountId) -> Option<&nssa::PrivateKey> {
self.imported_public_accounts
.get(&account_id)
.or_else(|| self.public_key_tree.get_node(account_id).map(Into::into))
}
/// Generated new private key for privacy preserving transactions.
///
/// Returns the `account_id` of new account.
pub fn generate_new_privacy_preserving_transaction_key_chain(
&mut self,
parent_cci: Option<ChainIndex>,
) -> (AccountId, ChainIndex) {
match parent_cci {
Some(parent_cci) => self
.private_key_tree
.generate_new_node(&parent_cci)
.expect("Parent must be present in a tree"),
None => self
.private_key_tree
.generate_new_node_layered()
.expect("Search for new node slot failed"),
}
}
/// Returns the signing key for public transaction signatures.
#[must_use]
pub fn get_private_account(
&self,
account_id: AccountId,
) -> Option<&(KeyChain, nssa_core::account::Account)> {
self.imported_private_accounts
.get(&account_id)
.or_else(|| self.private_key_tree.get_node(account_id).map(Into::into))
}
/// Returns the signing key for public transaction signatures.
pub fn get_private_account_mut(
&mut self,
account_id: &AccountId,
) -> Option<&mut (KeyChain, nssa_core::account::Account)> {
// First seek in defaults
if let Some(key) = self.imported_private_accounts.get_mut(account_id) {
Some(key)
// Then seek in tree
} else {
self.private_key_tree
.get_node_mut(*account_id)
.map(Into::into)
}
}
pub fn private_account_key_chains(
&self,
) -> impl Iterator<Item = (AccountId, &KeyChain, Option<u32>)> {
self.imported_private_accounts
.iter()
.map(|(account_id, (key_chain, _))| (*account_id, key_chain, None))
.chain(
self.private_key_tree
.key_map
.iter()
.map(|(chain_index, keys_node)| {
(
keys_node.account_id(),
&keys_node.value.0,
chain_index.index(),
)
}),
)
}
pub fn add_imported_public_account(&mut self, private_key: nssa::PrivateKey) {
let account_id = AccountId::from(&nssa::PublicKey::new_from_private_key(&private_key));
self.imported_public_accounts
.insert(account_id, private_key);
}
pub fn add_imported_private_account(
&mut self,
key_chain: KeyChain,
account: nssa_core::account::Account,
) {
let account_id = AccountId::from(&key_chain.nullifier_public_key);
let entry = self.imported_private_accounts.entry(account_id);
if let Entry::Occupied(occupied) = &entry {
let existing_account = &occupied.get().1;
if existing_account != &account {
warn!(
"Overwriting existing imported private account for account ID {account_id}. \
Existing account: {existing_account:?}, new account: {account:?}",
);
}
}
entry.insert_entry((key_chain, account));
}
pub fn insert_private_account(
&mut self,
account_id: AccountId,
account: nssa_core::account::Account,
) -> Result<()> {
let imported_entry = self.imported_private_accounts.entry(account_id);
match imported_entry {
Entry::Occupied(occupied) => {
debug!("inserting at imported address {account_id}, account {account:?}");
occupied.into_mut().1 = account;
Ok(())
}
Entry::Vacant(_) => self
.private_key_tree
.account_id_map
.get(&account_id)
.map_or_else(
|| Err(anyhow!("Account ID {account_id} not found")),
|chain_index| {
self.private_key_tree
.key_map
.entry(chain_index.clone())
.and_modify(|data| {
debug!("inserting at address {account_id}, account {account:?}");
data.value.1 = account;
});
Ok(())
},
),
}
}
pub fn account_ids(&self) -> impl Iterator<Item = (AccountIdWithPrivacy, Option<&ChainIndex>)> {
self.public_account_ids()
.map(|(account_id, chain_index)| {
(AccountIdWithPrivacy::Public(account_id), chain_index)
})
.chain(self.private_account_ids().map(|(account_id, chain_index)| {
(AccountIdWithPrivacy::Private(account_id), chain_index)
}))
}
pub fn public_account_ids(&self) -> impl Iterator<Item = (AccountId, Option<&ChainIndex>)> {
self.imported_public_accounts
.keys()
.map(|account_id| (*account_id, None))
.chain(
self.public_key_tree
.account_id_map
.iter()
.map(|(account_id, chain_index)| (*account_id, Some(chain_index))),
)
}
pub fn private_account_ids(&self) -> impl Iterator<Item = (AccountId, Option<&ChainIndex>)> {
self.imported_private_accounts
.keys()
.map(|account_id| (*account_id, None))
.chain(
self.private_key_tree
.account_id_map
.iter()
.map(|(account_id, chain_index)| (*account_id, Some(chain_index))),
)
}
pub(super) fn to_persistent(&self) -> Vec<PersistentAccountData> {
let Self {
public_key_tree,
private_key_tree,
imported_public_accounts,
imported_private_accounts,
} = self;
let mut vec_for_storage = vec![];
for (account_id, chain_index) in &public_key_tree.account_id_map {
if let Some(data) = public_key_tree.key_map.get(chain_index) {
vec_for_storage.push(PersistentAccountData::Public(PersistentAccountDataPublic {
account_id: *account_id,
chain_index: chain_index.clone(),
data: data.clone(),
}));
}
}
for (account_id, key) in &private_key_tree.account_id_map {
if let Some(data) = private_key_tree.key_map.get(key) {
vec_for_storage.push(PersistentAccountData::Private(Box::new(
PersistentAccountDataPrivate {
account_id: *account_id,
chain_index: key.clone(),
data: data.clone(),
},
)));
}
}
for (account_id, key) in imported_public_accounts {
vec_for_storage.push(PersistentAccountData::ImportedPublic(
PublicAccountPrivateInitialData {
account_id: *account_id,
pub_sign_key: key.clone(),
},
));
}
for (account_id, (key_chain, account)) in imported_private_accounts {
vec_for_storage.push(PersistentAccountData::ImportedPrivate(Box::new(
PrivateAccountPrivateInitialData {
account_id: *account_id,
account: account.clone(),
key_chain: key_chain.clone(),
},
)));
}
vec_for_storage
}
#[expect(
clippy::wildcard_enum_match_arm,
reason = "We perform search for specific variants only"
)]
pub(super) fn from_persistent(persistent_accounts: Vec<PersistentAccountData>) -> Self {
let mut imported_public_accounts = BTreeMap::new();
let mut imported_private_accounts = BTreeMap::new();
let public_root = persistent_accounts
.iter()
.find(|data| match data {
&PersistentAccountData::Public(data) => data.chain_index == ChainIndex::root(),
_ => false,
})
.cloned()
.expect("Malformed persistent account data, must have public root");
let private_root = persistent_accounts
.iter()
.find(|data| match data {
&PersistentAccountData::Private(data) => data.chain_index == ChainIndex::root(),
_ => false,
})
.cloned()
.expect("Malformed persistent account data, must have private root");
let mut public_key_tree = KeyTreePublic::new_from_root(match public_root {
PersistentAccountData::Public(data) => data.data,
_ => unreachable!(),
});
let mut private_key_tree = KeyTreePrivate::new_from_root(match private_root {
PersistentAccountData::Private(data) => data.data,
_ => unreachable!(),
});
for pers_acc_data in persistent_accounts {
match pers_acc_data {
PersistentAccountData::Public(data) => {
public_key_tree.insert(data.account_id, data.chain_index, data.data);
}
PersistentAccountData::Private(data) => {
private_key_tree.insert(data.account_id, data.chain_index, data.data);
}
PersistentAccountData::ImportedPublic(data) => {
imported_public_accounts.insert(data.account_id, data.pub_sign_key);
}
PersistentAccountData::ImportedPrivate(data) => {
imported_private_accounts
.insert(data.account_id, (data.key_chain, data.account));
}
}
}
Self {
public_key_tree,
private_key_tree,
imported_public_accounts,
imported_private_accounts,
}
}
}
impl Default for UserKeyChain {
fn default() -> Self {
let (seed_holder, _mnemonic) = SeedHolder::new_mnemonic("");
Self::new_with_accounts(
KeyTreePublic::new(&seed_holder),
KeyTreePrivate::new(&seed_holder),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_account() {
let mut user_data = UserKeyChain::default();
let (account_id_private, _) = user_data
.generate_new_privacy_preserving_transaction_key_chain(Some(ChainIndex::root()));
let is_key_chain_generated = user_data.get_private_account(account_id_private).is_some();
assert!(is_key_chain_generated);
let account_id_private_str = account_id_private.to_string();
println!("{account_id_private_str:#?}");
let key_chain = &user_data.get_private_account(account_id_private).unwrap().0;
println!("{key_chain:#?}");
}
#[test]
fn add_imported_public_account() {
let mut user_data = UserKeyChain::default();
let private_key = nssa::PrivateKey::new_os_random();
let account_id = AccountId::from(&nssa::PublicKey::new_from_private_key(&private_key));
user_data.add_imported_public_account(private_key);
let is_account_added = user_data.get_pub_account_signing_key(account_id).is_some();
assert!(is_account_added);
}
#[test]
fn add_imported_private_account() {
let mut user_data = UserKeyChain::default();
let key_chain = KeyChain::new_os_random();
let account_id = AccountId::from(&key_chain.nullifier_public_key);
let account = nssa_core::account::Account::default();
user_data.add_imported_private_account(key_chain, account);
let is_account_added = user_data.get_private_account(account_id).is_some();
assert!(is_account_added);
}
#[test]
fn insert_private_imported_account() {
let mut user_data = UserKeyChain::default();
let key_chain = KeyChain::new_os_random();
let account_id = AccountId::from(&key_chain.nullifier_public_key);
let account = nssa_core::account::Account::default();
user_data.add_imported_private_account(key_chain, account.clone());
let new_account = nssa_core::account::Account {
balance: 100,
..account
};
user_data
.insert_private_account(account_id, new_account)
.unwrap();
let retrieved_account = &user_data.get_private_account(account_id).unwrap().1;
assert_eq!(retrieved_account.balance, 100);
}
#[test]
fn insert_private_non_imported_account() {
let mut user_data = UserKeyChain::default();
let (account_id, _chain_index) = user_data
.generate_new_privacy_preserving_transaction_key_chain(Some(ChainIndex::root()));
let new_account = nssa_core::account::Account {
balance: 100,
..nssa_core::account::Account::default()
};
user_data
.insert_private_account(account_id, new_account)
.unwrap();
let retrieved_account = &user_data.get_private_account(account_id).unwrap().1;
assert_eq!(retrieved_account.balance, 100);
}
#[test]
fn insert_private_non_existent_account() {
let mut user_data = UserKeyChain::default();
let key_chain = KeyChain::new_os_random();
let account_id = AccountId::from(&key_chain.nullifier_public_key);
let new_account = nssa_core::account::Account {
balance: 100,
..nssa_core::account::Account::default()
};
let result = user_data.insert_private_account(account_id, new_account);
assert!(result.is_err());
}
#[test]
fn private_key_chain_iteration() {
let mut user_data = UserKeyChain::default();
let key_chain = KeyChain::new_os_random();
let account_id1 = AccountId::from(&key_chain.nullifier_public_key);
let account = nssa_core::account::Account::default();
user_data.add_imported_private_account(key_chain, account);
let (account_id2, chain_index2) = user_data
.generate_new_privacy_preserving_transaction_key_chain(Some(ChainIndex::root()));
let (account_id3, chain_index3) = user_data
.generate_new_privacy_preserving_transaction_key_chain(Some(chain_index2.clone()));
let key_chains: Vec<(AccountId, &KeyChain, Option<u32>)> =
user_data.private_account_key_chains().collect();
assert_eq!(key_chains.len(), 3);
assert_eq!(key_chains[0].0, account_id1);
assert_eq!(key_chains[0].2, None);
assert_eq!(key_chains[1].0, account_id2);
assert_eq!(key_chains[1].2, chain_index2.index());
assert_eq!(key_chains[2].0, account_id3);
assert_eq!(key_chains[2].2, chain_index3.index());
}
}

View File

@ -0,0 +1,39 @@
use std::collections::BTreeMap;
use key_protocol::key_management::key_tree::{
chain_index::ChainIndex, keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic,
};
use serde::{Deserialize, Serialize};
use testnet_initial_state::{PrivateAccountPrivateInitialData, PublicAccountPrivateInitialData};
use crate::account::{AccountIdWithPrivacy, Label};
#[derive(Serialize, Deserialize)]
pub struct PersistentStorage {
pub accounts: Vec<PersistentAccountData>,
pub last_synced_block: u64,
#[serde(default)]
pub labels: BTreeMap<Label, AccountIdWithPrivacy>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PersistentAccountData {
Public(PersistentAccountDataPublic),
Private(Box<PersistentAccountDataPrivate>),
ImportedPublic(PublicAccountPrivateInitialData),
ImportedPrivate(Box<PrivateAccountPrivateInitialData>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistentAccountDataPublic {
pub account_id: nssa::AccountId,
pub chain_index: ChainIndex,
pub data: ChildKeysPublic,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistentAccountDataPrivate {
pub account_id: nssa::AccountId,
pub chain_index: ChainIndex,
pub data: ChildKeysPrivate,
}

View File

@ -30,7 +30,7 @@ impl WalletCore {
) -> Result<AccountPreparedData, ExecutionFailureKind> {
let Some((from_keys, from_acc)) = self
.storage
.user_data
.key_chain()
.get_private_account(&account_id)
.cloned()
else {
@ -403,7 +403,7 @@ impl WalletCore {
)
.unwrap();
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
let signing_key = self.storage.key_chain().get_pub_account_signing_key(&from);
let Some(signing_key) = signing_key else {
return Err(ExecutionFailureKind::KeyNotFoundError);
@ -469,7 +469,7 @@ impl WalletCore {
)
.unwrap();
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
let signing_key = self.storage.key_chain().get_pub_account_signing_key(&from);
let Some(signing_key) = signing_key else {
return Err(ExecutionFailureKind::KeyNotFoundError);
@ -531,7 +531,7 @@ impl WalletCore {
)
.unwrap();
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
let signing_key = self.storage.key_chain().get_pub_account_signing_key(&from);
let Some(signing_key) = signing_key else {
return Err(ExecutionFailureKind::KeyNotFoundError);