layer_climb_cli/command/
wallet.rs

1use anyhow::Result;
2use bip39::Mnemonic;
3use clap::Subcommand;
4use layer_climb::{prelude::*, proto::abci::TxResponse};
5use rand::Rng;
6
7#[derive(Debug, Clone, Subcommand)]
8pub enum WalletCommand {
9    /// Creates a wallet with a random mnemonic
10    Create,
11    /// Shows the balance and address for a given mnemonic
12    /// If no mnemonic is provided, the default client mnemonic will be used
13    Show {
14        #[arg(long)]
15        mnemonic: Option<String>,
16    },
17    /// Shows the balances for a given address
18    Balance {
19        #[arg(long)]
20        /// The address to show the balance for
21        address: String,
22        /// Denom to show the balance for, if not set will default to the chain's gas denom
23        #[arg(long)]
24        denom: Option<String>,
25    },
26    AllBalances {
27        #[arg(long)]
28        /// The address to show the balances for
29        address: String,
30    },
31    /// Transfer funds to another address
32    Transfer {
33        #[arg(long)]
34        /// The address to send the funds to
35        to: String,
36        /// The amount to send
37        #[arg(long)]
38        amount: u128,
39        /// The denom of the funds to send, if not set will use the chain gas denom
40        #[arg(long)]
41        denom: Option<String>,
42    },
43}
44
45impl WalletCommand {
46    pub async fn run(
47        &self,
48        client: impl Into<AnyClient>,
49        rng: &mut impl Rng,
50        log: impl Fn(WalletLog),
51    ) -> Result<()> {
52        let client = client.into();
53        match self {
54            WalletCommand::Create => {
55                let (addr, mnemonic) =
56                    create_wallet(client.as_querier().chain_config.clone(), rng).await?;
57                log(WalletLog::Create { addr, mnemonic });
58            }
59            WalletCommand::Show { mnemonic } => {
60                let addr = match mnemonic {
61                    None => client.as_signing().addr.clone(),
62                    Some(mnemonic) => {
63                        let signer = KeySigner::new_mnemonic_str(mnemonic, None)?;
64                        client
65                            .as_querier()
66                            .chain_config
67                            .address_from_pub_key(&signer.public_key().await?)?
68                    }
69                };
70
71                let balances = client.as_querier().all_balances(addr.clone(), None).await?;
72
73                if balances.is_empty() {
74                    log(WalletLog::Show {
75                        addr,
76                        balances: vec![],
77                    });
78                } else {
79                    log(WalletLog::Show {
80                        addr,
81                        balances: balances.clone(),
82                    });
83                }
84            }
85            WalletCommand::Balance { address, denom } => {
86                let addr = client.as_querier().chain_config.parse_address(address)?;
87                let balance = client
88                    .as_querier()
89                    .balance(addr.clone(), denom.clone())
90                    .await?;
91                let denom = denom
92                    .clone()
93                    .unwrap_or_else(|| client.as_querier().chain_config.gas_denom.clone());
94                log(WalletLog::Balance {
95                    addr,
96                    balance: new_coin(balance.unwrap_or_default(), denom),
97                });
98            }
99            WalletCommand::AllBalances { address } => {
100                let addr = client.as_querier().chain_config.parse_address(address)?;
101                let balances = client.as_querier().all_balances(addr.clone(), None).await?;
102                log(WalletLog::AllBalances { addr, balances });
103            }
104            WalletCommand::Transfer { to, amount, denom } => {
105                let to = client.as_querier().chain_config.parse_address(to)?;
106                let tx_resp = client
107                    .as_signing()
108                    .transfer(*amount, &to, denom.as_deref(), None)
109                    .await?;
110                log(WalletLog::Transfer {
111                    to,
112                    amount: *amount,
113                    denom: denom
114                        .clone()
115                        .unwrap_or_else(|| client.as_querier().chain_config.gas_denom.clone()),
116                    tx_resp: Box::new(tx_resp),
117                });
118            }
119        }
120        Ok(())
121    }
122}
123
124pub enum WalletLog {
125    Create {
126        addr: Address,
127        mnemonic: Mnemonic,
128    },
129    Show {
130        addr: Address,
131        balances: Vec<Coin>,
132    },
133    Balance {
134        addr: Address,
135        balance: Coin,
136    },
137    AllBalances {
138        addr: Address,
139        balances: Vec<Coin>,
140    },
141    Transfer {
142        to: Address,
143        amount: u128,
144        denom: String,
145        tx_resp: Box<TxResponse>,
146    },
147}
148
149pub async fn create_wallet(
150    chain_config: ChainConfig,
151    rng: &mut impl Rng,
152) -> Result<(Address, Mnemonic)> {
153    let entropy: [u8; 32] = rng.random();
154    let mnemonic = Mnemonic::from_entropy(&entropy)?;
155
156    let signer = KeySigner::new_mnemonic_iter(mnemonic.words(), None)?;
157    let addr = chain_config.address_from_pub_key(&signer.public_key().await?)?;
158
159    Ok((addr, mnemonic))
160}