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

# Monaco Protocol SDK v1.0.61

This release covers the consumer-facing changes since v1.0.60. A perp market now publishes its **full notional-bracket margin ladder** on `riskTiers` instead of the single synthetic tier the field used to carry, and the same ladder prices read-time margin and liquidation across the position, portfolio and margin-account endpoints — so anything that read `riskTiers[0]` as "the" tier, or derived a requirement from the top-level ratios alone, now needs to select a bracket. No field was removed or renamed, but the array's length and meaning both change and a caller that does not migrate reads tier-1 limits for a higher-notional position without any error — so it is listed as breaking. The order-risk simulations return `expectedMatchResult` — the engine's own simulated fill for the previewed size, in the same `MatchResult` shape a placement carries — plus `referencePrice`, and accept an optional `slippageToleranceBps` so the preview runs under the band the real order will. And a margin reduce-only replace may now name a close size at or below what the order has already filled, which was refused on every order before. If you read `riskTiers`, carry a previewed slippage tolerance into placement, or resize partially filled reduce-only closes, read the three upgrade notes before you bump.

## Breaking

### `riskTiers` carries the full margin ladder

`GET /api/v1/market/pairs/{trading_pair_id}/perp/config` (`sdk.market.getPerpMarketConfig`; gRPC `MarketService.GetPerpMarketConfig`, public, no auth) now lists **every** bracket in `riskTiers`, ascending by `lowerBoundNotional`. The field previously reported a single synthetic tier, so its length and its meaning both change.

Each `RiskTier` gains five fields:

| Field                        | Type             | Meaning                                                                                                |
| ---------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------ |
| `tierLevel`                  | number           | 1-based bracket index; `1` is the lowest bracket                                                       |
| `lowerBoundNotional`         | string           | USD notional where the bracket starts; `"0"` for tier 1                                                |
| `maxLeverage`                | string, optional | the bracket's leverage cap — absent only on a single-tier market configured from explicit margin rates |
| `initialMarginDeduction`     | string           | continuity term of the ladder floor; `"0"` for tier 1                                                  |
| `maintenanceMarginDeduction` | string           | continuity term of the maintenance requirement; `"0"` for tier 1                                       |

The deductions keep the ladder continuous where one bracket hands over to the next. For a position of notional `N` in bracket `k`:

```
IM_floor(N) = N x initialMarginRatio_k - initialMarginDeduction_k
MM(N)       = N x maintenanceMarginRatio_k - maintenanceMarginDeduction_k
```

Each bracket's rates are marginal, so reading a bracket's `initialMarginRatio` as a flat rate against the whole notional overstates **that bracket's ladder floor** at the bottom of the bracket. It is not the position's initial margin in either direction: that is the greater of the floor and the selected-leverage requirement below, which at low leverage is far above the flat-rate figure.

A position's bracket is the **last** tier whose `lowerBoundNotional` is at or below its notional, `abs(quantity) x markPrice`. Its actual initial margin is the **greater** of `IM_floor(N)` and the selected-leverage initial margin scaled to the current mark — the ladder sets a floor, it does not replace the leverage you chose.

The top-level `maxLeverage`, `initialMarginRatio` and `maintenanceMarginRatio` are unchanged and remain the **tier-1** values, so a caller reading only those reads the lowest bracket rather than the position's own. `maxPositionSize` stays a market-wide maximum absolute base-asset quantity repeated on every tier, and its deprecated `maxPositionNotional` alias is unchanged — it is a base quantity, not a notional.

The same ladder now prices maintenance margin and liquidation prices on the position, portfolio and margin-account reads, so a position whose notional sits in a higher bracket reports a higher maintenance requirement and a closer liquidation price than the flat tier-1 rate would have given. Positions inside tier 1 are unaffected: its deductions are `"0"`, which leaves the arithmetic exactly as before.

