monaco-sdk 1.0.7

Typed Rust client for the Monaco REST API — generated from the OpenAPI specification
Documentation
# rust-sdk Agent Notes

## Purpose

generated Rust REST client SDK.

## Read First

- Follow the repo-wide instructions in [../AGENTS.md]../AGENTS.md.
- Detailed crate workflow lives below.

## Detailed Workflow

# Rust SDK Crate (`monaco-sdk`)

The `rust-sdk` crate provides a **typed Rust client** for the Monaco REST API. All client code and request/response types are **auto-generated at build time** from a vendored OpenAPI 3.0 YAML specification via [progenitor](https://docs.rs/progenitor).

## How Code Generation Works

```
openapi/openapi.yaml   (vendored OpenAPI 3.0.3 spec)
build.rs               (reads YAML → parses OpenAPI → calls progenitor::Generator)
$OUT_DIR/api.rs        (generated Client + types module)
src/lib.rs             (include!(concat!(env!("OUT_DIR"), "/api.rs")))
```

- `build.rs` is the only build-time logic. It deserializes the YAML spec via `serde_yaml``serde_json``openapiv3::OpenAPI`, then feeds it to `progenitor::Generator::generate_tokens()`. The output is formatted with `prettyplease` and written to `$OUT_DIR/api.rs`.
- `src/lib.rs` contains only doc comments and the `include!()` directive — **no hand-written Rust logic**.
- All public types live under `monaco_sdk::types` (generated).
- The generated `Client` struct has two constructors: `Client::new(base_url)` and `Client::new_with_client(base_url, reqwest::Client)`.

## File Layout

```
rust-sdk/
├── Cargo.toml                 # Crate manifest (published as `monaco-sdk`)
├── README.md                  # Usage docs & API coverage table
├── build.rs                   # Code generation — progenitor from OpenAPI
├── openapi/
│   └── openapi.yaml           # Vendored OpenAPI spec (source of truth)
├── src/
│   └── lib.rs                 # Doc comments + include!() of generated code
└── tests/                     # Integration tests (see Testing Approach)
```

## API Coverage

The generated client exposes every Monaco REST endpoint:

| Category | Methods |
|----------|---------|
| Market data | `list_trading_pairs`, `get_trading_pair_by_id`, `get_market_metadata`, `get_candles` |
| Orderbook | `get_orderbook_snapshot` |
| Trades | `get_trades` |
| Orders | `create_order`, `replace_order`, `cancel_order`, `get_orders`, `get_order_by_id` |
| Batch orders | `batch_create_orders`, `batch_replace_orders`, `batch_cancel_orders`, `batch_cancel_all`, `batch_cancel_all_by_pair` |
| Accounts | `get_user_profile`, `get_user_balances`, `get_user_balance_by_asset`, `get_user_movements` |
| Sub-accounts | `list_sub_accounts_with_balances`, `create_sub_account_limit`, `get_sub_account_limits`, `update_sub_account_limit`, `delete_sub_account_limit` |
| Auth | `create_challenge`, `verify_signature`, `refresh_token`, `revoke_session`, `authenticate_backend` |
| Fees | `simulate_fees` |
| Whitelist | `submit_whitelist` |
| Faucet | `mint_tokens` |
| Health | `health_check` |

## Authentication Pattern

Monaco uses **noncustodial session-key auth**, not Bearer JWTs. Public
endpoints need only `Client::new(url)`. Authenticated endpoints require
per-request ed25519 signatures:

1. Generate a random ed25519 session keypair locally.
2. `create_challenge` with the wallet address.
3. Sign the challenge with the wallet private key (`alloy::signers`) — this
   wallet signature authorizes the session public key.
4. `verify_signature` registers the session key.
5. Every authenticated request is then signed with the **session private key**
   and carries `X-Monaco-PublicKey`, `X-Monaco-Timestamp`, and
   `X-Monaco-Signature` headers over the canonical message
   `METHOD\nPATH_WITH_QUERY\nTIMESTAMP_MS\nSHA256_BODY_HEX`.

⚠️ The generated progenitor `Client` does **not** yet provide a per-request
signing layer, so authenticated calls cannot be made through it out of the box —
the public challenge/verify endpoints work, but a request-signing middleware
must be added (tracked follow-up). `tests/common/mod.rs::authed_client()` is
currently a plain-client stub for this reason.

See `tests/common/mod.rs` for the canonical `authenticate()`,
`authenticate_funded()`, and `authenticate_and_faucet()` helpers.

## Common Gotchas

- **Never edit generated code directly.** All Rust source is produced by `build.rs`. To change the client API, update `openapi/openapi.yaml` and rebuild. If you need to inspect the generated code, look at `target/debug/build/monaco-sdk-*/out/api.rs`.
- **Keep the vendored spec in sync.** The OpenAPI YAML under `openapi/` is a snapshot. Before publishing SDK changes, regenerate it from the repo-level docs artifact (`make docs-gen` at repo root) so the crate matches the latest contract.
- **progenitor version matters.** The `build.rs` depends on `progenitor = "0.13"` and `progenitor-client = "0.13"` must match at runtime. Bumping one without the other will cause compilation failures.
- **String-typed fields from OpenAPI.** Many fields that look like they should be UUIDs, decimals, or enums arrive as generated newtypes with `FromStr`/`Display`. Use `.parse().unwrap()` or `.into()` — check the generated `types` module for the exact wrapper.
- **All integration tests are `#[ignore]`.** They require a live API server. Run with `cargo nextest run -p monaco-sdk --run-ignored only`. Set `API_BASE_URL` for non-localhost targets. Order tests additionally need `PRIVATE_KEY` for a funded wallet.
- **`reqwest` uses `rustls` (no OpenSSL).** The crate disables default features and enables only `json`, `stream`, and `rustls`. Do not add `native-tls` unless there is a specific reason.
- **`regress` crate for regex validation.** progenitor generates runtime regex validation for OpenAPI `pattern` constraints using `regress` (ECMAScript-compatible regex). This is a runtime dependency, not just build-time.

## Testing Approach

Tests are **integration tests against a live API** — there are no unit tests because there is no hand-written logic to unit-test.

```bash
# Run packaging regression tests (no server needed)
cargo nextest run -p monaco-sdk

# Run integration tests against local API
cargo nextest run -p monaco-sdk --run-ignored only

# Run against staging
API_BASE_URL=https://staging.apimonaco.xyz cargo nextest run -p monaco-sdk --run-ignored only

# Order tests need a funded wallet
PRIVATE_KEY=0x... cargo nextest run -p monaco-sdk --run-ignored only
```

Test coverage spans `packaging_regressions.rs` (compile-time sanity checks —
spec file exists, TLS enabled, README accuracy — runs without a server) plus
several live-server suites covering public endpoints, the auth flow, order
lifecycle/batch ops, account/balance/fee queries, the faucet, sub-accounts, and
an end-to-end flow. `tests/common/mod.rs` holds the shared helpers:
`client()`, `authed_client()`, `authenticate()`, `authenticate_funded()`,
`authenticate_and_faucet()`, `first_pair_id()`.

## Dependencies

- **Build-time:** `progenitor` + `openapiv3`/`serde_yaml`/`serde_json` (parse spec), `prettyplease`/`syn` (format output).
- **Runtime:** `progenitor-client`, `reqwest` (rustls), `serde`/`serde_json`, `chrono`, `uuid`, `futures-core`, `regress`.
- **Dev/test:** `alloy` + `ed25519-dalek` + `rand` (wallet/session-key signing in tests), `dotenvy`, `tokio`.

## When Making Changes

1. If the REST API contract changed, update `openapi/openapi.yaml` first (run `make docs-gen` at repo root)
2. Rebuild to regenerate the client: `cargo build -p monaco-sdk`
3. Run packaging regression tests: `cargo nextest run -p monaco-sdk`
4. If you have a running API, run full integration tests: `cargo nextest run -p monaco-sdk --run-ignored only`
5. If you bumped `progenitor` in `[build-dependencies]`, also bump `progenitor-client` in `[dependencies]` to match
6. Update `README.md` API coverage table if new endpoints were added