Skip to main content

nautilus_blockchain/
factories.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Factory for creating blockchain data clients.
17
18use 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/// Factory for creating blockchain data clients.
46///
47/// This factory creates `BlockchainDataClient` instances configured for different blockchain networks
48/// (Ethereum, Arbitrum, Base, Polygon) with appropriate RPC and HyperSync configurations.
49#[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    /// Creates a new [`BlockchainDataClientFactory`] instance.
62    #[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/// Factory for creating blockchain execution clients.
107#[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    /// Creates a new [`BlockchainExecutionClientFactory`] instance.
116    #[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}