kabu_core_config/
kabu_config.rs

1use crate::config_loader::{ConfigLoader, ConfigLoaderSync, LoadConfigError, load_from_file, load_from_file_sync};
2use alloy_primitives::Address;
3use async_trait::async_trait;
4use eyre::Result;
5use serde::Deserialize;
6use std::collections::HashMap;
7use strum_macros::Display;
8
9/// Node type (Geth or Reth)
10#[derive(Clone, Debug, Default, Deserialize, Display)]
11#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
12#[serde(rename_all = "lowercase")]
13pub enum NodeType {
14    #[default]
15    Geth,
16    Reth,
17}
18
19/// Remote node configuration
20#[derive(Clone, Debug, Deserialize)]
21pub struct RemoteNodeConfig {
22    /// WebSocket URL for the node (e.g., ws://localhost:8545)
23    pub ws_url: String,
24    /// HTTP URL for the node (e.g., http://localhost:8545)
25    pub http_url: String,
26    /// Node type (Geth or Reth)
27    pub node: NodeType,
28}
29
30impl RemoteNodeConfig {
31    /// Get the WebSocket URL
32    pub fn ws_url(&self) -> &str {
33        &self.ws_url
34    }
35
36    /// Get the HTTP URL
37    pub fn http_url(&self) -> &str {
38        &self.http_url
39    }
40}
41
42/// Signer configuration
43#[derive(Clone, Debug, Deserialize)]
44pub struct SignerConfig {
45    pub private_key: String,
46}
47
48/// Builder (relay) configuration for MEV submission
49#[derive(Clone, Debug, Deserialize)]
50pub struct BuilderConfig {
51    pub id: u8,
52    pub name: String,
53    pub url: String,
54    #[serde(default)]
55    pub no_sign: bool,
56}
57
58/// InfluxDB configuration for metrics
59#[derive(Clone, Debug, Default, Deserialize)]
60pub struct InfluxDbConfig {
61    pub url: String,
62    pub database: String,
63    pub tags: HashMap<String, String>,
64}
65
66/// Web server configuration
67#[derive(Clone, Debug, Deserialize)]
68pub struct WebserverConfig {
69    pub host: String,
70}
71
72impl Default for WebserverConfig {
73    fn default() -> Self {
74        WebserverConfig { host: "127.0.0.1:3333".to_string() }
75    }
76}
77
78/// Database configuration
79#[derive(Clone, Debug, Deserialize)]
80pub struct DatabaseConfig {
81    pub url: String,
82}
83
84/// Main Kabu configuration
85#[derive(Clone, Debug, Deserialize)]
86pub struct KabuConfig {
87    /// Optional remote node configuration (for remote mode)
88    pub remote_node: Option<RemoteNodeConfig>,
89
90    /// Multiple signers for redundancy and balance management
91    pub signers: Vec<SignerConfig>,
92
93    /// MEV builders/relays for transaction submission
94    #[serde(default)]
95    pub builders: Vec<BuilderConfig>,
96
97    /// Multicaller contract address
98    pub multicaller_address: Address,
99
100    /// Optional InfluxDB configuration
101    pub influxdb: Option<InfluxDbConfig>,
102
103    /// Optional web server configuration
104    pub webserver: Option<WebserverConfig>,
105
106    /// Optional database configuration
107    pub database: Option<DatabaseConfig>,
108}
109
110impl KabuConfig {
111    /// Load configuration from file (sync version)
112    pub fn load_from_file(file_name: String) -> Result<KabuConfig> {
113        let config = load_from_file_sync(file_name)?;
114        Ok(config)
115    }
116
117    /// Load configuration from file (async version)
118    pub async fn load_from_file_async(file_name: String) -> Result<KabuConfig> {
119        let config = load_from_file(file_name).await?;
120        Ok(config)
121    }
122}
123
124#[async_trait]
125impl ConfigLoader for KabuConfig {
126    type SectionType = KabuConfig;
127
128    async fn load_from_file(file_name: String) -> Result<Self::SectionType, LoadConfigError> {
129        load_from_file(file_name).await
130    }
131}
132
133impl ConfigLoaderSync for KabuConfig {
134    type SectionType = KabuConfig;
135
136    fn load_from_file_sync(file_name: String) -> Result<Self::SectionType, LoadConfigError> {
137        load_from_file_sync(file_name)
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_node_type_display() {
147        assert_eq!(NodeType::Geth.to_string(), "geth");
148        assert_eq!(NodeType::Reth.to_string(), "reth");
149    }
150
151    #[test]
152    fn test_remote_node_config() {
153        let config = RemoteNodeConfig {
154            ws_url: "ws://localhost:8545".to_string(),
155            http_url: "http://localhost:8545".to_string(),
156            node: NodeType::Reth,
157        };
158
159        assert_eq!(config.ws_url(), "ws://localhost:8545");
160        assert_eq!(config.http_url(), "http://localhost:8545");
161    }
162
163    #[test]
164    fn test_config_parsing_with_remote_node() {
165        let toml_str = r#"
166            signers = []
167            builders = []
168            multicaller_address = "0x0000000000000000000000000000000000000000"
169
170            [remote_node]
171            ws_url = "ws://localhost:8545"
172            http_url = "http://localhost:8545"
173            node = "reth"
174        "#;
175
176        let config: KabuConfig = toml::from_str(toml_str).unwrap();
177        assert!(config.remote_node.is_some());
178        let remote = config.remote_node.unwrap();
179        assert_eq!(remote.ws_url, "ws://localhost:8545");
180        assert_eq!(remote.http_url, "http://localhost:8545");
181    }
182
183    #[test]
184    fn test_config_parsing_without_remote_node() {
185        let toml_str = r#"
186            signers = []
187            builders = []
188            multicaller_address = "0x0000000000000000000000000000000000000000"
189        "#;
190
191        let config: KabuConfig = toml::from_str(toml_str).unwrap();
192        assert!(config.remote_node.is_none());
193    }
194}