Monaco Protocol SDK v1.0.53
This release covers the consumer-facing changes since v1.0.51. Three migrations come first: switch sweeper derivation to the factory’sforwarder without exposing a new address for an existing registration until its stored row is migrated, remove selectedTradingPairIds from cross risk-bucket funding, and treat simulated estimatedFee as a signed whole-order estimate that may be absent. Order creation gains durable idempotency keys, stateful WebSocket subscriptions gain typed baseline callbacks, and portfolio margin rows identify their own scope. Order admission, triggered-close event delivery, first-use isolated simulation, and isolated-position backing are also corrected.
Breaking
Sweeper addresses now derive through the factory forwarder
predictSweeperAddress and sweeperCloneInitCode now take the factory’s forwarder address instead of its Sweeper implementation. SweeperAddressInput.implementation is renamed to forwarder, and every derived address changes.
Read the input from factory.forwarder(). Fresh registrations persist and monitor the new address. An existing registration created before v1.0.53 needs an operator-side stored-row migration before you display or fund its new address: calling register returns and monitors the forwarder-derived address in the running resolver, but does not update that row’s stored sweeperAddress, so a resolver restart reloads only the old address. Re-registration alone is not restart-durable. The forwarder is fixed while it resolves the current implementation per call, so later logic upgrades do not move deposit addresses. sweeperImmutableArgs(user, clientId, hubToken?) also gains the optional hub-token input; user-facing deposit addresses use the zero address.
The regenerated SWEEPER_ABI and SWEEPER_FACTORY_ABI include the new multi-chain entrypoints and configuration surface. See Registered Deposit Addresses.
Cross risk-bucket funding no longer declares market scope
FormarginMode: "CROSS", sdk.marginAccounts.transferCollateralToRiskBucket now accepts only { asset, amount, marginMode: "CROSS" }. Remove selectedTradingPairIds: the TypeScript SDK rejects it before sending, and transfer_collateral_to_risk_bucket removes it from the MCP tool.
Cross scope is derived from trading. A pair joins when a cross order on it succeeds. Removal after its positions close is lazy: the pair remains listed until a later successful cross order on a different pair reconciles the bucket, so a funding call has no scope to replace. Read current membership from the cross row returned by listMarginAccounts() or from the transfer response. The raw REST field remains deprecated for compatibility and is ignored in cross mode; the generated gRPC field is deprecated. simulateRiskBucketOrderRisk is unchanged and still requires the pair list in cross mode.
This also lets parent-margin simulation and pair-filtered margin summaries resolve a never-traded pair to the active cross risk bucket. See Margin Accounts.
estimatedFee is a signed whole-order estimate
estimatedFee on all three authenticated order-risk simulation responses now estimates fees for the order’s complete size: taker pricing, including the application taker fee, for the quantity that would cross immediately; maker pricing for the quantity that would rest. A fully crossing order is unchanged.
The field stays an optional string, so this change does not produce a compile error:
- A negative value is a net maker rebate. Do not parse or render it as an unsigned cost.
- Absence means the engine could not price the order. Treat it as unknown, never zero.
- The estimate remains available for reduce-only orders and can be present when
acceptedis false. It assumes a resting remainder eventually fills.
POST /api/v1/margin/accounts/{margin_account_id}/simulate-order-risk, POST /api/v1/margin/parent-margin-account/simulate-order-risk, and POST /api/v1/margin/risk-buckets/simulate-order-risk; the matching gRPC methods are on MarginAccountsService. See Order Risk Simulation.
Added
Durable order-submission idempotency
Single order create and each batch-create item accept an optionalidempotencyKey. For 24 hours after acceptance, retrying the same normalized payload with the same key returns the original committed submission result without executing again, including after the order fills, is cancelled, or the matching engine restarts. Reusing the key with different details returns REST 409 IDEMPOTENCY_KEY_CONFLICT / gRPC ALREADY_EXISTS.
The TypeScript SDK generates a UUID when the key is omitted, including one per batch item. That protects one SDK invocation; a later call receives a new key. Generate, persist, and reuse an explicit key when recovery must survive another call or process restart. clientOrderId remains a separate reusable correlation label and cannot prevent duplicate execution.
Raw REST, generated Rust/Python, and gRPC callers must generate their own keys. The field remains optional during this rollout; requests rejected before acceptance do not reserve it. See Safe Retries with Idempotency Keys.
Typed WebSocket baseline callbacks
Statefulsdk.ws subscriptions accept an optional trailing onSnapshot callback. It receives the subscribe-time baseline as one typed array, separate from the live event handler. Use the array to replace baseline state; reducing it as live deltas can double-apply data. Empty state arrives as onSnapshot([]), malformed arrays are withheld rather than delivered partially, and reconnect resubscription re-arms the callback.
The server currently sends baselines for orders, user orders, balances, conditional orders, positions, and instruments. The other stateful method signatures are ready for future server arms; their callbacks remain inert until those arms ship. Event streams (orderbook, ohlcv, trades, and movements) have no onSnapshot.
@0xmonaco/types adds SnapshotHandler<T> and OrderSnapshotItem. useInstruments, useUserBalances, and useUserOrders now seed and repair state from these baselines, including reconnect omissions and version-ranked order reconciliation. See Subscribe-Time Snapshots.
Portfolio risk-bucket rows identify their scope
EachPortfolioRiskBucket returned by sdk.portfolio.getMargin() now includes required marginMode ("ISOLATED" | "CROSS") and optional tradingPairId, present only for isolated rows. Callers can identify each portfolio row’s mode and isolated-pair scope without another lookup. Cross multi-market membership is not carried by this response; read selectedTradingPairIds from the cross risk-bucket row returned by sdk.marginAccounts.listMarginAccounts().
The response’s positions.cross and positions.isolated counts span all of the user’s margin accounts, while riskBuckets belongs to the oldest parent account. Do not treat grouping the rows by marginMode as a checksum over those counts. See Portfolio Margin.
Changed
Matching-engine calls have bounded server deadlines
The matching-engine’s internal gRPC listener now defaults to 1,024 concurrent handlers and HTTP/2 streams per connection, a 30-second deadline for ordinary RPCs, and a 600-second deadline for batch-order RPCs. State-snapshot capture stays single-flight and has no deadline by default so large proposer bootstraps can finish. For public API callers, an internal deadline expiry is classified as REST503 / gRPC Unavailable. A mutating request has an unknown outcome at that point: submitted sequencer or blocking work may still commit after the response times out. Reconcile state before retrying. For order creation, reuse the same idempotencyKey; never substitute a new key for an unresolved submission. Keyed batchCreate is the exception to the transport status: it returns overall REST 200 / gRPC OK and reports an expired item as results[].error.code === "SERVICE_UNAVAILABLE". The TypeScript SDK keys every item, so inspect every result and retain the original key for any item you retry.
Fixed
Small collateral gaps no longer discard otherwise admissible orders
A bucket-scoped resting maker fill can now cover a collateral shortfall from the parent’s unallocated capacity, capped at the position’sinitialMarginRequired - maintenanceMarginRequired. The resting remainder of a partially crossing taker can fund only deterioration caused by that order since admission; it stops at the same liquidation bound and is also capped by the remaining admission budget — parent capacity for an existing bucket, or the unused fee-reserve allowance for a bucket the order created. Previously a small adverse mark move could silently remove the maker or refuse the taker remainder even when repair was admissible. The tightest cap and liquidation freezes still win; there is no client switch to enable.
Triggered TP/SL closes publish the full execution event tail
A take-profit or stop-loss close now publishes the same orderbook, order, trade, OHLCV, position, and balance updates as an ordinary execution. Previously the close could execute while subscribers received the conditional-order trigger but missed some downstream state updates. Continue subscribing to both conditional-order events and the ordinary state channels: the former confirms the trigger, while the latter carries execution and resulting state. See Conditional Order Lifecycle.First-use isolated order-risk simulation works before a bucket exists
simulateRiskBucketOrderRisk({ marginMode: "ISOLATED", ... }) no longer fails when the trading pair has no isolated risk bucket yet. The engine models the bucket virtually with the collateral a real first order would attach, so equityAfter and freeCollateralAfter describe the bucket that placement would create. An account unknown to the risk engine because it has never been funded still reports margin account not found.
Open isolated positions report their current backing
isolatedMargin on open isolated positions now reports the position’s current backing: allocated risk-bucket collateral adjusted by settled realized PnL and funding, net of collateral reserved for open orders, and clamped at zero. The previous value was stale posted margin after partial closes or partial liquidations. REST reads, gRPC reads, the positions snapshot, and live position_update frames agree while live risk state is available; a persisted fallback is gross of open-order reserves. Cross positions and terminal rows retain their existing meanings. See Positions.
Upgrade
factory.forwarder(). Remove selectedTradingPairIds from cross funding, and update fee displays for negative and absent estimatedFee values. Persist explicit idempotency keys when retries cross calls or restarts, and consume onSnapshot as a replaceable baseline rather than as live events.
