> ## 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.54

# Monaco Protocol SDK v1.0.54

This release covers the consumer-facing changes since v1.0.53. No **wire** field changes shape, but there is one source-level compatibility break: `totalTradeCountLtd` is retyped from string to integer in the generated SDKs, which stops typed callers that treat it as a string from compiling. Two further items need attention before you upgrade — keyed order submission gains two retryable error codes, and the order endpoints now declare a per-account rate limit whose budget keys you can read today. Alongside those, your own resting quote no longer hides third-party liquidity from your taker, every WebSocket gap now closes the connection with the same resync signal, withdrawals commit the authenticated wallet as the on-chain owner, and portfolio analytics move to a dedicated reader.

## Breaking

### `totalTradeCountLtd` is retyped from string to integer

`totalTradeCountLtd` is corrected to `integer` / `int64` for `GET /api/v1/market/pairs/{tradingPairId}/metadata`, `GET /api/v1/market/screener`, and `GET /api/v1/market/stats`. The server always emitted a JSON number; the spec incorrectly declared a string, so runtime responses and requests are unchanged. These three endpoints require no authentication.

**This is a source-level break for typed callers, even though the wire is unchanged.** `@0xmonaco/types` now declares `totalTradeCountLtd?: number | null` where it previously declared `string | null`, and the generated Rust REST SDK produces an integer model field where it previously produced a `String`. Only string-specific uses break: `parseInt(...)`, calling a `String` method such as `.startsWith()` or reading `.length`, assigning to a `string`-typed field, or passing it to a parameter declared `string`. Replace those with direct numeric use. Generic conversion and formatting keep compiling untouched — `Number(...)`, `String(...)`, `.toLocaleString()` and template interpolation all accept a number — so there is no need to strip them out. Untyped JavaScript and raw REST callers are unaffected, because they were already receiving a number.

## Changed

### Keyed order submission reports capacity and availability separately

`POST /api/v1/orders` and `POST /api/v1/orders/batch-create` (`OrdersService.CreateOrder` / `BatchCreateOrders`) expose two structured codes for the optional `idempotencyKey`. Both are retryable, both map to HTTP `503` / gRPC `UNAVAILABLE`, and in a batch each arrives on the failing item's `error` field rather than failing the whole call.

* `ORDER_SUBMISSION_CAPACITY_EXCEEDED` refuses a **new** key when retained submission receipts are at capacity because responses are protected by pending durability or persistence. Existing retained receipts still replay while storage is full; only a new key that needs another receipt is refused. The refusal does not accept or reserve the key, so nothing was applied. Capacity frees as protected receipts finish acknowledgement and persistence and become evictable — usually seconds, not the 24-hour retention window — so retry the same key with bounded backoff.
* `ORDER_SUBMISSION_UNAVAILABLE` means the original response for a retained key cannot currently be retrieved, or keyed admission is busy. Unlike a capacity refusal it is **not** evidence the order was rejected — a submission under that key may already exist and may already have executed. It never authorizes duplicate execution, so retry the same key with the same normalized payload and let the replay report the outcome.

