spl_forge/commands/
wallet.rs1use 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}