Monaco Protocol SDK v1.0.42
This release adds typed WebSocket helpers for themarket_stats and account channels so you no longer hand-roll the raw subscription and key-mapping; lets the candlestick endpoint chart the mark-price series alongside the trade-derived one; and tightens clientOrderId so a value can be held by only one of your resting orders at a time. One thing to act on: if you reuse a clientOrderId across two of your own resting orders it is now rejected — that rejection identifies the order already holding the handle, but its absence is not evidence that an earlier submit failed, so read the action item before using it to decide whether to retry.
Added
Typed market_stats and account WebSocket helpers
@0xmonaco/core gains three typed subscription helpers so the market_stats and account channels no longer require the raw WebSocket protocol:
sdk.ws.marketStats(tradingPairId, handler)— per-market statistics for one pair (public): index / mark / mid prices, best bid/ask, open interest and its limit, last trade, 24h volume / high / low and price change, and funding state.sdk.ws.marketStatsAll(handler)— a slower-cadence reduced summary for every market in a single frame (public), each row carrying amarketStatuslabel (TRADABLE,POST_ONLY,HALTED,NOT_READY, orUNKNOWN).sdk.ws.account(handler)— the authenticatedaccountchannel: a margin account’s health (equity, collateral, free collateral, margin ratio, maintenance requirement).
@0xmonaco/types adds the shapes these deliver — MarketStatsEvent, MarketStatsData, MarketStatsAllEvent, MarketStatsSummary, and the MarketStatus union — with perp-only fields optional so spot markets flow through the same helpers. The helpers hand you the camelCased ergonomic shape; string decimals stay strings and omitted (perp-only) fields surface as undefined. As in 1.0.41, the account producer is gated off by default and not yet enabled in the deployed environments, so sdk.ws.account subscribes successfully but frames arrive only once the producer is enabled.
Mark-price candlestick series (priceType)
The candlestick endpoint accepts an optional priceType (getCandlesticks’s GetCandlesticksParams.priceType in @0xmonaco/core / @0xmonaco/types; the priceType query parameter on GET /api/v1/market/pairs/charts/candlestick/{tradingPairId}/{interval}; GetCandlesRequest.price_type on gRPC MarketService.GetCandles). "trade" (the default) returns the trade-derived OHLCV series, exactly as before. "mark" returns the mark-price series for margin pairs: a close-only series where each candle’s open, high, low, and close are the minute’s closing mark and volume is 0. Requesting "mark" on a spot pair returns a 400.
Changed
clientOrderId is unique among your resting orders
A clientOrderId may no longer be held by two of your resting orders at once. “Resting” means sitting on the book — an order that never rests never claims the handle. On a single create or replace (placeLimitOrder / placeMarketOrder / replaceOrder in @0xmonaco/core; POST /api/v1/orders, PUT /api/v1/orders/{orderId}; gRPC CreateOrderRequest / ReplaceOrderRequest; the place_limit_order / place_market_order / replace_order MCP tools), reusing a clientOrderId still carried by another of your own resting orders is rejected with HTTP 409 / gRPC ALREADY_EXISTS, code CLIENT_ORDER_ID_CONFLICT; the message names the order already holding it. In a batch (batchCreate / batchReplace; POST /api/v1/orders/batch-create and .../batch-replace) the conflict is a per-item failure — the request returns 200 / OK and only the offending item carries error.code CLIENT_ORDER_ID_CONFLICT in its result slot — so inspect results[] rather than waiting for a transport-level 409. (A malformed handle is not reported the same way on both endpoints: batchCreate fails only that item with INVALID_CLIENT_ORDER_ID, while batchReplace validates every handle up front and rejects the whole request with a 400.)
Where the conflict fires it is a reliable signal. If you resubmit with a clientOrderId still held by one of your resting orders, CLIENT_ORDER_ID_CONFLICT proves that order exists and is still resting — the check runs in the matching engine against live state, not against replicated history, so unlike an order-list lookup it cannot report “not found” for an order that did land. Read the named order rather than placing a second one.
Its absence proves nothing, so do not resubmit to probe for it. The handle is claimed only when an order comes to rest and is released the moment it reaches a terminal state, so no conflict is raised when the first attempt filled, was cancelled, was rejected, or never rested at all. A resubmission then places a genuinely new order and can execute a second time. After a lost response, reconcile on the order stream (sdk.ws.userOrders), which carries your clientOrderId on every event including orders that never rested, and resubmit only once you have positive evidence the intent did not land.
Scope is per user, among resting orders only. Two different users may hold the same value at once, and the value frees the moment the order holding it reaches a terminal state (filled, cancelled, expired, rejected), so a stable per-quote label can be reused indefinitely. A replacement may restate the original’s handle — the original releases it as the replacement claims it, matching the existing guidance to restate the value on every replace. Orders that carry no clientOrderId are unaffected, and orders already resting when this shipped keep whatever handle they carry.
Upgrade
1.0.42 too; it runs via npx -y @0xmonaco/mcp-server, so restarting your assistant picks up the new version. Nothing in this release is breaking. The one thing worth acting on: if you assign a clientOrderId and a single create’s response is lost, do not blind-resubmit — reconcile on the order stream (sdk.ws.userOrders) first. A 409 / CLIENT_ORDER_ID_CONFLICT confirms an order is still resting under that handle, but no conflict does not mean nothing landed: an order that filled on arrival or never rested releases the handle, and resubmitting can execute again.
