monaco-sdk 1.0.15

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://staging.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://staging.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

The client exposes every operation in the canonical Monaco REST API. The vendored spec is resynced from that canonical spec by make docs-gen (see Gotchas), so this surface tracks the API automatically.

Category Methods
Market data list_trading_pairs, get_trading_pair_by_id, get_market_metadata, get_candles, get_screener, get_market_stats, get_open_interest
Perp markets get_perp_market_config, get_perp_market_summary, get_mark_price, get_index_price, get_funding_state, list_funding_history
Orderbook get_orderbook_snapshot
Trades get_trades, get_trade_by_id, get_user_trades
Orders create_order, replace_order, cancel_order, get_orders, get_order_by_id
Conditional orders cancel_conditional_order, list_conditional_orders
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, list_funding_payments, get_portfolio_stats, get_portfolio_chart
Sub-accounts list_sub_accounts_with_balances, create_sub_account_limit, get_sub_account_limits, update_sub_account_limit, delete_sub_account_limit
Margin accounts list_margin_accounts, get_margin_account_summary, get_parent_margin_account_summary, get_margin_account_movements, get_parent_margin_account_movements, get_available_collateral, transfer_collateral_to_margin_account, transfer_collateral_from_margin_account, transfer_collateral_to_parent_margin_account, transfer_collateral_from_parent_margin_account, transfer_collateral_to_risk_bucket, simulate_order_risk, simulate_parent_margin_order_risk, simulate_risk_bucket_order_risk
Positions list_positions, get_position, close_position, batch_close_all_positions, get_position_risk, add_position_margin, reduce_position_margin, list_position_history, attach_position_tp_sl, get_position_pnl_history
Delegated agents upsert_delegated_agent, list_delegated_agents, list_delegated_agent_owners, revoke_delegated_agent, create_delegated_session
Applications get_application_config, get_application_stats, list_application_orders, list_application_users, list_application_movements, list_application_balances
Auth create_challenge, verify_signature, refresh_session, revoke_session, authenticate_backend
Fees simulate_fees, get_my_fee_tier
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. The vendored openapi/openapi.yaml is not hand-edited — it is regenerated from the canonical spec (docs/src/proto-openapi/api/openapi.yaml) by make docs-gen, which runs openapi-postprocess's sync-rust-sdk-spec transform. CI fails if the committed copy drifts from a fresh regeneration, so a proto/endpoint change reaches this SDK automatically.
  • 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 nextest run -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 nextest run -p monaco-sdk

# Full live suite against an environment
API_BASE_URL=https://staging.apimonaco.xyz \
PRIVATE_KEY=0x...your_funded_test_key \
cargo nextest run -p monaco-sdk --run-ignored all --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
release_smoke.rs Ignored runtime smoke 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.