Monaco Protocol SDK v1.0.43
This release adds server-side TWAP parent orders that slice a large order across a time window; in-place partial cancel that shrinks a resting order without losing its queue position; a mark-price candle series on the OHLCV WebSocket channel; awrappedNative balance flag that changes how the vault client deposits the chain’s wrapped-native asset; and four spot cost-basis fields on portfolio assets. It also makes a full-close take-profit / stop-loss supersede the position’s previous full-close leg of the same type in one atomic step, and fixes the marginRatio reported for accounts at zero or negative equity. Two things to act on: if you deposit the wrapped-native asset (e.g. WSEI) it now goes in as native coin with no ERC20 approval — see the vault change below; and marginRatio can now come back as a very large sentinel, so clamp it for display.
Added
TWAP parent orders
Submit a single TWAP parent order and the matching engine slices it into a schedule of market child orders across an execution window, averaging price over time instead of paying market impact in one print. Four authenticated methods onsdk.trading (createTwapOrder(tradingPairId, side, quantity, options) — POST /api/v1/orders/twap, gRPC OrdersService.CreateTwapOrder; getTwapOrder(twapOrderId) — GET /api/v1/orders/twap/{twapOrderId}, gRPC GetTwapOrder; listTwapOrders(params?) — GET /api/v1/orders/twap, gRPC ListTwapOrders; cancelTwapOrder(twapOrderId) — POST /api/v1/orders/twap/{twapOrderId}/cancel, gRPC CancelTwapOrder).
The window is exactly one of durationSeconds (default bounds 60 s – 30 days) or an endTime (RFC 3339 UTC with a Z suffix — non-UTC offsets are rejected — optionally with a startTime); other options cover limitPrice, slippageToleranceBps (default 100), randomize (default true; seeded ±30% size / ±30% timing jitter), reduceOnly, the usual margin context (tradingMode, marginAccountId, riskBucketId, marginMode, leverage), and a clientTwapId (≤36 chars, unique per user + application — a duplicate is rejected with 409). Both spot and margin markets are supported; delegated-agent sessions are rejected. Live progress streams on the new authenticated twap_orders WebSocket channel via sdk.ws.twapOrders(handler, tradingPairId?), delivering a TwapOrderEvent with the full parent state on every lifecycle transition. @0xmonaco/types adds the full type set (TwapOrderState, CreateTwapOrderOptions, CreateTwapOrderResponse, CancelTwapOrderResponse, TwapOrder, ListTwapOrdersParams / ListTwapOrdersResponse, TwapOrderEvent) with Zod validation of the window and margin constraints. See TWAP Orders for the execution model.
In-place partial cancel
sdk.trading.cancelOrder(orderId, { remainingQuantityTarget }) (POST /api/v1/orders/cancel CancelOrderRequest.remainingQuantityTarget; gRPC CancelOrderRequest.remaining_quantity_target) reduces a resting order in place to the target remaining quantity, keeping its queue position — unlike replaceOrder, which retires the order and rests a new one at the back of the book even for a pure size-down. The released delta unlocks exactly as a cancel does (spot unlock / margin reserve). The target means “leave this much resting”, so a retried request converges instead of compounding; it must be a positive multiple of the market’s quantity step and strictly below the current remaining quantity (emptying an order is a full cancel’s job, growing it is a replace). Omit the options argument for the full cancel that existing calls already perform. A new non-terminal OrderPartiallyCancelled event announces the reduction on the orders WebSocket channel — the order is still resting — carrying the cumulative filledQuantity, the reduced remainingQuantity, the cancellation reason (null when none — the cancel request takes no reason input), and reducedAt.
Mark-price candles on the OHLCV WebSocket channel
sdk.ws.ohlcv(tradingPairId, tradingMode, interval, handler, priceType?) accepts an optional priceType ("trade", "mark", or "*"). Omitting it — or passing "trade" — subscribes to the trade-derived candles exactly as before; "mark" subscribes to the close-only mark-price series for margin pairs (each candle’s open = high = low = close = the minute’s closing mark, volume 0), published on 1m; "*" receives both. The argument appends a fifth token to the ohlcv channel string, so existing four-token subscriptions are unchanged and never receive the mark series. @0xmonaco/types adds the OHLCVPriceType union, and OHLCVEvent carries an optional priceType so a "*" subscriber can tell the two streams apart. (The REST candlestick priceType shipped in 1.0.42.)
wrappedNative balance flag
GET /api/v1/accounts/balances and GET /api/v1/accounts/balances/{asset_id} (gRPC AccountsService.GetBalances / GetBalanceByAsset) now return an optional wrappedNative boolean per balance, surfaced as AccountBalance.wrappedNative in @0xmonaco/types. It is true for the chain’s wrapped-native asset (e.g. WSEI) and false for standard ERC-20 assets. GET /api/v1/accounts/balances/{asset_id} returns the flag even when that asset’s balance is zero; the list endpoint GET /api/v1/accounts/balances omits zero-balance rows. It is the signal the vault client keys native-coin deposits on (see the vault change below).
Spot cost-basis fields on portfolio assets
sdk.portfolio.getAssets() (GET /api/v1/accounts/me/portfolio/assets; gRPC GetPortfolioAssets) surfaces four optional spot cost-basis fields per asset row — avgCost, unrealizedPnl, unrealizedRoe, and cumRealized (all optional decimal strings). They are populated from the spot cost-basis engine when a checkpoint exists for the asset; unrealizedPnl and unrealizedRoe additionally require a mark price, and unrealizedRoe is additionally omitted when the cost basis is zero.
Changed
Vault deposits key native-coin handling on wrappedNative
The vault client now decides native-coin handling from the balances API’s wrappedNative flag instead of a zero token address. The exchange’s wrapped-native asset (e.g. WSEI) carries a real ERC20 address — the vault wraps native deposits into it, so address(0) never appears:
sdk.vault.deposit(assetId, amount)routes awrappedNativeasset to the payabledepositNative, sendingamountasmsg.value(the vault wraps on entry) — no ERC20 approval is required or checked. Every other asset keeps the approve +depositERC20flow, and an absent orfalseflag is treated as a standard ERC-20.sdk.vault.needsApproval(assetId, amount)returnsfalsefor awrappedNativeasset without reading the on-chain allowance, sincedepositNativeconsumes none.- Withdrawals of the wrapped-native asset pay out native coin (the vault unwraps); this is decided server-side and needs no request change.
Full-close TP/SL supersedes the previous full-close leg
Attaching a full-close (closePosition: true) take-profit or stop-loss with sdk.positions.attachPositionTpSl() (REST POST /api/v1/positions/{positionId}/tp-sl; gRPC PositionsService.AttachPositionTpSl) now supersedes that position’s previously active full-close legs of the same condition type: the old legs are cancelled atomically in the same engine step that creates the replacement, each announcing a conditional_order_update event with reason: "cancelled" before the replacement’s "created". Moving a TP or SL is therefore one call, with no window in which the position holds both levels or neither — the non-atomic cancel-then-reattach workaround is no longer needed. Three things survive a supersede: the superseded leg’s OCO sibling, every fixed-quantity leg (laddered partial exits still stack), and legs attached to an entry order rather than to the position (entry-attached legs are excluded in both directions).
Fixed
marginRatio for accounts at zero or negative equity
GetPositionRisk (GET /api/v1/positions/{positionId}/risk) and the authenticated account WebSocket channel (AccountEventData.marginRatio) now report a maintenance-aware marginRatio for accounts whose equity is not positive. An account that still carries an open maintenance requirement at zero or negative equity now reports the maximum-distress ratio instead of 0 (equity exactly zero) or a negative ratio (equity gone negative from bad debt) that previously sorted as safer than a healthy account. Accounts with no open positions continue to report 0. The maximum-distress value is a very large decimal string (Decimal::MAX, ~7.9e28); treat a ratio at or above the liquidation threshold as maximal distress and clamp it for display.
Upgrade
1.0.43 too; it runs via npx -y @0xmonaco/mcp-server, so restarting your assistant picks up the new version. Nothing in this release is breaking. Two things worth acting on: if you deposit the wrapped-native asset (e.g. WSEI), drop the ERC20 approval step — deposit now sends it as native coin through the vault’s payable depositNative, and needsApproval returns false for it; and if you read marginRatio, treat a value at or above the liquidation threshold as maximal distress and clamp the Decimal::MAX sentinel for display.
