mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-04-16 16:13:41 +00:00
feat: remove initial accounts data from wallet, rely on import command instead
This commit is contained in:
parent
f975b98d44
commit
0b070e5ad2
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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" }
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@ -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}")
|
||||
|
||||
@ -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"
|
||||
);
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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,
|
||||
}),
|
||||
)
|
||||
|
||||
@ -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?;
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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],
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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:#?}");
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
#![expect(clippy::print_stdout, reason = "TODO: fix later")]
|
||||
|
||||
pub mod key_management;
|
||||
pub mod key_protocol_core;
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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}"));
|
||||
|
||||
@ -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"] }
|
||||
|
||||
@ -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
137
wallet/src/account.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
73
wallet/src/cli/import.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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?;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)?;
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
266
wallet/src/storage.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
524
wallet/src/storage/key_chain.rs
Normal file
524
wallet/src/storage/key_chain.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
39
wallet/src/storage/persistent.rs
Normal file
39
wallet/src/storage/persistent.rs
Normal 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,
|
||||
}
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user