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