Skip to main content

Monaco Protocol SDK v1.0.57

This release covers the consumer-facing changes since v1.0.56. The order listing is now unified: GET /api/v1/orders returns book orders, TWAP parents and conditional orders as one timestamp-sorted stream, and orderType gains two response-only values to name each row’s kind — code that switches exhaustively on it must handle them. @0xmonaco/contracts ships a breaking sweeper ABI change that binds a quoted sweep to an exact amount. Batch replace now scopes every pre-sequencing validation failure to its own item instead of rejecting the whole batch, so a malformed item comes back as a failed item inside a 2xx rather than a top-level rejection — read results[] per item. A TWAP parent’s resting slice child can no longer be cancelled directly, closing a trap that charged taker fees for an order the caller believed cancelled. Unused execution collateral now returns to the margin account after any positive fill on a first-use isolated market order, not only a full one. And authenticated reads now draw a per-account budget, measured but not yet enforced.

Breaking

SweepParams binds a sweep to an exact amount

SWEEPER_ABI and SWEEPER_FACTORY_ABI change shape. SweepParams gains a leading amount field, so the complete tuple is now (uint256 amount, uint256 minSwapOut, uint256 minBridgeOut, bytes lzExtraOptions) — four fields, where it previously held the latter three. Calldata encoded against the previous shape produces arguments the new contracts reject — re-encode before upgrading. amount is the exact balance the quote was computed for. The sweep moves exactly that much and leaves any excess — a deposit that landed after the quote — for the next, re-quoted sweep, so the minSwapOut / minBridgeOut minimums always cover the full swept amount and a later arrival can never dilute them. If the balance has fallen below the quoted amount by the time the sweep executes, the call reverts with the new StaleQuote error rather than sweeping less against minimums quoted for more; re-quote and retry. amount: 0n binds nothing and sweeps the execution-time balance. It is accepted only on the direct CCTP route, whose burn is exact-amount and carries no minimum that could go stale. Every other route — Stargate, swap-then-bridge, and the hub unswap — rejects the zero sentinel with the new QuoteNotBound error. A sweep against an empty balance still reverts with NothingToSweep. Both new errors are declared on SWEEPER_ABI only. SWEEPER_FACTORY_ABI gains the amount field on its sweepWithParams input but declares neither error, so a decoder built from the factory ABI alone cannot name a StaleQuote or QuoteNotBound revert bubbled up from the sweeper — give it both ABIs. predictSweeperAddress(...) and encodeDepositApplicationData(...) are unaffected — address derivation does not read SweepParams. See Registered Deposit Addresses.

Changed

One order listing for every kind of order

GET /api/v1/orders (gRPC ListOrders; sdk.trading.getPaginatedOrders and iterateOrders) returns book orders, TWAP parents and conditional orders in one stream sorted by timestamp. There is no separate open-orders endpoint: filter status to SUBMITTED,PARTIALLY_FILLED for the working set across all three kinds, in one request rather than one per kind.
orderType names each row’s kind and gains two response-only values, TWAP and CONDITIONAL. Neither can be placed: order creation still takes LIMIT or MARKET, and the two new kinds are created through their own endpoints. In @0xmonaco/types this is a type-level change — Order.orderType widens from OrderType to the new ListedOrderType ("LIMIT" | "MARKET" | "TWAP" | "CONDITIONAL"), while OrderType itself is unchanged and remains what you can place. An exhaustive switch or a never exhaustiveness check on order.orderType stops compiling until it handles the two new values. A row of either kind carries its full detail under the new optional twap / conditional fields — the same shapes GET /api/v1/orders/twap/{twapOrderId} and the conditional-order endpoints return, so a client already handling those needs no new parsing. Exactly one is present, and only when orderType names that kind; a book order carries neither. The shared fields are populated from that detail, so a list renders without special-casing: a parent’s quantity is its total and filledQuantity what it has executed, and status is its state expressed as an order status — a parent mid-execution reads PARTIALLY_FILLED, one that finished having executed nothing reads EXPIRED, and a conditional still waiting on its trigger reads SUBMITTED. Existing filters (status, tradingPairId, tradingMode, marginAccountId), both pagination modes and the sort order are unchanged and apply to the merged stream. tradingMode=SPOT excludes conditional orders, which are margin-only. The dedicated listTwapOrders and listConditionalOrders calls are unchanged and remain the way to list one kind with its own filters. The WebSocket orders channel is not affected: it still carries book orders only, and its subscribe-time snapshot has no field for either detail. See Order queries and List Orders.

Batch replace fails per item, not per batch

