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