Skip to main content

Monaco Protocol SDK v1.0.70

This release adds copy trading — sdk.copyTrading, the owner-private copy WebSocket channel, React hooks and MCP tools — makes the order and trade listings cursor-only, and has order frames name the margin position an order is bound to before it fills. Margin order placement that names a riskBucketId answers differently for buckets you do not own and for copy trading risk buckets, the movements and funding-payment listings bound their totals by their own pagination reach, and idempotent order submission gets a per-owner receipt share. Action items: if you pass page to getPaginatedOrders, listConditionalOrders, listTwapOrders or getUserTrades, switch to pageToken — the SDK no longer accepts it and REST answers 400; if you read total / totalPages from user trades, they are gone; and if you place margin orders with an explicit riskBucketId, handle the new 404 and 400 answers.

Breaking

Order and trade listings are cursor-only

getPaginatedOrders, listConditionalOrders, listTwapOrders and getUserTrades (GET /api/v1/orders, GET /api/v1/orders/conditional, GET /api/v1/orders/twap, GET /api/v1/accounts/trades; gRPC OrdersService.ListOrders, ListConditionalOrders, ListTwapOrders, TradesService.ListUserTrades and AccountsService.GetUserTrades) no longer accept page. Every call is a cursor walk: send pageToken (empty on the first request) and follow nextPageToken. pageSize has a single ceiling of 1000 on all of them, instead of 100 for page-number requests. REST answers 400 naming pageToken when a request still sends page, and gRPC drops the field as unknown. The page-number response fields are removed: page from the four listing responses, and total / totalPages from the user-trades responses, whose table is too large to count. ListOrdersResponse keeps total, totalPages and totalCapped; the conditional-order and TWAP listings keep total, now populated on the cursor path and bounded at 10,000. A request that sends neither page nor pageToken used to read only recent rows; it now starts a cursor walk over recent and archived history, so it can return rows it previously could not reach. The iterateOrders and iterateUserTrades async generators already walk cursors and need no change. @0xmonaco/types drops page from GetPaginatedOrdersParams, ListConditionalOrdersParams, ListTwapOrdersParams and GetUserTradesParams and from their Zod schemas. See Trades.

riskBucketId answers on margin order placement

Margin orders that name a risk bucket by riskBucketId on POST /api/v1/orders, POST /api/v1/orders/batch-create and POST /api/v1/orders/twap (gRPC OrdersService.CreateOrder, BatchCreateOrders, CreateTwapOrder) answer differently:
  • A riskBucketId that belongs to another user or application returns 404 Risk bucket not found (NOT_FOUND), exactly as an unknown id does, whatever tradingPairId or marginAccountId the request carries. Previously it returned 400 riskBucketId does not belong to tradingPairId, 400 riskBucketId does not belong to marginAccountId, or 404 Margin account not found.
  • For your own risk bucket the pair and account checks are unchanged: 400 riskBucketId does not belong to tradingPairId / riskBucketId does not belong to marginAccountId (INVALID_ARGUMENT).
  • A riskBucketId naming one of your risk buckets under a non-default strategyKey — your copy trading risk buckets included — returns 400 riskBucketId names a copy trading risk bucket; copy risk buckets are managed by the engine (INVALID_ARGUMENT). Only the copy engine trades copy trading risk buckets.
Omit riskBucketId, name one of your default-strategy risk buckets, or route strategy-keyed orders by strategyKey to place a user order. See Trade.

Added

Copy trading

