Skip to main content

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​

PathUse
This guideResting bids/asks, market-making on the book, “post to book” without Opshuns API RFQ
Settle matched intents with your own RFQOff-chain RFQ match → on-chain executeIntent or executeFixedPackageIntent
Opshuns API RFQOpshuns 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​

  1. Load the network registry: GET /api/networks: mothership / factory, and often listed payment tokens.
  2. Resolve the pair CLOB for your (token0, token1, premiumToken) triple (factory getPair / create-pair flow). The trading app does this before any post.
  3. 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:

FieldTypeNotes
optionTypeuint8Call / put (OptionType)
startExerciseTimestampuint320 = American (anytime before expiry); European-style when start equals expiry
expiryTimestampuint32Unix seconds
premiumTickint320 = zero premium; else R = ⌊1e18 × 1.0001^tick⌋
minNotionalBitsuint8Usually 0 for UI posts; mothership may materialize min-notional sentinels
strikeWaduint144Strike 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​

SideWhat you postToken pulled
Buy (orderBuy)Bid for the optionPremium token, plus maker-post on size that queues, plus referral
Sell (orderSell)Offer / writeUnderlying, 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​

  1. Mothership pulls ERC-20 from msg.sender (fees included).
  2. If postOnly, the clone reverts with WouldTake when the order would immediately take.
  3. Clone tries to cross resting opposite-side liquidity (seeded by LadderHint / tick bitmap).
  4. Any remainder is enqueued as an order NFT (0xF0… buy / 0xF1… sell). Matched sell size mints a short-lot NFT (0xF2…).
  5. Min-notional floors apply on CLOB posts (BelowMinNotional if too small). Bilateral RFQ fillMatched skips 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 / claimSell call; unlinks the order and refunds the unmatched deposit. Maker-post already sent to the treasury is not returned (legacy cancel* was removed from the CLOB)
  • Exercise longs (buyId): American vs European window from startExerciseTimestamp
  • Settle shorts with settleShort(lotId) on the short-lot NFT (0xF2…): not fungible sellId
// 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​

  1. Pair exists and is the CLOB you intend to trade.
  2. PairKey matches the terms you advertise off-chain (strikeWad + premiumTick; use strikePriceViews for human display).
  3. Allowance covers size, maker-post on any queued remainder, and referral. The fill fee comes out of the posted amount.
  4. Non-zero premiumTick is in range and yields meaningful floor/ceil premium at your book size.
  5. You handle partial fills / remainder order NFTs in your indexer; filled inventory is keyed by series (premium cleared).