The response cache behind these keys is now bounded and evictable, so a full cache no longer refuses new keyed submissions on its own — durable responses can be evicted while exact key history stays retained. Replay preserves the original order identity and never executes a second order. The 24-hour retry boundary is unchanged and applies only to previously accepted keys; a different payload on a retained key still returns `IDEMPOTENCY_KEY_CONFLICT` (HTTP `409` / gRPC `ALREADY_EXISTS`). See [Error Handling](/sdk/typescript/error-handling#retrying-order-mutations-safely) and [Safe Retries with Idempotency Keys](/developers/trade#safe-retries-with-idempotency-keys).

### Order and movement endpoints declare a per-account rate limit

The order endpoints document a `429` rejection with code `RATE_LIMIT_EXCEEDED`, a `Retry-After` header, and `details.retryAfter` carrying the denying window's real horizon — about 1 second for a spike, up to 60 seconds for sustained overflow. On gRPC the equivalent is `RESOURCE_EXHAUSTED` with `google.rpc.RetryInfo`.

Budgets are keyed to the account and weighted by order items, split into two classes, named here by REST path because the SDK, REST and gRPC spellings differ. **Order creation** covers `POST /api/v1/orders`, the replace endpoints, and the batch create/replace items without `reduceOnly`. **Risk reduction**, at twice the creation budget, covers every cancel — `/orders/cancel`, `/orders/batch-cancel`, and the conditional and TWAP cancels — plus any create item with `reduceOnly: true`. Each class enforces a per-second and a per-minute window and a request must fit both; an over-budget batch is rejected as a **whole request**, never per item, and a mixed batch charges each class for its own items. Mass exit — `POST /api/v1/orders/batch-cancel-all` and its by-pair form — is never rate limited. Withdrawal initiation and the margin collateral transfers share a separate per-user movement budget.

Successful gated order responses return the live budget. The keys are emitted lowercase — `x-ratelimit-limit-second`, `x-ratelimit-remaining-second`, `x-ratelimit-limit-minute`, `x-ratelimit-remaining-minute` — and serve both transports. Over REST you may look them up in any case, since HTTP header names are case-insensitive; over gRPC they arrive as response metadata and **must** be looked up lowercase. Movement endpoints return no budget keys. Delegated agent sessions draw their owner's budget, sub-accounts have their own, and PrivateLink connections draw their own 4x budgets.

Enforcement is being rolled out per environment. Until it is enabled where you trade, an over-budget request is admitted and only measured — the budgets and headers above are already returned, so build back-off handling against them now. See [Rate Limits](/developers/rate-limits).

### `balance_update` carries `tokenSymbol` on margin-collateral frames

The `balance_update` WebSocket event now reports `tokenSymbol` on margin-collateral frames — reasons `trade`, `fee`, `rebate`, `funding`, `liquidation`, and `adl`. These could previously arrive with `tokenSymbol: null`, leaving `tokenAddress` as the only identifier, for a margin account whose collateral symbol was not recoverable after a matching-engine restart.

A collateral transfer now binds the symbol it carries rather than only the balance id and token address, and the emitter resolves a still-missing symbol from the configured asset registry by the account's collateral token. The symbol is therefore present whenever the token is a configured asset, regardless of per-account binding state. `tokenSymbol` remains typed `string | null` and is null only when the account has no bound collateral token, or the token is not a configured asset. This is wire-compatible: symbol-keyed clients see a value where they previously saw null. See [Balance Updates](/websocket#balance-updates).

### Maker fill events carry `postOnly`

`OrderFilled` and `OrderPartiallyFilled` with `role: "maker"` now carry `postOnly: true` when the resting order was admitted post-only. This was the one order-event variant without the flag, and the one that matters most: an admitted post-only order can never fill as taker, so its fills are exactly the frames that previously omitted it.

The flag follows the same absent-rather-than-false contract as the acknowledgement, taker fills, and the cancellation and expiry events, so `data.postOnly ?? false` now reads correctly on maker fills. It is denormalized onto the trade at fill time, so it survives the fill that fully retires the order. The change is additive and wire-compatible — `postOnly` was already declared optional on the shared order-event type, so no TypeScript shapes change. A maker fill emitted by a server predating this change still omits it. See [Post-Only on Order Events](/sdk/typescript/websockets#order-event-types).

## Fixed

### An own resting quote no longer hides third-party liquidity

Order submission through `sdk.trading.placeLimitOrder` / `placeMarketOrder` and authenticated `POST /api/v1/orders` (`OrdersService.CreateOrder`) now counts only non-self liquidity during the early self-trade check. An own resting quote previously consumed that check's depth budget, so a third-party order resting behind yours — including at the same price — was invisible to an incoming taker and the request was rejected before reaching the book.

Your own quotes remain untouched. The sequenced engine check is still authoritative: if the matching walk encounters an own order and eligible third-party depth cannot cover the whole request, `SELF_TRADE_NOT_ALLOWED` still rejects before any fills. Price limits, market-order slippage limits, and margin admission still apply. Request and response fields are unchanged, and no selectable self-trade prevention mode is added. See [Self-Trade Prevention](/developers/trade#self-trade-prevention).

### Every WebSocket gap closes the connection with the resync signal

A detected broadcast gap now closes the **whole** connection with the existing `1013` resync-required signal on every subscription mix, including sockets holding only `positions`, `balances`, `account`, `instruments`, `orderbook`, `ohlcv`, or market-stat subscriptions. The close frame is the machine-readable gap signal; no new JSON error protocol is introduced and no history is replayed automatically.

With automatic reconnect enabled the SDK opens a new socket and sends its authentication and subscription requests before invoking `onResync`, without waiting for acknowledgements or snapshots. Applications still own REST reconciliation, especially for the channels that have no subscribe-time snapshot. See [Slow-client disconnect](/sdk/typescript/websockets#slow-client-disconnect-close-code-1013).

### Master withdrawals commit the authenticated wallet as on-chain owner

Authenticated master withdrawals through `POST /api/v1/withdrawals` and `WithdrawalsService.InitiateWithdrawal` now commit the authenticated wallet as the on-chain withdrawal owner. The commitment previously carried a zero owner, so a payout whose destination rejected it could not be recovered by anyone.

Request and response fields are unchanged. Owners can use the vault's owner-authorized redirection to recover a **future** withdrawal whose original destination rejects payment. This is not retroactive: historical withdrawals retain their original commitments and replay behavior.

### Collateral transfers emit a balance update when either component parses

A successful authenticated margin account collateral transfer now emits the `balance_update` WebSocket event when **either** matching-engine balance component parses. The event overlays each valid `availableRaw` or `lockedRaw` value onto the prior wallet balance and recomputes the total; an invalid component retains its prior value. No event is emitted when neither component parses, or when a risk bucket allocation moves no wallet balance. Requests and responses are unchanged.

### Portfolio analytics read from a dedicated reader

`sdk.portfolio.getSummary()`, `getPerformance()`, `getRealizedPnl()`, `getAssets()`, and the `sdk.profile` aggregate stats and chart methods now use a dedicated read replica and **may reflect replica lag**. A reader outage returns HTTP `503` / gRPC `Unavailable` rather than moving analytics onto the writer.

Request fields and successful response fields are unchanged. `sdk.portfolio.getMargin()` and trade history keep their existing freshness requirements: they are writer-backed rather than replica-backed. Note that `getMargin()`'s withdrawal bounds are not unconditionally live, and the fallback has two scopes. If the matching engine serves no bounds at all, the account total **and every** risk bucket row fall back to a persisted reconstruction. If the engine serves bounds but one bucket is missing from them, only that row falls back while the rest stay live. The reconstruction is pinned to the same formulas the withdrawal gates use, but it cannot see the gates' resting-order reserves or the parent margin account's live allocation constraint, so treat a fallback value as an estimate rather than an enforceable bound. See [Portfolio Analytics](/sdk/typescript/portfolio). The affected REST endpoints are `GET /api/v1/accounts/me/portfolio` and its `/chart`, `/summary`, `/performance`, `/realized-pnl`, and `/assets` paths; the matching gRPC methods are on `AccountsService`.

## Upgrade

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

Before upgrading, treat `ORDER_SUBMISSION_CAPACITY_EXCEEDED` and `ORDER_SUBMISSION_UNAVAILABLE` as retryable rather than terminal, and retry them with the original key and normalized payload. Start reading the `X-RateLimit-*` budget headers while enforcement is still rolling out, and make sure a batch submitter can handle a whole-request rejection rather than a per-item one. Update string-specific handling of `totalTradeCountLtd` — a `parseInt`, a `String` method call, or a `string`-typed assignment will not compile until you read it as a number. Generic formatting such as `String(...)` or template interpolation needs no change. If you display a portfolio number next to a live one, expect the analytics side to lag slightly and to return `503` during a reader outage.


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