Call the CLOB directly
Use this guide when you run your own RFQ / matching stack (or no RFQ at all) and want to post options liquidity straight onto an Opshuns pair book.
You talk to OpshunsMothership: not the clone ABI as a primary entrypoint. The mothership pulls ERC-20 collateral, applies protocol (and optional referral) fees, then settles on the pair OpshunsClob.
If you already matched a maker and taker off-chain and need bilateral settlement, see Settle matched intents with your own RFQ instead.
Prefer the sibling @opshuns/sdk (../sdk) for ABIs, opshunsMothership, and native-router helpers. see TypeScript SDK.
When to use this
| Path | Use |
|---|---|
| This guide | Resting bids/asks, market-making on the book, “post to book” without Opshuns API RFQ |
| Settle matched intents with your own RFQ | Off-chain RFQ match → on-chain executeIntent or executeFixedPackageIntent |
| Opshuns API RFQ | Opshuns hosts the quote auction; you only sign / fill |
Architecture
Your system (price, size, PairKey)
│
├─ Resolve pair (CLOB) for (token0, token1, premiumToken)
├─ ERC-20 approve mothership (premium and/or underlying + fees)
└─ Wallet → mothership.orderBuy / orderSell
└─ OpshunsClob: cross book or enqueue remainder
Concept overview of the book: CLOB (OpshunsClob). Fee schedule: Fees.
1. Discover addresses
- Load the network registry:
GET /api/networks: mothership / factory, and often listed payment tokens. - Resolve the pair CLOB for your
(token0, token1, premiumToken)triple (factorygetPair/ create-pair flow). The trading app does this before any post. - Confirm
clob.mothership()matches the mothership you approve against.
Approve the mothership, not only the clone.
With the SDK:
import { OpshunsClient, opshunsMothership, opshunsClob } from "@opshuns/sdk"
const client = new OpshunsClient({ baseUrl: "http://127.0.0.1:4000" })
const networks = await client.listNetworks()
const mothershipAddress = /* networks[…].contracts.mothership / factory */
const ms = opshunsMothership(mothershipAddress, signer)
const core = opshunsClob(clobAddress, signer)
2. Build a PairKey
Every resting order is keyed by option terms:
| Field | Type | Notes |
|---|---|---|
optionType | uint8 | Call / put (OptionType) |
startExerciseTimestamp | uint32 | 0 = American (anytime before expiry); European-style when start equals expiry |
expiryTimestamp | uint32 | Unix seconds |
premiumTick | int32 | 0 = zero premium; else R = ⌊1e18 × 1.0001^tick⌋ |
minNotionalBits | uint8 | Usually 0 for UI posts; mothership may materialize min-notional sentinels |
strikeWad | uint144 | Strike WAD ratio (must be non-zero) |
Prefer mothership resolvePairKey(clob, pairKey) so off-chain ids match on-chain packing (min-notional sentinels, encoding). Do not invent ERC-6909 ids by hand. Pool vs order / short-lot prefixes: ERC-6909 token id namespaces.
3. Approve collateral
| Side | What you post | Token pulled |
|---|---|---|
Buy (orderBuy) | Bid for the option | Premium token, plus maker-post on size that queues, plus referral |
Sell (orderSell) | Offer / write | Underlying, plus maker-post on size that queues, plus referral |
The fill protocol fee is taken later from the posted amount (half rate if that size rested). A full cross pays no maker-post. Powers and who pays: Fees.
IERC20(pullToken).approve(mothership, type(uint256).max);
// or exact allowance ≥ quoted pull total
Permit2 variants (orderBuyPermit2 / orderSellPermit2) exist on the mothership if you already approve Permit2 instead of mothership allowance.
Standard ERC-20 only. no fee-on-transfer or rebasing tokens.
4. Post to the book
// Bid: post premium against the book
mothership.orderBuy(
clob, // pair OpshunsClob
pairKey, // terms including premiumTick and strikeWad
amount, // underlying units of size
to, // beneficiary of inventory / order NFT (usually msg.sender)
false, // postOnly — true reverts with WouldTake if the order would cross
hint, // LadderHint { prevTick, nextTick }; zeros = no hint
referrer // address(0) = no referral fee
);
// Offer: post underlying
mothership.orderSell(
clob,
pairKey,
amount,
to,
false, // postOnly
hint, // LadderHint
referrer
);
What happens on-chain
- Mothership pulls ERC-20 from
msg.sender(fees included). - If
postOnly, the clone reverts withWouldTakewhen the order would immediately take. - Clone tries to cross resting opposite-side liquidity (seeded by
LadderHint/ tick bitmap). - Any remainder is enqueued as an order NFT (
0xF0…buy /0xF1…sell). Matched sell size mints a short-lot NFT (0xF2…). - Min-notional floors apply on CLOB posts (
BelowMinNotionalif too small). Bilateral RFQfillMatchedskips that gate: see Settle matched intents with your own RFQ.
referrer is chosen by the caller and is not part of an RFQ maker signature.
Filled inventory clears premiumTick into one series bucket (ZP and premium fills of the same strike share buyId / filledStates). See token ids.
SDK example
import {
opshunsMothership,
encodeMothershipCall,
opshunsNativeRouter,
ABIS,
ZERO_ADDRESS,
} from "@opshuns/sdk"
const ms = opshunsMothership(mothershipAddress, signer)
const emptyHint = { prevTick: 0, nextTick: 0 }
await ms.orderBuy(clob, pairKey, amount, wallet.address, false, emptyHint, ZERO_ADDRESS)
// or
await ms.orderSell(clob, pairKey, amount, wallet.address, false, emptyHint, ZERO_ADDRESS)
// Optional: pay the pull with native gas via OpshunsNativeRouter
const router = opshunsNativeRouter(nativeRouterAddress, signer)
const data = encodeMothershipCall(ABIS.OpshunsMothership, "orderBuy", [
clob,
pairKey,
amount,
wallet.address, // beneficiary = user
false,
emptyHint,
ZERO_ADDRESS,
])
await router.execute(data, { value: pullAmountWei })
Full SDK map: TypeScript SDK.
5. After you post
Once you hold ERC-6909 inventory or resting order NFTs you can (via mothership / OrdersFacet):
- Claim filled order NFTs (
0xF0…buy /0xF1…sell): mints option tokens into your wallet - Withdraw unfilled resting orders: same
claimBuy/claimSellcall; unlinks the order and refunds the unmatched deposit. Maker-post already sent to the treasury is not returned (legacycancel*was removed from the CLOB) - Exercise longs (
buyId): American vs European window fromstartExerciseTimestamp - Settle shorts with
settleShort(lotId)on the short-lot NFT (0xF2…): not fungiblesellId
// Withdraw an unfilled buy order (refund collateral)
await ms.claimBuy(clob, orderId, wallet.address, ZERO_ADDRESS)
// Withdraw an unfilled sell order
await ms.claimSell(clob, orderId, wallet.address, ZERO_ADDRESS)
// Claim a filled buy order (mint buyId tokens)
await ms.claimBuy(clob, orderId, wallet.address, ZERO_ADDRESS)
Prefer mothership entrypoints so approvals and fees stay consistent. Id namespaces: token ids. Portfolio shapes: Portfolio.
Checklist
- Pair exists and is the CLOB you intend to trade.
PairKeymatches the terms you advertise off-chain (strikeWad+premiumTick; usestrikePriceViewsfor human display).- Allowance covers size, maker-post on any queued remainder, and referral. The fill fee comes out of the posted amount.
- Non-zero
premiumTickis in range and yields meaningful floor/ceil premium at your book size. - You handle partial fills / remainder order NFTs in your indexer; filled inventory is keyed by series (premium cleared).
Related
- TypeScript SDK:
@opshuns/sdkfrom../sdk - Settle matched intents with your own RFQ. your RFQ match →
executeIntentorexecuteFixedPackageIntent - CLOB overview: book vs bilateral
fillMatched - ERC-6909 token id namespaces.
0xF0/0xF1/0xF2prefixes - Orders & exclusive fills: optional REST recording / exclusive-fill bonds
- Trading app: UI “post to book” path for singles