> ## Documentation Index
> Fetch the complete documentation index at: https://docs.0xmonaco.com/llms.txt
> Use this file to discover all available pages before exploring further.

# V1.0.40

# Monaco Protocol SDK v1.0.40

This is a large release. The headline is a collateral-model change: the two position-margin methods that sourced collateral from the spot wallet are **removed**, and adjusting a position's margin now flows through its risk bucket and the parent margin account. Alongside it, the WebSocket surface is brought fully in line with the wire — a **breaking casing change** on the `positions` channel, a locked cross-language drift gate, new `market_stats` channels, and richer `balance_update` / movement / position events — the market-data endpoints gain dollarized open-interest and volume figures, and the candlestick endpoint finally honors its own bounds. Two things to act on before upgrading: migrate off `addPositionMargin` / `reducePositionMargin`, and update any WebSocket position consumer that matches on the PascalCase `Open` status.

## Breaking

### `addPositionMargin` / `reducePositionMargin` removed — adjust margin through the risk bucket

`positions.addPositionMargin()` and `positions.reducePositionMargin()` (`@0xmonaco/core`), the `perpRoutes.positions.addMargin` / `reduceMargin` route helpers, `usePositions()`'s `addPositionMargin` / `reducePositionMargin` (`@0xmonaco/react`), the `add_position_margin` / `reduce_position_margin` MCP tools, the REST endpoints `POST /api/v1/positions/{position_id}/margin/add` and `.../margin/reduce`, and the gRPC `PositionsService.AddPositionMargin` / `ReducePositionMargin` RPCs are all removed, along with the `AddPositionMarginRequest` / `ReducePositionMarginRequest` / `PositionMarginResponse` types and their validation schemas.

Both sourced collateral from the **spot wallet**, so collateral already held in the parent margin account could not be deployed to a position at all, and releasing margin pushed it out of the margin account entirely rather than back to the parent.

**Migration.** Adjust a position's margin through its risk bucket, moving collateral between the bucket and the **parent margin account** — neither leg touches the spot wallet. Fund the parent first if it has no free collateral.

* **Allocate** — `transferCollateralToRiskBucket({ asset, amount, tradingPairId, marginMode: "ISOLATED" })`
* **Release** — `transferCollateralFromMarginAccount(parentMarginAccountId, { asset, amount, tradingPairId })`

### WebSocket position status is now SCREAMING\_SNAKE\_CASE

Position events on the `positions` channel now carry `status` as `OPEN`, `LIQUIDATING` (a closing position), `CLOSED`, `LIQUIDATED`, or `EXPIRED` — matching the REST position status strings and the published `@0xmonaco/types` contract exactly — instead of the previous PascalCase (`Open`), which diverged from REST for the same field. `PositionEventStatus` has been narrowed accordingly. A consumer that matched on `"Open"` must switch to `"OPEN"`. The position event data also now carries optional `tradeId` and `orderId`, referencing the trade and/or order that caused the update (omitted when not applicable).

## Added

### `marginMode` on collateral transfer-out — releasing from a cross bucket

`marginAccounts.transferCollateralFromMarginAccount()` (`@0xmonaco/core`; `TransferCollateralRequest` in `@0xmonaco/types`; REST `POST /api/v1/margin/accounts/{margin_account_id}/collateral/transfer-out`; gRPC `TransferCollateralFromMarginAccountRequest.margin_mode`) accepts an optional `marginMode`. A cross risk bucket has no trading pair, so `tradingPairId` could not name it and there was no way to address a cross bucket on a transfer-out at all — collateral could enter a cross bucket and never leave. `marginMode: "CROSS"` selects the cross bucket, and the release lands in the parent margin account like every other bucket release. The two selectors are mutually exclusive: `marginMode: "CROSS"` together with `tradingPairId` is rejected client-side.

### Dollarized open interest and 24h volume on market-data endpoints

`getPerpMarketSummary` now returns `openInterestBase`, `openInterestNotional`, and `quoteVolume24h`, and `getMarketMetadata` now returns `quoteVolume24h` (gRPC `GetPerpMarketSummaryResponse` / `GetMarketMetadataResponse`). The perp summary previously exposed only base-asset `openInterest` and base-asset `volume24h`, so labelling either as USDC was wrong by roughly the mark price. `openInterestNotional` is `openInterestBase × mark price` (falling back to the latest close), and `quoteVolume24h` is the dollar figure directly; `openInterest` remains a base-asset alias of `openInterestBase`.

`getScreener` (`GET /api/v1/market/screener`) additionally returns `openInterestBase` and `openInterestNotional` on every `ScreenerItem`, so a client can read all pairs' open interest in one call instead of a per-pair `getPerpMarketSummary` fan-out. Both are `null` for spot pairs, and a margin pair with no open positions reports `"0"`. The screener notional is valued at the pair's latest trade price (a snapshot); the mark-accurate notional lives on `getPerpMarketSummary` / `getOpenInterest`.

### `market_stats` WebSocket channels

