monaco-sdk 0.8.8

Typed Rust client for the Monaco REST API — generated from the OpenAPI specification
Documentation

Monaco SDK

Typed Rust client for the Monaco DEX REST API, auto-generated at build time from the vendored OpenAPI spec via progenitor. The build.rs reads rust-sdk/openapi/openapi.yaml, generates typed Rust code, and includes it via include!.

Code Generation

graph LR
    YAML[openapi/openapi.yaml] --> BS[build.rs]
    BS --> Gen[progenitor::Generator]
    Gen --> OUT["$OUT_DIR/api.rs"]
    OUT --> Lib["src/lib.rs<br/>include!()"]
    Lib --> Client[monaco_sdk::Client]

Quick Start

[dependencies]
monaco-sdk = "0.5"
tokio = { version = "1", features = ["full"] }
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = monaco_sdk::Client::new("https://develop.apimonaco.xyz");
    let pairs = client.list_trading_pairs(None, Some(true), None, None, None, None).await?.into_inner();
    println!("{:?}", pairs.data);
    Ok(())
}

Authentication

Monaco uses noncustodial session-key auth. The client generates a random ed25519 keypair locally and registers the public key through the challenge → wallet-sign → verify flow; the wallet signature authorizes that session public key. Subsequent authenticated requests are signed with the session private key — the server never holds a credential that can impersonate the user.

use monaco_sdk::types::{ChallengeRequest, VerifyRequest};

let client = monaco_sdk::Client::new("https://develop.apimonaco.xyz");

// 1. Generate an ed25519 session keypair locally (e.g. via ed25519-dalek) and
//    hex-encode the public key.
let session_public_key = "<64-char-lowercase-hex-ed25519-pubkey>".to_string();

// 2. Request a challenge (binds the session public key into the signed message)
let challenge = client
    .create_challenge(&ChallengeRequest {
        address: "0xYourAddress".parse().unwrap(),
        chain_id: None,
        client_id: None,
        session_public_key: session_public_key.parse().unwrap(),
    })
    .await?
    .into_inner();

// 3. Sign the challenge message with your wallet and verify
let verify = client
    .verify_signature(&VerifyRequest {
        address: "0xYourAddress".parse().unwrap(),
        chain_id: None,
        client_id: None,
        nonce: challenge.nonce.unwrap().parse().unwrap(),
        signature: "<wallet-signed-message>".parse().unwrap(),
        session_public_key: session_public_key.parse().unwrap(),
    })
    .await?
    .into_inner();
// `verify` returns { expires_at, user } — no tokens.

Authenticated requests must carry per-request signature headers (X-Monaco-PublicKey, X-Monaco-Timestamp, X-Monaco-Signature) over METHOD\nPATH_WITH_QUERY\nTIMESTAMP_MS\nSHA256_BODY_HEX, signed with the session private key. A built-in request-signing layer for this generated client is a pending follow-up; until then, inject those headers via your own reqwest layer when calling authenticated endpoints.

API Coverage

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
Other submit_whitelist, mint_tokens, health_check

All request/response types are in monaco_sdk::types.

Gotchas

  • Generated code: The entire client is generated at build time. To update, refresh the vendored openapi.yaml from the repo-level docs artifact before publishing.
  • No runtime codegen: The spec is included via include! from OUT_DIR, so builds require the OpenAPI YAML to be present.

Build & Test

By default cargo test -p monaco-sdk runs only the static packaging_regressions checks — the rest of the suite requires a live Monaco backend and is gated behind #[ignore].

# Static checks only (no backend needed)
cargo test -p monaco-sdk

# Full live suite against an environment
API_BASE_URL=https://develop.apimonaco.xyz \
PRIVATE_KEY=0x...your_funded_test_key \
cargo test -p monaco-sdk -- --include-ignored --test-threads=1

The live tests are grouped by responsibility and each file can be targeted individually (--test auth, --test faucet, etc.):

File Covers
public.rs Unauthenticated market data: list_trading_pairs, get_orderbook_snapshot, get_candles, get_trades, health_check, …
auth.rs Challenge / verify / refresh round-trip, plus 401 rejection on an unauthenticated profile call
accounts.rs get_user_balances, get_user_movements, simulate_fees
faucet.rs mint_tokens contract (minted entries carry asset IDs, amounts, tx hashes) and rate-limit slot accounting
sub_accounts.rs list_sub_accounts_with_balances + 4xx envelope on an unknown sub-account
orders.rs Full order lifecycle (create / replace / cancel), batch create/cancel
end_to_end.rs Two user-visible journeys: fresh wallet onboarding (challenge → verify → faucet → simulate fees) and funded wallet order lifecycle

For published-crate smoke testing, the sibling sdk-release-verify harness consumes monaco-grpc-sdk from crates.io so it exercises the crate an external cargo add user would see.