Skip to main content

Monaco Protocol SDK v1.0.55

This release covers the consumer-facing changes since v1.0.54. Nothing in it changes a request or response field, and nothing changes for a client that upgrades and does nothing else. REST session authentication now accepts a second, opt-in signing protocol — RFC 9421 HTTP message signatures over the same Ed25519 session keys — alongside the legacy X-Monaco-* headers, which remain supported with no retirement date. The TypeScript and Rust SDKs can produce the new signatures, but both default to legacy, so opting in is a deliberate per-application step and one you should only take against a gateway that already verifies it. Alongside that, the OpenAPI specification now declares an authentication requirement on 24 endpoints that previously declared none — a specification correction, not a change in what the gateway enforces.

Added

Opt-in RFC 9421 request signing

REST session authentication accepts RFC 9421 HTTP message signatures as an alternative to the legacy X-Monaco-PublicKey / X-Monaco-Timestamp / X-Monaco-Signature headers. Both protocols use the same wallet-authorized Ed25519 session keys and run the same session expiry, revocation, delegated-agent permission, and account-restriction checks. WebSocket and gRPC signing are unchanged. A signed request carries three headers:
Monaco verifies a bounded application profile rather than arbitrary RFC 9421 input. The signature label is monaco and exactly one dictionary member is accepted; the four covered components appear in the order shown; created, keyid, and alg are required; and extra components, extra labels, extra parameters, or another digest algorithm are rejected. created is an integer Unix timestamp in seconds within 30 seconds of server time — legacy timestamps are milliseconds. keyid is the registered session public key as 64 lowercase hexadecimal characters. @query begins with ?, keeps raw ordering, duplicates and percent encoding, and is ? when the query is absent. Content-Digest is RFC 9530 SHA-256 over the exact body bytes, including the empty body on a GET. Presence of Signature or Signature-Input selects RFC verification, and failure never falls back to legacy: there is no automatic protocol downgrade. Sending Signature or Signature-Input alongside any X-Monaco-* header is rejected as 401 Unauthorized. Content-Digest on its own is ordinary HTTP integrity metadata — it neither selects RFC verification nor overrides the body hash legacy verification computes independently. The 30-second window bounds how long a captured signature stays usable; it does not stop an identical signed request from being accepted again inside that window, and Monaco keeps no nonce or replay cache. Use the endpoint’s idempotency mechanism for exactly-once behavior, and when re-signing an aged-out retry, keep the original idempotencyKey and payload. See HTTP message signatures for the full profile, signature base, and migration sequence.

SDKConfig.requestSigning selects the protocol in TypeScript

createMonacoSDK (and new MonacoSDK) accepts requestSigning: "legacy" | "rfc9421", defaulting to "legacy". The RequestSigning type is exported from @0xmonaco/types.
In rfc9421 mode the SDK signs the final URL path and query plus the serialized body, requires the body to be a string, sets redirect: "error" on authenticated requests so a signed request is never forwarded unchanged, and throws if the caller supplies any signature header itself. Public and backend-key requests keep their existing authentication either way. See TypeScript migration.

Client::new_authenticated in the Rust REST SDK

The Rust REST SDK gains a signing client constructor:
It signs the session-authenticated operations the OpenAPI contract classifies and leaves public and backend-key operations unsigned. Authenticated clients disable HTTP redirects, which is why the constructor is available on native targets only — redirect policy is not enforceable on wasm32. Existing unsigned constructors are unchanged. RequestSigning::Legacy selects the legacy headers for a gateway that has not migrated, and the lower-level SessionSigner::sign_at helper signs a reusable reqwest request, with the caller responsible for preserving the signed bytes and disabling redirects. Streaming bodies are rejected rather than signed as empty. See Rust migration and Sign Authenticated Requests.

Fixed

The specification declares authentication on 24 previously undeclared endpoints

The OpenAPI specification gains a monacoHttpSignature security scheme next to the existing monacoSignature, and every session-authenticated operation now lists both. While making that change, 24 operations were found to declare no security requirement at all, so the generated API reference and any client generated from the specification presented them as public:
  • seven endpoints under /api/v1/positions — GET /api/v1/positions, GET /api/v1/positions/history, GET /api/v1/positions/{positionId}, GET /api/v1/positions/{positionId}/risk, POST /api/v1/positions/batch-close-all, POST /api/v1/positions/{positionId}/close, and POST /api/v1/positions/{positionId}/tp-sl. GET /api/v1/positions/{positionId}/pnl/history already declared its requirement and is unaffected
  • all fourteen endpoints under /api/v1/margin — the margin account and parent margin account reads and their movements, GET /api/v1/margin/collateral/available, the collateral transfer-in and transfer-out endpoints, and the three simulate-order-risk endpoints
  • the three conditional order endpoints, GET /api/v1/orders/conditional, GET /api/v1/orders/conditional/{conditionalOrderId}, and DELETE /api/v1/orders/conditional/{conditionalOrderId}
The gateway always required a session signature on all of them — this corrects the published contract, not the enforced one, so a client that was already signing these calls needs no change. A generated client that relied on the specification to decide what to sign was not signing them, and now will. A test asserts the specification’s declared authentication against the gateway’s registered route table, so an endpoint can no longer ship with the wrong contract.

Request-body buffering is scoped to session-authenticated routes

The gateway buffered and hashed every request body globally so that signature verification could cover it. That buffer is now mounted only on session-authenticated resources. Signed routes still read and hash their bodies under the same 2 MiB bound; public and backend-key routes use their normal extractor limits instead. No request or response shape changes, and body hashing is not removed from any signed route.

Upgrade

Upgrading changes nothing on its own: requestSigning defaults to "legacy" and the Rust SDK’s existing constructors are untouched. Before selecting rfc9421, confirm the gateway you target verifies it, because there is no automatic downgrade — a client that opts in early fails closed. Never mix the two credential sets on one request. If you sign requests with your own client rather than an SDK, read the profile end to end before implementing it: the covered-component list, their order, the seconds-based created, and the exact digest encoding are all verified strictly. If you generate a client from the OpenAPI specification, regenerate it so the 24 corrected endpoints are signed, and audit any client generated before v1.0.55 to confirm it was not calling them unsigned.