A new public per-market channel — subscribe with `market_stats:<tradingPairId>` — delivers periodic per-market frames: index / mark / mid prices, best bid/ask, open interest and its limit, last trade price, 24h volume / high / low and both absolute and percentage price change, and funding state (current rate, estimated next rate, next funding timestamp, premium, and clamp bounds).

A companion all-markets summary channel — subscribe with `market_stats` (or `market_stats:*`) — delivers, on a slower cadence, a single frame carrying a reduced per-market summary for every perp market: trading pair id, symbol, last trade price, mark price, absolute and percentage 24h price change, 24h base/quote volume and high/low, open interest, current funding rate, next funding timestamp, and a `market_status` label (`TRADABLE`, `POST_ONLY`, `HALTED`, `NOT_READY`, or `UNKNOWN`). Both channels are public — no session handshake required. SPOT markets flow through both, carrying orderbook, last trade, 24h stats, and a `TRADABLE` / `HALTED` label, with the perp-only fields (index/mark price, open interest and limit, funding rate/timestamp/premium, base interest rate, funding clamps) omitted.

### Richer position, balance, and movement events

* Position events now also emit on liquidation-worker and ADL closes and for counterparties impacted by a fill: a terminal `LIQUIDATED` update — with `reason` `"liquidation"` for a maintenance-margin liquidation and `"adl"` for an auto-deleverage close — when a position is force-closed, plus live `OPEN` refreshes for surviving or partially-reduced positions. Funding settlements now also emit a `position_update` with `reason: "funding"` carrying the position's post-settlement state (status `OPEN`, unchanged size), so a subscriber can attribute a live position change to funding.
* The `balance_update` `reason` union gains `fee`, `rebate`, `funding`, `margin_deposit`, `liquidation`, and `adl`, so a collateral change can be attributed to a fee charged, a maker rebate credited, a funding settlement, a deposit routed into margin collateral, a liquidation, or an auto-deleverage. Perp/margin fills on the order-fill path now emit a `trade`-reason collateral frame for **every** margin account the fill touched — the resting maker(s), not only the aggressing taker — and a separate `rebate` frame when a maker earned a rebate (negative maker fee). Each frame carries the user's resulting balance for one token (the envelope's `userId` plus the payload's `tokenAddress` — the user's aggregate for that token, with no per-margin-account key): it is a terminal-state snapshot, not an additive delta, so apply the latest balance per `(userId, tokenAddress)` rather than summing the `reason` frames.
* Funding settlements surface on the `movements` channel with `transactionType: "funding"`, reconciling with the matching `balance_update` whose `reason` is `funding`.

## Changed

### A margin shortfall is no longer reported as an insufficient balance

`InsufficientInitialMargin` and `InsufficientFreeCollateral` previously surfaced as `Insufficient balance: required=..., available=...`. They now carry the risk engine's own wording:

```
insufficient initial margin: required=..., available=...
insufficient free collateral: requested=..., available=...
```

The HTTP status and error type are unchanged (`400` / invalid input); only the message differs. Clients that match on the message string to detect a funding problem must be updated — the account's wallet is not short, its margin is, and depositing does not fix a leverage or position-size problem. Genuine wallet shortfalls are untouched and still read `Insufficient balance`.

### The candlestick endpoint now honors `startTime`, `endTime`, and `limit`

`GET /api/v1/market/pairs/charts/candlestick/{tradingPairId}/{interval}` (`getCandlesticks`) accepted all three parameters and then returned far more data than either bound allowed — a 20-minute window could return 350 bars spanning 5.8 hours, 330 of them before `startTime`. Two behaviors caused it, and both are gone:

* **The window is now absolute.** A range holding fewer bars than `limit` was topped up with history from before `startTime`; it now comes back short instead, and no bar outside `[startTime, endTime]` is ever returned. Both bounds are inclusive.
* **`limit` is now a hard cap.** Gap-filled flat bars (buckets with no trades, carrying the previous close) are now counted towards `limit`, and the bar the request anchored on is the one that survives the cap — a forward page (`startTime`) still begins at `startTime`, while a latest or backward page still ends at its newest bar.

Unchanged: `limit` still defaults to 350 and clamps to 500, bars are still ascending oldest-to-newest, and forward / backward pagination still walk from `startTime` / end at `endTime`. Clients that relied on the old padding to fill a chart should widen their requested range, or drop `startTime` to get the most recent `limit` bars.

### Consistent order-event fill quantities

