Monaco Protocol SDK v1.0.66
This release adds trailing stops for perp positions and fixes two WebSocket client gaps. A trailing stop is a reduce-only market close whose trigger follows the best mark reached since it armed: attach one to an open position withsdk.positions.attachPositionTpSl(positionId, { trailingStop }), or to a margin entry order with trailingStop on placeLimitOrder / placeMarketOrder. Position TP/SL attach now draws from the order-creation rate-limit budget, and Position.version now advances only on a position whose content actually changed. On the socket, the client stops reconnecting in a loop after the server ends a connection for lost authentication — it calls the new onReauthenticationRequired option instead — and onError now names the channel a per-channel Error frame concerns. Action items: pass onReauthenticationRequired if your app holds authenticated WebSocket subscriptions, so it can supply a fresh session when one is revoked or expires; and if you attach TP/SL at high frequency, budget each attach as one order-creation item.
Added
Trailing stops
sdk.positions.attachPositionTpSl (POST /api/v1/positions/{position_id}/tp-sl; gRPC PositionsService.AttachPositionTpSl) accepts a trailingStop: TrailingStopLeg, alone or alongside takeProfit / stopLoss, and the response gains trailingStopOrderId. sdk.trading.placeLimitOrder / placeMarketOrder (POST /api/v1/orders; gRPC OrdersService.CreateOrder) accept the same leg as options.trailingStop (ParentTrailingStopLegParams) on margin entry orders that are not reduce-only, and CreateOrderResponse gains trailingStopOrderId — present only when the trailing stop materializes: the order fully fills on arrival or leaves a resting remainder. A partial fill whose remainder is cancelled (an IOC or MARKET order) creates no trailing stop and omits the id.
The leg takes:
trailBps— integer, 10 to 2000 basis points behind the watermarkactivationPrice?— the mark level that starts tracking and seeds the watermark; rejected if the mark has already reached it. Omitted, tracking starts at the current markquantity?/closePosition?— the same sizing contract as a TP/SL leg, mutually exclusiveslippageToleranceBps?— 0 to 10000expiresAt?— ISO 8601
- One per position. A second attach is rejected with
409(gRPCALREADY_EXISTS) — as is an attach while a resting entry order on the same trading pair and risk bucket carries a pending trailing stop — never replaced, and a trailing stop never supersedes or is superseded by a full-close TP/SL leg. It coexists with fixed TP/SL and never joinsoco. - No modify. Cancel it and attach a new one.
- Entry-order attach arms only when the order fully fills, and is cancelled with the order otherwise.
- Reads.
ConditionalOrder(getConditionalOrder,listConditionalOrders, and the conditional detail of order history) readsconditionType: "TRAILING_STOP"and gainstrailBps,activationPrice,watermarkPriceandtrailArmedAt. An entry-attached stop waits asPENDING_PARENTuntil its order fully fills; from then on (and from the start for a position attach) the row staysstate: "ACTIVE"while waiting for activation and while tracking;trailArmedAtis absent until tracking begins, andtriggerPriceis provisional until the first mark after arming. - Streaming.
sdk.ws.conditionalOrdersframes (snapshot and live) carry the same four fields, read leniently. Live frames carryreason: "armed"when tracking begins andreason: "ratcheted"on every trigger move, with the newtriggerPriceandwatermarkPrice.
trailBps outside 10–2000 or not an integer, quantity together with closePosition, and a trailing stop on a SPOT or reduce-only order before any request is made. @0xmonaco/types adds TrailingStopLeg, ParentTrailingStopLegParams, "TRAILING_STOP" on ConditionalOrderConditionType, "armed" / "ratcheted" on ConditionalOrderEventReason, and the matching Zod schemas.
See Trailing Stop and Positions.
Changed
Position TP/SL attach is metered by the order-creation rate limit
attachPositionTpSl (POST /api/v1/positions/{positionId}/tp-sl; gRPC PositionsService.AttachPositionTpSl) now draws one item per call from the order-creation budget it shares with order creates, replaces, batch items and TWAP creation — whatever legs the call carries. The account-family tier, the PrivateLink column and the X-RateLimit-* headers apply exactly as on the order endpoints. Over budget it returns 429 RATE_LIMIT_EXCEEDED with Retry-After on REST and RESOURCE_EXHAUSTED with RetryInfo on gRPC. Position closes stay unmetered. Like the other order budgets, enforcement is warn-only until it is turned on for the environment, so handle 429 / RESOURCE_EXHAUSTED now and back off on the server’s retry hint rather than waiting for a rejection. See Rate Limits.
Position.version advances only when the position changed
version on position reads now advances exactly when that row’s versioned content changed at a sequence (including a change undone within one persistence batch), instead of on every open position of the margin account whenever a sequenced command touched it. An unchanged position keeps its version. A ranked live position_update frame for an untouched sibling of a filled position can therefore carry a higher version than the REST row while describing identical content — rank it as newer; nothing changes. The per-field-group merge rule is unchanged, and there is no field or type change. See Positions.
Fixed
The WebSocket client stops reconnecting after authentication is lost
After aSESSION_INVALID Error frame followed by a 1008 close (the session was revoked or expired), or an AUTH_FAILED frame after the connection sent Authenticate, the client now stops reconnecting instead of retrying in a loop. It drops the rejected session keypair, keeps your subscription handlers, reports "disconnected", and calls the new onReauthenticationRequired({ code, message, closeCode }) option once. Supplying a fresh session (sdk.login, or setSessionKeypair on a createMonacoWebSocket client) reconnects, authenticates, resubscribes every channel you still hold, and fires onResync. Every other abnormal close — including a 1008 for the inbound message-rate cap and a 1013 slow-client shed — keeps the backoff reconnect. @0xmonaco/types exports ReauthenticationRequiredHandler and WebSocketReauthenticationInfo. See Session loss.
onError names the channel a per-channel failure concerns
onError now delivers { code, message, channel } for a per-channel Error frame (INVALID_SUBSCRIPTION, AUTH_REQUIRED, SUBSCRIPTION_LIMIT, SNAPSHOT_UNAVAILABLE), with channel echoed exactly as you sent it — so an app can tell which channel of a batched or reconnect Subscribe failed, and refetch only that one, without matching the human-readable message. A connection-wide failure is still delivered as exactly { code, message }. WebSocketErrorInfo gains the optional channel?: string. See Server error frames.
Upgrade
onReauthenticationRequired), and each attachPositionTpSl call now counts against the order-creation budget.
