Monaco Protocol SDK v1.0.48
A small release. Risk-bucket rows across the margin and portfolio surfaces gainavailableOrderCollateral — the risk engine’s own answer to “how much more can this bucket commit to new orders”, which is a different figure from withdrawableCollateral and deliberately so. React gains useInstruments, a typed hook over the public instrument channel. And the conditionalOrders WebSocket parser stops throwing away whole frames it does not fully recognize, which comes with one type-level breaking change: ConditionalOrderEventData now declares what the server can actually send, so an exhaustive switch over timeInForce or reason needs a default branch to keep compiling.
Breaking
ConditionalOrderEventData declares the values the server can really send
Two fields widen. timeInForce goes from "GTC" | "IOC" to the full TimeInForce union: the creation paths validate the narrow pair, but the field mirrors an unconstrained server column that is mapped exhaustively over every variant, so a value written by any other path reaches the wire as-is. And reason goes from the ConditionalOrderEventReason union to string, because the wire field is a free-form server string rather than a closed enum — the subscribe-time snapshot already reuses the same shape with "snapshot".
ConditionalOrderEventReason is still exported and still documents the canonical live values (created, activated, cancelled, parent_cancelled, triggered, failed, oco_cancelled); it is now a narrowing aid rather than a closed set.
Code that switched exhaustively over timeInForce or reason needs a default branch. More generally, every value union on this interface is now documented as “the set the server can send today”, not a runtime guarantee — see the Fixed section for why. See Conditional Orders Channel.
Added
availableOrderCollateral on risk-bucket rows
Risk-bucket rows returned by getMarginAccountSummary, getParentMarginAccountSummary, and listMarginAccounts (GET /api/v1/margin/accounts/{margin_account_id}, GET /api/v1/margin/parent-margin-account, GET /api/v1/margin/accounts; gRPC MarginAccountsService) now carry an optional availableOrderCollateral, and so do the riskBuckets rows from sdk.portfolio.getMargin() (GET /api/v1/accounts/me/portfolio/margin; gRPC AccountsService.GetPortfolioMargin).
It is the collateral still available to open new orders in that bucket: the largest increase in the bucket’s worst-case order reserve that the risk engine’s admission gate will accept, with automatic top-up from the parent margin account’s unallocated capacity already included. It is zero while the account or bucket is frozen for liquidation, and for an isolated bucket whose market is halted — a cross bucket picks its market per order, so a halt cannot be folded into its figure, and a halted pair still refuses orders.
This is the trading-side sibling of withdrawableCollateral, and the two differ on purpose: this one credits an unrealized gain in full, because a gain is legitimate initial margin for a new position, while a withdrawal never counts one. It already contains the bucket’s live free collateral, so do not add freeCollateral or unrealizedPnl on top; it can also legitimately differ from the same row’s freeCollateral, which is a persisted reconstruction that cannot see the engine’s worst-case resting-order reserve.
Two things to get right before you build on it. It is not additive across rows — every bucket’s figure includes the same parent unallocated headroom, so summing them multiply-counts it, and committing collateral in one bucket shrinks its siblings’ figures. And it is a requirement budget, not an order size: an order is charged the increase it causes in the bucket’s worst-case per-pair reserve, so a reducing, reduce-only, or opposite-side order sheltered by a larger resting ladder needs less of it or none at all — and can be admitted even when this field reads zero — while an order crossing a losing position can need more than its own margin plus fees. Divide by a market’s initial margin rate for indicative notional capacity; the authoritative per-order answer is still simulateRiskBucketOrderRisk.
The field is present only on risk-bucket rows served from a live risk engine snapshot. It is absent — the key is omitted, never null — on parent rows and on persisted-fallback reads, where neither term of the gate is reconstructable, so handle undefined. On the portfolio surface it is truncated toward zero at quote decimals, like the withdrawableCollateral beside it. See Margin Accounts and Perps Collateral.
useInstruments React hook
useInstruments(tradingPairId?) is a typed hook over the public instrument WebSocket channel, subscribing through sdk.ws.instruments() and unsubscribing on unmount. It applies the subscribe-time snapshot and every subsequent change frame as a terminal-state entry per market. Three change kinds are emitted today — listing, delisting, and config_change; halt and unhalt exist on InstrumentChangeKind for a future market-regime producer and are not emitted by the admin trading-pair path.
It returns instruments — an InstrumentEventData[] ordered by symbol and then by trading pair id, so a spot and a perp market sharing a symbol keep a stable order — plus instrumentsByPair keyed by that id, subscribed, error, and clearError. Omit the argument to follow every market. A delisted market stays in the list carrying isActive: false rather than disappearing, so a consumer can tell “no longer tradable” from “never seen”; filter on isActive for the tradable set.
subscribed means “registered”, not “receiving” — the client accepts subscriptions while the socket is down and sends them on the next open — and error is a bad-usage signal (an empty tradingPairId is the reachable case), not a transport one. See useInstruments.
Fixed
The conditionalOrders parser no longer drops whole frames on an unexpected value
An unexpected enum value anywhere in a conditional_order_update frame used to throw inside the WebSocket message handler, losing the entire update silently. A timeInForce of FOK or GTD, or any reason the server added, was enough to do it.
orderType, timeInForce, state, side, positionSide, conditionType, triggerSource, associationType, and reason are now read leniently and passed through as-is, matching what the position, liquidation, and market-stats parsers already do — a dropped frame is a worse failure than an unhandled value. event_type stays strict, so a mis-routed frame is still rejected. Match on these fields with a default branch. See Conditional Orders Channel.
Upgrade
1.0.48 too; it runs via npx -y @0xmonaco/mcp-server, so restarting your assistant picks up the new version. Every SDK bump is a patch release.
One action item, and it is compile-time only: if you switch exhaustively over ConditionalOrderEventData.timeInForce or .reason, add a default branch. Nothing else here is breaking — availableOrderCollateral is an optional field, and useInstruments is a new export. If you already size orders from freeCollateral or withdrawableCollateral, read the availableOrderCollateral notes above before switching: it answers a different question, it is not additive across buckets, and it is absent on parent rows and fallback reads.