See [Margin ladders](/trading-mechanics/perps#margin-ladders) for the derivation and [`getPerpMarketConfig`](/sdk/typescript/market) for the field reference.

## Added

### Order-risk previews return the engine's simulated fill

`sdk.marginAccounts.simulateOrderRisk` (`POST /api/v1/margin/accounts/{margin_account_id}/simulate-order-risk`), `simulateParentMarginOrderRisk` (`POST /api/v1/margin/parent-margin-account/simulate-order-risk`) and `simulateRiskBucketOrderRisk` (`POST /api/v1/margin/risk-buckets/simulate-order-risk`) — and their gRPC equivalents `MarginAccountsService.SimulateOrderRisk`, `SimulateParentMarginOrderRisk` and `SimulateRiskBucketOrderRisk` — now publish the match the engine already ran to admit the order. All three need an authenticated session and are owner-scoped: a preview only ever runs against the caller's own margin accounts.

`SimulateOrderRiskResponse.expectedMatchResult?: MatchResult` is the **same** shape `CreateOrderResponse.matchResult` carries for a placement (and `ReplaceOrderResponse.matchResult`, `BatchCreateResult.matchResult`, `BatchReplaceResult.matchResult`; the `Order` read model does not carry it), from the same walk over the live book: `totalFilled`, `remainingQuantity`, `averageFillPrice`, `executionPriceRange`, `status`, `actualSlippageBps` and `maxSlippageBps`. It is not a client-side re-walk, so a preflight check and the placement that follows read the same numbers for the same book — but the book moves, so treat the figures as indicative of the size's impact rather than as a guaranteed execution. `remainingQuantity` is the partial-fill indicator — it is what the band or the depth would leave unfilled on the previewed book, and the preview's `status` reads `CANCELLED` for it, because a MARKET remainder cannot rest. Placement runs against a later book, so a non-zero remainder here is a warning that the size does not clear, not a prediction of the fill: read the placement's own `status` and quantities for what actually happened.

`SimulateOrderRiskResponse.referencePrice?: string` is the touch on the taking side the MARKET preview was measured from — best ask for a BUY, best bid for a SELL — and is what `actualSlippageBps` is measured against.

Both fields are present **only when `accepted` is true**, and absent — never zeros — on every refused preview, including the post-match maker-risk rejection. Treat absence as unknown rather than as a zero fill. `referencePrice` is additionally MARKET-only: a LIMIT or IOC preview is measured against its own limit price and publishes nothing here, and its `actualSlippageBps` is `null` when every fill improved on that price.

The three requests gain an optional `slippageToleranceBps` (MARKET only, `0`–`1000`), validated client-side by the newly exported `MarketSlippageToleranceBpsSchema`: a LIMIT preview carrying the field, or a value above 1,000 bps, fails validation before the request is sent, and reaches the server as a `400` (gRPC `INVALID_ARGUMENT`) carrying placement's own message if sent raw. It tightens the previewed walk to the band the real order will run under, so `expectedMatchResult` and `estimatedFee` price under your own tolerance; the public handler previously always previewed under the default band. It can only tighten — it never widens the 1,000 bps protective band.

The preview also now runs under the **caller's wallet** rather than a placeholder, so self-trade prevention classifies your own resting orders exactly as placement does.

These figures price against the book **at the moment of the preview**. A take-profit or stop-loss closes later, on a different book and under the wider band a triggered market leg carries, anchored at trigger time — so size a TP/SL target by applying `expectedMatchResult.actualSlippageBps` to your **trigger** price as an impact figure, rather than displaying this call's absolute `averageFillPrice`.

A preview cannot carry a post-only flag, TP/SL legs or a per-order self-trade-prevention override, so a post-only order that would cross is refused by placement, not by the preview.

On the MCP server, `simulate_parent_margin_order_risk` and `simulate_risk_bucket_order_risk` accept the same input and surface both response fields. The MCP server ships at `1.0.61` too; it runs via `npx -y @0xmonaco/mcp-server`, so restarting your assistant picks up the new tool schema.

See [Pre-trade simulation](/sdk/typescript/margin-accounts#pre-trade-simulation).

## Fixed

### A margin reduce-only replace can be sized down below its filled amount

`sdk.trading.replaceOrder` (`PUT /api/v1/orders/{orderId}`) and each item of `sdk.trading.batchReplace` (`POST /api/v1/orders/batch-replace`; gRPC `OrdersService.ReplaceOrder` and `BatchReplaceOrders`) rejected `quantity` at or below the order's `filledQuantity` on **every** order, with `New quantity N must be greater than filled quantity 3`.

That rule belongs to **total** semantics: a replacement quantity is the order's new total, and a total at or below what already filled leaves nothing to rest. A margin reduce-only quantity is deliberately not a total — it is a close size measured against the live position and placed exactly as given — so the rule never applied to it. Applying it anyway made a partially filled close unshrinkable: on a reduce-only close of 8 with 3 filled, both `quantity: "2"` and `quantity: "3"` were refused, leaving no way to reprice the close downward except by naming a size above the order's own fill count.

Both are now accepted and placed at the requested close size. As with any replacement, success does not mean the order is *resting*: a reduce-only close still goes through matching and self-trade prevention, so it can fill immediately, or be cancelled outright under `CANCEL_TAKER` / `CANCEL_BOTH` or as a `SKIP` remainder crossing one of your surviving orders — read the new order's status rather than inferring it. The replacement still inherits the original's fill history, so the row reads `quantity = filled + remaining`.

Nothing else changes. Total semantics, the `total - filled` subtraction and the must-exceed-filled rejection all behave exactly as before for every order that is not margin reduce-only, and the reduce-only position bound still rejects a close larger than the live position. No request or response shape changed — this is a server-side correction, so clients on any SDK version receive it; only the `quantity` field descriptions on `ReplaceOrderRequest` and `BatchReplaceOrderItem` now state the exception.

See [Replacing Orders](/developers/trade#replace-orders).

## Upgrade

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

Three things to check before upgrading. If you **read `riskTiers`**, stop treating it as one row: select the last tier whose `lowerBoundNotional` is at or below `abs(quantity) x markPrice`, take the position's initial margin as the greater of that bracket's ladder floor and your selected-leverage requirement, and handle `maxLeverage` being absent on a single-tier market. The top-level ratios are tier 1, not the position's tier. If you **carry a previewed slippage tolerance into placement**, note the units differ: previews take `slippageToleranceBps` as integer basis points, while `placeMarketOrder` and the batch helpers take `slippageTolerance` as a **ratio**, so pass `slippageToleranceBps / 10_000` — a `slippageToleranceBps` key on those calls is not forwarded. And if you **resize partially filled reduce-only closes**, the `400` you were coding around is gone; a close size at or below the fill count is now a legitimate size-down, so drop any client-side floor that was compensating for it.

The rest is additive. `expectedMatchResult` and `referencePrice` are present only on an accepted preview, so read them behind an `accepted` check rather than assuming a shape. Existing preview calls that omit `slippageToleranceBps` keep the default band and are unchanged, except that the walk now runs under your own wallet for self-trade prevention.
