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}