Skip to main content

tai_core/
config.rs

1//! Network configuration: which Tai deployment to talk to.
2
3use crate::error::TaiError;
4use crate::ids::ObjectId;
5use serde::{Deserialize, Serialize};
6use std::str::FromStr;
7
8/// Which Sui network to target.
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum Network {
12    /// Sui testnet — the network the v1 package is currently published to.
13    Testnet,
14    /// Sui mainnet.
15    Mainnet,
16    /// Sui devnet (for local SDK development).
17    Devnet,
18    /// A custom RPC endpoint (e.g., a local validator).
19    Custom,
20}
21
22impl Network {
23    /// Default public JSON-RPC endpoint for the network.
24    pub fn default_rpc_url(self) -> &'static str {
25        match self {
26            Network::Testnet => "https://fullnode.testnet.sui.io",
27            Network::Mainnet => "https://fullnode.mainnet.sui.io",
28            Network::Devnet => "https://fullnode.devnet.sui.io",
29            Network::Custom => "",
30        }
31    }
32}
33
34impl FromStr for Network {
35    type Err = TaiError;
36    fn from_str(s: &str) -> Result<Self, Self::Err> {
37        match s.to_ascii_lowercase().as_str() {
38            "testnet" => Ok(Network::Testnet),
39            "mainnet" => Ok(Network::Mainnet),
40            "devnet" => Ok(Network::Devnet),
41            "custom" => Ok(Network::Custom),
42            other => Err(TaiError::Config(format!("unknown network: {other}"))),
43        }
44    }
45}
46
47/// All the on-chain pointers a client needs to interact with a Tai
48/// deployment: the package id (immutable), the shared `LaunchpadConfig`
49/// id, and the RPC endpoint.
50#[derive(Clone, Debug, Serialize, Deserialize)]
51pub struct TaiConfig {
52    /// Which Sui network this config targets.
53    pub network: Network,
54    /// HTTP JSON-RPC endpoint.
55    pub rpc_url: String,
56    /// Address of the deployed `tai` Move package.
57    pub package_id: ObjectId,
58    /// Object id of the shared `LaunchpadConfig` created at publish time.
59    pub config_id: ObjectId,
60}
61
62impl TaiConfig {
63    /// The current canonical testnet deployment of the v1 Tai package.
64    /// Points at v1.1.1 — the in-place upgrade of v1.1.0 that added
65    /// spec/receipt length bounds (L4). The package_id is the upgraded
66    /// package; the config_id is unchanged across the upgrade (Sui
67    /// upgrades don't move existing objects).
68    ///
69    /// Source: `move/published.json` in this repo. Upgraded 2026-05-28 at
70    /// checkpoint 342196917, tx HmVRYzXdgnxTy97h71bUH6N2m567afy1Bc9wjuUjokLn.
71    /// Type/event anchor remains the v1.1.0 package
72    /// 0x7d86697afc21895a94687ee5c16012384862d43dfd8a6841e2e4a0ac0690efb3.
73    pub fn testnet_v1() -> Self {
74        TaiConfig {
75            network: Network::Testnet,
76            rpc_url: Network::Testnet.default_rpc_url().to_string(),
77            package_id: ObjectId::from_bytes(hex_lit(
78                "c5d0d885f6c652413034d3e44a1f9a7ab6ef6d94b6e951b6ee885e2edee3a421",
79            )),
80            config_id: ObjectId::from_bytes(hex_lit(
81                "4a8bdc697738df24f01f6161af29e70136b326db072e3d7e3630b3711f673c50",
82            )),
83        }
84    }
85}
86
87const fn hex_lit(s: &str) -> [u8; 32] {
88    let bytes = s.as_bytes();
89    assert!(bytes.len() == 64, "hex literal must be 64 chars");
90    let mut out = [0u8; 32];
91    let mut i = 0;
92    while i < 32 {
93        out[i] = nibble(bytes[2 * i]) * 16 + nibble(bytes[2 * i + 1]);
94        i += 1;
95    }
96    out
97}
98
99const fn nibble(c: u8) -> u8 {
100    match c {
101        b'0'..=b'9' => c - b'0',
102        b'a'..=b'f' => 10 + (c - b'a'),
103        b'A'..=b'F' => 10 + (c - b'A'),
104        _ => panic!("invalid hex char"),
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn testnet_v1_has_known_ids() {
114        let cfg = TaiConfig::testnet_v1();
115        assert_eq!(cfg.network, Network::Testnet);
116        assert_eq!(
117            cfg.package_id.to_string(),
118            "0xc5d0d885f6c652413034d3e44a1f9a7ab6ef6d94b6e951b6ee885e2edee3a421"
119        );
120        assert_eq!(
121            cfg.config_id.to_string(),
122            "0x4a8bdc697738df24f01f6161af29e70136b326db072e3d7e3630b3711f673c50"
123        );
124        assert_eq!(cfg.rpc_url, "https://fullnode.testnet.sui.io");
125    }
126
127    #[test]
128    fn network_parses_case_insensitive() {
129        assert_eq!("Testnet".parse::<Network>().unwrap(), Network::Testnet);
130        assert_eq!("MAINNET".parse::<Network>().unwrap(), Network::Mainnet);
131        assert!("polygon".parse::<Network>().is_err());
132    }
133}