1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// Wallet settings that can be configured for long-term use
use serde_derive::{Deserialize, Serialize};
use std::{collections::HashMap, io, path::Path};
use url::Url;

lazy_static! {
    pub static ref CONFIG_FILE: Option<String> = {
        dirs_next::home_dir().map(|mut path| {
            path.extend(&[".config", "solana", "cli", "config.yml"]);
            path.to_str().unwrap().to_string()
        })
    };
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct Config {
    pub json_rpc_url: String,
    pub websocket_url: String,
    pub keypair_path: String,
    #[serde(default)]
    pub address_labels: HashMap<String, String>,
    #[serde(default)]
    pub commitment: String,
}

impl Default for Config {
    fn default() -> Self {
        let keypair_path = {
            let mut keypair_path = dirs_next::home_dir().expect("home directory");
            keypair_path.extend(&[".config", "solana", "id.json"]);
            keypair_path.to_str().unwrap().to_string()
        };
        let json_rpc_url = "https://api.mainnet-beta.solana.com".to_string();

        // Empty websocket_url string indicates the client should
        // `Config::compute_websocket_url(&json_rpc_url)`
        let websocket_url = "".to_string();

        let mut address_labels = HashMap::new();
        address_labels.insert(
            "11111111111111111111111111111111".to_string(),
            "System Program".to_string(),
        );

        let commitment = "confirmed".to_string();

        Self {
            json_rpc_url,
            websocket_url,
            keypair_path,
            address_labels,
            commitment,
        }
    }
}

impl Config {
    pub fn load(config_file: &str) -> Result<Self, io::Error> {
        crate::load_config_file(config_file)
    }

    pub fn save(&self, config_file: &str) -> Result<(), io::Error> {
        crate::save_config_file(self, config_file)
    }

    pub fn compute_websocket_url(json_rpc_url: &str) -> String {
        let json_rpc_url: Option<Url> = json_rpc_url.parse().ok();
        if json_rpc_url.is_none() {
            return "".to_string();
        }
        let json_rpc_url = json_rpc_url.unwrap();
        let is_secure = json_rpc_url.scheme().to_ascii_lowercase() == "https";
        let mut ws_url = json_rpc_url.clone();
        ws_url
            .set_scheme(if is_secure { "wss" } else { "ws" })
            .expect("unable to set scheme");
        if let Some(port) = json_rpc_url.port() {
            let port = port.checked_add(1).expect("port out of range");
            ws_url.set_port(Some(port)).expect("unable to set port");
        }
        ws_url.to_string()
    }

    pub fn import_address_labels<P>(&mut self, filename: P) -> Result<(), io::Error>
    where
        P: AsRef<Path>,
    {
        let imports: HashMap<String, String> = crate::load_config_file(filename)?;
        for (address, label) in imports.into_iter() {
            self.address_labels.insert(address, label);
        }
        Ok(())
    }

    pub fn export_address_labels<P>(&self, filename: P) -> Result<(), io::Error>
    where
        P: AsRef<Path>,
    {
        crate::save_config_file(&self.address_labels, filename)
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn compute_websocket_url() {
        assert_eq!(
            Config::compute_websocket_url(&"http://api.devnet.solana.com"),
            "ws://api.devnet.solana.com/".to_string()
        );

        assert_eq!(
            Config::compute_websocket_url(&"https://api.devnet.solana.com"),
            "wss://api.devnet.solana.com/".to_string()
        );

        assert_eq!(
            Config::compute_websocket_url(&"http://example.com:8899"),
            "ws://example.com:8900/".to_string()
        );
        assert_eq!(
            Config::compute_websocket_url(&"https://example.com:1234"),
            "wss://example.com:1235/".to_string()
        );

        assert_eq!(Config::compute_websocket_url(&"garbage"), String::new());
    }
}