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        let client = BlockchainDataClient::new(ClientId::from(name), 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.adapters.blockchain", from_py_object)
110)]
111pub struct BlockchainExecutionClientFactory;
112
113impl BlockchainExecutionClientFactory {
114    /// Creates a new [`BlockchainExecutionClientFactory`] instance.
115    #[must_use]
116    pub const fn new() -> Self {
117        Self
118    }
119}
120
121impl Default for BlockchainExecutionClientFactory {
122    fn default() -> Self {
123        Self::new()
124    }
125}
126
127impl ExecutionClientFactory for BlockchainExecutionClientFactory {
128    fn create(
129        &self,
130        trader_id: TraderId,
131        name: &str,
132        config: &dyn ClientConfig,
133        cache: CacheView,
134        _clock: Rc<RefCell<dyn Clock>>,
135    ) -> anyhow::Result<Box<dyn ExecutionClient>> {
136        let blockchain_execution_config = config
137            .as_any()
138            .downcast_ref::<BlockchainExecutionClientConfig>()
139            .ok_or_else(|| {
140                anyhow::anyhow!(
141                    "Invalid config type for BlockchainExecutionClientFactory. Expected `BlockchainExecutionClientConfig`, was {config:?}"
142                )
143            })?;
144
145        let core_execution_client = ExecutionClientCore::new(
146            trader_id,
147            ClientId::from(name),
148            *BLOCKCHAIN_VENUE,
149            OmsType::Netting,
150            blockchain_execution_config.client_id,
151            AccountType::Wallet,
152            None,
153            cache,
154        );
155
156        let client = BlockchainExecutionClient::new(
157            core_execution_client,
158            blockchain_execution_config.clone(),
159        )?;
160
161        Ok(Box::new(client))
162    }
163
164    fn name(&self) -> &'static str {
165        BLOCKCHAIN
166    }
167
168    fn config_type(&self) -> &'static str {
169        "BlockchainExecutionClientConfig"
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use std::sync::Arc;
176
177    use nautilus_common::factories::DataClientFactory;
178    use nautilus_model::defi::chain::{Blockchain, chains};
179    use rstest::rstest;
180
181    use crate::{
182        config::BlockchainDataClientConfig, constants::BLOCKCHAIN,
183        factories::BlockchainDataClientFactory,
184    };
185
186    #[rstest]
187    fn test_blockchain_data_client_config_creation() {
188        let chain = Arc::new(chains::ETHEREUM.clone());
189        let config = BlockchainDataClientConfig::builder()
190            .chain(chain)
191            .http_rpc_url("https://eth-mainnet.example.com".into())
192            .build();
193
194        assert_eq!(config.chain.name, Blockchain::Ethereum);
195        assert_eq!(
196            config.http_rpc_url.expose_secret(),
197            "https://eth-mainnet.example.com",
198        );
199    }
200
201    #[rstest]
202    fn test_factory_creation() {
203        let factory = BlockchainDataClientFactory::new();
204        assert_eq!(factory.name(), BLOCKCHAIN);
205        assert_eq!(factory.config_type(), "BlockchainDataClientConfig");
206    }
207}