sdk.copyTrading adds thirteen methods over CopyTradingService (REST under /api/v1/copy-trading). Public, with no session: listLeadTraders (sort roi, pnl, followers, allocated, win_rate or aum; window 7d, 30d, 90d or all), getLeadTrader and listLeadTraderClosedTrades. Authenticated: listLeadTraderOpenPositions (current followers only), upsertLeadTrader, getMyLeadTrader, listMyFollowers, upsertFollow, previewFollow, stopFollow, listMyFollows, getFollow and listFollowLeaderPositions. upsertLeadTrader and an edit through upsertFollow replace the settings as sent, so an omitted bio or slPct is cleared. previewFollow (POST /api/v1/copy-trading/follows/preview) returns totals only — estimatedMargin, estimatedFee, copiedPositions, skippedPositions and truncated — and spends the order-risk preview budget; upsertFollow accepts copyExisting on a create. getFollow returns copyAccountEquity, the copy account’s marked equity. sdk.ws.copy(handler, onSnapshot?) subscribes to the authenticated follower’s copy channel, carrying copy_relationship_update and copy_skip frames; onSnapshot receives the live follows. React adds useCopyTrading() and useCopyEvents(), and the MCP server adds the matching copy trading tools. Entry and growth — activating a lead trader, creating a follow, increasing an allocation — are gated by a server-side switch and are refused with Copy trading is not available while it is off; stops, pauses and closes are always admitted. See Copy Trading. MarginAccountSummary and PortfolioRiskBucket gain optional copyFollowId and copyLeaderHandle, present only on copy risk bucket rows, and order events gain optional origin (USER, LIQUIDATION, SYSTEM, ADL or COPY; absent means not reported) and copyRelationshipId. realizedPnl on a risk bucket row is the risk bucket’s cash balance, not trading profit. See Margin Accounts.

Order frames name their margin position

orders events carry optional positionId (wire position_id), the margin position the order is bound to — the same id getOrder returns. A margin order placed while its margin account (cross) or risk bucket (isolated) already holds an open position in the pair is bound when it is admitted, so a resting close or reduce-only order names its position before it fills; otherwise its first fill binds it. It is omitted, never null, while the order is unbound, for spot, and by older servers. OrderPlaced adds marginAccountId, leverage and reduceOnly for margin orders, and orders snapshot rows add postOnly, expirationDate, triggerPrice and positionSide, as REST returns them. See WebSockets.

Changed

Movement and funding-payment totals are bounded by pagination reach

sdk.profile.getPaginatedUserMovements() and sdk.profile.listFundingPayments() (GET /api/v1/accounts/movements, GET /api/v1/accounts/funding-payments) return total exact up to pageSize × 10,000 matching rows and saturated there, and totalPages saturated at 10,000, the last page the listing can reach. Wallets under that reach see the same numbers as before; loops that stop at page >= totalPages keep terminating. See Profile.

Per-owner share for idempotency receipts

Create-order requests that carry an idempotency key share receipt capacity per owner: each user and application may hold at most 10,000 receipts (32 MiB of payload) that are not yet safe to evict — responses still awaiting durable acknowledgement or persistence. An owner at its share gets the existing retryable ORDER_SUBMISSION_CAPACITY_EXCEEDED (503 / UNAVAILABLE) for new keys while every other owner keeps placing orders. Retries of an existing key and orders without a key are unaffected, and the share frees up as responses are persisted, expire after 24 hours, or are replaced.

Fixed

Delegated replacements check notional in base-asset units

Delegated sessions using ReplaceOrder (PUT /api/v1/orders/{order_id}) or BatchReplaceOrders (POST /api/v1/orders/batch-replace) without a quantity evaluate maxOrderNotional in base-asset units, inheriting the remaining order quantity (or the original quantity when the remaining one is unavailable). A delegated create or replace whose notional is too large to compute is refused with 403 Delegated agent order notional limit exceeded (PERMISSION_DENIED) instead of failing as a server error. The POST /api/v1/delegated-agents types now mark agentAddress, allowedActions and allowedTradingPairIds as required, matching the server. See Delegated Agents.

Funding history lists only charged windows

GET /api/v1/market/pairs/{trading_pair_id}/funding/history and GET /api/v1/market/funding/history (gRPC MarketService.ListFundingHistory, ListAllFundingHistory) list a settlement only once the risk engine has applied it, so every listed window was charged. A window the engine refuses is never charged later, so history can skip that window’s time span while epochs stay consecutive. Rows written before this release are unchanged.

Upgrade

Code that passes page to getPaginatedOrders, listConditionalOrders, listTwapOrders or getUserTrades, or reads page from their responses or total / totalPages from user trades, no longer type-checks — switch to pageToken / nextPageToken. Every other new field is optional.