mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-12 02:59:29 +00:00
feat(wallet)!: add group CLI commands with --for-gms account creation
BREAKING CHANGE: `NewSubcommand::Private` has new required fields (`for_gms`, `pda`, `seed`, `program_id`). Code constructing this variant must include them (use `None`/`false` for defaults). `shared_accounts` value type changed from `Account` to `SharedAccountEntry`.
This commit is contained in:
parent
7be0ed926c
commit
cd545819e7
@ -21,6 +21,15 @@ pub struct UserPrivateAccountData {
|
||||
pub accounts: Vec<(Identifier, Account)>,
|
||||
}
|
||||
|
||||
/// Metadata for a shared account (GMS-derived), stored alongside the cached plaintext state.
|
||||
/// The group label and identifier are needed to re-derive keys during sync.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SharedAccountEntry {
|
||||
pub group_label: String,
|
||||
pub identifier: Identifier,
|
||||
pub account: Account,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct NSSAUserData {
|
||||
/// Default public accounts.
|
||||
@ -37,11 +46,12 @@ pub struct NSSAUserData {
|
||||
#[serde(default)]
|
||||
pub group_key_holders: BTreeMap<String, GroupKeyHolder>,
|
||||
/// Cached plaintext state of shared accounts (PDAs and regular shared accounts),
|
||||
/// keyed by `AccountId`. Updated after each transaction by decrypting the circuit output.
|
||||
/// The sequencer only stores encrypted commitments, so this local cache is the
|
||||
/// only source of plaintext state for these accounts.
|
||||
#[serde(default, alias = "group_pda_accounts", alias = "pda_accounts")]
|
||||
pub shared_accounts: BTreeMap<nssa::AccountId, nssa_core::account::Account>,
|
||||
/// keyed by `AccountId`. Each entry stores the group label and identifier needed
|
||||
/// to re-derive keys during sync.
|
||||
/// Old wallet files with `pda_accounts` (plain Account values) are incompatible with
|
||||
/// this type. The `default` attribute ensures they deserialize as empty rather than failing.
|
||||
#[serde(default)]
|
||||
pub shared_accounts: BTreeMap<nssa::AccountId, SharedAccountEntry>,
|
||||
}
|
||||
|
||||
impl NSSAUserData {
|
||||
|
||||
@ -83,14 +83,27 @@ pub enum NewSubcommand {
|
||||
label: Option<String>,
|
||||
},
|
||||
/// Single-account convenience: creates a key node and auto-registers one account with a random
|
||||
/// identifier.
|
||||
/// identifier. When `--for-gms` is provided, derives keys from the named group instead of
|
||||
/// the wallet's key tree.
|
||||
Private {
|
||||
#[arg(long)]
|
||||
/// Chain index of a parent node.
|
||||
/// Chain index of a parent node (ignored when --for-gms is used).
|
||||
cci: Option<ChainIndex>,
|
||||
#[arg(short, long)]
|
||||
/// Label to assign to the new account.
|
||||
label: Option<String>,
|
||||
#[arg(long)]
|
||||
/// Derive keys from a group's GMS instead of the wallet tree.
|
||||
for_gms: Option<String>,
|
||||
#[arg(long, requires = "for_gms")]
|
||||
/// Create a PDA account (requires --seed and --program-id).
|
||||
pda: bool,
|
||||
#[arg(long, requires = "pda")]
|
||||
/// PDA seed as 64-character hex string.
|
||||
seed: Option<String>,
|
||||
#[arg(long, requires = "pda")]
|
||||
/// Program ID as hex string.
|
||||
program_id: Option<String>,
|
||||
},
|
||||
/// Recommended for receiving from multiple senders: creates a key node (npk + vpk) without
|
||||
/// registering any account.
|
||||
@ -144,7 +157,14 @@ impl WalletSubcommand for NewSubcommand {
|
||||
|
||||
Ok(SubcommandReturnValue::RegisterAccount { account_id })
|
||||
}
|
||||
Self::Private { cci, label } => {
|
||||
Self::Private {
|
||||
cci,
|
||||
label,
|
||||
for_gms,
|
||||
pda,
|
||||
seed,
|
||||
program_id,
|
||||
} => {
|
||||
if let Some(label) = &label
|
||||
&& wallet_core
|
||||
.storage
|
||||
@ -155,36 +175,132 @@ impl WalletSubcommand for NewSubcommand {
|
||||
anyhow::bail!("Label '{label}' is already in use by another account");
|
||||
}
|
||||
|
||||
let (account_id, chain_index) = wallet_core.create_new_account_private(cci);
|
||||
if let Some(group_name) = for_gms {
|
||||
// GMS-derived account
|
||||
let holder = wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.group_key_holder(&group_name)
|
||||
.context(format!("Group '{group_name}' not found"))?;
|
||||
|
||||
let node = wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.key_map
|
||||
.get(&chain_index)
|
||||
.expect("Node was just inserted");
|
||||
let key = &node.value.0;
|
||||
if pda {
|
||||
// PDA shared account
|
||||
let seed_hex = seed.context("--seed is required for PDA accounts")?;
|
||||
let pid_hex =
|
||||
program_id.context("--program-id is required for PDA accounts")?;
|
||||
|
||||
if let Some(label) = label {
|
||||
wallet_core
|
||||
let seed_bytes: [u8; 32] = hex::decode(&seed_hex)
|
||||
.context("Invalid seed hex")?
|
||||
.try_into()
|
||||
.map_err(|_err| anyhow::anyhow!("Seed must be exactly 32 bytes"))?;
|
||||
let pda_seed = nssa_core::program::PdaSeed::new(seed_bytes);
|
||||
|
||||
let pid_bytes = hex::decode(&pid_hex).context("Invalid program ID hex")?;
|
||||
if pid_bytes.len() != 32 {
|
||||
anyhow::bail!("Program ID must be exactly 32 bytes");
|
||||
}
|
||||
let mut pid: nssa_core::program::ProgramId = [0; 8];
|
||||
for (i, chunk) in pid_bytes.chunks_exact(4).enumerate() {
|
||||
pid[i] = u32::from_le_bytes(chunk.try_into().unwrap());
|
||||
}
|
||||
|
||||
let keys = holder.derive_keys_for_pda(&pda_seed);
|
||||
let npk = keys.generate_nullifier_public_key();
|
||||
let vpk = keys.generate_viewing_public_key();
|
||||
let account_id = nssa::AccountId::for_private_pda(&pid, &pda_seed, &npk);
|
||||
|
||||
if let Some(label) = label {
|
||||
wallet_core
|
||||
.storage
|
||||
.labels
|
||||
.insert(account_id.to_string(), Label::new(label));
|
||||
}
|
||||
|
||||
wallet_core.register_shared_account(
|
||||
account_id,
|
||||
group_name.clone(),
|
||||
u128::MAX,
|
||||
);
|
||||
|
||||
println!("PDA shared account from group '{group_name}'");
|
||||
println!("AccountId: {account_id}");
|
||||
println!("NPK: {}", hex::encode(npk.0));
|
||||
println!("VPK: {}", hex::encode(&vpk.0));
|
||||
|
||||
wallet_core.store_persistent_data().await?;
|
||||
Ok(SubcommandReturnValue::RegisterAccount { account_id })
|
||||
} else {
|
||||
// Regular shared account. The tag is derived deterministically
|
||||
// from the identifier so that keys can be re-derived without
|
||||
// storing the tag separately.
|
||||
let identifier: nssa_core::Identifier = rand::random();
|
||||
let tag = {
|
||||
use sha2::Digest as _;
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(b"/LEE/v0.3/SharedAccountTag/\x00\x00\x00\x00\x00");
|
||||
hasher.update(identifier.to_le_bytes());
|
||||
let result: [u8; 32] = hasher.finalize().into();
|
||||
result
|
||||
};
|
||||
|
||||
let keys = holder.derive_keys_for_shared_account(&tag);
|
||||
let npk = keys.generate_nullifier_public_key();
|
||||
let vpk = keys.generate_viewing_public_key();
|
||||
let account_id = nssa::AccountId::from((&npk, identifier));
|
||||
|
||||
if let Some(label) = label {
|
||||
wallet_core
|
||||
.storage
|
||||
.labels
|
||||
.insert(account_id.to_string(), Label::new(label));
|
||||
}
|
||||
|
||||
wallet_core.register_shared_account(
|
||||
account_id,
|
||||
group_name.clone(),
|
||||
identifier,
|
||||
);
|
||||
|
||||
println!("Shared account from group '{group_name}'");
|
||||
println!("AccountId: Private/{account_id}");
|
||||
println!("NPK: {}", hex::encode(npk.0));
|
||||
println!("VPK: {}", hex::encode(&vpk.0));
|
||||
|
||||
wallet_core.store_persistent_data().await?;
|
||||
Ok(SubcommandReturnValue::RegisterAccount { account_id })
|
||||
}
|
||||
} else {
|
||||
// Standard wallet-tree-derived account
|
||||
let (account_id, chain_index) = wallet_core.create_new_account_private(cci);
|
||||
|
||||
let node = wallet_core
|
||||
.storage
|
||||
.labels
|
||||
.insert(account_id.to_string(), Label::new(label));
|
||||
.user_data
|
||||
.private_key_tree
|
||||
.key_map
|
||||
.get(&chain_index)
|
||||
.expect("Node was just inserted");
|
||||
let key = &node.value.0;
|
||||
|
||||
if let Some(label) = label {
|
||||
wallet_core
|
||||
.storage
|
||||
.labels
|
||||
.insert(account_id.to_string(), Label::new(label));
|
||||
}
|
||||
|
||||
println!(
|
||||
"Generated new account with account_id Private/{account_id} at path {chain_index}"
|
||||
);
|
||||
println!("With npk {}", hex::encode(key.nullifier_public_key.0));
|
||||
println!(
|
||||
"With vpk {}",
|
||||
hex::encode(key.viewing_public_key.to_bytes())
|
||||
);
|
||||
|
||||
wallet_core.store_persistent_data().await?;
|
||||
Ok(SubcommandReturnValue::RegisterAccount { account_id })
|
||||
}
|
||||
|
||||
println!(
|
||||
"Generated new account with account_id Private/{account_id} at path {chain_index}"
|
||||
);
|
||||
println!("With npk {}", hex::encode(key.nullifier_public_key.0));
|
||||
println!(
|
||||
"With vpk {}",
|
||||
hex::encode(key.viewing_public_key.to_bytes())
|
||||
);
|
||||
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
Ok(SubcommandReturnValue::RegisterAccount { account_id })
|
||||
}
|
||||
Self::PrivateAccountsKey { cci } => {
|
||||
let chain_index = wallet_core.create_private_accounts_key(cci);
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use clap::Subcommand;
|
||||
use key_protocol::key_management::group_key_holder::GroupKeyHolder;
|
||||
use nssa::AccountId;
|
||||
use nssa_core::program::PdaSeed;
|
||||
|
||||
use crate::{
|
||||
WalletCore,
|
||||
cli::{SubcommandReturnValue, WalletSubcommand},
|
||||
};
|
||||
|
||||
/// Group PDA management commands.
|
||||
/// Group key management commands.
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum GroupSubcommand {
|
||||
/// Create a new group with a fresh random GMS.
|
||||
@ -24,29 +22,15 @@ pub enum GroupSubcommand {
|
||||
/// Raw GMS as 64-character hex string.
|
||||
#[arg(long)]
|
||||
gms: String,
|
||||
/// Epoch (defaults to 0).
|
||||
#[arg(long, default_value = "0")]
|
||||
epoch: u32,
|
||||
},
|
||||
/// Export the raw GMS hex for backup or manual distribution.
|
||||
Export {
|
||||
/// Group name.
|
||||
name: String,
|
||||
},
|
||||
/// List all groups with their epochs.
|
||||
/// List all groups.
|
||||
#[command(visible_alias = "ls")]
|
||||
List,
|
||||
/// Derive keys for a PDA seed and show the resulting AccountId.
|
||||
Derive {
|
||||
/// Group name.
|
||||
name: String,
|
||||
/// PDA seed as 64-character hex string.
|
||||
#[arg(long)]
|
||||
seed: String,
|
||||
/// Program ID as hex string (u32x8 little-endian).
|
||||
#[arg(long)]
|
||||
program_id: String,
|
||||
},
|
||||
/// Remove a group from the wallet.
|
||||
Remove {
|
||||
/// Group name.
|
||||
@ -56,9 +40,9 @@ pub enum GroupSubcommand {
|
||||
Invite {
|
||||
/// Group name.
|
||||
name: String,
|
||||
/// Recipient's viewing public key as hex string.
|
||||
/// Recipient's sealing public key as hex string.
|
||||
#[arg(long)]
|
||||
vpk: String,
|
||||
key: String,
|
||||
},
|
||||
/// Unseal a received GMS and store it (join a group).
|
||||
Join {
|
||||
@ -67,15 +51,10 @@ pub enum GroupSubcommand {
|
||||
/// Sealed GMS as hex string (from the inviter).
|
||||
#[arg(long)]
|
||||
sealed: String,
|
||||
/// Account label or Private/<id> whose VSK to use for decryption.
|
||||
/// Account ID whose viewing secret key to use for decryption.
|
||||
#[arg(long)]
|
||||
account: String,
|
||||
},
|
||||
/// Ratchet the GMS to exclude removed members.
|
||||
Ratchet {
|
||||
/// Group name.
|
||||
name: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl WalletSubcommand for GroupSubcommand {
|
||||
@ -88,28 +67,25 @@ impl WalletSubcommand for GroupSubcommand {
|
||||
if wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_group_key_holder(&name)
|
||||
.group_key_holder(&name)
|
||||
.is_some()
|
||||
{
|
||||
anyhow::bail!("Group '{name}' already exists");
|
||||
}
|
||||
|
||||
let holder = GroupKeyHolder::new();
|
||||
wallet_core
|
||||
.storage_mut()
|
||||
.user_data
|
||||
.insert_group_key_holder(name.clone(), holder);
|
||||
wallet_core.insert_group_key_holder(name.clone(), holder);
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
println!("Created group '{name}' at epoch 0");
|
||||
println!("Created group '{name}'");
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
|
||||
Self::Import { name, gms, epoch } => {
|
||||
Self::Import { name, gms } => {
|
||||
if wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_group_key_holder(&name)
|
||||
.group_key_holder(&name)
|
||||
.is_some()
|
||||
{
|
||||
anyhow::bail!("Group '{name}' already exists");
|
||||
@ -118,16 +94,13 @@ impl WalletSubcommand for GroupSubcommand {
|
||||
let gms_bytes: [u8; 32] = hex::decode(&gms)
|
||||
.context("Invalid GMS hex")?
|
||||
.try_into()
|
||||
.map_err(|_| anyhow::anyhow!("GMS must be exactly 32 bytes"))?;
|
||||
.map_err(|_err| anyhow::anyhow!("GMS must be exactly 32 bytes"))?;
|
||||
|
||||
let holder = GroupKeyHolder::from_gms_and_epoch(gms_bytes, epoch);
|
||||
wallet_core
|
||||
.storage_mut()
|
||||
.user_data
|
||||
.insert_group_key_holder(name.clone(), holder);
|
||||
let holder = GroupKeyHolder::from_gms(gms_bytes);
|
||||
wallet_core.insert_group_key_holder(name.clone(), holder);
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
println!("Imported group '{name}' at epoch {epoch}");
|
||||
println!("Imported group '{name}'");
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
|
||||
@ -135,14 +108,12 @@ impl WalletSubcommand for GroupSubcommand {
|
||||
let holder = wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_group_key_holder(&name)
|
||||
.group_key_holder(&name)
|
||||
.context(format!("Group '{name}' not found"))?;
|
||||
|
||||
let gms_hex = hex::encode(holder.dangerous_raw_gms());
|
||||
let epoch = holder.epoch();
|
||||
|
||||
println!("Group: {name}");
|
||||
println!("Epoch: {epoch}");
|
||||
println!("GMS: {gms_hex}");
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
@ -152,60 +123,15 @@ impl WalletSubcommand for GroupSubcommand {
|
||||
if holders.is_empty() {
|
||||
println!("No groups found");
|
||||
} else {
|
||||
for (name, holder) in holders {
|
||||
println!("{name} (epoch {})", holder.epoch());
|
||||
for name in holders.keys() {
|
||||
println!("{name}");
|
||||
}
|
||||
}
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
|
||||
Self::Derive {
|
||||
name,
|
||||
seed,
|
||||
program_id,
|
||||
} => {
|
||||
let holder = wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_group_key_holder(&name)
|
||||
.context(format!("Group '{name}' not found"))?;
|
||||
|
||||
let seed_bytes: [u8; 32] = hex::decode(&seed)
|
||||
.context("Invalid seed hex")?
|
||||
.try_into()
|
||||
.map_err(|_| anyhow::anyhow!("Seed must be exactly 32 bytes"))?;
|
||||
let pda_seed = PdaSeed::new(seed_bytes);
|
||||
|
||||
let pid_bytes =
|
||||
hex::decode(&program_id).context("Invalid program ID hex")?;
|
||||
if pid_bytes.len() != 32 {
|
||||
anyhow::bail!("Program ID must be exactly 32 bytes");
|
||||
}
|
||||
let mut pid: nssa_core::program::ProgramId = [0; 8];
|
||||
for (i, chunk) in pid_bytes.chunks_exact(4).enumerate() {
|
||||
pid[i] = u32::from_le_bytes(chunk.try_into().unwrap());
|
||||
}
|
||||
|
||||
let keys = holder.derive_keys_for_pda(&pda_seed);
|
||||
let npk = keys.generate_nullifier_public_key();
|
||||
let vpk = keys.generate_viewing_public_key();
|
||||
let account_id = AccountId::for_private_pda(&pid, &pda_seed, &npk);
|
||||
|
||||
println!("Group: {name}");
|
||||
println!("NPK: {}", hex::encode(npk.0));
|
||||
println!("VPK: {}", hex::encode(&vpk.0));
|
||||
println!("AccountId: {account_id}");
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
|
||||
Self::Remove { name } => {
|
||||
if wallet_core
|
||||
.storage_mut()
|
||||
.user_data
|
||||
.group_key_holders
|
||||
.remove(&name)
|
||||
.is_none()
|
||||
{
|
||||
if wallet_core.remove_group_key_holder(&name).is_none() {
|
||||
anyhow::bail!("Group '{name}' not found");
|
||||
}
|
||||
|
||||
@ -214,18 +140,18 @@ impl WalletSubcommand for GroupSubcommand {
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
|
||||
Self::Invite { name, vpk } => {
|
||||
Self::Invite { name, key } => {
|
||||
let holder = wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_group_key_holder(&name)
|
||||
.group_key_holder(&name)
|
||||
.context(format!("Group '{name}' not found"))?;
|
||||
|
||||
let vpk_bytes = hex::decode(&vpk).context("Invalid VPK hex")?;
|
||||
let recipient_vpk =
|
||||
nssa_core::encryption::shared_key_derivation::Secp256k1Point(vpk_bytes);
|
||||
let key_bytes = hex::decode(&key).context("Invalid key hex")?;
|
||||
let recipient_key =
|
||||
nssa_core::encryption::shared_key_derivation::Secp256k1Point(key_bytes);
|
||||
|
||||
let sealed = holder.seal_for(&recipient_vpk);
|
||||
let sealed = holder.seal_for(&recipient_key);
|
||||
println!("{}", hex::encode(&sealed));
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
@ -238,7 +164,7 @@ impl WalletSubcommand for GroupSubcommand {
|
||||
if wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_group_key_holder(&name)
|
||||
.group_key_holder(&name)
|
||||
.is_some()
|
||||
{
|
||||
anyhow::bail!("Group '{name}' already exists");
|
||||
@ -246,11 +172,8 @@ impl WalletSubcommand for GroupSubcommand {
|
||||
|
||||
let sealed_bytes = hex::decode(&sealed).context("Invalid sealed hex")?;
|
||||
|
||||
// Resolve the account to get the VSK
|
||||
let account_id: nssa::AccountId = account
|
||||
.parse()
|
||||
.context("Invalid account ID (use Private/<base58>)")?;
|
||||
let (keychain, _) = wallet_core
|
||||
let account_id: nssa::AccountId = account.parse().context("Invalid account ID")?;
|
||||
let (keychain, _, _) = wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_private_account(account_id)
|
||||
@ -260,34 +183,10 @@ impl WalletSubcommand for GroupSubcommand {
|
||||
let holder = GroupKeyHolder::unseal(&sealed_bytes, &vsk)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to unseal: {e:?}"))?;
|
||||
|
||||
let epoch = holder.epoch();
|
||||
wallet_core
|
||||
.storage_mut()
|
||||
.user_data
|
||||
.insert_group_key_holder(name.clone(), holder);
|
||||
wallet_core.insert_group_key_holder(name.clone(), holder);
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
println!("Joined group '{name}' at epoch {epoch}");
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
|
||||
Self::Ratchet { name } => {
|
||||
let holder = wallet_core
|
||||
.storage_mut()
|
||||
.user_data
|
||||
.group_key_holders
|
||||
.get_mut(&name)
|
||||
.context(format!("Group '{name}' not found"))?;
|
||||
|
||||
let mut salt = [0_u8; 32];
|
||||
rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut salt);
|
||||
holder.ratchet(salt);
|
||||
|
||||
let epoch = holder.epoch();
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
println!("Ratcheted group '{name}' to epoch {epoch}");
|
||||
println!("Re-invite remaining members with 'group invite'");
|
||||
println!("Joined group '{name}'");
|
||||
Ok(SubcommandReturnValue::Empty)
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ use crate::{
|
||||
account::AccountSubcommand,
|
||||
chain::ChainSubcommand,
|
||||
config::ConfigSubcommand,
|
||||
group::GroupSubcommand,
|
||||
programs::{
|
||||
amm::AmmProgramAgnosticSubcommand, ata::AtaSubcommand,
|
||||
native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand,
|
||||
@ -25,6 +26,7 @@ use crate::{
|
||||
pub mod account;
|
||||
pub mod chain;
|
||||
pub mod config;
|
||||
pub mod group;
|
||||
pub mod programs;
|
||||
|
||||
pub(crate) trait WalletSubcommand {
|
||||
@ -57,6 +59,9 @@ pub enum Command {
|
||||
/// Associated Token Account program interaction subcommand.
|
||||
#[command(subcommand)]
|
||||
Ata(AtaSubcommand),
|
||||
/// Group key management (create, invite, join, derive keys).
|
||||
#[command(subcommand)]
|
||||
Group(GroupSubcommand),
|
||||
/// Check the wallet can connect to the node and builtin local programs
|
||||
/// match the remote versions.
|
||||
CheckHealth,
|
||||
@ -164,6 +169,7 @@ pub async fn execute_subcommand(
|
||||
Command::Token(token_subcommand) => token_subcommand.handle_subcommand(wallet_core).await?,
|
||||
Command::AMM(amm_subcommand) => amm_subcommand.handle_subcommand(wallet_core).await?,
|
||||
Command::Ata(ata_subcommand) => ata_subcommand.handle_subcommand(wallet_core).await?,
|
||||
Command::Group(group_subcommand) => group_subcommand.handle_subcommand(wallet_core).await?,
|
||||
Command::Config(config_subcommand) => {
|
||||
config_subcommand.handle_subcommand(wallet_core).await?
|
||||
}
|
||||
|
||||
@ -287,6 +287,41 @@ impl WalletCore {
|
||||
(account_id, cci)
|
||||
}
|
||||
|
||||
/// Insert a group key holder into storage.
|
||||
pub fn insert_group_key_holder(
|
||||
&mut self,
|
||||
name: String,
|
||||
holder: key_protocol::key_management::group_key_holder::GroupKeyHolder,
|
||||
) {
|
||||
self.storage.user_data.insert_group_key_holder(name, holder);
|
||||
}
|
||||
|
||||
/// Remove a group key holder from storage. Returns the removed holder if it existed.
|
||||
pub fn remove_group_key_holder(
|
||||
&mut self,
|
||||
name: &str,
|
||||
) -> Option<key_protocol::key_management::group_key_holder::GroupKeyHolder> {
|
||||
self.storage.user_data.group_key_holders.remove(name)
|
||||
}
|
||||
|
||||
/// Register a shared account in storage for sync tracking.
|
||||
pub fn register_shared_account(
|
||||
&mut self,
|
||||
account_id: AccountId,
|
||||
group_label: String,
|
||||
identifier: nssa_core::Identifier,
|
||||
) {
|
||||
use key_protocol::key_protocol_core::SharedAccountEntry;
|
||||
self.storage.user_data.shared_accounts.insert(
|
||||
account_id,
|
||||
SharedAccountEntry {
|
||||
group_label,
|
||||
identifier,
|
||||
account: Account::default(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Get account balance.
|
||||
pub async fn get_account_balance(&self, acc: AccountId) -> Result<u128> {
|
||||
Ok(self.sequencer_client.get_account_balance(acc).await?)
|
||||
|
||||
@ -337,7 +337,7 @@ async fn private_pda_preparation(
|
||||
.user_data
|
||||
.shared_accounts
|
||||
.get(&account_id)
|
||||
.cloned()
|
||||
.map(|e| e.account.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
let exists = acc != nssa_core::account::Account::default();
|
||||
@ -388,7 +388,7 @@ async fn private_shared_preparation(
|
||||
.user_data
|
||||
.shared_accounts
|
||||
.get(&account_id)
|
||||
.cloned()
|
||||
.map(|e| e.account.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
let exists = acc != nssa_core::account::Account::default();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user