Order WebSocket events now report a consistent filled-quantity representation across taker, maker, and IOC/cancel events. `filledQuantity` is always the cumulative order fill (a maker event previously put only that trade's quantity there); a new optional `lastFillQuantity` / `lastFillQuantityRaw` carries the incremental fill contributed by the event (the summed trades for a taker, the single trade for a maker) and is omitted when the event carries no fill. The IOC/market partial-cancel event now also carries `filledQuantity` / `filledQuantityRaw`, mirroring the fill events; its legacy `totalFilled` / `totalFilledRaw` remain as aliases. The full order-cancelled payload's `cancelledAt` is now RFC3339 with a `Z` suffix, matching `executedAt`, and its typed shape now also declares `averageFillPrice` (already present on the wire).

### Batch order requests are capped at 100 items

Batch create / replace / cancel requests are now capped at 100 items per request (Zenith audit #11). `BatchCreateOrdersSchema` and `BatchReplaceOrdersSchema` reject arrays longer than 100 with a validation error before the request is sent, and the new `MAX_BATCH_ORDER_ITEMS` constant (100) is exported from `validation/trading`. The REST and gRPC APIs reject an oversized batch create/replace/cancel with a `400` and the message `At most 100 orders per batch request`; the server-enumerated cancel-all endpoint is unaffected. Under sequencer overload the APIs may now return a retryable `503` with error code `OVERLOADED` instead of queueing requests unboundedly.

### `tokenSymbol` populated on deposit/withdrawal balance events

The `balance_update` event (authenticated `balances` channel) now carries the registry-configured `tokenSymbol` (e.g. `"USDC"`) on `deposit` and `withdrawal` events; it was previously `null` on those reasons, forcing clients to fall back to `tokenAddress`. The field type is unchanged (`string | null`) — `null` now only appears for tokens without a registry entry.

### WebSocket event surface locked with a drift gate

Every MonacoWebSocket channel (`orders`, `userOrders`, `orderbook`, `ohlcv`, `trades`, `movements`, `balances`, `conditionalOrders`, `positions`, `liquidations`) now has a golden wire-format fixture and a test asserting `@0xmonaco/types` declares a field for every wire key the SDK surfaces, plus a coverage assertion that fails if a future channel lands with a loose handler or without a drift test. This keeps the typed event surface in sync with the Rust wire format; the visible effect this release is the movement-event fields below and the order/position event corrections above.

## Fixed

### WebSocket movement event parser reads the current wire

The movement event parser now reads the serialized `token` key (with a `token_address` fallback) and surfaces `status`, `assetId`, and `blockNumber` from the wire. Previously it read only the legacy `token_address` alias — absent from the current wire — and dropped those fields. `UserMovementEventData` now declares `token` / `status` / `assetId` / `blockNumber` (`token` optional; `tokenAddress` kept as a populated back-compat alias).

### IOC position closes accept and require `limitPrice`

`ClosePositionSchema` and the `close-position` MCP tool now accept — and require — `limitPrice` on IOC close requests, matching the server contract. The server executes an IOC close as a `LIMIT`+`IOC` order and rejects it without a price, but the schema previously rejected any IOC request that carried one, making IOC closes impossible through the SDK. `limitPrice` is now required for `LIMIT` and `IOC` closes and forbidden only for `MARKET`.

## Deprecated

### `TIME_IN_FORCE_GTD` (`"GTD"`)

`GTD` is declared in the protos but was never implemented, and order admission has always rejected it. Submitting `timeInForce: "GTD"` on create or batch-create fails with HTTP `400` / gRPC `INVALID_ARGUMENT` and the message `Invalid timeInForce: Time in force must be GTC, IOC, or FOK` — unchanged behavior; only the declaration is now marked deprecated, and the documentation that incorrectly advertised `GTD` as supported has been corrected to `GTC`. The enum value is retained so generated clients keep compiling. For an order that expires at a set time, send `timeInForce: "GTC"` with `expirationDate` — the GTC expiration job enforces it.

## Upgrade

```bash theme={null}
npm install @0xmonaco/core@1.0.40 @0xmonaco/types@1.0.40 @0xmonaco/react@1.0.40 @0xmonaco/contracts@1.0.40
# or bun
bun add @0xmonaco/core@1.0.40 @0xmonaco/types@1.0.40 @0xmonaco/react@1.0.40 @0xmonaco/contracts@1.0.40
```

The MCP server ships at `1.0.40` too; it runs via `npx -y @0xmonaco/mcp-server`, so restarting your assistant picks up the new version, the removed position-margin tools, and the corrected `close-position` contract.

The two breaking changes and the things worth acting on:

1. **Migrate off `addPositionMargin` / `reducePositionMargin`.** Allocate with `transferCollateralToRiskBucket({ ..., marginMode: "ISOLATED" })` and release with `transferCollateralFromMarginAccount(parentMarginAccountId, { ..., tradingPairId })`; both move collateral between the risk bucket and the parent margin account, never the spot wallet.
2. **Update WebSocket position consumers for the new status casing.** Match on `OPEN` / `LIQUIDATING` / `CLOSED` / `LIQUIDATED` / `EXPIRED`, not the old PascalCase `Open`.
3. **Re-check margin-shortfall handling.** Match on the new `insufficient initial margin` / `insufficient free collateral` messages, not `Insufficient balance`, when detecting a margin (not wallet) shortfall.
4. **Size batch requests to 100 items** and handle a retryable `503` `OVERLOADED`, and pass `limitPrice` on IOC position closes.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.