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 / GETtopic: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
POST /api/pubsub/premiumwith{ "chain_id": 31337 }- Join Phoenix topic
quotes:31337:requests - 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.intentsmust match the open RFQtaker_address_hashmust match the RFQmaker_signaturemust be a valid EIP-712 signature over the mothership digest for that request- When
verify_offer_signaturesis 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=…(orX-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
| Mode | When to use |
|---|---|
Inventory executeIntent | Zero-premium leg-by-leg only. No package premium. Not the API auction. |
| Dutch | Taker-signed decaying/ramping package premium → maker calls executeDutchIntent |
| Authority | Dutch / FixedPackage with requiredAuthority != 0 → *WithAuthority + EIP-712 AuthorityApproval |
Full tables, digests, and exclusive-fill rules: On-chain fill modes.
Tips
- Keep
maker_expiryas a futureuint32unix time. - Use a unique
maker_nonceper 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.