Skip to main content

spl_forge/commands/
config.rs

1use crate::cli::{ConfigArgs, ConfigCommand};
2use crate::common::paths::{get_solana_keypair_path, path};
3use crate::common::theme;
4use anyhow::Result;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::path::PathBuf;
8
9#[derive(Serialize, Deserialize, Debug, Clone)]
10pub struct ConfigData {
11    pub json_rpc_url: String,
12    pub websocket_url: String,
13    pub keypair_path: String,
14    pub commitment: String,
15    pub address_labels: HashMap<String, String>,
16}
17
18impl ConfigData {
19    pub fn default_values() -> Self {
20        let default_keypair_path = dirs::home_dir()
21            .map(|p| p.join(".config/spl-forge/id.json").to_string_lossy().to_string())
22            .unwrap_or_else(|| "spl-forge-keypair.json".to_string());
23
24        let mut labels = HashMap::new();
25        labels.insert(
26            "11111111111111111111111111111111".to_string(),
27            "System Program".to_string(),
28        );
29
30        Self {
31            json_rpc_url: "https://api.mainnet-beta.solana.com".to_string(),
32            websocket_url: "wss://api.mainnet-beta.solana.com/".to_string(),
33            keypair_path: default_keypair_path,
34            commitment: "confirmed".to_string(),
35            address_labels: labels,
36        }
37    }
38
39    pub fn load() -> Result<Self> {
40        let path = path()?;
41        let config_data = std::fs::read_to_string(path)?;
42        Ok(serde_json::from_str(&config_data)?)
43    }
44
45    pub fn save(&self) -> Result<()> {
46        let path = path()?;
47        let config_data = serde_json::to_string_pretty(self)?;
48        std::fs::write(path, config_data)?;
49        Ok(())
50    }
51}
52
53pub async fn handle_config(args: ConfigArgs) -> Result<()> {
54    match args.command {
55        ConfigCommand::Get => handle_get().await,
56        ConfigCommand::Reset => handle_reset().await,
57        ConfigCommand::Set {
58            network,
59            url,
60            keypair,
61            commitment,
62        } => {
63            handle_set(network, url, keypair, commitment).await
64        }
65    }
66}
67
68fn apply_network_preset(config: &mut ConfigData, network: &str) -> Result<()> {
69    match network.to_ascii_lowercase().as_str() {
70        "devnet" => {
71            config.json_rpc_url = "https://api.devnet.solana.com".to_string();
72            config.websocket_url = "wss://api.devnet.solana.com/".to_string();
73            config.commitment = "confirmed".to_string();
74        }
75        "mainnet" | "mainnet-beta" => {
76            config.json_rpc_url = "https://api.mainnet-beta.solana.com".to_string();
77            config.websocket_url = "wss://api.mainnet-beta.solana.com/".to_string();
78            config.commitment = "confirmed".to_string();
79        }
80        "localhost" | "localnet" => {
81            config.json_rpc_url = "http://127.0.0.1:8899".to_string();
82            config.websocket_url = "ws://127.0.0.1:8900/".to_string();
83            config.commitment = "confirmed".to_string();
84        }
85        _ => {
86            anyhow::bail!(
87                "Invalid network preset '{}'. Use one of: devnet, mainnet, localhost",
88                network
89            );
90        }
91    }
92
93    Ok(())
94}
95
96async fn handle_get() -> Result<()> {
97    let config = ConfigData::load()?;
98    println!();
99    println!("{}", theme::heading("Current Configuration:"));
100    println!("  {:<15} {}", theme::label("Config Path:"), path()?.to_string_lossy());
101    println!("  {:<15} {}", theme::label("RPC URL:"), config.json_rpc_url);
102    println!("  {:<15} {}", theme::label("Websocket URL:"), config.websocket_url);
103    println!("  {:<15} {}", theme::label("Keypair Path:"), config.keypair_path);
104    println!("  {:<15} {}", theme::label("Commitment:"), config.commitment);
105    println!();
106    Ok(())
107}
108
109async fn handle_reset() -> Result<()> {
110    let config = ConfigData::default_values();
111    config.save()?;
112    println!();
113    println!("{}", theme::success("Configuration has been reset to default values."));
114    handle_get().await?; // Show the newly reset config
115    Ok(())
116}
117
118async fn handle_set(
119    network: Option<String>,
120    url: Option<String>,
121    keypair: Option<PathBuf>,
122    commitment: Option<String>,
123) -> Result<()> {
124    if network.is_none() && url.is_none() && keypair.is_none() && commitment.is_none() {
125        println!();
126        println!("{}", theme::warning("No values provided to set. Use --url, --keypair, or --commitment."));
127        println!(
128            "{}",
129            theme::muted(
130                "Examples: spl-forge config set devnet | spl-forge config set localhost | spl-forge config set --url https://api.devnet.solana.com"
131            )
132        );
133        println!();
134        return Ok(());
135    }
136
137    let mut config = ConfigData::load()?;
138
139    if let Some(network) = network {
140        apply_network_preset(&mut config, &network)?;
141    }
142
143    if let Some(url) = url {
144        config.json_rpc_url = url;
145    }
146
147    if let Some(keypair) = keypair {
148        if keypair.to_string_lossy() == "solana-cli" {
149            let solana_keypair_path = get_solana_keypair_path();
150            config.keypair_path = solana_keypair_path.to_string_lossy().to_string();
151        } else {
152            config.keypair_path = keypair.to_string_lossy().to_string();
153        }
154    }
155
156    if let Some(commitment) = commitment {
157        config.commitment = commitment;
158    }
159
160    config.save()?;
161    println!();
162    println!("{}", theme::success("Configuration saved successfully."));
163    handle_get().await?;
164    Ok(())
165}