Monaco Protocol SDK v1.0.51
This release covers the consumer-facing changes since v1.0.50. Three migrations come first: stop scaling WebSocket fillfee values yourself, update order-event field and null checks, and give switches over WebSocket value fields a default branch. Orders gain a shared reconciliation version, connection-scoped WebSocket errors reach an onError callback, and React order state handles unfamiliar values and REST repair more reliably. Timestamp formatting, portfolio valuation, subscription snapshots, liquidation cleanup, and fee refresh also receive corrections; batchCancelAll now refuses requests above 20,000 active orders without cancelling any.
Breaking
Fill fee is a decimal quote-asset amount
On OrderFilled, OrderPartiallyFilled, and OrderMatched, fee now reports the normalized decimal quote-asset amount: "0.004178" USDC instead of the old raw "4178" at six quote decimals. Stop dividing fee by 10^quoteDecimals. The new feeRaw (wire fee_raw) carries the integer raw quote-unit value if your accounting works in raw units; negative values mean a maker rebate.
Two existing properties still matter. This is the Monaco fee component only, excluding the application taker fee, so it need not equal the order’s totalTakerFees. A taker event aggregates the fees of every trade it covers; a maker event covers one trade. feeRaw is required on OrderMatched and optional in the published OrderFilled and OrderPartiallyFilled types. See Order Events.
Order-event types now distinguish absent fields from null values
takerOrderId and makerOrderId are removed from the three fill-event types: neither existed on the wire. Use orderId for the event’s own order and tradeId for its trade reference. totalFilled is also removed from those fill types; use filledQuantity for the cumulative order fill and lastFillQuantity for this event’s increment. totalFilled remains on cancellation and rejection payloads, where the server sends it.
OrderMatched gains its missing execution fields and now describes the taker payload exactly. Update fixtures and consumers to distinguish these groups:
- Required:
role,status,tradesCount,filledQuantity,filledQuantityRaw,remainingQuantity,remainingQuantityRaw,fee, andfeeRaw. - Always present, nullable:
tradeId,executionPrice,executedAt,averageFillPrice,referencePrice,bestPrice,worstPrice,actualSlippageBps, andmaxSlippageBps. The trade fields are null when no trade is covered; price and slippage fields are null when unavailable. - Optional incremental-fill fields:
lastFillQuantityandlastFillQuantityRaw, omitted when the event carries no incremental fill.
OrderExpired likewise makes filledQuantity, filledQuantityRaw, remainingQuantity, remainingQuantityRaw, and averageFillPrice required string | null: the keys are always present. Branch on null rather than assuming an absent key. OrderFilled and OrderPartiallyFilled still cover both maker and taker payloads, so their execution fields are not tightened to the taker-only shape. See WebSockets for the individual payloads.
WebSocket value fields accept unfamiliar server strings
Many event value fields widen from a closed alias toKnownAlias | (string & {}), matching the parser’s delivery of unknown values. This affects order values (including status, role, terminal reason, side, type, time in force, and trading mode), conditional-order values, position side/status, market status, TWAP values, instrument change kinds, OHLCV interval/mode/price type, orderbook mode/side, trade mode/maker side, and balance reasons.
The finite aliases remain exported for autocomplete, narrowing, REST filters, and validation. Add a default branch or an explicit narrowing check before assigning a received value to a closed model type. The broad OrderEvent.eventType also widens, but named variants keep their closed eventType discriminants — narrowing OrderPlacedEvent and its siblings still works.
Added
Connection-scoped WebSocket errors reach onError
The ws configuration accepts onError({ code, message }), also available on createMonacoWebSocket options. @0xmonaco/types exports WebSocketErrorCode, WebSocketErrorInfo, and ErrorHandler. Server Error frames belong to the connection and match no channel subscription; previously they reached no consumer handler.
Known codes are AUTH_REQUIRED, AUTH_FAILED, CONNECTION_LIMIT, SUBSCRIPTION_LIMIT, INVALID_SUBSCRIPTION, INVALID_MESSAGE, SESSION_INVALID, and SNAPSHOT_UNAVAILABLE. Recovery depends on the code:
AUTH_REQUIREDmeans the server rejected the subscription. Authenticate, release the last local handler for that channel, then subscribe again; authentication alone leaves the locally registered subscription inactive on the server.SESSION_INVALIDrequires re-authentication.SNAPSHOT_UNAVAILABLEleaves the subscription active and live diffs flowing, but without its baseline. Fetch a REST baseline and reconcile it with live state.
A shared version for order reconciliation
Order detail and list reads (GetOrder / ListOrders, including sdk.trading.getOrder() / getPaginatedOrders()), every order WebSocket event, and the orders subscription snapshot now carry an optional version. It is an opaque comparator for states of the same order: a higher value is newer, gaps are normal, and values from different orders are not comparable.
On reconnect, keep applying live order events and accept a REST snapshot row when snapshot.version >= local.version. Use >=, not >: one sequencer step can emit placement and fill events with the same version, while the REST row represents that step’s final state and wins the tie. An absent version means unknown, never zero; fall back to buffering and replaying events across the fetch for those rows.
The resting snapshot cannot recover orders that stopped resting during the disconnect. Page the execution-time-ordered sdk.profile.getUserTrades back through the gap for fills, and use targeted sdk.trading.getOrder reads for locally resting orders missing from the snapshot to resolve cancellations and expiries. Ordering the order list by creation time cannot substitute for the trade tape. See the Market-Maker Runbook.
Conditional-order versions on REST and subscription snapshots
Conditional-order REST reads and theconditional_orders subscription snapshot gain the same kind of optional, opaque version. Compare versioned snapshots for the same conditional order with snapshot.version >= local.version; absent means unknown, gaps are normal, and comparison across conditional orders or against ordinary orders is invalid.
Live conditional-order events deliberately omit version. Apply them as state updates without version ranking. The subscription snapshot reads from the database, so a transition during that read can fall into a database-lag gap; re-read affected rows when their state matters at reconnect. See Conditional Orders Channel.
Post-only state and missing raw execution fields
Order events exposepostOnly?: boolean on placement, taker payloads, partial cancellation, cancellation, and expiry. A carrying variant sends true or omits the key; maker fills and rejections never carry it. An admitted post-only order rests and fills as a maker, so retain the order-level flag from placement or REST — absence on a maker fill does not mean false. useUserOrders now carries this state onto its order rows and preserves it through updates.
OrderRejected declares totalFilledRaw and remainingQuantityRaw; OrderExpired declares filledQuantityRaw and remainingQuantityRaw. These previously invisible fields carry integer base-unit precision alongside their decimal counterparts. The missing OrderMatched execution fields are covered by the exact-shape migration above.
Changed
Selected persisted order timestamps use fixed UTC microseconds
REST read models now render ordinary-ordercreatedAt, updatedAt, and expirationDate; every conditional-order timestamp; persisted TWAP window and lifecycle timestamps; and application order-view createdAt / updatedAt with exactly six fractional digits and a Z suffix, for example 2026-09-05T01:23:45.123456Z. This replaces variable fractional widths and +00:00 on those fields. RFC 3339 parsers keep working; exact-string fixtures may need updates.
Ordinary-order lifecycle stamps — cancelledAt, filledAt, expiredAt, submittedAt, and acknowledgedAt — retain chrono’s variable fractional width, although they use a Z suffix. The guarantee also does not extend to CreateTwapOrderResponse.firstSliceAt or .endTime, which retain their separate rendering.
Formatting does not align the clocks behind order detail and list updatedAt values. Use version for reconciliation rather than comparing those timestamps.
batchCancelAll refuses more than 20,000 active orders
sdk.trading.batchCancelAll now caps a request at 20,000 active orders. Above the cap it cancels nothing and returns REST 400 / gRPC InvalidArgument. Treat this as a rejected operation, not a partial cancellation. See Trading for the request’s filtering options.
Fixed
WebSocket parsers retain fields and unfamiliar values
The movements parser now deliverssymbol, balanceBeforeRaw, balanceAfterRaw, lockedBeforeRaw, and lockedAfterRaw, which were declared but discarded while building callbacks. TWAP updates with an unfamiliar state and instrument updates with an unfamiliar change no longer lose the whole frame. OHLCV preserves an unfamiliar priceType instead of blanking the field, so wildcard subscriptions can distinguish newly introduced series.
useUserOrders narrows values and merges REST repair with live state
The hook no longer inserts an unknown status into the closed Order model: it uses the status implied by the event type or retains the previous status when no status is implied. Placements remain SUBMITTED. A placement with an unknown orderType, side, or tradingMode cannot honestly form an order row, so the hook declines it and schedules a coalesced REST repair; an unknown optional timeInForce remains absent.
Refresh and repair responses merge with live state instead of replacing updates that arrived while REST was in flight. The released hook still reconciles using updatedAt; the new order version contract above is available to consumers implementing their own reconciliation. See useUserOrders.
Portfolio performance can return partial withdrawal valuation
An external withdrawal without a usable historical price no longer fails the entire portfolio-performance response solely for that missing valuation. The unpriceable movement is omitted from withdrawal totals and the deposit totals that depend on them, and a successful response reportsvaluationStatus: "partial". Recognized quote assets retain unit pricing; reconciliation errors can still fail the request. See Portfolio.
Subscription snapshots, liquidation cleanup, and fee refresh
Order, conditional-order, and position subscription snapshots now use stable keyset paging, preventing concurrent updates from shifting page boundaries and duplicating or skipping rows. This improves paging consistency; it does not eliminate the conditional-order database-lag gap described above. When a liquidation maker fill closes a position, its remaining reduce-only orders are now cancelled withPOSITION_CLOSED, releasing their reserves while preserving order history and PnL enrichment.
Fee-schedule refresh uses bounded timeouts and a consistent repeatable-read snapshot, retains the last known good schedule on failure, and retries. Transient refresh trouble no longer freezes the refresh loop or mixes fee tiers from different reads.
Upgrade
fee, migrate the removed and nullable order-event fields, and narrow unfamiliar WebSocket values before assigning them to closed models. Register onError so rejected subscriptions and missing baselines become visible, and use same-order version comparisons plus gap recovery when implementing reconnect reconciliation.
