nautilus_blockchain/
factories.rs1use std::{any::Any, cell::RefCell, rc::Rc};
19
20use nautilus_common::{
21 cache::CacheView,
22 clients::{DataClient, ExecutionClient},
23 clock::Clock,
24 factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
25};
26use nautilus_live::ExecutionClientCore;
27use nautilus_model::{
28 enums::{AccountType, OmsType},
29 identifiers::{ClientId, TraderId},
30};
31
32use crate::{
33 config::{BlockchainDataClientConfig, BlockchainExecutionClientConfig},
34 constants::{BLOCKCHAIN, BLOCKCHAIN_VENUE},
35 data::client::BlockchainDataClient,
36 execution::client::BlockchainExecutionClient,
37};
38
39impl ClientConfig for BlockchainDataClientConfig {
40 fn as_any(&self) -> &dyn Any {
41 self
42 }
43}
44
45#[derive(Debug, Clone)]
50#[cfg_attr(
51 feature = "python",
52 pyo3::pyclass(module = "nautilus_trader.adapters.blockchain", from_py_object)
53)]
54#[cfg_attr(
55 feature = "python",
56 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.blockchain")
57)]
58pub struct BlockchainDataClientFactory;
59
60impl BlockchainDataClientFactory {
61 #[must_use]
63 pub const fn new() -> Self {
64 Self
65 }
66}
67
68impl Default for BlockchainDataClientFactory {
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74impl DataClientFactory for BlockchainDataClientFactory {
75 fn create(
76 &self,
77 name: &str,
78 config: &dyn ClientConfig,
79 _cache: CacheView,
80 _clock: Rc<RefCell<dyn Clock>>,
81 ) -> anyhow::Result<Box<dyn DataClient>> {
82 let blockchain_config = config
83 .as_any()
84 .downcast_ref::<BlockchainDataClientConfig>()
85 .ok_or_else(|| {
86 anyhow::anyhow!(
87 "Invalid config type for BlockchainDataClientFactory. Expected `BlockchainDataClientConfig`, was {config:?}"
88 )
89 })?;
90
91 blockchain_config.validate()?;
92 let client = BlockchainDataClient::new(ClientId::from(name), blockchain_config.clone())?;
93
94 Ok(Box::new(client))
95 }
96
97 fn name(&self) -> &'static str {
98 BLOCKCHAIN
99 }
100
101 fn config_type(&self) -> &'static str {
102 "BlockchainDataClientConfig"
103 }
104}
105
106#[derive(Debug, Clone)]
108#[cfg_attr(
109 feature = "python",
110 pyo3::pyclass(module = "nautilus_trader.adapters.blockchain", from_py_object)
111)]
112pub struct BlockchainExecutionClientFactory;
113
114impl BlockchainExecutionClientFactory {
115 #[must_use]
117 pub const fn new() -> Self {
118 Self
119 }
120}
121
122impl Default for BlockchainExecutionClientFactory {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128impl ExecutionClientFactory for BlockchainExecutionClientFactory {
129 fn create(
130 &self,
131 trader_id: TraderId,
132 name: &str,
133 config: &dyn ClientConfig,
134 cache: CacheView,
135 _clock: Rc<RefCell<dyn Clock>>,
136 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
137 let blockchain_execution_config = config
138 .as_any()
139 .downcast_ref::<BlockchainExecutionClientConfig>()
140 .ok_or_else(|| {
141 anyhow::anyhow!(
142 "Invalid config type for BlockchainExecutionClientFactory. Expected `BlockchainExecutionClientConfig`, was {config:?}"
143 )
144 })?;
145
146 let core_execution_client = ExecutionClientCore::new(
147 trader_id,
148 ClientId::from(name),
149 *BLOCKCHAIN_VENUE,
150 OmsType::Netting,
151 blockchain_execution_config.client_id,
152 AccountType::Wallet,
153 None,
154 cache,
155 );
156
157 let client = BlockchainExecutionClient::new(
158 core_execution_client,
159 blockchain_execution_config.clone(),
160 )?;
161
162 Ok(Box::new(client))
163 }
164
165 fn name(&self) -> &'static str {
166 BLOCKCHAIN
167 }
168
169 fn config_type(&self) -> &'static str {
170 "BlockchainExecutionClientConfig"
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use std::{cell::RefCell, rc::Rc, sync::Arc};
177
178 use nautilus_common::{
179 cache::Cache, clock::VirtualClock, config::ConfigError, factories::DataClientFactory,
180 };
181 use nautilus_model::defi::{
182 DexType,
183 chain::{Blockchain, chains},
184 };
185 use rstest::rstest;
186
187 use crate::{
188 config::BlockchainDataClientConfig, constants::BLOCKCHAIN,
189 factories::BlockchainDataClientFactory,
190 };
191
192 #[rstest]
193 fn test_blockchain_data_client_config_creation() {
194 let chain = Arc::new(chains::ETHEREUM.clone());
195 let config = BlockchainDataClientConfig::builder()
196 .chain(chain)
197 .http_rpc_url("https://eth-mainnet.example.com".into())
198 .build();
199
200 assert_eq!(config.chain.name, Blockchain::Ethereum);
201 assert_eq!(
202 config.http_rpc_url.expose_secret(),
203 "https://eth-mainnet.example.com",
204 );
205 }
206
207 #[rstest]
208 fn test_factory_creation() {
209 let factory = BlockchainDataClientFactory::new();
210 assert_eq!(factory.name(), BLOCKCHAIN);
211 assert_eq!(factory.config_type(), "BlockchainDataClientConfig");
212 }
213
214 #[rstest]
215 fn test_factory_rejects_duplicate_selections_before_setup() {
216 let config = BlockchainDataClientConfig::builder()
217 .chain(Arc::new(chains::ETHEREUM.clone()))
218 .http_rpc_url("https://rpc.example.com".into())
219 .dex_ids(vec![DexType::UniswapV2, DexType::UniswapV2])
220 .build();
221 let cache = Rc::new(RefCell::new(Cache::default()));
222 let clock = Rc::new(RefCell::new(VirtualClock::new()));
223 let error = BlockchainDataClientFactory::new()
224 .create("BLOCKCHAIN", &config, cache.into(), clock)
225 .err()
226 .expect("duplicate config must fail before client setup");
227
228 assert_eq!(
229 error.downcast_ref::<ConfigError>(),
230 Some(&ConfigError::duplicate("dex_ids", None))
231 );
232 }
233}