layer_climb_cli/command/
wallet.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use anyhow::Result;
use bip39::Mnemonic;
use clap::Subcommand;
use layer_climb::{prelude::*, proto::abci::TxResponse};
use rand::Rng;

#[derive(Debug, Clone, Subcommand)]
pub enum WalletCommand {
    /// Creates a wallet with a random mnemonic
    Create,
    /// Shows the current signing wallet balance and address
    Show,
    /// Shows the balances for a given address
    Balance {
        #[arg(long)]
        /// The address to show the balance for
        address: String,
        /// Denom to show the balance for, if not set will default to the chain's gas denom
        #[arg(long)]
        denom: Option<String>,
    },
    AllBalances {
        #[arg(long)]
        /// The address to show the balances for
        address: String,
    },
    /// Transfer funds to another address
    Transfer {
        #[arg(long)]
        /// The address to send the funds to
        to: String,
        /// The amount to send
        #[arg(long)]
        amount: u128,
        /// The denom of the funds to send, if not set will use the chain gas denom
        #[arg(long)]
        denom: Option<String>,
    },
}

impl WalletCommand {
    pub async fn run(
        &self,
        client: impl Into<AnyClient>,
        rng: &mut impl Rng,
        log: impl Fn(WalletLog),
    ) -> Result<()> {
        let client = client.into();
        match self {
            WalletCommand::Create => {
                let (addr, mnemonic) =
                    create_wallet(client.as_querier().chain_config.clone(), rng).await?;
                log(WalletLog::Create { addr, mnemonic });
            }
            WalletCommand::Show => {
                let balances = client
                    .as_querier()
                    .all_balances(client.as_signing().addr.clone(), None)
                    .await?;

                if balances.is_empty() {
                    log(WalletLog::Show {
                        addr: client.as_signing().addr.clone(),
                        balances: vec![],
                    });
                } else {
                    log(WalletLog::Show {
                        addr: client.as_signing().addr.clone(),
                        balances: balances.clone(),
                    });
                }
            }
            WalletCommand::Balance { address, denom } => {
                let addr = client.as_querier().chain_config.parse_address(address)?;
                let balance = client
                    .as_querier()
                    .balance(addr.clone(), denom.clone())
                    .await?;
                let denom = denom
                    .clone()
                    .unwrap_or_else(|| client.as_querier().chain_config.gas_denom.clone());
                log(WalletLog::Balance {
                    addr,
                    balance: new_coin(balance.unwrap_or_default(), denom),
                });
            }
            WalletCommand::AllBalances { address } => {
                let addr = client.as_querier().chain_config.parse_address(address)?;
                let balances = client.as_querier().all_balances(addr.clone(), None).await?;
                log(WalletLog::AllBalances { addr, balances });
            }
            WalletCommand::Transfer { to, amount, denom } => {
                let to = client.as_querier().chain_config.parse_address(to)?;
                let tx_resp = client
                    .as_signing()
                    .transfer(*amount, &to, denom.as_deref(), None)
                    .await?;
                log(WalletLog::Transfer {
                    to,
                    amount: *amount,
                    denom: denom
                        .clone()
                        .unwrap_or_else(|| client.as_querier().chain_config.gas_denom.clone()),
                    tx_resp: Box::new(tx_resp),
                });
            }
        }
        Ok(())
    }
}

pub enum WalletLog {
    Create {
        addr: Address,
        mnemonic: Mnemonic,
    },
    Show {
        addr: Address,
        balances: Vec<Coin>,
    },
    Balance {
        addr: Address,
        balance: Coin,
    },
    AllBalances {
        addr: Address,
        balances: Vec<Coin>,
    },
    Transfer {
        to: Address,
        amount: u128,
        denom: String,
        tx_resp: Box<TxResponse>,
    },
}

pub async fn create_wallet(
    chain_config: ChainConfig,
    rng: &mut impl Rng,
) -> Result<(Address, Mnemonic)> {
    let entropy: [u8; 32] = rng.random();
    let mnemonic = Mnemonic::from_entropy(&entropy)?;

    let signer = KeySigner::new_mnemonic_iter(mnemonic.words(), None)?;
    let addr = chain_config.address_from_pub_key(&signer.public_key().await?)?;

    Ok((addr, mnemonic))
}