Skip to main content

Monaco Protocol SDK v1.0.37

This is an additive release — no breaking changes. It adds a post-only flag to limit-order placement and replacement across every client. postOnly is a maker-only guarantee, not a new order type: a placement or replacement that would cross the book is rejected instead of matching, so a resting quote never accidentally takes. Existing code keeps working unchanged; the flag is opt-in. The one thing to know before adopting it is how a crossing rejection surfaces — a message match for single orders, a structured error code for batches.

Added

Post-only orders

Set postOnly: true alongside a normal limit placement or replacement to guarantee the order can only add liquidity. If the price would cross the book at admission time (BUY at or above the best ask, or SELL at or below the best bid), Monaco rejects the order instead of letting it take.
  • Exposed on every limit-order surface: sdk.trading.placeLimitOrder, replaceOrder, batchCreate, and batchReplace (@0xmonaco/core), the useTrade() equivalents (@0xmonaco/react), and the REST create_order / batch_create_orders / replace_order / batch_replace_orders endpoints (POST /api/v1/orders, POST /api/v1/orders/batch-create, PUT /api/v1/orders/{order_id}, POST /api/v1/orders/batch-replace) and their gRPC OrdersService equivalents. The MCP place_limit_order and replace_order tools take an optional postOnly too.
  • Maker-only, not a new order type. postOnly composes with LIMIT orders using GTC time in force only. It is rejected on MARKET orders and rejected when combined with IOC or FOK — both are immediate-or-cancel semantics that contradict a maker-only guarantee. It is not available on placeMarketOrder. The TypeScript schemas enforce these combinations client-side on placeLimitOrder and batchCreate so a bad combination fails fast without a round trip — but that client check is for fast feedback only; the server’s own validation is authoritative and always enforced.
  • Crossing rejection. A crossing postOnly order or replacement is rejected with REST 400 Bad Request / gRPC InvalidArgument, message containing "post-only order would cross" (internally logged as POST_ONLY_WOULD_CROSS). For a single placeLimitOrder or replaceOrder, that code is not part of the response body — match on the message text. batchCreate and batchReplace are different: each per-item result carries a structured error.code, set to POST_ONLY_WOULD_CROSS for a post-only crossing rejection — match on code there.
  • Replace semantics. postOnly is re-stated explicitly on every replacement; it is never inherited from the original order. A single replaceOrder validates postOnly before cancelling the original, so a rejected replacement leaves the original resting, untouched. batchReplace is cancel-first: a batch item whose postOnly replacement is rejected for crossing reports the error with its original already cancelled, matching the existing failed-to-place batch-replace contract — refresh your live orders before retrying.
  • Admission-time check only. The crossing predicate is evaluated once, when the order is admitted (placed or replaced) — postOnly is not a perpetual anti-taker guarantee. Once a non-crossing postOnly order is admitted and rests, it behaves like an ordinary resting order of the same time in force: a later book move can trade against it like any other resting order. The flag is not stored on the order and not echoed back in order-detail responses.
See Post-Only Orders for the full crossing predicate and the replace / batch-replace resting-order contract.
Corrections since this release. The original wording is left in place above; three statements have been superseded:
  • “The flag is not stored on the order and not echoed back in order-detail responses” — false since v1.0.38: postOnly is persisted on the order and returned on order detail and list responses, and since v1.0.51 it is also echoed on the order WebSocket events (the placement acknowledgement, taker fills, cancellations, and expiry). Absent means not post-only; the key is never sent as false.
  • “For a single placeLimitOrder or replaceOrder, that code is not part of the response body — match on the message text” — superseded in v1.0.38: a crossing rejection now carries the stable error code POST_ONLY_WOULD_CROSS on every surface (the REST error envelope’s code field, the gRPC google.rpc.ErrorInfo reason, and the batch per-item error.code). Matching the message substring still works but is a legacy fallback.
  • “a batch item whose postOnly replacement is rejected for crossing reports the error with its original already cancelled” — superseded in v1.0.47: the crossing check now runs in the pre-cancel preflight, evaluated against the book as it will stand once the batch’s own cancels are applied, so a refused item ordinarily leaves its original resting with the same order ID and queue position. See Batch Replace for the narrow cases where the original is still lost.

Upgrade

The MCP server ships at 1.0.37 too; it runs via npx -y @0xmonaco/mcp-server, so restarting your assistant picks up the new version and the postOnly tool inputs. Nothing in this release is breaking. One thing worth adopting:
  1. Use postOnly for maker quoting. Set postOnly: true on limit placements and replacements where an accidental take is worse than a rejected order you can immediately re-price — market-maker quoting, or rebuilding quotes right after a market reopens. Handle the crossing rejection by the message for single orders ("post-only order would cross") and by error.code === "POST_ONLY_WOULD_CROSS" for batches, and remember batchReplace cancels first — a rejected item leaves no resting order behind.