RFQ Flow
How Request for Quote (RFQ) trading works in the Diffusal protocol
The RFQ (Request for Quote) system enables users to trade options against registered RFQ market-maker signers. In the current backend, /api/rfq/request runs a timed auction across connected market makers and returns the winning signed quote, while on-chain settlement still validates the quote signer via PositionManager.isMmm(quote.mmm).
Overview
RFQ trading combines off-chain quoting with on-chain settlement:
| Phase | Location | Description |
|---|---|---|
| Quote Request | Off-chain | User or integrator requests a price through the RFQ service |
| Quote Response | Off-chain | A registered MM signer returns the winning EIP-712 quote |
| Execution | On-chain | User submits the signed quote to the contract |
| Settlement | On-chain | Positions created, premiumBalance updated, and RFQ fee is collected |
Key Benefits:
- Instant execution — No waiting for order book matching
- Professional pricing — Market makers provide competitive, market-aware quotes
- Atomic settlement — Positions created in a single transaction
- Flexible sizing — Partial fills supported
Participants
Registered RFQ Signer (mmm)
The quote signer stored in the mmm field is a privileged address that:
| Capability | Description |
|---|---|
| Quote provision | Responds to RFQ auctions or direct off-chain requests |
| Signature authority | Signs EIP-712 quotes |
| Position taking | Takes the opposite side of user trades |
| Liquidation immunity | Cannot be liquidated by the protocol while marked isMmm |
The PositionManager can authorize multiple market-maker signer addresses. Each returned quote still names exactly one signer in quote.mmm, and the contract checks that signer with isMmm during execution.
User (Quote Taker)
The user:
- Requests quotes via the RFQ API auction or another off-chain MM channel
- Receives a signed quote with price, size, and expiry
- Submits the quote on-chain to execute the trade
- optionBalance and premiumBalance updated for both parties
- Receives a long or short position — settlement at expiry determines actual cash flow
Quote Structure
An RFQ quote contains all parameters needed for on-chain execution:
Quote Fields
| Field | Description |
|---|---|
mmm | Registered market-maker signer address |
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 signer from accidentally quoting the wrong series.
Execution Flow
High-Level Flow
Off-Chain Phase:
- User requests a quote through
/api/rfq/request(e.g., "I want to buy 10 ETH-USD calls, strike $3000, Dec 31") - The RFQ service opens a short auction and connected market makers compete on price
- The winning signer creates the quote (e.g., $150 per contract, size = 10), signs it with EIP-712, and returns it to the user
On-Chain Phase: 4. User submits the quote to the RFQ contract 5. Contract validates: signature is from a registered isMmm signer, quote not expired, series not expired, nonce matches that signer'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), delegates fill accounting to DiffusalRfqExecutor, 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 |
| MM registration | Signer must be a registered isMmm address |
| 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 the signer'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:
Taker Buys Long (takerIsBuying = true)
The responding RFQ market maker creates a pair, gives the long position to the taker, and keeps the short. Premium balances are updated for both parties.
After trade (Four-Instrument Model):
- Taker: +optionBalance (long), -premiumBalance (payer)
- RFQ market maker: -optionBalance (short), +premiumBalance (receiver)
- No premium changes hands (only fees; settlement at expiry determines actual P&L)
Taker Buys Short (takerIsBuying = false)
The responding RFQ market maker creates a pair, gives the short position to the taker, and keeps the long. Premium balances are updated for both parties.
After trade (Four-Instrument Model):
- Taker: -optionBalance (short), +premiumBalance (receiver)
- RFQ market maker: +optionBalance (long), -premiumBalance (payer)
- No premium changes hands (only fees; settlement at expiry determines actual P&L)
Trade Examples
Example 1: User Buys Calls
User wants to buy 10 ETH-USD $3000 calls at $150 premium each. Before the trade, the user has no position and $2,000 USDT deposited; the responding RFQ market maker has no position and $50,000 USDT deposited.
After the trade (takerIsBuying = true, fillAmount = 10):
- User: optionBalance = +10 (long), premiumBalance = -$1,500 (payer)
- RFQ market maker: optionBalance = -10 (short), premiumBalance = +$1,500 (receiver)
- Deposits unchanged: User still has $2,000, RFQ market maker still has $50,000
Example 2: User Sells Puts
User wants to sell 5 ETH-USD $3000 puts at $200 premium each (user takes short). Before the trade, the user has no position and $5,000 USDT deposited; the responding RFQ market maker has no position and $50,000 USDT deposited.
After the trade (takerIsBuying = false, fillAmount = 5):
- User: optionBalance = -5 (short), premiumBalance = +$1,000 (receiver)
- RFQ market maker: optionBalance = +5 (long), premiumBalance = -$1,000 (payer)
- Deposits unchanged: User still has $5,000, RFQ market maker still has $50,000
Premium Balance Recording
No premium changes hands at trade time—only fees are transferred. Premium balances are updated:
| Direction | Taker Gets | RFQ Market Maker Gets |
|---|---|---|
takerIsBuying = true | Long, -premiumDelta (payer) | Short, +premiumDelta (receiver) |
takerIsBuying = false | Short, +premiumDelta (receiver) | Long, -premiumDelta (payer) |
At expiry, positions settle using the net settlement formula.
Fee Structure
RFQ trades have a dedicated fee rate. See Fee Structure for RFQ fee calculation and flow.
Nonce Management
Each registered MM signer has an independent nonce counter for replay protection.
How Nonces Work
| Property | Description |
|---|---|
| Per-signer counter | Each registered signer has its own nonce |
| Quote validation | Quote nonce must match the current signer 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 signer |
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 when a quote is filled, it's created automatically. See Options Creation for validation rules and the creation flow.
Security Considerations
Signature Replay Protection
| Protection | Mechanism |
|---|---|
| Nonce | Quote nonce must match the signer's current nonce |
| Fill tracking | quoteFilled[quoteHash] prevents double-filling |
| Expiry | Quotes have limited validity via quoteExpiry |
Signer Verification
- Only registered MM signers can have their quotes executed
- Signature must match the
mmmaddress in the quote - MM 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 market makers to provide exclusive quotes to specific users.
Premium Balance Direction
The takerIsBuying flag creates unambiguous premium balance direction:
- Buyer gets negative premiumBalance (payer)
- Seller gets positive premiumBalance (receiver)
- No ambiguity about settlement obligations
Contract Integration
Storage
| Storage | Purpose |
|---|---|
mmmNonce[mmm] | Replay protection per signer |
quoteFilled[quoteHash] | Partial fill tracking |
rfqFeeBps | Fee configuration |
Dependencies
The RFQ flow integrates with:
- DiffusalOptionsRFQ: For EIP-712 quote validation, taker authorization, nonce accounting, and config
- DiffusalRfqExecutor: For series creation, portfolio routing, position updates, collateral transfers, fees, and post-trade checks
- DiffusalOptionsPositionManager: For signer authorization and position state
- DiffusalCollateralVault: For collateral transfers driven by the executor
Summary
| Component | Description |
|---|---|
| Quote | EIP-712 signed message from a registered MM signer with price, size, and series params |
| Execution | RFQ validation + delegated execution through DiffusalRfqExecutor |
| Direction | takerIsBuying=true: user gets long; false: user gets short |
| Premium Balance | Updated at trade time; only fees transferred (premium settles at expiry) |
| Fee | RFQ fee collected from taker, sent to feeCollector (a dedicated protocol wallet EOA) |
| Nonce | Per-signer 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 — Registered market makers 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 | Quote validation, taker authorization, nonce accounting |
| DiffusalRfqExecutor | Series creation, routing, fills, fees, and margin checks |
| DiffusalOptionsPositionManager | Registered signer authorization and position storage |
| DiffusalOptionsSeriesRegistry | Lazy series registration and validation |
| DiffusalCollateralVault | Collateral for margin requirements |
Related
Protocol Documentation
- 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
- Fees — RFQ fee structure
Contract Documentation
- DiffusalOptionsRFQ — Core RFQ logic and EIP-712 validation
- DiffusalOptionsPositionManager — Registered signer authorization and position creation
- DiffusalOptionsSeriesRegistry — Lazy series registration and validation
- DiffusalCollateralVault — Collateral for margin requirements