Market Makers
Integration guide for market makers on Aftermath Perpetuals — the requote loop, wire formats, live data, and risk controls.
There is no designated market maker program, special rebates, or latency advantages. Anyone is welcome to market make on Aftermath Perpetuals.
For technical integration questions, join our Discord or message us on X.
API Base
API prefix
https://aftermath.finance/api/...
Swagger UI
OpenAPI spec
Agent skills
The OpenAPI spec is the source of truth for field names and types. Generate your types from it rather than hand-writing request bodies.
Recommended Integration
We recommend using the native Perpetuals REST endpoints (/api/perpetuals/account/transactions/*) rather than the CCXT layer. The native endpoints give you full control over gas management and access to Sui-native features like Programmable Transaction Blocks (PTBs).
All native endpoints return a TxKindResponse containing a base64-encoded TransactionKind. You decode it, wrap it in a full Transaction, sign with your wallet, and submit via your Sui client.
Key Endpoints
Cancel and Place Orders (POST /api/perpetuals/account/transactions/cancel-and-place-orders)
This is the primary endpoint for market makers. It atomically cancels existing orders and places new ones in a single PTB, which is significantly cheaper on gas than separate cancel and place transactions. There is no intermediate state between the two operations, so your book depth never drops to zero mid-requote. If you are refreshing quotes or managing order grids, this is the endpoint to use.
Place Limit Order (POST /api/perpetuals/account/transactions/place-limit-order)
For individual order placement. Supports inline SL/TP attachment in the same transaction.
Place Market Order (POST /api/perpetuals/account/transactions/place-market-order)
For immediate execution at market price. Also supports inline SL/TP.
Place Scale Order (POST /api/perpetuals/account/transactions/place-scale-order)
Distributes size across a price range in a single transaction. Useful for DCA-style entries, liquidity ladders, or spreading risk across multiple price levels.
TWAP Orders (POST /api/perpetuals/account/transactions/create-twap-orders, edit-twap-orders, cancel-twap-orders)
Schedules execution of a size over time. Read scheduled and processed amounts back from POST /api/perpetuals/account/twap-order-datas. Useful for unwinding accumulated inventory without paying the spread all at once.
The Requote Loop
cancel-and-place-orders is the endpoint you will call thousands of times a day, so it is worth knowing every field.
accountId
Numeric account ID, sent as a BigInt string ("123n"). Use vaultId instead when quoting from a vault.
accountCapId
Optional object ID of the account capability authorizing the transaction.
walletAddress
Required. Receives any refunded gas coins.
marketId
Required. The clearing house being quoted.
orderIdsToCancel
Chain-assigned order IDs, as BigInt strings. Omit to cancel nothing.
clientOrderIdsToCancel
Cancel by your own IDs instead of chain IDs. Can be combined with orderIdsToCancel.
shouldAbortOnMissingId
Defaults to false. See below — this default is what you want.
ordersToPlace
Array of { side, price, size, clientOrderId? }. Omit to place nothing.
orderType
Execution type for the whole batch. See the table below.
reduceOnly
If true, placed orders can never increase position size.
hasPosition
Required. Whether the account currently holds a position in this market.
expiryTimestamp
Optional ms-epoch expiry for the placed orders.
leverage
Optional leverage override for the placed orders.
shouldDeallocateFreeCollateral
If true, sweeps margin not backing a position back to the wallet in the same transaction.
sponsor
{ walletAddress, bytes, signature, gasBudget? } for GasPool sponsorship. bytes is base64 of Aftermath Terms and Conditions; gasBudget is in MIST.
builderCode
{ integratorId, integratorFee } — only relevant if you route flow as an integrator.
txKind
An existing base64 TransactionKind to extend, so you can compose the requote into a larger PTB of your own.
Order Types
orderType applies to every order in ordersToPlace:
0
GTC
Rests on the book until filled or cancelled.
1
FOK
Must fill entirely in one match, or the whole order is cancelled.
2
Post-Only
Added to the book only if it would not immediately match. Rejected if it would take liquidity.
3
IOC
Fills what it can immediately, cancels the remainder.
Market makers should quote with 2. It guarantees you never cross and never pay a taker fee.
Client Order IDs
Every placed order accepts an optional clientOrderId — a u64 you choose, sent as a BigInt string. Tag your quotes with your own IDs and you can cancel them via clientOrderIdsToCancel without ever reading the chain-assigned ID back.
This removes a round trip from the requote loop. Instead of place → read back order IDs → cancel by chain ID → place again, you keep quoting against IDs your strategy already knows. Client order IDs also come back on pendingOrders in position state and on the order streams, so you can reconcile fills against your own book directly.
clientOrderId is accepted on place-limit-order and, as clientOrderIds, on place-scale-order.
Missing IDs Are Not an Error by Default
An order you are about to cancel may fill or expire between your decision and your transaction landing. With shouldAbortOnMissingId: false (the default) the transaction tolerates the missing ID: the remaining cancels and all of your new orders still go through.
Set it to true only if a partially-applied requote is worse for you than no requote at all. For most quoting strategies it is not — an aborted transaction leaves stale quotes on the book, which is the exact situation you were trying to fix.
Example Request
side is 0 for bid and 1 for ask.
Wire Format Rules
These four rules account for most failed first integrations.
BigInt fields need the trailing n. Native BigInt fields are JSON strings with an n suffix — "123n". Plain 123 and "123" both fail. Responses come back the same way. Not every number is a BigInt: ordinary counters and millisecond timestamps stay plain JSON numbers, so follow each endpoint's schema rather than converting globally.
accountId is numeric, not an object ID. Native endpoints take the numeric account ID. CCXT write endpoints take an account capability object ID (0x...) under the same name, and CCXT read endpoints use accountNumber. Passing a 0x... capability ID where a numeric accountId belongs is the single most common integration failure.
Preview responses are success-or-error unions. /api/perpetuals/account/previews/* can return HTTP 200 with a body of { "error": "..." } and the header X-Error-Message: true. Check for the error shape before reading the success fields — a 200 alone does not mean the preview succeeded.
Responses are deterministically ordered. Markets sort by symbol, positions by market ID, and pending bids and asks each sort by order ID. You can diff successive account snapshots positionally instead of rebuilding a map on every poll.
Real-Time Market Data
All live streams — orderbook, oracle, market state, account updates, and candles — multiplex over a single WebSocket:
wss://aftermath.finance/api/perpetuals/ws/updates
You subscribe per stream by sending a JSON frame after open:
The streams a quoting bot usually wants:
topOfOrderbook
Bucketed top-N snapshots. Each frame is a complete snapshot, so no REST seed and no delta bookkeeping.
orderbook
Full depth deltas, when bucketed top-of-book is not enough.
oracle
Oracle price updates for the market.
subscribeUserOrders
SDK helper for account order updates, including clientOrderId.
subscribeUserCollateralChanges
SDK helper for account collateral movements.
marketCandles
OHLCV updates at 1m … 1mo.
Full frame schemas, error frames, and client examples are on the WebSockets page.
On reconnect, resync before you resume. Re-fetch snapshots, replace local state atomically, then resume applying deltas. Never continue from in-memory state that predates a disconnect.
If you prefer server-sent events, SSE streams are available under /api/ccxt/stream/*.
Risk Controls
Check size before you send it. POST /api/perpetuals/account/max-order-size returns the largest order the account can place given current risk limits, for a given marketId and side (0 bid, 1 ask). Call it before scaling up quote size or changing leverage rather than discovering the limit through a failed transaction.
There is no dead man's switch. The API has no scheduled-cancel or auto-expiry-on-disconnect endpoint. If your process dies, your quotes stay on the book. Build a heartbeat kill switch that cancels all open orders when your strategy loop stalls, and wire it to SIGINT/SIGTERM as well as to the heartbeat timeout. Verify the cancels landed before you exit.
Account state goes stale immediately. After any fill, cancel, deposit, withdrawal, or leverage change, refresh account and position state before computing the next quote.
Serialize anything that touches the same coin object. Concurrent signed transactions can race on the same USDC coin or gas coin and fail with version or equivocation errors. Serialize collateral operations, and give parallel submitters their own gas coins.
Gas Optimization
The biggest gas savings come from how you structure your transactions. Using cancel-and-place-orders atomically avoids paying for separate transactions and removes the gap between cancelling stale quotes and placing replacements.
Inefficient: Separate Transactions
Efficient: Atomic Cancel-and-Place with Batching
In a sample of 17 successful mainnet transactions, cancel-and-place PTBs with two new limit orders used roughly 0.0006–0.0007 SUI total (about 0.0003 SUI per new order). Treat this as an example rather than a fixed fee: net gas is computation plus storage cost minus the storage rebate, and it changes with the PTB's contents and the reference gas price.
Tips
Always use
cancel-and-place-orders— never send cancel and place as separate transactions.Batch 5+ orders per call — the
ordersToPlacearray accepts multiple orders. More orders per tx = lower gas per order.Quote both sides in one tx — place bids and asks in the same
ordersToPlacearray.Use Post-Only (
orderType: 2) — guarantees maker execution and avoids taker fees.Tag orders with
clientOrderId— cancel by your own IDs and skip the read-back round trip.Use agent wallets — delegate signing to an agent wallet via
grant-agent-walletso your main key stays cold. Agent wallets can trade but cannot withdraw collateral.Use gas pool sponsorship — pre-fund a gas pool (
/api/gas-pool/*) and pass the sponsor wallet plus its reusable terms signature for predictable gas costs.Compose with
txKind— pass an existingTransactionKindto fold the requote into a larger PTB instead of sending two transactions.Don't add your own oracle updates — the API handles oracle freshness internally.
Don't overpay gas to jump the queue — priority-gas transactions carry an extra taker fee (
priorityTakerFeein market params) and are rejected outright when that parameter is unset. See Reference Gas Price.
CCXT
If your firm already runs on CCXT infrastructure, we support the CCXT standard interface as well. See the CCXT Docs. Note that CCXT endpoints return transactionBytes and signingDigest rather than raw TransactionKind, so you have less control over gas and PTB composition. Sign the signingDigest, not the transactionBytes, and submit signatures[] — when sender and gas owner differ, both signatures go in that array.
CCXT's OrderRequest is also narrower than the native surface: timeInForce and postOnly are not supported by /api/ccxt/build/createOrders and are ignored if sent. If post-only quoting matters to you — and for a market maker it should — use the native endpoints.
For full API reference, see the Swagger documentation.
Last updated