Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
386 changes: 366 additions & 20 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"crates/adapters/bybit",
"crates/adapters/coinbase_intx",
"crates/adapters/databento",
"crates/adapters/dydx",
"crates/adapters/hyperliquid",
"crates/adapters/okx",
"crates/adapters/tardis",
Expand Down
89 changes: 89 additions & 0 deletions crates/adapters/dydx/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
[package]
name = "nautilus-dydx"
readme = "README.md"
publish = false # Do not publish to crates.io for now
version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
description = "dYdX v4 exchange integration adapter for the Nautilus trading engine"
categories.workspace = true
keywords.workspace = true
documentation.workspace = true
repository.workspace = true
homepage.workspace = true

[lints]
workspace = true

[lib]
name = "nautilus_dydx"
crate-type = ["rlib", "cdylib"]

[features]
default = []
extension-module = [
"nautilus-common/extension-module",
"nautilus-core/extension-module",
"nautilus-data/extension-module",
"nautilus-model/extension-module",
"nautilus-network/extension-module",
"python",
"pyo3/extension-module",
]
python = [
"nautilus-common/python",
"nautilus-core/python",
"nautilus-data/python",
"nautilus-model/python",
"nautilus-network/python",
"pyo3",
"pyo3-async-runtimes",
]

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
nautilus-common = { workspace = true }
nautilus-core = { workspace = true }
nautilus-data = { workspace = true }
nautilus-execution = { workspace = true }
nautilus-live = { workspace = true }
nautilus-model = { workspace = true }
nautilus-network = { workspace = true }

anyhow = { workspace = true }
bip32 = "0.5"
chrono = { workspace = true }
cosmrs = { version = "0.21", features = ["bip32"] }
derive_builder = { workspace = true }
hex = "0.4"
prost = "0.13"
prost-types = "0.13"
reqwest = { workspace = true }
rust_decimal = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_with = "3.11"
strum = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
tokio-util = { workspace = true }
tonic = "0.13"
ustr = { workspace = true }
zeroize = { version = "1.8", features = ["derive"] }

pyo3 = { workspace = true, optional = true }
pyo3-async-runtimes = { workspace = true, optional = true }

[dev-dependencies]
nautilus-testkit = { workspace = true }
criterion = { workspace = true }
rstest = { workspace = true }
rust_decimal_macros = { workspace = true }
tracing-test = { workspace = true }
url = { workspace = true }
axum = { workspace = true }
1 change: 1 addition & 0 deletions crates/adapters/dydx/LICENSE
103 changes: 103 additions & 0 deletions crates/adapters/dydx/src/common/consts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// -------------------------------------------------------------------------------------------------
// Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
// https://nautechsystems.io
//
// Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -------------------------------------------------------------------------------------------------

//! Core constants shared across the dYdX adapter components.

use std::sync::LazyLock;

use nautilus_model::identifiers::Venue;
use reqwest::StatusCode;
use ustr::Ustr;

/// dYdX adapter name.
pub const DYDX: &str = "DYDX";

/// dYdX venue identifier.
pub static DYDX_VENUE: LazyLock<Venue> = LazyLock::new(|| Venue::new(Ustr::from(DYDX)));

/// dYdX mainnet chain ID.
pub const DYDX_CHAIN_ID: &str = "dydx-mainnet-1";

/// dYdX testnet chain ID.
pub const DYDX_TESTNET_CHAIN_ID: &str = "dydx-testnet-4";

/// Cosmos SDK bech32 address prefix for dYdX.
pub const DYDX_BECH32_PREFIX: &str = "dydx";

/// USDC gas denomination (native chain token).
pub const USDC_GAS_DENOM: &str =
"ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5";

/// USDC asset denomination for transfers.
pub const USDC_DENOM: &str = "uusdc";

/// HD wallet derivation path for dYdX accounts (Cosmos SLIP-0044).
/// Format: m/44'/118'/0'/0/{account_index}
pub const DYDX_DERIVATION_PATH_PREFIX: &str = "m/44'/118'/0'/0";

/// Coin type for Cosmos ecosystem (SLIP-0044).
pub const COSMOS_COIN_TYPE: u32 = 118;

/// Determines if an HTTP status code should trigger a retry.
///
/// Retries on:
/// - 429 (Too Many Requests)
/// - 500-599 (Server Errors)
///
/// Does NOT retry on:
/// - 400 (Bad Request) - indicates client error that won't be fixed by retrying
/// - 401 (Unauthorized) - not applicable for dYdX Indexer (no auth required)
/// - 403 (Forbidden) - typically compliance/screening issues
/// - 404 (Not Found) - resource doesn't exist
#[must_use]
pub const fn should_retry_error_code(status: &StatusCode) -> bool {
matches!(status.as_u16(), 429 | 500..=599)
}

////////////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////////////

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_should_retry_429() {
assert!(should_retry_error_code(&StatusCode::TOO_MANY_REQUESTS));
}

#[test]
fn test_should_retry_server_errors() {
assert!(should_retry_error_code(&StatusCode::INTERNAL_SERVER_ERROR));
assert!(should_retry_error_code(&StatusCode::BAD_GATEWAY));
assert!(should_retry_error_code(&StatusCode::SERVICE_UNAVAILABLE));
assert!(should_retry_error_code(&StatusCode::GATEWAY_TIMEOUT));
}

#[test]
fn test_should_not_retry_client_errors() {
assert!(!should_retry_error_code(&StatusCode::BAD_REQUEST));
assert!(!should_retry_error_code(&StatusCode::UNAUTHORIZED));
assert!(!should_retry_error_code(&StatusCode::FORBIDDEN));
assert!(!should_retry_error_code(&StatusCode::NOT_FOUND));
}

#[test]
fn test_should_not_retry_success() {
assert!(!should_retry_error_code(&StatusCode::OK));
assert!(!should_retry_error_code(&StatusCode::CREATED));
}
}
Loading
Loading