Skip to main content

spl_forge/commands/
wallet.rs

1use crate::cli::{WalletArgs, WalletCommand};
2use crate::client::SplForgeClient;
3use crate::commands::config::ConfigData;
4use crate::common::theme;
5use anyhow::{Context, Result};
6use solana_sdk::pubkey::Pubkey;
7use solana_sdk::signer::Signer;
8use std::str::FromStr;
9use std::thread;
10use std::time::Duration;
11
12const LAMPORTS_PER_SOL: f64 = 1_000_000_000.0;
13
14pub async fn handle_wallet(args: WalletArgs) -> Result<()> {
15    let client = SplForgeClient::new().context("Failed to initialize client from config")?;
16
17    match args.command {
18        WalletCommand::Address => {
19            println!("{} {}", theme::label("Address:"), client.signer.pubkey());
20            Ok(())
21        }
22        WalletCommand::Balance { address } => {
23            let address = match address {
24                Some(value) => Pubkey::from_str(&value).context("Invalid --address public key")?,
25                None => client.signer.pubkey(),
26            };
27
28            let lamports = client
29                .client
30                .get_balance(&address)
31                .context("Failed to fetch wallet balance")?;
32            let sol = lamports as f64 / LAMPORTS_PER_SOL;
33
34            println!("{} {}", theme::label("Address:"), address);
35            println!("{} {:.9} SOL", theme::label("Balance:"), sol);
36            Ok(())
37        }
38        WalletCommand::Status => {
39            let config = ConfigData::load().context("Failed to load config")?;
40            let address = client.signer.pubkey();
41            let lamports = client
42                .client
43                .get_balance(&address)
44                .context("Failed to fetch wallet balance")?;
45            let sol = lamports as f64 / LAMPORTS_PER_SOL;
46
47            println!();
48            println!("{}", theme::heading("Wallet Status"));
49            println!("  {:<12} {}", theme::label("Address:"), address);
50            println!("  {:<12} {:.9} SOL", theme::label("Balance:"), sol);
51            println!("  {:<12} {}", theme::label("RPC URL:"), config.json_rpc_url);
52            println!("  {:<12} {}", theme::label("Commitment:"), config.commitment);
53            println!();
54
55            Ok(())
56        }
57        WalletCommand::Airdrop { amount } => {
58            let config = ConfigData::load().context("Failed to load config")?;
59            let rpc = config.json_rpc_url.to_ascii_lowercase();
60
61            let allowed = rpc.contains("devnet")
62                || rpc.contains("127.0.0.1")
63                || rpc.contains("localhost");
64
65            if !allowed {
66                anyhow::bail!(
67                    "Airdrop is only allowed on devnet or localnet. Current RPC: {}",
68                    config.json_rpc_url
69                );
70            }
71
72            if amount <= 0.0 {
73                anyhow::bail!("Airdrop amount must be greater than 0");
74            }
75
76            let lamports = (amount * LAMPORTS_PER_SOL).round() as u64;
77            let address = client.signer.pubkey();
78            let before = client
79                .client
80                .get_balance(&address)
81                .context("Failed to fetch pre-airdrop balance")?;
82
83            println!(
84                "{} {:.9} SOL {}",
85                theme::action("Requesting airdrop of"),
86                amount,
87                theme::muted("to active wallet...")
88            );
89
90            let sig = client
91                .client
92                .request_airdrop(&address, lamports)
93                .context("Airdrop request failed")?;
94
95            let mut after = before;
96            for _ in 0..30 {
97                let _ = client.client.get_signature_status(&sig);
98                after = client.client.get_balance(&address).unwrap_or(before);
99                if after >= before + lamports {
100                    break;
101                }
102                thread::sleep(Duration::from_millis(500));
103            }
104
105            if after < before + lamports {
106                anyhow::bail!(
107                    "Airdrop did not finalize in time. Requested {:.9} SOL",
108                    amount
109                );
110            }
111
112            println!("{} {}", theme::success("Airdrop signature:"), sig);
113            println!(
114                "{} {:.9} SOL",
115                theme::success("New balance:"),
116                (after as f64) / LAMPORTS_PER_SOL
117            );
118            Ok(())
119        }
120    }
121}