nautilus_blockchain/
factories.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 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::{cache::Cache, clock::Clock};
21use nautilus_data::client::DataClient;
22use nautilus_execution::client::{ExecutionClient, base::ExecutionClientCore};
23use nautilus_model::{
24    enums::{AccountType, OmsType},
25    identifiers::ClientId,
26};
27use nautilus_system::{
28    ExecutionClientFactory,
29    factories::{ClientConfig, DataClientFactory},
30};
31
32use crate::{
33    config::{BlockchainDataClientConfig, BlockchainExecutionClientConfig},
34    constants::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.core.nautilus_pyo3.blockchain")
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: Rc<RefCell<Cache>>,
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        let client = BlockchainDataClient::new(blockchain_config.clone());
92
93        Ok(Box::new(client))
94    }
95
96    fn name(&self) -> &'static str {
97        "BLOCKCHAIN"
98    }
99
100    fn config_type(&self) -> &'static str {
101        "BlockchainDataClientConfig"
102    }
103}
104
105/// Factory for creating blockchain execution clients.
106#[derive(Debug, Clone)]
107#[cfg_attr(
108    feature = "python",
109    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.blockchain")
110)]
111#[cfg_attr(
112    feature = "python",
113    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.blockchain")
114)]
115pub struct BlockchainExecutionClientFactory;
116
117impl BlockchainExecutionClientFactory {
118    /// Creates a new [`BlockchainExecutionClientFactory`] instance.
119    #[must_use]
120    pub const fn new() -> Self {
121        Self
122    }
123}
124
125impl Default for BlockchainExecutionClientFactory {
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131impl ExecutionClientFactory for BlockchainExecutionClientFactory {
132    fn create(
133        &self,
134        name: &str,
135        config: &dyn ClientConfig,
136        cache: Rc<RefCell<Cache>>,
137        clock: Rc<RefCell<dyn Clock>>,
138    ) -> anyhow::Result<Box<dyn ExecutionClient>> {
139        let blockchain_execution_config = config
140            .as_any()
141            .downcast_ref::<BlockchainExecutionClientConfig>()
142            .ok_or_else(|| {
143                anyhow::anyhow!(
144                    "Invalid config type for BlockchainDataClientFactory. Expected `BlockchainDataClientConfig`, was {config:?}"
145                )
146            })?;
147
148        let core_execution_client = ExecutionClientCore::new(
149            blockchain_execution_config.trader_id,
150            ClientId::from(name),
151            *BLOCKCHAIN_VENUE,
152            OmsType::Netting,
153            blockchain_execution_config.client_id,
154            AccountType::Wallet,
155            None,
156            clock,
157            cache,
158        );
159
160        let client = BlockchainExecutionClient::new(
161            core_execution_client,
162            blockchain_execution_config.clone(),
163        );
164
165        Ok(Box::new(client))
166    }
167
168    fn name(&self) -> &'static str {
169        "BLOCKCHAIN"
170    }
171
172    fn config_type(&self) -> &'static str {
173        "BlockchainExecutionClientConfig"
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use std::sync::Arc;
180
181    use nautilus_model::defi::chain::{Blockchain, chains};
182    use nautilus_system::factories::DataClientFactory;
183    use rstest::rstest;
184
185    use crate::{config::BlockchainDataClientConfig, factories::BlockchainDataClientFactory};
186
187    #[rstest]
188    fn test_blockchain_data_client_config_creation() {
189        let chain = Arc::new(chains::ETHEREUM.clone());
190        let config = BlockchainDataClientConfig::new(
191            chain,
192            vec![],
193            "https://eth-mainnet.example.com".to_string(),
194            None,
195            None,
196            None,
197            false,
198            None,
199            None,
200            None,
201        );
202
203        assert_eq!(config.chain.name, Blockchain::Ethereum);
204        assert_eq!(config.http_rpc_url, "https://eth-mainnet.example.com");
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}