use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum SuiNetwork {
Mainnet,
Testnet,
Devnet,
Local,
Custom { chain_id: String },
}
impl SuiNetwork {
pub fn default_rpc_url(&self) -> &str {
match self {
SuiNetwork::Mainnet => "https://fullnode.mainnet.sui.io:443",
SuiNetwork::Testnet => "https://fullnode.testnet.sui.io:443",
SuiNetwork::Devnet => "https://fullnode.devnet.sui.io:443",
SuiNetwork::Local => "http://127.0.0.1:9000",
SuiNetwork::Custom { .. } => "",
}
}
pub fn chain_id(&self) -> &str {
match self {
SuiNetwork::Mainnet => "mainnet",
SuiNetwork::Testnet => "testnet",
SuiNetwork::Devnet => "devnet",
SuiNetwork::Local => "local",
SuiNetwork::Custom { chain_id } => chain_id.as_str(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CheckpointConfig {
pub require_certified: bool,
pub max_epoch_lookback: u64,
pub timeout_ms: u64,
}
impl Default for CheckpointConfig {
fn default() -> Self {
Self {
require_certified: true,
max_epoch_lookback: 5,
timeout_ms: 30_000,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TransactionConfig {
pub max_gas_budget: u64,
pub max_gas_price: u64,
pub confirmation_timeout_ms: u64,
pub max_retries: u32,
}
impl Default for TransactionConfig {
fn default() -> Self {
Self {
max_gas_budget: 1_000_000_000, max_gas_price: 1_000,
confirmation_timeout_ms: 60_000,
max_retries: 3,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SealContractConfig {
pub package_id: Option<String>,
pub module_name: String,
pub seal_type: String,
}
impl Default for SealContractConfig {
fn default() -> Self {
Self {
package_id: None, module_name: "csv_seal".to_string(),
seal_type: "Seal".to_string(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SuiConfig {
pub network: SuiNetwork,
pub rpc_url: String,
pub checkpoint: CheckpointConfig,
pub transaction: TransactionConfig,
pub seal_contract: SealContractConfig,
}
impl SuiConfig {
pub fn new(network: SuiNetwork) -> Self {
let rpc_url = network.default_rpc_url().to_string();
Self {
network,
rpc_url,
checkpoint: CheckpointConfig::default(),
transaction: TransactionConfig::default(),
seal_contract: SealContractConfig::default(),
}
}
pub fn chain_id(&self) -> &str {
self.network.chain_id()
}
pub fn validate(&self) -> Result<(), String> {
if self.rpc_url.is_empty() {
return Err("RPC URL cannot be empty".to_string());
}
match &self.seal_contract.package_id {
Some(id) if id.is_empty() => {
return Err("Seal contract package ID cannot be empty".to_string())
}
None => {
return Err(
"Seal contract package ID must be set — deploy the contract first".to_string(),
)
}
_ => {}
}
if self.transaction.max_gas_budget == 0 {
return Err("Max gas budget must be greater than 0".to_string());
}
if self.checkpoint.timeout_ms == 0 {
return Err("Checkpoint timeout must be greater than 0".to_string());
}
Ok(())
}
}
impl Default for SuiConfig {
fn default() -> Self {
Self::new(SuiNetwork::Testnet)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = SuiConfig::default();
assert_eq!(config.network, SuiNetwork::Testnet);
assert_eq!(config.rpc_url, "https://fullnode.testnet.sui.io:443");
}
#[test]
fn test_config_custom_rpc() {
let config = SuiConfig::new(SuiNetwork::Mainnet);
assert_eq!(config.network, SuiNetwork::Mainnet);
assert_eq!(config.rpc_url, "https://fullnode.mainnet.sui.io:443");
}
#[test]
fn test_config_validation() {
let mut config = SuiConfig::default();
config.seal_contract.package_id = Some("0x1234".to_string());
assert!(config.validate().is_ok());
config.rpc_url = "".to_string();
assert!(config.validate().is_err());
}
#[test]
fn test_network_chain_ids() {
assert_eq!(SuiNetwork::Mainnet.chain_id(), "mainnet");
assert_eq!(SuiNetwork::Testnet.chain_id(), "testnet");
assert_eq!(SuiNetwork::Devnet.chain_id(), "devnet");
}
#[test]
fn test_invalid_config() {
let mut config = SuiConfig::default();
config.seal_contract.package_id = Some("".to_string());
assert!(config.validate().is_err());
}
}