use solana_client::rpc_client::RpcClient;
use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signer},
native_token::LAMPORTS_PER_SOL,
transaction::Transaction,
program_pack::Pack,
};
use solana_system_interface::instruction as system_instruction;
use solana_commitment_config::CommitmentConfig;
use spl_token::state::Mint;
#[allow(deprecated)]
use spl_token::instruction as token_instruction;
use std::time::Duration;
use tokio::time::sleep;
use anyhow::{Result, Context};
use serde_json::json;
use crate::utils::{log_action, log_info, load_keypair_from_env, output_json, truncate_pubkey};
pub async fn run(rpc_url: &str, json_output: bool) -> Result<()> {
dotenvy::dotenv().context("Failed to load .env file. Make sure you're in the project directory and have run 'xforth init' first.")?;
if !json_output {
log_info("Funding test wallets...");
}
let client = RpcClient::new_with_commitment(rpc_url.to_string(), CommitmentConfig::confirmed());
let payer_keypair = load_keypair_from_env("PAYER_KEYPAIR")?;
let facilitator_keypair = load_keypair_from_env("FACILITATOR_KEYPAIR")?;
let payer_tx = airdrop_with_retry(&client, &payer_keypair.pubkey(), "Payer", json_output).await?;
let facilitator_tx = airdrop_with_retry(&client, &facilitator_keypair.pubkey(), "Facilitator", json_output).await?;
let mint_pubkey = mint_test_tokens(&client, &payer_keypair, json_output).await?;
if json_output {
output_json(&json!({
"command": "fund",
"result": "success",
"payer_airdrop_tx": payer_tx,
"facilitator_airdrop_tx": facilitator_tx,
"mint_pubkey": mint_pubkey.to_string(),
"xusd_minted": 1000,
}));
}
Ok(())
}
async fn airdrop_with_retry(
client: &RpcClient,
pubkey: &Pubkey,
label: &str,
json_output: bool,
) -> Result<String> {
let amount_sol = 0.5;
let amount_lamports = (amount_sol * LAMPORTS_PER_SOL as f64) as u64;
if !json_output {
log_action(&format!("Airdropping {} SOL to {}...", amount_sol, label));
}
let mut last_error = None;
for attempt in 1..=5 {
match client.request_airdrop(pubkey, amount_lamports) {
Ok(sig) => {
for _ in 0..30 {
match client.get_signature_status(&sig) {
Ok(Some(Ok(_))) => {
if !json_output {
log_action(&format!("{} funded: {} Tx: {}", label, truncate_pubkey(&pubkey.to_string()), sig));
}
return Ok(sig.to_string());
}
Ok(Some(Err(e))) => {
return Err(anyhow::anyhow!("Transaction failed: {:?}", e));
}
_ => {
sleep(Duration::from_millis(1000)).await;
}
}
}
return Err(anyhow::anyhow!("Transaction confirmation timeout"));
}
Err(err) => {
last_error = Some(err.to_string());
if err.to_string().contains("429") || err.to_string().to_lowercase().contains("too many") {
let delay_ms = 500 * (2_u64.pow(attempt - 1)); if !json_output {
log_info(&format!("Server responded with 429... Retrying after {}ms...", delay_ms));
}
sleep(Duration::from_millis(delay_ms)).await;
} else {
return Err(anyhow::anyhow!("Airdrop failed: {}", err));
}
}
}
}
Err(anyhow::anyhow!("Max retries exceeded for airdrop. Last error: {:?}", last_error))
}
async fn mint_test_tokens(
client: &RpcClient,
payer: &Keypair,
json_output: bool,
) -> Result<Pubkey> {
if !json_output {
log_action("Minting 1000 xUSD test tokens to Payer...");
}
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let decimals = 6;
let mint_rent = client.get_minimum_balance_for_rent_exemption(Mint::LEN)?;
let create_mint_ix = system_instruction::create_account(
&payer.pubkey(),
&mint_pubkey,
mint_rent,
Mint::LEN as u64,
&spl_token::id(),
);
let init_mint_ix = token_instruction::initialize_mint(
&spl_token::id(),
&mint_pubkey,
&payer.pubkey(),
None,
decimals,
)?;
let recent_blockhash = client.get_latest_blockhash()?;
let transaction = Transaction::new_signed_with_payer(
&[create_mint_ix, init_mint_ix],
Some(&payer.pubkey()),
&[payer, &mint_keypair],
recent_blockhash,
);
let _sig = client.send_and_confirm_transaction(&transaction)?;
if !json_output {
log_action(&format!("Minted 1000 xUSD to Payer. Mint: {}", truncate_pubkey(&mint_pubkey.to_string())));
}
std::fs::write(".env.mint", format!("XUSD_MINT={}\n", mint_pubkey.to_string()))?;
Ok(mint_pubkey)
}