Skip to main content

csv_adapter_aptos/
config.rs

1//! Aptos adapter configuration
2//!
3//! This module provides configuration for the Aptos adapter including
4//! network selection, RPC endpoints, and production settings.
5
6use serde::{Deserialize, Serialize};
7
8/// Aptos network types with known chain IDs and RPC endpoints.
9#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
10pub enum AptosNetwork {
11    /// Aptos Mainnet
12    Mainnet,
13    /// Aptos Testnet
14    Testnet,
15    /// Aptos Devnet
16    Devnet,
17    /// Custom network with user-defined chain ID
18    Custom {
19        /// Chain ID for the custom network
20        chain_id: u8,
21        /// Human-readable name for the network
22        name: String,
23    },
24}
25
26impl AptosNetwork {
27    /// Returns the chain ID for this network.
28    pub fn chain_id(&self) -> u8 {
29        match self {
30            AptosNetwork::Mainnet => 1,
31            AptosNetwork::Testnet => 2,
32            AptosNetwork::Devnet => 4,
33            AptosNetwork::Custom { chain_id, .. } => *chain_id,
34        }
35    }
36
37    /// Returns the default fullnode RPC URL for this network.
38    pub fn default_rpc_url(&self) -> &'static str {
39        match self {
40            AptosNetwork::Mainnet => "https://fullnode.mainnet.aptoslabs.com/v1",
41            AptosNetwork::Testnet => "https://fullnode.testnet.aptoslabs.com/v1",
42            AptosNetwork::Devnet => "https://fullnode.devnet.aptoslabs.com/v1",
43            AptosNetwork::Custom { .. } => "",
44        }
45    }
46
47    /// Returns the default indexer URL for this network.
48    pub fn default_indexer_url(&self) -> &'static str {
49        match self {
50            AptosNetwork::Mainnet => "https://indexer.mainnet.aptoslabs.com/v1/graphql",
51            AptosNetwork::Testnet => "https://indexer.testnet.aptoslabs.com/v1/graphql",
52            AptosNetwork::Devnet => "",
53            AptosNetwork::Custom { .. } => "",
54        }
55    }
56
57    /// Returns the explorer URL for viewing transactions.
58    pub fn explorer_url(&self) -> &'static str {
59        match self {
60            AptosNetwork::Mainnet => "https://explorer.aptoslabs.com",
61            AptosNetwork::Testnet => "https://explorer.aptoslabs.com",
62            AptosNetwork::Devnet => "",
63            AptosNetwork::Custom { .. } => "",
64        }
65    }
66
67    /// Known validator count for 2f+1 verification calculations.
68    pub fn known_validator_count(&self) -> u64 {
69        match self {
70            AptosNetwork::Mainnet => 100, // ~100 validators on mainnet
71            AptosNetwork::Testnet => 10,  // ~10 validators on testnet
72            AptosNetwork::Devnet => 4,    // 4 validators on devnet
73            AptosNetwork::Custom { .. } => 4,
74        }
75    }
76}
77
78/// Checkpoint verification configuration.
79#[derive(Clone, Debug, Serialize, Deserialize)]
80pub struct CheckpointConfig {
81    /// Require checkpoint to be certified by 2f+1 validators.
82    pub require_certified: bool,
83    /// Maximum number of epochs to look back for certification.
84    pub max_epoch_lookback: u64,
85    /// Timeout for checkpoint verification in milliseconds.
86    pub timeout_ms: u64,
87}
88
89impl Default for CheckpointConfig {
90    fn default() -> Self {
91        Self {
92            require_certified: true,
93            max_epoch_lookback: 5,
94            timeout_ms: 30_000,
95        }
96    }
97}
98
99/// Transaction submission configuration.
100#[derive(Clone, Debug, Serialize, Deserialize)]
101pub struct TransactionConfig {
102    /// Maximum gas units for a transaction.
103    pub max_gas: u64,
104    /// Timeout waiting for transaction confirmation in milliseconds.
105    pub confirmation_timeout_ms: u64,
106    /// Number of retries on transient failures.
107    pub max_retries: u32,
108    /// Base retry delay in milliseconds (exponential backoff).
109    pub retry_delay_ms: u64,
110}
111
112impl Default for TransactionConfig {
113    fn default() -> Self {
114        Self {
115            max_gas: 100_000,
116            confirmation_timeout_ms: 30_000,
117            max_retries: 3,
118            retry_delay_ms: 1_000,
119        }
120    }
121}
122
123/// CSVSeal Move contract configuration.
124#[derive(Clone, Debug, Serialize, Deserialize)]
125pub struct SealContractConfig {
126    /// Account address where the CSVSeal module is deployed.
127    pub module_address: String,
128    /// Module name (without account prefix).
129    pub module_name: String,
130    /// Resource name for seals.
131    pub seal_resource: String,
132}
133
134impl Default for SealContractConfig {
135    fn default() -> Self {
136        Self {
137            module_address: "0x1".to_string(),
138            module_name: "csv_seal".to_string(),
139            seal_resource: "Seal".to_string(),
140        }
141    }
142}
143
144/// Complete configuration for the Aptos adapter.
145#[derive(Clone, Debug, Serialize, Deserialize)]
146pub struct AptosConfig {
147    /// Network to connect to.
148    pub network: AptosNetwork,
149    /// RPC URL for the Aptos fullnode.
150    pub rpc_url: String,
151    /// Optional indexer URL for GraphQL queries.
152    pub indexer_url: Option<String>,
153    /// Checkpoint verification settings.
154    pub checkpoint: CheckpointConfig,
155    /// Transaction submission settings.
156    pub transaction: TransactionConfig,
157    /// CSVSeal contract deployment details.
158    pub seal_contract: SealContractConfig,
159}
160
161impl Default for AptosConfig {
162    fn default() -> Self {
163        let network = AptosNetwork::Devnet;
164        Self {
165            network: network.clone(),
166            rpc_url: network.default_rpc_url().to_string(),
167            indexer_url: None,
168            checkpoint: CheckpointConfig::default(),
169            transaction: TransactionConfig::default(),
170            seal_contract: SealContractConfig::default(),
171        }
172    }
173}
174
175impl AptosConfig {
176    /// Create a new config for the given network with default RPC URL.
177    pub fn new(network: AptosNetwork) -> Self {
178        Self {
179            rpc_url: network.default_rpc_url().to_string(),
180            network,
181            ..Self::default()
182        }
183    }
184
185    /// Create a config with a custom RPC URL.
186    pub fn with_rpc(network: AptosNetwork, rpc_url: impl Into<String>) -> Self {
187        Self {
188            rpc_url: rpc_url.into(),
189            network,
190            ..Self::default()
191        }
192    }
193
194    /// Validate the configuration is correct for the target network.
195    pub fn validate(&self) -> Result<(), String> {
196        if self.rpc_url.is_empty() {
197            return Err("RPC URL cannot be empty".to_string());
198        }
199        if self.transaction.max_gas == 0 {
200            return Err("Max gas must be greater than 0".to_string());
201        }
202        if self.transaction.confirmation_timeout_ms == 0 {
203            return Err("Confirmation timeout must be greater than 0".to_string());
204        }
205        if self.checkpoint.max_epoch_lookback == 0 {
206            return Err("Epoch lookback must be greater than 0".to_string());
207        }
208        if self.seal_contract.module_address.is_empty() {
209            return Err("Seal contract address cannot be empty".to_string());
210        }
211        Ok(())
212    }
213
214    /// Returns the chain ID for quick network identification.
215    pub fn chain_id(&self) -> u8 {
216        self.network.chain_id()
217    }
218
219    /// Returns the expected 2f+1 threshold for validator signatures.
220    /// In production, this should match the actual validator set.
221    pub fn f_plus_one(&self) -> u64 {
222        let n = self.network.known_validator_count();
223        // 2f + 1 where 3f + 1 = n => f = (n - 1) / 3
224        // 2f + 1 = 2 * (n - 1) / 3 + 1
225        (2 * n) / 3 + 1
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn test_network_chain_ids() {
235        assert_eq!(AptosNetwork::Mainnet.chain_id(), 1);
236        assert_eq!(AptosNetwork::Testnet.chain_id(), 2);
237        assert_eq!(AptosNetwork::Devnet.chain_id(), 4);
238        assert_eq!(
239            AptosNetwork::Custom {
240                chain_id: 99,
241                name: "local".to_string()
242            }
243            .chain_id(),
244            99
245        );
246    }
247
248    #[test]
249    fn test_default_rpc_urls() {
250        assert!(AptosNetwork::Mainnet.default_rpc_url().contains("mainnet"));
251        assert!(AptosNetwork::Testnet.default_rpc_url().contains("testnet"));
252    }
253
254    #[test]
255    fn test_config_validation() {
256        let config = AptosConfig::default();
257        assert!(config.validate().is_ok());
258    }
259
260    #[test]
261    fn test_config_custom_rpc() {
262        let config = AptosConfig::with_rpc(AptosNetwork::Mainnet, "https://custom.example.com");
263        assert_eq!(config.rpc_url, "https://custom.example.com");
264        assert_eq!(config.network.chain_id(), 1);
265    }
266
267    #[test]
268    fn test_f_plus_one() {
269        let config = AptosConfig::new(AptosNetwork::Devnet);
270        // For 4 validators: 2f+1 where f=(4-1)/3=1, so 2*1+1=3
271        assert!(config.f_plus_one() >= 3);
272    }
273
274    #[test]
275    fn test_invalid_config() {
276        let mut config = AptosConfig::default();
277        config.rpc_url = "".to_string();
278        assert!(config.validate().is_err());
279
280        let mut config = AptosConfig::default();
281        config.transaction.max_gas = 0;
282        assert!(config.validate().is_err());
283    }
284}