Skip to main content

Fill options RFQ quotes

Options RFQs are short quote auctions. A taker opens a window with shared intents + polarity. Makers compete with signed package offers (one absolute package premium). The taker picks an offer and settles on-chain.

The trading app settles that path with mothership executeFixedPackageIntent. Inventory executeIntent is zero-premium only (no per-leg premium). Dutch and Authority fills are separate. See On-chain fill modes. How the resting book relates: CLOB.

Bots should use @opshuns/sdk (../sdk): OpshunsClient.createQuoteSession / startMakerSession, signRfqPackageOffer, placeBid. Guide: TypeScript SDK.

Lifecycle​

Taker: POST /api/quote
→ API broadcasts rfq_request on quotes:<chain_id>:requests
→ Taker joins rfq_stream:<stream_token>

Maker: receives rfq_request (no taker address)
→ builds request + EIP-712 maker_signature
→ POST /api/quote/:id

Taker: receives rfq_offer on private stream
→ selects offer in UI / bot
→ wallet: executeFixedPackageIntent(...)

For Dutch (taker-signed) or FixedPackage / Authority settlement, use the mothership entrypoints in On-chain fill modes instead of (or in addition to) this API auction shape. API-brokered Dutch competition (dutch_only create + both-side bids): API quotes. Dutch. Trading UI premium-range fallback: Trading app. Dutch.

Create quote (taker): reference​

POST /api/quote (no login)

{
"chain_id": 31337,
"taker_address": "0x...",
"intents": [
{
"opshuns6909_contract": "0x...",
"option_type": "call",
"structure": 0,
"start_exercise_timestamp": 0,
"expiry_timestamp": 1785950335,
"strike_wad": "1000000000000000000",
"amount": "1000000000000000000"
}
],
"polarity": "a_is_buy",
"expires_in": 60
}

Response 201 includes:

  • quote: id, directions, taker_address_hash, salts, state…
  • stream_token: secret for the taker stream / GET
  • topic: rfq_stream:<stream_token>
  • events: { "offer": "rfq_offer", "result": "rfq_result" }

Polarity + structure determine per-leg Directions (Buy=0, Sell=1). Those directions, the taker, the salt, and referrer are hashed into taker_address_hash. Makers copy that hash. They never see the raw taker address on the public feed.

Standard POST /api/quote is stored as package_only even if the body says legByLegOnly. Named multi-leg strategies on the trading UI (straddle, butterfly, iron condor, …) expand into multiple intents with mixed structure values. Payloads: API-only option quotes. Payoff notes: Option spreads. App walkthrough: Trading app.

Maker: listen for requests​

  1. POST /api/pubsub/premium with { "chain_id": 31337 }
  2. Join Phoenix topic quotes:31337:requests
  3. Handle event rfq_request

Or with the SDK: client.startMakerSession(chainId, { apiKey, onEvent }) registers pubsub and joins filler + private streams for you.

Filler payload includes intents, taker_address_hash, offer_formats, expiry. not the raw taker address.

Also join rfq_maker:<your_address> (with api_key for bots) for private outcomes on quotes you bid.

Maker: post an offer​

POST /api/quote/:id
Headers: Authorization: Bearer <siwe-or-api-key>

{
"offer_type": "package",
"request": {
"intents": [ /* same intents as the RFQ; API normalizes them to token0/token1 */ ],
"package_premium": {
"token": "0xPremium",
"a_is_buy_b_is_sell_maker_pays_taker": false,
"b_is_buy_a_is_sell_maker_pays_taker": false,
"a_is_buy_b_is_sell": "1000000",
"b_is_buy_a_is_sell": "1000000"
},
"maker_expiry": 4294967295,
"taker_address_hash": "0x…",
"maker_nonce": "12345"
},
"maker_signature": "0x…"
}

package_terms is optional (strategy, custom_tag, maker_package_side). Omitting it signs Custom / tag 0 / no maker wrap. Both premium amounts must be zero or both non-zero. false on a pay flag means the taker pays that polarity.

Inventory leg_by_leg is a separate on-chain shape with no premium fields: intents, maker_expiry, taker_address_hash, maker_nonce. The API’s standard auction does not accept it. Old signatures that included per-leg premiums do not verify.

Requirements​

  • Maker rights (org admin or member key)
  • request.intents must match the open RFQ
  • taker_address_hash must match the RFQ
  • maker_signature must be a valid EIP-712 signature over the mothership digest for that request
  • When verify_offer_signatures is enabled (default outside tests), bad signatures are rejected at the API

See EIP-712 signing. Prefer signRfqPackageOffer from @opshuns/sdk.

Poll / stream as taker​

  • WebSocket: rfq_stream:<stream_token> → rfq_offer, rfq_result
  • HTTP: GET /api/quote/:id?stream_token=… (or X-Quote-Stream-Token)

On-chain settlement​

FixedPackage (standard API path)​

Taker (or their relayer) calls mothership FixedPackageFacet:

executeFixedPackageIntent(
request, // FixedPackageRequest the maker signed
makerSignature,
maker,
directions, // from the quote
takerSalt,
takerPermits, // empty ⇒ ERC-20 allowance pulls
referrer // must match takerAddressHash; address(0) ⇒ no referral
)

Legs fillMatched at premiumTick = 0 after the absolute package premium is pulled. There is no ClobOffer and no per-leg premium tick. Details: On-chain fill modes.

Framed embeds with ?referralAddress=0x… put that wallet on the quote as referrer. The fill must pass the same address. Otherwise use address(0).

Approve the mothership for the package premium and/or underlying. When the quote maker funds a leg, that protocol fee is half the taker rate. Referral is never halved, and package premium has no referral. Rates: Fees.

Other fill modes​

ModeWhen to use
Inventory executeIntentZero-premium leg-by-leg only. No package premium. Not the API auction.
DutchTaker-signed decaying/ramping package premium → maker calls executeDutchIntent
AuthorityDutch / FixedPackage with requiredAuthority != 0 → *WithAuthority + EIP-712 AuthorityApproval

Full tables, digests, and exclusive-fill rules: On-chain fill modes.

Tips​

  • Keep maker_expiry as a future uint32 unix time.
  • Use a unique maker_nonce per offer; reused nonces revert on-chain.
  • Prefer joining the filler feed before high-traffic hours so you do not race the quote TTL.
  • Local fake MM signs package offers with the Anvil maker key against the live mothership digest. Mirror that in production bots.