RFQ Flow
How Request for Quote (RFQ) trading works in the Diffusal protocol
The RFQ (Request for Quote) system enables users to trade options directly with the Main Market Maker (MMM). Unlike the limit order book where users trade against each other, RFQ provides instant execution at prices quoted by professional market makers.
Overview
RFQ trading combines off-chain quoting with on-chain settlement:
| Phase | Location | Description |
|---|---|---|
| Quote Request | Off-chain | User requests a price from the MMM |
| Quote Response | Off-chain | MMM signs and sends a quote with EIP-712 |
| Execution | On-chain | User submits the signed quote to the contract |
| Settlement | On-chain | Positions are minted, premium is transferred |
Key Benefits:
- Instant execution — No waiting for order book matching
- Professional pricing — MMMs provide competitive, market-aware quotes
- Atomic settlement — Positions created in a single transaction
- Flexible sizing — Partial fills supported
Participants
Main Market Maker (MMM)
The MMM is a privileged address that:
| Capability | Description |
|---|---|
| Quote provision | Provides prices to users off-chain |
| Signature authority | Signs EIP-712 quotes |
| Position taking | Takes the opposite side of user trades |
| Liquidation immunity | Cannot be liquidated by the protocol |
The MMM is registered by the protocol owner and can be queried via the PositionManager.
User (Quote Taker)
The user:
- Requests quotes from MMM off-chain (via API, chat, etc.)
- Receives a signed quote with price, size, and expiry
- Submits the quote on-chain to execute the trade
- Pays or receives premium based on trade direction
- Receives a long or short position
Quote Structure
An RFQ quote contains all parameters needed for on-chain execution:
Quote Fields
| Field | Description |
|---|---|
mmm | Main Market Maker address (quote signer) |
taker | Authorized taker address (or zero for anyone) |
seriesId | Option series identifier |
takerIsBuying | Direction flag: true = taker buys long |
price | Premium per contract in WAD (1e18 = $1.00) |
size | Maximum contracts available |
nonce | Replay protection counter |
quoteExpiry | Quote validity deadline (unix timestamp) |
Series Parameters
The quote also includes full series parameters for lazy registration:
| Field | Description |
|---|---|
pairId | Trading pair (e.g., ETH-USD) |
strike | Strike price in WAD |
optionExpiry | Option expiration timestamp |
isCall | true for call, false for put |
Series ID Verification:
The contract verifies that the provided seriesId matches the hash of the series parameters. This prevents the MMM from accidentally signing for the wrong series.
Execution Flow
High-Level Flow
Off-Chain Phase:
- User requests a quote from the MMM (e.g., "I want to buy 10 ETH-USD calls, strike $3000, Dec 31")
- MMM calculates the price, creates a quote (e.g., $150 per contract, size = 10), and signs it with EIP-712
- MMM sends the signed quote to the user
On-Chain Phase: 4. User submits the quote to the RFQ contract 5. Contract validates: signature is from registered MMM, quote not expired, series not expired, nonce matches MMM's current nonce, fill amount ≤ remaining quote size, and taker is authorized (or quote allows anyone) 6. Contract executes: registers series if new (lazy creation), mints positions (long to buyer, short to seller), transfers premium from buyer to seller, and collects RFQ fee from taker 7. Emits event with fill details
Validation Steps
The contract performs these checks before executing any trade:
| Check | Requirement |
|---|---|
| Quote expiry | block.timestamp < quoteExpiry |
| Option expiry | block.timestamp < optionExpiry |
| MMM registration | Signer must be a registered MMM |
| Signature validity | EIP-712 signature must match quote hash |
| Taker authorization | Caller must be authorized (or quote is open) |
| Nonce validity | Quote nonce must match MMM's current nonce |
| Fill capacity | Requested amount must not exceed remaining |
| Series ID match | seriesId == hash(pairId, strike, optionExpiry, isCall) |
Trade Direction
The takerIsBuying flag determines who gets which position and who pays:
Taker Buys Long (takerIsBuying = true)
The MMM mints a pair, gives the long position to the taker and keeps the short. The taker pays the premium to the MMM.
After trade:
- Taker: holds long position, paid premium
- MMM: holds short position, received premium
Taker Buys Short (takerIsBuying = false)
The MMM mints a pair, gives the short position to the taker and keeps the long. The MMM pays the premium to the taker (compensating them for taking on the obligation).
After trade:
- Taker: holds short position, received premium
- MMM: holds long position, paid premium
Trade Examples
Example 1: User Buys Calls
User wants to buy 10 ETH-USD 150 premium each. Before the trade, the user has no position and 50,000 USDC.
After the trade (takerIsBuying = true, fillAmount = 10):
- User receives +10 position (long), MMM receives -10 position (short)
- Premium: 10 × 1,500 flows from User to MMM
- After: User has 51,500 USDC
Example 2: User Sells Puts
User wants to sell 5 ETH-USD 200 premium each (user takes short, receives premium). Before the trade, the user has no position and 50,000 USDC.
After the trade (takerIsBuying = false, fillAmount = 5):
- User receives -5 position (short), MMM receives +5 position (long)
- Premium: 5 × 1,000 flows from MMM to User
- After: User has 49,000 USDC
Premium Calculation
The premium transferred is calculated from the quote price and fill amount:
Buyer always pays seller:
| Direction | Buyer | Seller | Premium Flow |
|---|---|---|---|
takerIsBuying = true | Taker | MMM | Taker → MMM |
takerIsBuying = false | MMM | Taker | MMM → Taker |
Fee Structure
RFQ trades have a dedicated fee rate separate from limit order fees.
Fee Calculation
where rfqFeeBps is the RFQ fee rate in basis points (1 BPS = 0.01%).
Fee Flow
The premium flows from buyer to seller. Additionally, the RFQ fee is always paid by the taker to the feeRecipient, regardless of trade direction.
Nonce Management
Each MMM has an independent nonce counter for replay protection.
How Nonces Work
| Property | Description |
|---|---|
| Per-MMM counter | Each MMM has its own nonce |
| Quote validation | Quote nonce must match current MMM nonce |
| Increment effect | Incrementing nonce invalidates ALL pending quotes |
Quote Cancellation
Individual quotes cannot be explicitly cancelled. To invalidate quotes:
| Method | Effect |
|---|---|
| Wait for expiry | Quote has a quoteExpiry timestamp |
| Increment nonce | Invalidates ALL pending quotes from that MMM |
Partial Fills
Quotes support partial fills—the taker can fill less than the full quote size.
Fill Tracking
| State | Description |
|---|---|
quoteFilled[quoteHash] | Amount already filled |
| Remaining | quote.size - quoteFilled[quoteHash] |
A quote can be filled multiple times by the same or different takers until fully filled.
Lazy Series Registration
RFQ supports lazy series registration—if the series doesn't exist, it's created automatically.
When filling an RFQ quote, the contract first checks if the series exists. If yes, it proceeds with the fill. If no, it validates the series parameters (verifying the hash matches seriesId, pairId is registered, optionExpiry is in the future, and strike > 0), then registers the series and proceeds with the fill.
This enables trading of new strikes/expiries without upfront admin setup.
Security Considerations
Signature Replay Protection
| Protection | Mechanism |
|---|---|
| Nonce | Quote nonce must match MMM's current nonce |
| Fill tracking | quoteFilled[quoteHash] prevents double-filling |
| Expiry | Quotes have limited validity via quoteExpiry |
MMM Verification
- Only registered MMMs can have their quotes executed
- Signature must match the
mmmaddress in the quote - MMM status is checked on every fill
Taker Restriction
| Quote Configuration | Who Can Fill |
|---|---|
quote.taker = address(0) | Anyone |
quote.taker = specificAddress | Only that address |
This allows MMMs to provide exclusive quotes to specific users.
Premium Direction
The takerIsBuying flag creates unambiguous payment direction:
- Buyer always pays premium
- Seller always receives premium
- No ambiguity about who pays whom
Contract Integration
Storage
| Storage | Purpose |
|---|---|
mmmNonce[mmm] | Replay protection per MMM |
quoteFilled[quoteHash] | Partial fill tracking |
rfqFeeBps | Fee configuration |
Dependencies
The DiffusalOptionsRFQ contract integrates with:
- DiffusalOptionsPositionManager: For
isMMM(address)to verify MMM registration, andupdatePosition()to mint long/short positions - IERC20 (Collateral Token): For premium transfers between buyer and seller, and fee collection to feeRecipient
Summary
| Component | Description |
|---|---|
| Quote | EIP-712 signed message from MMM with price, size, and series params |
| Execution | On-chain validation + position minting + premium transfer |
| Direction | takerIsBuying=true: user gets long; false: user gets short |
| Premium | Buyer pays seller; calculated as price × fillAmount |
| Fee | RFQ fee collected from taker, sent to feeRecipient |
| Nonce | Per-MMM counter; increment to invalidate all pending quotes |
| Partial fills | Supported via fill amount tracking per quote hash |
The RFQ system provides:
- Instant execution — No need to wait for order book matching
- Professional pricing — MMMs provide competitive quotes
- Atomic settlement — Positions created in a single transaction
- Flexible sizing — Partial fills supported via
fillAmount - Security — EIP-712 signatures, nonce protection, taker restrictions
Contract Implementation
| Contract | Role |
|---|---|
| DiffusalOptionsRFQ | Core RFQ logic, EIP-712 validation, and fee collection |
| DiffusalOptionsPositionManager | MMM registration and position minting |
| DiffusalOptionsSeriesRegistry | Lazy series registration and validation |
| DiffusalCollateralVault | Collateral for margin requirements |
Related
- Order Book — Peer-to-peer limit order trading where anyone can be a maker
- Options Creation — How series are lazily registered on first trade
- Margin System — Collateral requirements for positions