# 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

| Type | Purpose |
|------|---------|
| `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:

| Attempt | Delay |
|---------|-------|
| 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`
