# Architecture — AsterDex Futures SDK v3 (Rust)
## Overview
Single-crate Rust library (`lib.rs`). No binary, no server, no database, no Docker. The SDK is consumed as a `path` or `git` dependency by caller applications. All network I/O is async via Tokio; callers drive the runtime.
### Module Layout
```
src/
├── lib.rs — public re-exports + top-level type aliases
├── auth/
│ ├── credentials.rs — env var loading, Credentials struct
│ └── eip712.rs — EIP-712 signing engine
├── rest/
│ ├── client.rs — RestClient struct + raw_* methods
│ ├── request.rs — request builder
│ ├── response.rs — ApiResponse<T> wrapper
│ └── error.rs — AsterDexError enum
├── ws/
│ ├── client.rs — WebSocketClient, WebSocketStream, UserDataStream
│ ├── reconnect.rs — exponential backoff state machine
│ └── ping_pong.rs — transparent ping/pong handler
├── futures/ — futures trading domain (authoritative)
│ ├── mod.rs — pub use RestClient as FuturesClient
│ ├── models/
│ │ ├── mod.rs
│ │ ├── trading.rs — order/position request and response types
│ │ ├── market.rs — market data response types
│ │ ├── account.rs — account/balance/leverage types
│ │ ├── websocket.rs — typed WebSocket market stream event structs
│ │ ├── user_data.rs — user data stream event structs
│ │ └── de.rs — shared serde helpers
│ └── endpoints/
│ ├── mod.rs
│ ├── trading.rs — place/cancel/query order methods
│ ├── market_data.rs — public market data methods
│ ├── account.rs — balance, leverage, position mode methods
│ ├── batch.rs — batch order methods
│ ├── settings.rs — leverage and margin type methods
│ ├── listen_key.rs — create/renew/delete listen key
│ ├── advanced_trading.rs — allOrders, userTrades, income, adlQuantile, etc.
│ ├── mmp.rs — MMP management endpoints
│ └── asset.rs — asset wallet transfer, sub-account bind
├── spot/
│ └── mod.rs — placeholder for spot trading domain (not yet implemented)
├── models/
│ └── mod.rs — backward-compat shim: re-exports from futures::models
└── endpoints/
└── mod.rs — empty stub (implementations moved to futures/endpoints/)
```
### Public API
| `FuturesClient` | Primary type for all futures REST operations — type alias for `RestClient` |
| `RestClient` | Underlying HTTP client type (`FuturesClient = RestClient`) |
| `WebSocketClient` | All WebSocket subscriptions — market streams and user data stream |
| `AsterDexError` | Typed error enum — all error variants the caller must handle |
`FuturesClient` is constructed via `FuturesClient::from_env()` (private endpoints) or `FuturesClient::new_public(base_url)` (public endpoints). All endpoint methods live on `impl RestClient` inside `src/futures/endpoints/` and are automatically available on `FuturesClient`.
Additionally: all model structs, `ApiResponse<T>`, and stream message types (`WebSocketMessage`, `UserDataEvent`) are public under `asterdex_sdk::futures::models::*` (or via the backward-compat `asterdex_sdk::models::*` re-exports).
---
## Top Architecture Decisions
### ADR-002: EIP-712 via alloy-rs
All private REST endpoints use EIP-712 Ethereum structured-data signing — not HMAC-SHA256, not API key pairs.
- Library: `alloy-signer-local` (`PrivateKeySigner`) + `alloy-sol-types` (`sol!` macro)
- Domain: `name="AsterSignTransaction"`, `version="1"`, `chainId=1666`, `verifyingContract=address(0)`
- Primary type: `Message(string msg)` where `msg` = URL-encoded query string of all request parameters
- Digest: `keccak256(0x1901 || domainSeparator || structHash)`
- Output: 0x-prefixed 130-char hex signature appended as `&signature=<hex>` to the request URL
`ethers-rs` was rejected (deprecated). Manual byte manipulation was rejected (error-prone). The `sol!` macro catches schema errors at compile time.
### ADR-003: reqwest + rustls-tls (No OpenSSL)
`reqwest ^0.12` with `rustls-tls`, `default-features = false`. Pure-Rust TLS: no system `libssl` required, no OpenSSL dependency. Eliminates the most common cross-platform compilation failure.
`Cargo.toml`: `reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }`
### ADR-007: All Prices and Quantities as String (Never f64)
All price, quantity, and percentage fields in request and response structs use `String`. IEEE 754 `f64` precision loss is unacceptable in a trading context. Callers parse strings to their preferred decimal type (e.g., `rust_decimal::Decimal`).
### ADR-009: No SDK-Level Rate Limiting or Retry Logic
The SDK does NOT retry any request and does NOT implement a rate limiter.
- HTTP 429 → `Err(AsterDexError::RateLimited { retry_after: Option<Duration> })` — caller backs off
- HTTP 418 → `Err(AsterDexError::IpBanned { retry_after: Option<Duration> })` — caller MUST stop
- Rate limit headers (`X-MBX-USED-WEIGHT-1MINUTE`, `X-MBX-ORDER-COUNT-1MINUTE`) are exposed on every `ApiResponse<T>` so callers can track usage
Imposing a retry policy in a library crate is wrong — different callers need different policies.
### ADR-011: Raw JSON Passthrough (`serde_json::Value`)
`RestClient` provides `raw_get(path, params)` and `raw_post(path, params)` (plus signed variants) returning `Result<ApiResponse<serde_json::Value>, AsterDexError>`. Full signing and error handling apply — these are not a bypass. Typed methods remain primary.
---
## WebSocket Design
### Connection Model (ADR-004)
`tokio-tungstenite ^0.21` with `rustls-tls-native-roots`. Combined stream endpoint: `wss://fstream.asterdex.com/stream?streams=<name1>/<name2>/...`
- Max 200 streams per connection (API limit)
- Market data: no auth required
- User data: authenticated via listen key in URL (`wss://.../ws/<listen_key>`)
### Reconnect Engine (ADR-006)
Exponential backoff starting at 1 second, doubling each attempt, capped at 30 seconds:
| 1 | 1s |
| 2 | 2s |
| 3 | 4s |
| 4 | 8s |
| 5 | 16s |
| 6+ | 30s |
After reconnect, all active stream subscriptions are automatically re-subscribed. The active stream list is maintained in `WebSocketClient` and replayed on reconnect.
### Ping/Pong
Server ping frames are handled transparently by `ping_pong.rs` — a pong is sent automatically and the ping is never surfaced to the caller's stream.
### Listen Key
Listen key renewal is the caller's responsibility (ADR-010). The SDK provides `create_listen_key()`, `renew_listen_key(key)`, and `delete_listen_key(key)` REST methods. Renew every ~30 minutes to prevent expiry. On expiry the user data stream closes with `AsterDexError::WebSocketError`.
---
## Security Notes
- `Credentials` has no `Debug` derive — private key is never printed
- `tracing` events never capture key material or signature bytes
- Private key held in-process as `alloy_signer_local::PrivateKeySigner` (standard in-process risk, mitigated by OS process isolation)
- All connections use TLS via `rustls` — no OpenSSL, certificate validation enforced
- Nonce = microsecond Unix timestamp; server deduplicates last 100 nonces per key
---
Full ADR details: `.teamfw/state/design/04_adr.md`