DiffusalOptionsRFQ
On-chain settlement for EIP-712 signed RFQ quotes from Main Market Makers
The DiffusalOptionsRFQ contract enables Request-for-Quote trading between users and Main Market Makers (MMMs). Users request quotes off-chain from MMMs, who sign EIP-712 quotes that users then submit on-chain to execute trades.
Overview
RFQ trading provides an alternative to the order book, offering:
| Feature | Description |
|---|---|
| Professional Pricing | MMMs provide tight spreads and deep liquidity |
| Instant Execution | No order matching needed—quotes are pre-agreed |
| Large Orders | Better for size without moving the market |
| Partial Fills | Quotes can be filled incrementally |
RFQ vs Order Book
| Aspect | RFQ | Order Book |
|---|---|---|
| Counterparty | Main Market Maker only | Any user |
| Price Discovery | MMM quotes | Market-driven bids/asks |
| Order Creation | MMM signs quote off-chain | Maker signs order off-chain |
| Fees | RFQ fee (taker only) | Maker + taker fees |
| Best For | Large orders, professional pricing | Price discovery, liquidity provision |
Key Concepts
Trade Direction
The takerIsBuying flag determines position assignment:
takerIsBuying | Taker Gets | MMM Gets | Premium Flow |
|---|---|---|---|
true | Long (+size) | Short (-size) | Taker → MMM |
false | Short (-size) | Long (+size) | MMM → Taker |
Main Market Maker (MMM)
Only addresses registered as MMMs in the PositionManager can sign RFQ quotes:
if (!POSITION_MANAGER.isMmm(quote.mmm)) revert Errors.NotMmm();MMMs have special privileges:
- Cannot be liquidated (non-liquidatable status)
- Can sign RFQ quotes
- Can increment their nonce to invalidate all quotes
Nonce Management
Each MMM has an independent nonce:
- Quote nonce must match MMM's current nonce
incrementMmmNonce()invalidates all pending quotes- Unlike order book, only registered MMMs can increment their nonce
Storage & State
/// @custom:storage-location erc7201:diffusal.storage.DiffusalOptionsRFQ
struct DiffusalOptionsRfqStorage {
address owner;
mapping(address => uint256) mmmNonce; // MMM → current nonce
mapping(bytes32 => uint256) quoteFilled; // Quote hash → filled amount
uint256 rfqFeeBps; // RFQ fee rate
address feeRecipient; // Fee collector
address collateralVault; // Vault for margin checks
}RFQ Quote Struct
struct RfqQuote {
address mmm; // Main Market Maker address
address taker; // Authorized taker (address(0) = any)
bytes32 seriesId; // Option series identifier
bool takerIsBuying; // true = taker buys long, false = taker buys short
uint256 price; // Premium per contract (WAD)
uint256 size; // Maximum number of contracts
uint256 nonce; // Must match MMM's current nonce
uint256 quoteExpiry; // Quote validity deadline
bytes32 pairId; // Trading pair for series validation
uint256 strike; // Strike price for series validation
uint256 optionExpiry; // Option expiry for series validation
bool isCall; // Option type for series validation
}External Functions
Quote Filling
fillRfqQuote
Fills a single RFQ quote.
function fillRfqQuote(
RFQTypes.RfqQuote calldata quote,
bytes calldata signature,
uint256 fillAmount
) external returns (RfqFillResult memory result)| Parameter | Type | Description |
|---|---|---|
quote | RfqQuote | The quote to fill |
signature | bytes | EIP-712 signature from MMM |
fillAmount | uint256 | Amount of contracts to fill |
Returns: RfqFillResult with quote hash, filled amount, premium, and fee.
Validation checks:
fillAmount > 0- Quote not expired (
block.timestamp < quoteExpiry) - Option not expired (
block.timestamp < optionExpiry) - Option has sufficient time to expiry (≥ MIN_TIME_TO_EXPIRY)
- Signer is registered MMM
- Valid signature from MMM
- Taker authorized (if
quote.taker != address(0)) - Nonce matches MMM's current nonce
- Fill amount ≤ remaining size
- Series ID matches parameters
- Post-trade: both parties pass margin health check
Emits: RfqQuoteFilled
fillRfqQuoteBatch
Fills multiple quotes in a single transaction.
function fillRfqQuoteBatch(
RFQTypes.RfqQuote[] calldata quotes,
bytes[] calldata signatures,
uint256[] calldata fillAmounts
) external returns (RfqFillResult[] memory results)Nonce Management
incrementMmmNonce
Invalidates all pending quotes for the calling MMM.
function incrementMmmNonce() externalRequirements: Caller must be a registered MMM.
Emits: MmmNonceIncremented
View Functions
getQuoteHash
Computes the EIP-712 hash of a quote.
function getQuoteHash(RFQTypes.RfqQuote calldata quote) public view returns (bytes32)getQuoteStatus
Returns the complete status of a quote.
function getQuoteStatus(RFQTypes.RfqQuote calldata quote, bytes calldata signature)
external view returns (QuoteStatus memory status)Returns:
struct QuoteStatus {
bool isValid; // Signature valid
bool isExpired; // Quote expired
bool isOptionExpired; // Option expired
uint256 filled; // Amount filled
uint256 remaining; // Amount remaining
}verifySignature
Verifies a quote signature.
function verifySignature(RFQTypes.RfqQuote calldata quote, bytes calldata signature)
public view returns (bool)quoteFilled
Returns the filled amount for a quote hash.
function quoteFilled(bytes32 quoteHash) external view returns (uint256)mmmNonce
Returns an MMM's current nonce.
function mmmNonce(address mmm) external view returns (uint256)Owner Functions
setRfqFee
Sets the RFQ fee rate.
function setRfqFee(uint256 _rfqFeeBps) externalEmits: RFQFeeUpdated
setFeeRecipient
Sets the fee recipient address.
function setFeeRecipient(address _feeRecipient) externalEmits: FeeRecipientUpdated
setCollateralVault
Sets the collateral vault address.
function setCollateralVault(address _collateralVault) externalEmits: CollateralVaultUpdated
transferOwnership
Transfers contract ownership.
function transferOwnership(address newOwner) externalEmits: OwnershipTransferred
Events
| Event | Parameters | Description |
|---|---|---|
RfqQuoteFilled | quoteHash, mmm, taker, seriesId, takerIsBuying, price, fillAmount, premium, feeAmount | Quote fill executed |
MmmNonceIncremented | mmm, newNonce | MMM nonce increased |
RFQFeeUpdated | oldFeeBps, newFeeBps | Fee rate changed |
FeeRecipientUpdated | oldRecipient, newRecipient | Fee recipient changed |
CollateralVaultUpdated | oldVault, newVault | Vault address changed |
OwnershipTransferred | previousOwner, newOwner | Ownership changed |
Execution Flow
Fill Quote Sequence
1. Taker calls fillRfqQuote(quote, signature, fillAmount)
2. Basic Validation
├─ Check fillAmount > 0
├─ Check quote not expired
├─ Check option not expired
├─ Check sufficient time to expiry
└─ Check signer is registered MMM
3. Signature & State Validation
├─ Verify EIP-712 signature from MMM
├─ Check taker authorization (if specified)
├─ Check nonce matches
├─ Check fillAmount ≤ remaining
├─ Verify seriesId matches parameters
└─ Update fill state
4. Calculate Premium & Fee
├─ premium = (price × fillAmount) / 1e18
└─ rfqFee = (premium × rfqFeeBps) / 10000
5. Ensure Series Exists
└─ SeriesRegistry.getOrCreateSeries()
6. Execute Transfer
├─ If takerIsBuying (taker gets long):
│ ├─ Position MMM: -fillAmount (short)
│ ├─ Position taker: +fillAmount (long)
│ └─ Transfer premium: taker → MMM
└─ If !takerIsBuying (taker gets short):
├─ Position MMM: +fillAmount (long)
├─ Position taker: -fillAmount (short)
└─ Transfer premium: MMM → taker
7. Collect RFQ Fee
└─ Transfer rfqFee: taker → feeRecipient
8. Margin Check
├─ Check MMM isHealthy()
└─ Check taker isHealthy()
9. Emit RfqQuoteFilledIntegration Points
Depends On
| Contract | Purpose |
|---|---|
| DiffusalOptionsPositionManager | Position updates, MMM verification |
| DiffusalOptionsSeriesRegistry | Series validation/creation |
| DiffusalCollateralVault | Premium transfers, margin checks |
| USDC (ERC20) | Collateral token |
Used By
| Contract | Purpose |
|---|---|
| DiffusalInsuranceFund | Fee recipient |
| Off-chain MMM systems | Quote generation |
Security Considerations
MMM Authorization
Only registered MMMs can sign valid quotes:
if (!POSITION_MANAGER.isMmm(quote.mmm)) revert Errors.NotMmm();Taker Authorization
Quotes can be restricted to a specific taker:
if (quote.taker != address(0) && quote.taker != msg.sender) {
revert Errors.UnauthorizedTaker();
}Setting taker = address(0) allows anyone to fill the quote.
Series Validation
The quote includes full series parameters, which are validated against the seriesId:
bytes32 computedSeriesId = keccak256(abi.encodePacked(
quote.pairId, quote.strike, quote.optionExpiry, quote.isCall
));
if (computedSeriesId != quote.seriesId) revert Errors.InvalidSeriesId();Margin Enforcement
Both parties must pass health checks after the trade:
if (!vault.isHealthy(quote.mmm)) revert Errors.InsufficientMargin();
if (!vault.isHealthy(msg.sender)) revert Errors.InsufficientMargin();Code Reference
Source: packages/contracts/src/DiffusalOptionsRFQ.sol
Interface: packages/contracts/src/interfaces/IDiffusalOptionsRFQ.sol
RFQ Types: packages/contracts/src/utils/RFQTypes.sol
Testnet: View on MonadVision
EIP-712 Domain
bytes32 private constant EIP712_DOMAIN_TYPEHASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
string private constant NAME = "DiffusalOptionsRFQ";
string private constant VERSION = "1";Related
- RFQ Flow (Protocol) — High-level RFQ mechanics
- DiffusalOptionsOrderBook — Alternative peer-to-peer trading
- Protocol Design — MMM role and responsibilities