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::LeeTransaction}; use derive_more::Display; use futures::TryFutureExt as _; use lee::{ProgramDeploymentTransaction, program::Program}; use sequencer_service_rpc::RpcClient as _; pub use crate::helperfunctions::{read_mnemonic, read_pin}; use crate::{ WalletCore, account::{AccountIdWithPrivacy, Label}, cli::{ account::AccountSubcommand, chain::ChainSubcommand, config::ConfigSubcommand, group::GroupSubcommand, keycard::KeycardSubcommand, programs::{ amm::AmmProgramAgnosticSubcommand, ata::AtaSubcommand, native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand, vault::VaultSubcommand, }, }, storage::Storage, }; pub mod account; pub mod chain; pub mod config; pub mod group; pub mod keycard; pub mod programs; pub(crate) trait WalletSubcommand { async fn handle_subcommand(self, wallet_core: &mut WalletCore) -> Result; } /// Represents CLI command for a wallet. #[derive(Subcommand, Debug, Clone)] #[clap(about)] pub enum Command { /// Authenticated transfer subcommand. #[command(subcommand)] AuthTransfer(AuthTransferSubcommand), /// Generic chain info subcommand. #[command(subcommand)] ChainInfo(ChainSubcommand), /// Account view and sync subcommand. #[command(subcommand)] Account(AccountSubcommand), /// Pinata program interaction subcommand. #[command(subcommand)] Pinata(PinataProgramAgnosticSubcommand), /// Token program interaction subcommand. #[command(subcommand)] Token(TokenProgramAgnosticSubcommand), /// AMM program interaction subcommand. #[command(subcommand)] AMM(AmmProgramAgnosticSubcommand), /// Associated Token Account program interaction subcommand. #[command(subcommand)] Ata(AtaSubcommand), /// Vault program interaction subcommand. #[command(subcommand)] Vault(VaultSubcommand), /// Group key management (create, invite, join, derive keys). #[command(subcommand)] Group(GroupSubcommand), /// Check the wallet can connect to the node and builtin local programs /// match the remote versions. CheckHealth, /// Command to setup config, get and set config fields. #[command(subcommand)] Config(ConfigSubcommand), /// Restoring keys from given password at given `depth`. /// /// !!!WARNING!!! will rewrite current storage. RestoreKeys { #[arg(short, long)] /// Indicates, how deep in tree accounts may be. Affects command complexity. depth: u32, }, /// Deploy a program. DeployProgram { binary_filepath: PathBuf }, /// Keycard hardware wallet management. #[command(subcommand)] Keycard(KeycardSubcommand), } /// To execute commands, env var `LEE_WALLET_HOME_DIR` must be set into directory with config. /// /// All account addresses must be valid 32 byte base58 strings. /// /// All account `account_ids` must be provided as {`privacy_prefix}/{account_id`}, /// where valid options for `privacy_prefix` is `Public` and `Private`. #[derive(Parser, Debug)] #[clap(version, about)] pub struct Args { /// Continious run flag. #[arg(short, long)] pub continuous_run: bool, /// Basic authentication in the format `user` or `user:password`. #[arg(long)] pub auth: Option, /// Wallet command. #[command(subcommand)] pub command: Option, } #[derive(Debug, Clone)] pub enum SubcommandReturnValue { PrivacyPreservingTransfer { tx_hash: HashType }, RegisterAccount { account_id: lee::AccountId }, Account(lee::Account), Empty, SyncedToBlock(u64), } #[derive(Debug, Display, Clone, PartialEq, Eq, Hash)] pub enum CliAccountMention { #[display("{_0}")] Id(AccountIdWithPrivacy), #[display("{_0}")] Label(Label), #[display("{_0}")] KeyPath(String), } impl CliAccountMention { pub fn resolve(&self, storage: &Storage) -> Result { 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}`")), Self::KeyPath(path) => { let pin = read_pin()?; let id_str = keycard_wallet::KeycardWallet::get_public_account_id_for_path_with_connect( &pin, path, ) .map_err(anyhow::Error::from)?; AccountIdWithPrivacy::from_str(&id_str) .map_err(|e| anyhow::anyhow!("Invalid account id from keycard: {e}")) } } } #[must_use] pub fn key_path(&self) -> Option<&str> { match self { Self::KeyPath(path) => Some(path), Self::Id(_) | Self::Label(_) => None, } } #[must_use] pub fn into_public_identity(self, account_id: lee::AccountId) -> crate::AccountIdentity { match self { Self::KeyPath(key_path) => crate::AccountIdentity::PublicKeycard { account_id, key_path, }, Self::Id(_) | Self::Label(_) => crate::AccountIdentity::Public(account_id), } } } impl FromStr for CliAccountMention { type Err = std::convert::Infallible; fn from_str(s: &str) -> std::result::Result { if s.starts_with("m/") { return Ok(Self::KeyPath(s.to_owned())); } AccountIdWithPrivacy::from_str(s).map_or_else( |_| Ok(Self::Label(Label::new(s.to_owned()))), |account_id| Ok(Self::Id(account_id)), ) } } impl From