Skip to main content

nautilus_blockchain/python/
config.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//! Python bindings for blockchain configuration.
17
18use std::sync::Arc;
19
20use nautilus_core::{
21    python::to_pyvalue_err,
22    string::secret::{REDACTED, SecretString},
23};
24use nautilus_infrastructure::sql::pg::PostgresConnectOptions;
25use nautilus_model::{
26    defi::{Chain, DexType},
27    identifiers::AccountId,
28};
29use nautilus_network::websocket::TransportBackend;
30use pyo3::prelude::*;
31
32use crate::config::{
33    BlockchainChainAnchorConfig, BlockchainDataClientConfig, BlockchainExecutionClientConfig,
34    BlockchainProviderIdentity, BlockchainVerificationConfig, BlockchainVerificationProviderConfig,
35    DexPoolFilters, QuoteSpendLimit,
36};
37
38#[pymethods]
39#[pyo3_stub_gen::derive::gen_stub_pymethods(module = "nautilus_trader.adapters.blockchain")]
40impl DexPoolFilters {
41    /// Defines filtering criteria for the DEX pool universe that the data client will operate on.
42    #[new]
43    #[must_use]
44    pub fn py_new(remove_pools_with_empty_erc20_fields: Option<bool>) -> Self {
45        Self::builder()
46            .maybe_remove_pools_with_empty_erc20fields(remove_pools_with_empty_erc20_fields)
47            .build()
48    }
49}
50
51#[pymethods]
52#[pyo3_stub_gen::derive::gen_stub_pymethods(module = "nautilus_trader.adapters.blockchain")]
53impl QuoteSpendLimit {
54    /// Defines the maximum quote-token spend for a directed BUY swap pair.
55    #[new]
56    #[must_use]
57    fn py_new(
58        token_in: String,
59        token_out: String,
60        spend_token: String,
61        spend_token_decimals: u8,
62        max_amount: String,
63    ) -> Self {
64        Self::builder()
65            .token_in(token_in)
66            .token_out(token_out)
67            .spend_token(spend_token)
68            .spend_token_decimals(spend_token_decimals)
69            .max_amount(max_amount)
70            .build()
71    }
72}
73
74#[pymethods]
75#[pyo3_stub_gen::derive::gen_stub_pymethods(module = "nautilus_trader.adapters.blockchain")]
76impl BlockchainProviderIdentity {
77    /// Stable local identity for one RPC provider and its failure domains.
78    #[new]
79    #[must_use]
80    fn py_new(provider_id: String, operator_id: String, failure_domain_ids: Vec<String>) -> Self {
81        Self::builder()
82            .provider_id(provider_id)
83            .operator_id(operator_id)
84            .failure_domain_ids(failure_domain_ids)
85            .build()
86    }
87}
88
89#[pymethods]
90#[pyo3_stub_gen::derive::gen_stub_pymethods(module = "nautilus_trader.adapters.blockchain")]
91impl BlockchainVerificationProviderConfig {
92    /// Configuration for one read-only verification RPC provider.
93    #[new]
94    #[must_use]
95    fn py_new(identity: BlockchainProviderIdentity, http_rpc_url: String) -> Self {
96        Self::builder()
97            .identity(identity)
98            .http_rpc_url(SecretString::from(http_rpc_url))
99            .build()
100    }
101
102    fn __repr__(&self) -> String {
103        format!(
104            "BlockchainVerificationProviderConfig(identity={:?}, http_rpc_url={REDACTED})",
105            self.identity
106        )
107    }
108}
109
110#[pymethods]
111#[pyo3_stub_gen::derive::gen_stub_pymethods(module = "nautilus_trader.adapters.blockchain")]
112impl BlockchainChainAnchorConfig {
113    /// Locally trusted finalized chain checkpoint and freshness policy.
114    #[new]
115    #[expect(clippy::too_many_arguments)]
116    #[must_use]
117    fn py_new(
118        chain_id: u32,
119        chain_name: String,
120        checkpoint_height: u64,
121        checkpoint_hash: String,
122        checkpoint_timestamp: u64,
123        max_head_skew_blocks: u64,
124        max_head_age_secs: u64,
125        max_future_drift_secs: u64,
126    ) -> Self {
127        Self::builder()
128            .chain_id(chain_id)
129            .chain_name(chain_name)
130            .checkpoint_height(checkpoint_height)
131            .checkpoint_hash(checkpoint_hash)
132            .checkpoint_timestamp(checkpoint_timestamp)
133            .max_head_skew_blocks(max_head_skew_blocks)
134            .max_head_age_secs(max_head_age_secs)
135            .max_future_drift_secs(max_future_drift_secs)
136            .build()
137    }
138}
139
140#[pymethods]
141#[pyo3_stub_gen::derive::gen_stub_pymethods(module = "nautilus_trader.adapters.blockchain")]
142impl BlockchainVerificationConfig {
143    /// Independent verification topology and reviewed local deployment identity.
144    #[new]
145    fn py_new(
146        authoritative: BlockchainProviderIdentity,
147        verifiers: Vec<BlockchainVerificationProviderConfig>,
148        chain_anchor: BlockchainChainAnchorConfig,
149        manifest_version: String,
150        manifest_digest: String,
151        deployment_manifest_json: String,
152    ) -> PyResult<Self> {
153        let deployment_manifest = serde_json::from_str(&deployment_manifest_json)
154            .map_err(|_| to_pyvalue_err("Invalid deployment manifest JSON"))?;
155        Ok(Self::builder()
156            .authoritative(authoritative)
157            .verifiers(verifiers)
158            .chain_anchor(chain_anchor)
159            .manifest_version(manifest_version)
160            .manifest_digest(manifest_digest)
161            .deployment_manifest(deployment_manifest)
162            .build())
163    }
164
165    #[getter]
166    fn deployment_manifest_json(&self) -> PyResult<String> {
167        serde_json::to_string(&self.deployment_manifest).map_err(to_pyvalue_err)
168    }
169}
170
171#[pymethods]
172#[pyo3_stub_gen::derive::gen_stub_pymethods(module = "nautilus_trader.adapters.blockchain")]
173impl BlockchainDataClientConfig {
174    /// Configuration for blockchain data clients.
175    #[new]
176    #[expect(clippy::too_many_arguments)]
177    #[pyo3(signature = (chain, dex_ids, http_rpc_url, rpc_requests_per_second=None, multicall_calls_per_rpc_request=None, wss_rpc_url=None, use_hypersync_for_live_data=true, from_block=None, pool_filters=None, postgres_cache_database_config=None, proxy_url=None, transport_backend=None))]
178    fn py_new(
179        #[gen_stub(
180            override_type(
181                type_repr = "nautilus_trader.model.Chain",
182                imports = ("nautilus_trader.model",),
183            ),
184        )]
185        chain: &Chain,
186        #[gen_stub(
187            override_type(
188                type_repr = "typing.Sequence[nautilus_trader.model.DexType]",
189                imports = ("typing", "nautilus_trader.model"),
190            ),
191        )]
192        dex_ids: Vec<DexType>,
193        http_rpc_url: String,
194        rpc_requests_per_second: Option<u32>,
195        multicall_calls_per_rpc_request: Option<u32>,
196        wss_rpc_url: Option<String>,
197        use_hypersync_for_live_data: bool,
198        from_block: Option<u64>,
199        pool_filters: Option<DexPoolFilters>,
200        #[gen_stub(
201            override_type(
202                type_repr = "typing.Optional[nautilus_trader.infrastructure.PostgresConnectOptions]",
203                imports = ("typing", "nautilus_trader.infrastructure"),
204            ),
205        )]
206        postgres_cache_database_config: Option<PostgresConnectOptions>,
207        proxy_url: Option<String>,
208        transport_backend: Option<TransportBackend>,
209    ) -> Self {
210        Self::builder()
211            .chain(Arc::new(chain.clone()))
212            .dex_ids(dex_ids)
213            .http_rpc_url(SecretString::from(http_rpc_url))
214            .maybe_rpc_requests_per_second(rpc_requests_per_second)
215            .maybe_multicall_calls_per_rpc_request(multicall_calls_per_rpc_request)
216            .maybe_wss_rpc_url(wss_rpc_url.map(SecretString::from))
217            .use_hypersync_for_live_data(use_hypersync_for_live_data)
218            .maybe_from_block(from_block)
219            .maybe_pool_filters(pool_filters)
220            .maybe_postgres_cache_database_config(postgres_cache_database_config)
221            .maybe_proxy_url(proxy_url.map(SecretString::from))
222            .transport_backend(transport_backend.unwrap_or_default())
223            .build()
224    }
225
226    /// Returns the chain configuration.
227    #[getter]
228    #[gen_stub(
229        override_return_type(
230            type_repr = "nautilus_trader.model.Chain",
231            imports = ("nautilus_trader.model",),
232        ),
233    )]
234    fn chain(&self) -> Chain {
235        (*self.chain).clone()
236    }
237
238    /// Returns the RPC requests per second limit.
239    #[getter]
240    const fn rpc_requests_per_second(&self) -> Option<u32> {
241        self.rpc_requests_per_second
242    }
243
244    /// Returns whether to use HyperSync for live data.
245    #[getter]
246    const fn use_hypersync_for_live_data(&self) -> bool {
247        self.use_hypersync_for_live_data
248    }
249
250    /// Returns the starting block for sync.
251    #[getter]
252    #[expect(clippy::wrong_self_convention)]
253    const fn from_block(&self) -> Option<u64> {
254        self.from_block
255    }
256
257    #[getter]
258    const fn has_postgres_cache_database_config(&self) -> bool {
259        self.postgres_cache_database_config.is_some()
260    }
261
262    #[getter]
263    const fn has_proxy_url(&self) -> bool {
264        self.proxy_url.is_some()
265    }
266
267    /// Returns a string representation of the configuration.
268    fn __repr__(&self) -> String {
269        format!(
270            "BlockchainDataClientConfig(chain={:?}, http_rpc_url={REDACTED}, wss_rpc_url={:?}, use_hypersync_for_live_data={}, from_block={:?})",
271            self.chain.name,
272            self.wss_rpc_url.as_ref().map(|_| REDACTED),
273            self.use_hypersync_for_live_data,
274            self.from_block
275        )
276    }
277}
278
279#[pymethods]
280#[pyo3_stub_gen::derive::gen_stub_pymethods(module = "nautilus_trader.adapters.blockchain")]
281impl BlockchainExecutionClientConfig {
282    /// Configuration for blockchain execution clients.
283    #[new]
284    #[expect(clippy::too_many_arguments)]
285    #[pyo3(signature = (client_id, chain, wallet_address, http_rpc_url, signer_private_key_env, router_addresses, weth_address, max_fee_per_gas_wei, base_fee_buffer_bps, gas_limit, gas_buffer_bps, tokens=None, rpc_requests_per_second=None, unlimited_approval=false, postgres_cache_database_config=None, transport_backend=None, *, allowed_token_pairs=None, quote_spend_limits=None, slippage_bps=None, max_slippage_bps=None, max_order_amount=None, deadline_seconds=None, max_quote_age_blocks=None, receipt_timeout_secs=None, payload_key_env=None, payload_key_retired_env=None, payload_deployment_id=None, verification=None))]
286    fn py_new(
287        client_id: AccountId,
288        #[gen_stub(
289            override_type(
290                type_repr = "nautilus_trader.model.Chain",
291                imports = ("nautilus_trader.model",),
292            ),
293        )]
294        chain: &Chain,
295        wallet_address: String,
296        http_rpc_url: String,
297        signer_private_key_env: String,
298        router_addresses: Vec<String>,
299        weth_address: String,
300        max_fee_per_gas_wei: u64,
301        base_fee_buffer_bps: u32,
302        gas_limit: u64,
303        gas_buffer_bps: u32,
304        tokens: Option<Vec<String>>,
305        rpc_requests_per_second: Option<u32>,
306        unlimited_approval: bool,
307        #[gen_stub(
308            override_type(
309                type_repr = "typing.Optional[nautilus_trader.infrastructure.PostgresConnectOptions]",
310                imports = ("typing", "nautilus_trader.infrastructure"),
311            ),
312        )]
313        postgres_cache_database_config: Option<PostgresConnectOptions>,
314        transport_backend: Option<TransportBackend>,
315        allowed_token_pairs: Option<Vec<(String, String)>>,
316        quote_spend_limits: Option<Vec<QuoteSpendLimit>>,
317        slippage_bps: Option<u32>,
318        max_slippage_bps: Option<u32>,
319        max_order_amount: Option<u64>,
320        deadline_seconds: Option<u64>,
321        max_quote_age_blocks: Option<u64>,
322        receipt_timeout_secs: Option<u64>,
323        payload_key_env: Option<String>,
324        payload_key_retired_env: Option<Vec<String>>,
325        payload_deployment_id: Option<String>,
326        verification: Option<BlockchainVerificationConfig>,
327    ) -> Self {
328        Self::builder()
329            .client_id(client_id)
330            .chain(chain.clone())
331            .wallet_address(wallet_address)
332            .http_rpc_url(SecretString::from(http_rpc_url))
333            .signer_private_key_env(signer_private_key_env)
334            .router_addresses(router_addresses)
335            .weth_address(weth_address)
336            .max_fee_per_gas_wei(max_fee_per_gas_wei)
337            .base_fee_buffer_bps(base_fee_buffer_bps)
338            .gas_limit(gas_limit)
339            .gas_buffer_bps(gas_buffer_bps)
340            .maybe_allowed_token_pairs(allowed_token_pairs)
341            .maybe_quote_spend_limits(quote_spend_limits)
342            .maybe_slippage_bps(slippage_bps)
343            .maybe_max_slippage_bps(max_slippage_bps)
344            .maybe_max_order_amount(max_order_amount)
345            .maybe_deadline_seconds(deadline_seconds)
346            .maybe_max_quote_age_blocks(max_quote_age_blocks)
347            .maybe_receipt_timeout_secs(receipt_timeout_secs)
348            .maybe_payload_key_env(payload_key_env)
349            .payload_key_retired_env(payload_key_retired_env.unwrap_or_default())
350            .maybe_payload_deployment_id(payload_deployment_id)
351            .maybe_verification(verification)
352            .maybe_tokens(tokens)
353            .maybe_rpc_requests_per_second(rpc_requests_per_second)
354            .unlimited_approval(unlimited_approval)
355            .maybe_postgres_cache_database_config(postgres_cache_database_config)
356            .transport_backend(transport_backend.unwrap_or_default())
357            .build()
358    }
359
360    /// Returns the allowed (input token, output token) address pairs.
361    #[getter]
362    #[gen_stub(override_return_type(type_repr = "list[tuple[str, str]] | None",))]
363    fn allowed_token_pairs(&self) -> Option<Vec<(String, String)>> {
364        self.allowed_token_pairs.clone()
365    }
366
367    /// Returns the account ID.
368    #[getter]
369    const fn client_id(&self) -> AccountId {
370        self.client_id
371    }
372
373    /// Returns the chain configuration.
374    #[getter]
375    #[gen_stub(
376        override_return_type(
377            type_repr = "nautilus_trader.model.Chain",
378            imports = ("nautilus_trader.model",),
379        ),
380    )]
381    fn chain(&self) -> Chain {
382        self.chain.clone()
383    }
384
385    /// Returns the RPC requests per second limit.
386    #[getter]
387    const fn rpc_requests_per_second(&self) -> Option<u32> {
388        self.rpc_requests_per_second
389    }
390
391    #[getter]
392    const fn has_postgres_cache_database_config(&self) -> bool {
393        self.postgres_cache_database_config.is_some()
394    }
395
396    /// Returns a string representation of the configuration.
397    fn __repr__(&self) -> String {
398        format!(
399            "BlockchainExecutionClientConfig(chain={:?}, wallet_address={}, http_rpc_url={REDACTED})",
400            self.chain.name, self.wallet_address
401        )
402    }
403}