use sol_safekey::{KeyManager, solana_utils::*};
use solana_sdk::{signature::Keypair, signer::Signer};
use std::io::{self, BufRead};
use anyhow::{Result, Context};
use colored::Colorize;
const DEFAULT_WALLET_PATH: &str = "keystore.json";
const RPC_URL: &str = "https://api.devnet.solana.com";
fn read_password_from_stdin() -> Result<String> {
if atty::is(atty::Stream::Stdin) {
print!("π Enter wallet password: ");
std::io::Write::flush(&mut std::io::stdout())?;
let password = rpassword::read_password()
.context("Failed to read password")?;
return Ok(password);
}
let stdin = io::stdin();
let password = stdin.lock()
.lines()
.next()
.ok_or_else(|| anyhow::anyhow!("No password provided via stdin"))?
.context("Failed to read password from stdin")?
.trim()
.to_string();
if password.is_empty() {
anyhow::bail!("Password cannot be empty");
}
Ok(password)
}
fn ensure_wallet(wallet_path: &str, password: &str) -> Result<Keypair> {
if std::path::Path::new(wallet_path).exists() {
println!("{} Wallet found: {}", "β
".green(), wallet_path);
println!("{} Unlocking wallet...", "π".cyan());
let json = std::fs::read_to_string(wallet_path)
.context("Failed to read wallet file")?;
let keypair = KeyManager::keypair_from_encrypted_json(&json, password)
.map_err(|e| anyhow::anyhow!("Failed to unlock wallet: {}", e))?;
println!("{} Wallet unlocked!", "β
".green());
println!("π Address: {}", keypair.pubkey().to_string().yellow());
Ok(keypair)
} else {
println!("{} Wallet not found: {}", "β οΈ".yellow(), wallet_path);
println!("{} Creating new encrypted wallet...", "π".cyan());
println!();
let keypair = KeyManager::generate_keypair();
println!("{} Generated new keypair", "π".cyan());
println!("π Address: {}", keypair.pubkey().to_string().yellow());
let json = KeyManager::keypair_to_encrypted_json(&keypair, password)
.map_err(|e| anyhow::anyhow!("Failed to encrypt wallet: {}", e))?;
std::fs::write(wallet_path, json)
.context("Failed to save wallet file")?;
println!("{} Wallet saved to: {}", "πΎ".green(), wallet_path);
println!();
println!("{} IMPORTANT: Backup your wallet file and remember your password!", "β οΈ".yellow());
println!(" Wallet file: {}", wallet_path);
println!();
Ok(keypair)
}
}
fn check_balance(keypair: &Keypair, rpc_url: &str) -> Result<()> {
println!("\nββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!("{} Checking Balance", "π°".cyan());
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
let client = SolanaClient::new(rpc_url.to_string());
let balance_lamports = client.get_sol_balance(&keypair.pubkey())?;
let balance_sol = lamports_to_sol(balance_lamports);
println!("Address: {}", keypair.pubkey().to_string().yellow());
println!("Balance: {} SOL ({} lamports)",
balance_sol.to_string().green().bold(),
balance_lamports);
if balance_lamports == 0 {
println!();
println!("{} Get devnet SOL:", "π‘".yellow());
println!(" solana airdrop 2 {} --url devnet", keypair.pubkey());
}
Ok(())
}
fn demo_transfer(keypair: &Keypair, rpc_url: &str) -> Result<()> {
println!("\nββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!("{} Transfer SOL Demo", "π€".cyan());
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
let client = SolanaClient::new(rpc_url.to_string());
let balance = client.get_sol_balance(&keypair.pubkey())?;
if balance < 10_000_000 { println!("{} Insufficient balance for transfer demo (need 0.01 SOL)", "β οΈ".yellow());
println!(" Current balance: {} SOL", lamports_to_sol(balance));
println!(" Please fund your wallet on devnet:");
println!(" β https://faucet.solana.com");
println!(" β solana airdrop 1 {}", keypair.pubkey());
return Ok(());
}
println!("{} Sufficient balance for demo", "β
".green());
println!();
println!("Example code to transfer 0.001 SOL:");
println!("{}", "βββββββββββββββββββββββββββββββββββββ".dimmed());
println!("{}", "let recipient = Pubkey::from_str(\"<address>\")?;".yellow());
println!("{}", "let lamports = 1_000_000; // 0.001 SOL".yellow());
println!("{}", "let signature = client.transfer_sol(keypair, &recipient, lamports)?;".yellow());
println!("{}", "println!(\"Signature: {}\", signature);".yellow());
println!("{}", "βββββββββββββββββββββββββββββββββββββ".dimmed());
Ok(())
}
fn demo_wrap_sol(keypair: &Keypair, rpc_url: &str) -> Result<()> {
println!("\nββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!("{} Wrap SOL to WSOL Demo", "π".cyan());
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
let client = SolanaClient::new(rpc_url.to_string());
let balance = client.get_sol_balance(&keypair.pubkey())?;
if balance < 20_000_000 { println!("{} Insufficient balance for wrap demo (need 0.02 SOL)", "β οΈ".yellow());
println!(" Current balance: {} SOL", lamports_to_sol(balance));
return Ok(());
}
println!("{} Sufficient balance for demo", "β
".green());
println!();
println!("Example code to wrap 0.01 SOL to WSOL:");
println!("{}", "βββββββββββββββββββββββββββββββββββββ".dimmed());
println!("{}", "let lamports = 10_000_000; // 0.01 SOL".yellow());
println!("{}", "let signature = client.wrap_sol(keypair, lamports)?;".yellow());
println!("{}", "println!(\"Wrapped SOL to WSOL: {}\", signature);".yellow());
println!("{}", "βββββββββββββββββββββββββββββββββββββ".dimmed());
Ok(())
}
fn demo_unwrap_sol(_keypair: &Keypair, _rpc_url: &str) -> Result<()> {
println!("\nββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!("{} Unwrap WSOL to SOL Demo", "π".cyan());
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!("Example code to unwrap all WSOL back to SOL:");
println!("{}", "βββββββββββββββββββββββββββββββββββββ".dimmed());
println!("{}", "let signature = client.unwrap_sol(keypair)?;".yellow());
println!("{}", "println!(\"Unwrapped WSOL to SOL: {}\", signature);".yellow());
println!("{}", "βββββββββββββββββββββββββββββββββββββ".dimmed());
Ok(())
}
fn demo_create_nonce(keypair: &Keypair, rpc_url: &str) -> Result<()> {
println!("\nββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!("{} Durable Nonce Account Demo", "π".cyan());
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
let client = SolanaClient::new(rpc_url.to_string());
let balance = client.get_sol_balance(&keypair.pubkey())?;
if balance < 5_000_000 { println!("{} Insufficient balance for nonce account (need 0.005 SOL)", "β οΈ".yellow());
println!(" Current balance: {} SOL", lamports_to_sol(balance));
return Ok(());
}
println!("{} Sufficient balance for demo", "β
".green());
println!();
println!("What is a durable nonce account?");
println!(" - Allows offline transaction signing");
println!(" - Useful for scheduled or delayed transactions");
println!(" - Replaces recent_blockhash with durable nonce");
println!();
println!("Example code to create nonce account:");
println!("{}", "βββββββββββββββββββββββββββββββββββββ".dimmed());
println!("{}", "let (nonce_pubkey, sig) = client.create_nonce_account(keypair)?;".yellow());
println!("{}", "println!(\"Nonce account: {}\", nonce_pubkey);".yellow());
println!("{}", "println!(\"Signature: {}\", sig);".yellow());
println!("{}", "βββββββββββββββββββββββββββββββββββββ".dimmed());
Ok(())
}
fn demo_transfer_token(_keypair: &Keypair, _rpc_url: &str) -> Result<()> {
println!("\nββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!("{} Transfer SPL Token Demo", "πͺ".cyan());
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!("Example code to transfer SPL tokens:");
println!("{}", "βββββββββββββββββββββββββββββββββββββ".dimmed());
println!("{}", "let mint = Pubkey::from_str(\"<token_mint_address>\")?;".yellow());
println!("{}", "let recipient = Pubkey::from_str(\"<recipient_address>\")?;".yellow());
println!("{}", "let amount = 1000; // token amount (smallest units)".yellow());
println!("{}", "let signature = client.transfer_token(keypair, &recipient, &mint, amount)?;".yellow());
println!("{}", "println!(\"Token transfer: {}\", signature);".yellow());
println!("{}", "βββββββββββββββββββββββββββββββββββββ".dimmed());
Ok(())
}
fn main() -> Result<()> {
let args: Vec<String> = std::env::args().skip(1).collect();
if args.first().map(|s| s.as_str()) == Some("safekey") {
if let Err(e) = sol_safekey::interactive::show_main_menu() {
eprintln!("β {}", e);
std::process::exit(1);
}
return Ok(());
}
println!("\nββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!("β Complete Bot Integration Example - sol-safekey β");
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n");
let wallet_path = DEFAULT_WALLET_PATH;
let rpc_url = RPC_URL;
println!("βοΈ Configuration:");
println!(" Wallet: {}", wallet_path);
println!(" RPC: {}", rpc_url);
println!(" Network: devnet");
println!();
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!("{} Password Input", "π".cyan());
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
let password = read_password_from_stdin()
.context("Failed to read password")?;
println!("{} Password received", "β
".green());
println!();
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!("{} Wallet Setup", "π".cyan());
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
let keypair = ensure_wallet(wallet_path, &password)?;
drop(password);
println!("\nββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!("{} Bot Operations", "π€".cyan());
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
check_balance(&keypair, rpc_url)?;
demo_transfer(&keypair, rpc_url)?;
demo_wrap_sol(&keypair, rpc_url)?;
demo_unwrap_sol(&keypair, rpc_url)?;
demo_transfer_token(&keypair, rpc_url)?;
demo_create_nonce(&keypair, rpc_url)?;
println!("\nββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!("{} Bot Integration Complete!", "β
".green());
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!();
println!("{} Your bot can now:", "π".cyan());
println!(" β Create and manage encrypted wallets");
println!(" β Check SOL and token balances");
println!(" β Transfer SOL and SPL tokens");
println!(" β Wrap/unwrap SOL to WSOL");
println!(" β Create durable nonce accounts");
println!(" β Sign and submit transactions");
println!();
println!("{} Integration Pattern:", "π‘".cyan());
println!(" β’ Password via stdin pipe (never environment variables)");
println!(" β’ Wallet file encrypted with AES-256");
println!(" β’ Use startup-example.sh for secure password handling");
println!(" β’ All operations use synchronous Solana RPC client");
println!();
println!("{} Next Steps:", "π".cyan());
println!(" 1. Customize the operations for your trading strategy");
println!(" 2. Add your bot logic (monitoring, decision making, execution)");
println!(" 3. Integrate with your price feeds and trading signals");
println!(" 4. Test on devnet before moving to mainnet");
println!();
println!("{} For production use:", "β οΈ".yellow());
println!(" β’ Always test on devnet first");
println!(" β’ Keep your wallet file secure");
println!(" β’ Never share your password");
println!(" β’ Backup your keystore.json file");
println!();
Ok(())
}