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 leg_by_leg (or package) offers. The taker picks an offer and settles on-chain.

The trading app today settles the common path with mothership executeIntent (maker-signed leg-by-leg FillMatched, optional CLOB book takes). On-chain there are also FixedPackage, Dutch auction, and Authority-gated fills — see On-chain fill modes. How the resting book relates: CLOB.

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: executeIntent(...) // leg-by-leg FillMatched

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_price": "1000000000000000000",
"amount": "1000000000000000000"
}
],
"polarity": "a_is_buy",
"expires_in": 60,
"legByLegOnly": true
}

Response 201 includes:

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

Polarity + structure determine per-leg Directions (Buy=0, Sell=1). Those directions are hashed into taker_address_hash with the taker address and salt — makers must copy taker_address_hash into the signed request exactly.

Named multi-leg strategies on the trading UI (straddle, butterfly, iron condor, …) expand into multiple intents with mixed structure values. The quote box currently sets legByLegOnly: true. For the exact per-strategy payloads and how polarity + structure are hashed with the taker address, see 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

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": "leg_by_leg",
"request": {
"intents": [ /* same intents as the RFQ */ ],
"premiums": [
{
"buy": { "premium_rate_bps": 150, "clob_offers": [] },
"sell": { "premium_rate_bps": 200, "clob_offers": [] }
}
],
"maker_expiry": 4294967295,
"taker_address_hash": "0x…",
"maker_nonce": "12345"
},
"maker_signature": "0x…"
}

For package offers, replace premiums with package_premium (token + A/B amounts and pay flags).

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.

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

Leg-by-leg FillMatched (standard)

Taker (or their relayer) calls mothership RfqFacet:

executeIntent(
request, // OpshunIntentRequestLegByLeg
makerSignature,
maker,
directions, // from the quote
takerSalt,
takerPermits, // empty ⇒ ERC-20 allowance pulls
referrer // address(0) ⇒ no referral fee
)

If the maker offer included clob_offers, the mothership takes that book size first, then fillMatched any remainder at the quoted bps. Details: CLOB, On-chain fill modes.

Framed embeds with ?referralAddress=0x… pass that wallet as referrer; otherwise use address(0).

The taker must approve the mothership for premium (and/or underlying, depending on directions) via ERC-20 transferFrom and/or Permit2. A non-zero referrer may pull a little extra on maker-paid legs on top of protocol fees.

Other fill modes

ModeWhen to use
FixedPackageMaker-signed multi-leg with one absolute package premium → executeFixedPackageIntent
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 with the Anvil maker key against the live mothership digest — mirror that in production bots.
  • Optional clob_offers must fill exactly on-chain or the whole intent reverts.