Skip to main content

zing_cli/
config.rs

1use serde::{Deserialize, Serialize};
2use std::path::{Path, PathBuf};
3use std::str::FromStr;
4
5pub const DEFAULT_API_URL: &str = "https://search.zing.services";
6pub const DEFAULT_PLATFORM_USDC_ADDRESS: &str =
7    "0x9b1b8ff37a5fdc77141c58ca43a4800a82d6ce91cfaceb7ae7c62c7c80458299";
8
9fn zing_config_dir() -> String {
10    let home = std::env::var("HOME").unwrap_or_else(|_| "~".to_string());
11    format!("{}/.zing/zing_config", home)
12}
13
14#[derive(Debug)]
15pub struct ZingConfig {
16    pub rpc_url: String,
17    pub active_address: sui_sdk_types::Address,
18    pub api_base_url: String,
19    pub platform_usdc_address: sui_sdk_types::Address,
20    pub keystore_path: PathBuf,
21}
22
23#[derive(Debug, Deserialize, Serialize)]
24pub struct ZingClientConfig {
25    pub keystore: ZingKeystorePath,
26    pub active_address: Option<String>,
27    #[serde(default)]
28    pub active_env: Option<String>,
29    #[serde(default)]
30    pub envs: Vec<ZingEnv>,
31}
32
33#[derive(Debug, Deserialize, Serialize)]
34pub struct ZingKeystorePath {
35    #[serde(rename = "File")]
36    pub file: String,
37}
38
39#[derive(Debug, Deserialize, Serialize)]
40pub struct ZingEnv {
41    pub alias: String,
42    pub rpc: String,
43}
44
45pub fn load_config() -> anyhow::Result<ZingConfig> {
46    let config_dir = zing_config_dir();
47    let client_yaml_path = PathBuf::from(&config_dir).join("client.yaml");
48
49    if !client_yaml_path.exists() {
50        init_config(&config_dir, &client_yaml_path)?;
51    }
52
53    let client_yaml = std::fs::read_to_string(&client_yaml_path)
54        .map_err(|e| anyhow::anyhow!("Cannot read Zing config at {}: {}", client_yaml_path.display(), e))?;
55    let zing_config: ZingClientConfig = serde_yaml::from_str(&client_yaml)?;
56
57    let rpc_url = zing_config
58        .active_env
59        .as_deref()
60        .and_then(|alias| zing_config.envs.iter().find(|e| e.alias == alias))
61        .map(|e| e.rpc.clone())
62        .unwrap_or_else(|| "https://fullnode.mainnet.sui.io:443".to_string());
63
64    let addr_str = zing_config
65        .active_address
66        .ok_or_else(|| anyhow::anyhow!("No active_address set in Zing config at {}", client_yaml_path.display()))?;
67    let active_address = sui_sdk_types::Address::from_str(&addr_str)?;
68
69    let keystore_path = PathBuf::from(&config_dir).join(&zing_config.keystore.file);
70
71    let api_base_url = std::env::var("ZING_API_URL")
72        .unwrap_or_else(|_| DEFAULT_API_URL.to_string());
73
74    let platform_usdc_addr_str = std::env::var("ZING_PLATFORM_USDC_ADDRESS")
75        .unwrap_or_else(|_| DEFAULT_PLATFORM_USDC_ADDRESS.to_string());
76    let platform_usdc_address = sui_sdk_types::Address::from_str(&platform_usdc_addr_str)?;
77
78    Ok(ZingConfig {
79        rpc_url,
80        active_address,
81        api_base_url,
82        platform_usdc_address,
83        keystore_path,
84    })
85}
86
87fn init_config(config_dir: &str, client_yaml_path: &Path) -> anyhow::Result<()> {
88    use base64ct::{Base64, Encoding};
89    use rand::RngCore;
90    use sui_crypto::ed25519::Ed25519PrivateKey;
91
92    std::fs::create_dir_all(config_dir)
93        .map_err(|e| anyhow::anyhow!("Cannot create config directory {}: {}", config_dir, e))?;
94
95    let mut key_bytes = [0u8; 32];
96    rand::rngs::OsRng.fill_bytes(&mut key_bytes);
97    let keypair = Ed25519PrivateKey::new(key_bytes);
98    let address = keypair.public_key().derive_address();
99
100    let mut raw = vec![0x00u8];
101    raw.extend_from_slice(&key_bytes);
102    let encoded = Base64::encode_string(&raw);
103    let keystore_json = serde_json::to_string_pretty(&vec![encoded])?;
104    let keystore_path = PathBuf::from(config_dir).join("zing.keystore");
105    std::fs::write(&keystore_path, keystore_json).map_err(|e| {
106        anyhow::anyhow!("Cannot write keystore to {}: {}", keystore_path.display(), e)
107    })?;
108
109    let config = ZingClientConfig {
110        keystore: ZingKeystorePath {
111            file: "zing.keystore".to_string(),
112        },
113        active_address: Some(address.to_string()),
114        active_env: Some("mainnet".to_string()),
115        envs: vec![ZingEnv {
116            alias: "mainnet".to_string(),
117            rpc: "https://fullnode.mainnet.sui.io:443".to_string(),
118        }],
119    };
120    let config_yaml = serde_yaml::to_string(&config)?;
121    std::fs::write(client_yaml_path, config_yaml).map_err(|e| {
122        anyhow::anyhow!("Cannot write config to {}: {}", client_yaml_path.display(), e)
123    })?;
124
125    eprintln!(
126        "Created new Zing wallet.\n  Address: {}\n\n\
127         Fund this address with at least 0.01 USDC on Sui mainnet to use paid search.",
128        address
129    );
130
131    Ok(())
132}