libre_cli/
setup.rs

1use anyhow::{anyhow, Result};
2use dirs::home_dir;
3use serde::{Deserialize, Serialize};
4use solana_client::rpc_client::RpcClient;
5use solana_sdk::{
6    clock::Slot,
7    commitment_config::CommitmentConfig,
8    hash::Hash,
9    signature::{read_keypair_file, Keypair},
10};
11use std::{fs::File, path::PathBuf, str::FromStr};
12
13#[derive(Debug, Deserialize, Serialize)]
14struct SolanaConfig {
15    pub json_rpc_url: String,
16    pub keypair_path: String,
17    pub commitment: String,
18}
19
20pub struct CliConfig {
21    pub client: RpcClient,
22    pub keypair: Keypair,
23    pub recent_blockhash: Hash,
24    pub recent_slot: Slot,
25}
26
27#[derive(Debug, Default)]
28pub struct CliConfigBuilder {
29    pub json_rpc_url: Option<String>,
30    pub keypair_path: Option<PathBuf>,
31    pub commitment: Option<String>,
32}
33
34impl CliConfigBuilder {
35    pub fn new() -> Self {
36        Self {
37            json_rpc_url: None,
38            keypair_path: None,
39            commitment: None,
40        }
41    }
42    pub fn rpc_url(mut self, json_rpc_url: String) -> Self {
43        self.json_rpc_url = Some(json_rpc_url);
44        self
45    }
46    pub fn keypair_path(mut self, keypair_path: PathBuf) -> Self {
47        self.keypair_path = Some(keypair_path);
48        self
49    }
50    pub fn commitment(mut self, commitment: String) -> Self {
51        self.commitment = Some(commitment);
52        self
53    }
54    pub fn build(&self) -> Result<CliConfig> {
55        let rpc_url = self
56            .json_rpc_url
57            .clone()
58            .ok_or_else(|| anyhow!("No rpc url provided"))?;
59
60        let commitment = match self.commitment.clone() {
61            Some(commitment) => CommitmentConfig::from_str(&commitment)?,
62            None => CommitmentConfig::confirmed(),
63        };
64
65        let client = RpcClient::new_with_commitment(rpc_url, commitment);
66
67        let keypair_path = self
68            .keypair_path
69            .clone()
70            .ok_or_else(|| anyhow!("No keypair path provided"))?;
71
72        let keypair =
73            read_keypair_file(keypair_path).map_err(|_| anyhow!("Unable to read keypair file"))?;
74
75        let recent_blockhash = client.get_latest_blockhash()?;
76        let recent_slot = client.get_slot()?;
77
78        Ok(CliConfig {
79            client,
80            keypair,
81            recent_blockhash,
82            recent_slot,
83        })
84    }
85}
86
87impl CliConfig {
88    pub fn new(keypair_path: Option<PathBuf>, rpc_url: Option<String>) -> Result<Self> {
89        let mut builder = CliConfigBuilder::new();
90        let solana_config = parse_solana_config();
91
92        if let Some(config) = solana_config {
93            builder = builder
94                .rpc_url(config.json_rpc_url)
95                .keypair_path(config.keypair_path.into())
96                .commitment(config.commitment);
97        }
98
99        if let Some(keypair_path) = keypair_path {
100            builder = builder.keypair_path(keypair_path);
101        }
102
103        if let Some(rpc_url) = rpc_url {
104            builder = builder.rpc_url(rpc_url);
105        }
106
107        let config = builder.build()?;
108
109        Ok(config)
110    }
111
112    #[allow(unused)]
113    pub fn update_blocks(&mut self) -> Result<()> {
114        self.recent_blockhash = self.client.get_latest_blockhash()?;
115        self.recent_slot = self.client.get_slot()?;
116
117        Ok(())
118    }
119}
120
121fn parse_solana_config() -> Option<SolanaConfig> {
122    let home_path = home_dir().expect("Couldn't find home dir");
123
124    let solana_config_path = home_path
125        .join(".config")
126        .join("solana")
127        .join("cli")
128        .join("config.yml");
129
130    let config_file = File::open(solana_config_path).ok();
131
132    if let Some(config_file) = config_file {
133        let config: SolanaConfig = serde_yaml::from_reader(config_file).ok()?;
134        return Some(config);
135    }
136    None
137}