Monaco Protocol SDK v1.0.41
This release adds a client-assignedclientOrderId correlation handle across order placement, replacement, and every order read and WebSocket event; gives order-attached take-profit / stop-loss legs their own sizing so they no longer freeze at the entry order’s quantity; adds a new authenticated account WebSocket channel streaming a margin account’s live health; and emits a movements ledger entry for on-chain deposits and withdrawals so that channel lines up with balances. It also fixes — and corrects the 1.0.40 migration guidance for — funding an isolated risk bucket, which was silently ratcheting a sole open position’s stored margin. One thing to act on: if you followed the 1.0.40 note and used transferCollateralToRiskBucket to increase a position’s margin, that is not what it does — see the fix below.
Added
clientOrderId — a correlation handle on order placement
placeLimitOrder, placeMarketOrder, replaceOrder, batchCreate and batchReplace (@0xmonaco/core; POST /api/v1/orders, PUT /api/v1/orders/{orderId}, POST /api/v1/orders/batch-create and .../batch-replace; gRPC CreateOrderRequest, ReplaceOrderRequest, BatchCreateOrderItem, BatchReplaceOrderItem; the place_limit_order / place_market_order / replace_order MCP tools) now accept an optional clientOrderId — a label you choose that Monaco stores and echoes back. It is up to 64 characters from A-Za-z0-9._:-; surrounding whitespace is trimmed and a blank string is treated as absent. A longer value, or one containing any other character, is rejected with HTTP 400 / gRPC INVALID_ARGUMENT; on batch-create the rejection is per item with error.code INVALID_CLIENT_ORDER_ID, while batch-replace validates every item up front and rejects the whole request.
The handle is echoed on order detail and order list responses (clientOrderId, absent when the order carried none) and on every orders WebSocket event — placed, taker fill, maker fill, cancelled, rejected and expired. A maker fill carries the resting order’s own handle, so a filled quote identifies itself without a follow-up read, and a submit whose HTTP response was lost can be resolved from the stream alone.
It is a correlation handle, not an idempotency key: Monaco does not de-duplicate on it and does not enforce uniqueness, so sending the same value twice places two orders. It does not make a lost-response retry safe — reconcile against the order list first, matching on clientOrderId, and resubmit only if nothing landed. A replacement is a new order with a new id: it carries its own clientOrderId and inherits nothing from the original, the same way postOnly already behaves. Orders placed before this release report no clientOrderId.
Order-attached take-profit / stop-loss legs size independently
ThetakeProfit / stopLoss legs attached to an order at placement (ParentTpSlLeg; placeLimitOrder / placeMarketOrder / batchCreate in @0xmonaco/core, the matching REST and gRPC order requests) gain quantity and closePosition, the same sizing contract the position-attached TpSlLeg already carries. Previously an order-attached leg had no way to express its own size and was frozen at the parent order’s quantity, so a position that later grew past it kept a stop covering only part of what was held while still reading as attached. Omit both fields (or set closePosition: true) to resolve the full live position size at trigger time, or name a quantity for a deliberate partial that never resizes; the two are mutually exclusive, and quantity is required when closePosition is false.
account WebSocket channel — live margin-account health
A new authenticated channel — subscribe with account — carries a margin account’s health: equity, collateral, free collateral, margin ratio, and maintenance requirement (all decimal strings), plus the margin account id and update timestamp, under an account_update event. The channel routing and the AccountEvent type in @0xmonaco/types ship in this release so integrators can build against the contract, but the server-side producer that emits these frames is gated off by default and not yet enabled in the deployed environments — subscribing succeeds and the wire shape is stable, but no frames arrive yet. When the producer is enabled its lag policy is conflate (a slow subscriber is fast-forwarded to the latest state) and it republishes an unchanged frame as a keepalive after a bounded quiet interval, so treat repeats as keepalives and each frame as a terminal-state snapshot per margin account, not an additive delta. There is no sdk.ws helper, so subscribe over the raw WebSocket protocol with a Subscribe frame naming the account channel; the raw wire is snake_case (event_type, user_id, margin_account_id, free_collateral, margin_ratio, maintenance_requirement, updated_at) while the AccountEvent type is the camelCased ergonomic shape nothing converts to on this channel. See the WebSocket reference for the exact wire frame.
movements frames for on-chain deposits and withdrawals
On-chain deposits and withdrawals now emit a user_movement frame on the movements channel, co-ordered with their balance_update on the balances channel. Every deposit/withdrawal balance change now has a matching movements ledger entry — transaction type deposit or withdrawal, carrying the amount, token, symbol, and balance snapshot — so the two streams line up; previously a deposit or withdrawal produced a balances frame with no corresponding movements entry. The UserMovement wire shape is unchanged (transactionType is a free-form lowercase string); these are additional transaction types on the existing channel.
Fixed
Funding an isolated risk bucket no longer ratchets a sole position’s margin
Funding an isolated risk bucket throughtransferCollateralToRiskBucket() (@0xmonaco/core; the transfer_collateral_to_risk_bucket MCP tool; REST POST /api/v1/margin/risk-buckets/collateral/transfer-in; gRPC TransferCollateralToRiskBucket) no longer credits the bucket’s sole open position’s stored margin. It now always allocates reusable bucket collateral, as documented. Previously, when the bucket held exactly one open position, every transfer was silently treated as Adjust Margin, so routine bucket funding permanently ratcheted the position’s initial margin and collateral requirement instead of leaving the collateral redeployable.
This corrects the 1.0.40 migration note, which presented transferCollateralToRiskBucket as the addPositionMargin replacement. It is not: it funds reusable bucket collateral. To explicitly increase a sole open position’s stored margin, use the pair-scoped legacy transfer transferCollateralToMarginAccount(parentMarginAccountId, { asset, amount, tradingPairId }) (@0xmonaco/core; REST POST /api/v1/margin/accounts/{marginAccountId}/collateral/transfer-in with tradingPairId; gRPC TransferCollateralToMarginAccount). There is no MCP tool for this — the pair-scoped transfer is deliberately not exposed — so an assistant increases a position’s stored margin through the REST endpoint or @0xmonaco/core.
Upgrade
1.0.41 too; it runs via npx -y @0xmonaco/mcp-server, so restarting your assistant picks up the new version, the clientOrderId option on the order tools, and the corrected transfer_collateral_to_risk_bucket behavior.
Nothing in this release is breaking. The one thing worth acting on: if you followed the 1.0.40 migration and used transferCollateralToRiskBucket to add margin to a position, switch to transferCollateralToMarginAccount(parentMarginAccountId, { asset, amount, tradingPairId }) — the risk-bucket transfer funds reusable collateral and no longer ratchets a sole position’s stored margin.