POST /api/v1/orders/batch-replace (gRPC BatchReplaceOrders; sdk.trading.batchReplace) now scopes every pre-sequencing validation failure to its own item, matching batch create. A malformed orderId, price, quantity or clientOrderId, a postOnly violation against the persisted original, and a delegated-session item without a persisted original each fail alone — that item’s result slot carries a structured error.code from batch create’s vocabulary (INVALID_ORDER_ID, INVALID_PRICE, INVALID_QUANTITY, INVALID_CLIENT_ORDER_ID, INVALID_POST_ONLY, and FORBIDDEN for a delegated item without a persisted original) — while the batch’s valid items proceed to the engine. A failed item’s original order is never touched. Previously one malformed item rejected the whole request with REST 400 / gRPC INVALID_ARGUMENT before anything executed. A 400 still means the batch did not execute — what changed is that a malformed item no longer produces one, so the case you have to handle now is a 2xx carrying per-item failures: read results[] rather than inferring all-or-nothing from the top-level status. A batch whose items all fail validation returns 2xx with every result failed and no engine round trip. Empty and oversize (more than 100 items) batches are the only input validation that still rejects the whole request; the endpoint’s other top-level statuses (401, 429, 500) are unchanged. On both batch create and batch replace, totalSucceeded / totalFailed are now derived from the merged results[], so totalRequested === totalSucceeded + totalFailed holds on every response. An engine row that reports success without a payload is reported as an item error (UNKNOWN_ERROR) rather than counted as a success with no order to act on. No response shapes changed — results[] already carried per-item { code, message } errors positionally. Through the TypeScript SDK this is visible only for the checks the client-side schema cannot make, since its own Zod validation still rejects a malformed item before the request is sent: a postOnly shape violation against the persisted original, and a delegated item with no persisted original. Callers bypassing the schema see the full change. See Batch Replace.

Authenticated reads draw a per-account budget

Every authenticated read now draws one request from a single per-account read budget: on REST every signed GET (plus the POST order-risk previews under /api/v1/margin/…/simulate-order-risk, which are reads), and on gRPC every Get*, List* and Simulate* RPC. The default is 30 requests per second sustained with a burst of 100. Writes are unaffected — they are metered by the existing order and movement budgets — and public, unauthenticated market data is not metered at all. Delegated agent sessions share the owning account’s read budget, because the budget is keyed to the resolved account; sub-accounts have their own. Reads return no budget headers: an over-budget read is the same contract as the other classes — REST 429 RATE_LIMIT_EXCEEDED with Retry-After and details.retryAfter, gRPC RESOURCE_EXHAUSTED with a google.rpc.RetryInfo detail — and the affected read operations now declare 429 in the API contract. Like the order budgets, the counters are held per API task rather than fleet-wide, so the published figure is the per-task quota. Enforcement follows the same per-environment rollout as the order and movement budgets: until it is enabled where you trade, over-budget reads are admitted and only measured. Build the back-off handling now. Prefer the WebSocket streams for anything you would otherwise poll — order, position and balance updates arrive as events and cost no read budget. See Rate limits.

Fixed

A TWAP parent’s resting slice child can no longer be cancelled directly

A PASSIVE TWAP parent rests one slice child on the book, and that child is a real order in the owner’s book and index — so the public cancel path could reach it. Cancelling it succeeded, and that was a trap: the parent stayed live and swept the same slot as a taker at its deadline, so an order the trader believed cancelled came back and charged taker fees. POST /api/v1/orders/cancel (the order id travels in the request body; gRPC CancelOrder) and the batch cancel forms now refuse a slice child with order {orderId} is the resting slice child of TWAP parent {twapOrderId} and cannot be cancelled directly; cancel the parent instead. Replace already refused it with the matching wording, so the parent is now the only control surface for its children on both paths. POST /api/v1/orders/twap/{twapOrderId}/cancel is unchanged and remains the way to stop a TWAP — it cancels the resting child in the same atomic step. POST /api/v1/orders/batch-cancel-all and its by-pair form enumerate the owner’s resting orders, so a slice child now appears there as one refused item naming its parent while every other order in the sweep still cancels; cancel-all does not stop TWAP parents. See TWAP orders.

Unused execution collateral returns after a first-use isolated market order executes

Where automatic execution-collateral refunds are enabled, authenticated MARKET order creation through POST /api/v1/orders and batch order creation now release unused automatic collateral once a first-use ISOLATED order has executed. Any positive fill triggers it: a market order that filled only part of its quantity before the remainder was cancelled releases exactly as a fully filled one does, and the release is capped at the allocation the order originally drew. The initial execution-price and fee allowance still protects admission; settlement returns only the unused automatic allocation to the parent margin account, retaining position margin, fees, funding, losses, and the reserves of other live orders. Existing explicit collateral is untouched. POST /api/v1/margin/risk-buckets/simulate-order-risk applies the same release, so projected freeCollateralAfter and estimatedLiquidationPrice reflect the final backing. Request fields are unchanged. A position’s isolatedMargin can still sit above notional divided by leverage when the mark differs from the fill or other risk requirements consume collateral. An order that executed nothing at all releases through the ordinary cancellation-settlement rules instead. The rollout defaults to off and is enabled per environment by an administrator through the authenticated PUT /api/v1/feature-flags endpoint, only once compatible verifier and proposer instances are healthy. While it is off, placement and preview retain the prior allocation behavior, and orders already recorded replay the release policy captured with them regardless of later flag changes. See Perpetual futures.

Upgrade

Three things to check before upgrading. If you encode SweepParams yourself, re-encode against the new leading amount field and add handling for StaleQuote and QuoteNotBound — the previous tuple shape is rejected. If you switch exhaustively on order.orderType, add the TWAP and CONDITIONAL branches; a list that assumed GET /api/v1/orders returned book orders only will now see parent rows. And if you call batchReplace, read results[] per item rather than inferring the outcome from the status alone — a 400 still means nothing executed, but a malformed item now comes back as a failed item inside a 2xx. The rest is additive or inert. twap and conditional are optional and absent on a book order. The read budget is measured rather than enforced until it is turned on per environment, so nothing that works today starts failing on upgrade — but adding 429 handling now costs nothing. The TWAP cancel change only refuses a request that previously produced a misleading success, and the execution-collateral release is off by default; when it is on it returns collateral rather than withholding it, on any positive fill.