nautilus_blockchain/execution/
client.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
16use std::sync::Arc;
17
18use alloy::primitives::Address;
19use async_trait::async_trait;
20use nautilus_common::{
21    messages::{
22        ExecutionEvent,
23        execution::{
24            BatchCancelOrders, CancelAllOrders, CancelOrder, GenerateFillReports,
25            GenerateOrderStatusReport, GeneratePositionReports, ModifyOrder, QueryAccount,
26            QueryOrder, SubmitOrder, SubmitOrderList,
27        },
28    },
29    runner::get_exec_event_sender,
30};
31use nautilus_core::UnixNanos;
32use nautilus_execution::client::{ExecutionClient, base::ExecutionClientCore};
33use nautilus_live::execution::client::LiveExecutionClient;
34use nautilus_model::{
35    accounts::AccountAny,
36    defi::{SharedChain, validation::validate_address},
37    enums::OmsType,
38    identifiers::{AccountId, ClientId, Venue},
39    reports::{ExecutionMassStatus, FillReport, OrderStatusReport, PositionStatusReport},
40    types::{AccountBalance, MarginBalance, Money},
41};
42
43use crate::{config::BlockchainExecutionClientConfig, rpc::http::BlockchainHttpRpcClient};
44
45#[derive(Debug, Clone)]
46pub struct BlockchainExecutionClient {
47    core: ExecutionClientCore,
48    chain: SharedChain,
49    wallet_address: Address,
50    connected: bool,
51    http_rpc_client: Arc<BlockchainHttpRpcClient>,
52}
53
54impl BlockchainExecutionClient {
55    /// Creates a new [`BlockchainExecutionClient`] instance for the specified configuration.
56    ///
57    /// # Panics
58    ///
59    /// Panics if the wallet address in the configuration is invalid or malformed.
60    #[must_use]
61    pub fn new(core_client: ExecutionClientCore, config: BlockchainExecutionClientConfig) -> Self {
62        let http_rpc_client = Arc::new(BlockchainHttpRpcClient::new(
63            config.http_rpc_url.clone(),
64            config.rpc_requests_per_second,
65        ));
66        let wallet_address =
67            validate_address(config.wallet_address.as_str()).expect("Invalid wallet address");
68        Self {
69            core: core_client,
70            connected: false,
71            chain: Arc::new(config.chain),
72            http_rpc_client,
73            wallet_address,
74        }
75    }
76
77    async fn fetch_native_currency_balance(&self) -> anyhow::Result<Money> {
78        let balance_u256 = self
79            .http_rpc_client
80            .get_balance(&self.wallet_address, None)
81            .await?;
82
83        let native_currency = self.chain.native_currency();
84
85        // Convert from wei (18 decimals on-chain) to Money
86        let balance = Money::from_wei(balance_u256, native_currency);
87
88        Ok(balance)
89    }
90
91    async fn refresh_wallet_balances(&self) {
92        if let Ok(native_balance) = self.fetch_native_currency_balance().await {
93            tracing::info!(
94                "Blockchain wallet balance: {} {}",
95                native_balance.as_decimal(),
96                native_balance.currency
97            );
98        }
99    }
100}
101
102impl ExecutionClient for BlockchainExecutionClient {
103    fn is_connected(&self) -> bool {
104        self.connected
105    }
106
107    fn client_id(&self) -> ClientId {
108        self.core.client_id
109    }
110
111    fn account_id(&self) -> AccountId {
112        self.core.account_id
113    }
114
115    fn venue(&self) -> Venue {
116        self.core.venue
117    }
118
119    fn oms_type(&self) -> OmsType {
120        self.core.oms_type
121    }
122
123    fn get_account(&self) -> Option<AccountAny> {
124        todo!("implement get_account")
125    }
126
127    fn generate_account_state(
128        &self,
129        _balances: Vec<AccountBalance>,
130        _margins: Vec<MarginBalance>,
131        _reported: bool,
132        _ts_event: UnixNanos,
133    ) -> anyhow::Result<()> {
134        todo!("implement generate_account_state")
135    }
136
137    fn start(&mut self) -> anyhow::Result<()> {
138        todo!("implement start")
139    }
140
141    fn stop(&mut self) -> anyhow::Result<()> {
142        todo!("implement stop")
143    }
144
145    fn submit_order(&self, _cmd: &SubmitOrder) -> anyhow::Result<()> {
146        todo!("implement submit_order")
147    }
148
149    fn submit_order_list(&self, _cmd: &SubmitOrderList) -> anyhow::Result<()> {
150        todo!("implement submit_order_list")
151    }
152
153    fn modify_order(&self, _cmd: &ModifyOrder) -> anyhow::Result<()> {
154        todo!("implement modify_order")
155    }
156
157    fn cancel_order(&self, _cmd: &CancelOrder) -> anyhow::Result<()> {
158        todo!("implement cancel_order")
159    }
160
161    fn cancel_all_orders(&self, _cmd: &CancelAllOrders) -> anyhow::Result<()> {
162        todo!("implement cancel_all_orders")
163    }
164
165    fn batch_cancel_orders(&self, _cmd: &BatchCancelOrders) -> anyhow::Result<()> {
166        todo!("implement batch_cancel_orders")
167    }
168
169    fn query_account(&self, _cmd: &QueryAccount) -> anyhow::Result<()> {
170        todo!("implement query_account")
171    }
172
173    fn query_order(&self, _cmd: &QueryOrder) -> anyhow::Result<()> {
174        todo!("implement query_order")
175    }
176}
177
178#[async_trait(?Send)]
179impl LiveExecutionClient for BlockchainExecutionClient {
180    async fn connect(&mut self) -> anyhow::Result<()> {
181        if self.connected {
182            tracing::warn!("Blockchain execution client already connected");
183            return Ok(());
184        }
185
186        tracing::info!(
187            "Connecting to blockchain execution client on chain {}",
188            self.chain.name
189        );
190
191        self.refresh_wallet_balances().await;
192
193        self.connected = true;
194        tracing::info!(
195            "Blockchain execution client connected on chain {}",
196            self.chain.name
197        );
198        Ok(())
199    }
200
201    async fn disconnect(&mut self) -> anyhow::Result<()> {
202        todo!("implement disconnect")
203    }
204
205    fn get_message_channel(&self) -> tokio::sync::mpsc::UnboundedSender<ExecutionEvent> {
206        get_exec_event_sender()
207    }
208
209    fn get_clock(&self) -> std::cell::Ref<'_, dyn nautilus_common::clock::Clock> {
210        self.core.clock().borrow()
211    }
212
213    async fn generate_order_status_report(
214        &self,
215        _cmd: &GenerateOrderStatusReport,
216    ) -> anyhow::Result<Option<OrderStatusReport>> {
217        todo!("implement generate_order_status_report")
218    }
219
220    async fn generate_order_status_reports(
221        &self,
222        _cmd: &GenerateOrderStatusReport,
223    ) -> anyhow::Result<Vec<OrderStatusReport>> {
224        todo!("implement generate_order_status_reports")
225    }
226
227    async fn generate_fill_reports(
228        &self,
229        _cmd: GenerateFillReports,
230    ) -> anyhow::Result<Vec<FillReport>> {
231        todo!("implement generate_fill_reports")
232    }
233
234    async fn generate_position_status_reports(
235        &self,
236        _cmd: &GeneratePositionReports,
237    ) -> anyhow::Result<Vec<PositionStatusReport>> {
238        todo!("implement generate_position_status_reports")
239    }
240
241    async fn generate_mass_status(
242        &self,
243        _lookback_mins: Option<u64>,
244    ) -> anyhow::Result<Option<ExecutionMassStatus>> {
245        todo!("implement generate_mass_status")
246    }
247}