DiffusalOptionsRFQ
On-chain settlement for EIP-712 signed RFQ quotes from registered market-maker signers
The DiffusalOptionsRFQ contract enables Request-for-Quote trading between users and registered market-maker signers. In the current backend, /api/rfq/request typically sources those quotes through a timed multi-maker auction, but on-chain execution still comes down to a single signed EIP-712 quote naming one registered signer in quote.mmm.
The current implementation keeps quote validation and access control in DiffusalOptionsRFQ while delegating fill accounting to DiffusalRfqExecutor.
Overview
RFQ trading provides an alternative to the order book, offering:
| Feature | Description |
|---|---|
| Professional Pricing | Registered MM signers provide tight spreads and 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 | Registered RFQ market-maker signer | Any user |
| Price Discovery | Market-maker quotes / auction responses | Market-driven bids/asks |
| Order Creation | MM 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 | MM Signer Gets | Premium Flow |
|---|---|---|---|
true | Long (+size) | Short (-size) | Taker → MM |
false | Short (-size) | Long (+size) | MM → Taker |
Registered RFQ Signers (mmm Field)
The quote field is still named mmm, but the PositionManager can authorize multiple market-maker signer addresses. A quote is valid as long as the signer address is registered:
if (!POSITION_MANAGER.isMmm(quote.mmm)) revert Errors.NotMmm();Registered RFQ signers have special privileges:
- Cannot be liquidated while marked
isMmm - Can sign RFQ quotes
- Can increment their nonce to invalidate all quotes
Nonce Management
Each registered RFQ signer has an independent nonce:
- Quote nonce must match the signer's current nonce
incrementMmmNonce()invalidates all pending quotes- Unlike order book, only registered MM signers can increment their nonce
Storage & State
/// @custom:storage-location erc7201:diffusal.storage.DiffusalOptionsRFQ
struct DiffusalOptionsRfqStorage {
address owner;
address pendingOwner;
mapping(address account => bool isAdmin) admins;
mapping(address => uint256) mmmNonce; // signer → current nonce
mapping(bytes32 => uint256) quoteFilled; // Quote hash → filled amount
uint256 rfqFeeBps; // RFQ fee rate
address feeCollector; // Configurable fee collector
address collateralVault; // Vault for premium transfers
address marginCalculator; // Margin calculator for health checks
address liquidationEngine; // Liquidation-only fill path
address executor; // Delegated RFQ execution helper
}RFQ Quote Struct
struct RfqQuote {
address mmm; // Registered market-maker signer 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 the signer'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
fillRfqQuoteInPortfolio
Fills a single RFQ quote.
function fillRfqQuoteInPortfolio(
RFQTypes.RfqQuote calldata quote,
bytes calldata signature,
uint256 fillAmount,
uint256 portfolioId
) external returns (RfqFillResult memory result)| Parameter | Type | Description |
|---|---|---|
quote | RfqQuote | The quote to fill |
signature | bytes | EIP-712 signature from the MM signer |
fillAmount | uint256 | Amount of contracts to fill |
portfolioId | uint256 | Taker portfolio to settle into |
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 a registered
isMmmaddress - Valid signature from that signer
- Taker authorized (if
quote.taker != address(0)) - Nonce matches the signer's current nonce
- Fill amount ≤ remaining size
- Series ID matches parameters
- Post-trade: both parties pass margin health check
Emits: RfqQuoteFilled
fillRfqQuoteBatchInPortfolios
Fills multiple quotes in a single transaction.
function fillRfqQuoteBatchInPortfolios(
RFQTypes.RfqQuote[] calldata quotes,
bytes[] calldata signatures,
uint256[] calldata fillAmounts,
uint256[] calldata portfolioIds
) external returns (RfqFillResult[] memory results)Nonce Management
incrementMmmNonce
Invalidates all pending quotes for the calling registered MM signer.
function incrementMmmNonce() externalRequirements: Caller must be a registered MM signer.
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 a registered market-maker signer'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
setFeeCollector
Sets the fee collector address. In the current deployment model, this is a dedicated protocol wallet (EOA) separate from the insurance fund.
function setFeeCollector(address _feeCollector) externalEmits: FeeCollectorUpdated
setCollateralVault
Sets the collateral vault address (for premium transfers).
function setCollateralVault(address _collateralVault) externalEmits: CollateralVaultUpdated
setMarginCalculator
Sets the margin calculator address (for health checks after trades).
function setMarginCalculator(address _marginCalculator) externalEmits: MarginCalculatorUpdated
setExecutor
Sets the delegated RFQ execution helper.
function setExecutor(address _executor) externalEmits: ExecutorUpdated
setLiquidationEngine
Sets the liquidation engine allowed to call liquidation fills.
function setLiquidationEngine(address _liquidationEngine) externalEmits: LiquidationEngineUpdated
addAdmin / removeAdmin
Owner-managed admin set for non-owner operational controls.
function addAdmin(address admin) external
function removeAdmin(address admin) externaltransferOwnership
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 | Registered signer nonce increased |
RFQFeeUpdated | oldFeeBps, newFeeBps | Fee rate changed |
FeeCollectorUpdated | oldCollector, newCollector | Fee collector changed |
CollateralVaultUpdated | oldVault, newVault | Vault address changed |
MarginCalculatorUpdated | oldCalculator, newCalculator | Margin calculator changed |
ExecutorUpdated | oldExecutor, newExecutor | Executor changed |
LiquidationEngineUpdated | oldLiquidationEngine, newLiquidationEngine | Liquidation engine changed |
AdminAdded | admin | Admin added |
AdminRemoved | admin | Admin removed |
OwnershipTransferred | previousOwner, newOwner | Ownership changed |
OwnershipTransferStarted | previousOwner, newOwner | Ownership transfer started |
Execution Flow
Fill Quote Sequence
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. Taker calls fillRfqQuoteInPortfolio(quote, signature, fillAmount, portfolioId) │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 2. Basic Validation │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ • Check fillAmount > 0 │ │
│ │ • Check quote not expired │ │
│ │ • Check option not expired │ │
│ │ • Check sufficient time to expiry │ │
│ │ • Check signer is a registered `isMmm` address │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 3. Signature & State Validation │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ • Verify EIP-712 signature from the MM signer │ │
│ │ • 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. Delegate To DiffusalRfqExecutor │
│ Series creation, portfolio routing, fills, fees, and margin checks │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 6. Execute Transfer │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ takerIsBuying? │ │
│ │ │ │ │
│ │ ┌──────────────┴──────────────┐ │ │
│ │ ▼ ▼ │ │
│ │ [Yes] [No] │ │
│ │ Position signer: -fillAmount Position signer: +fillAmount │ │
│ │ Position taker: +fillAmount Position taker: -fillAmount │ │
│ │ Premium: taker → signer Premium: signer → taker │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 7. Emit RfqQuoteFilled │
└─────────────────────────────────────────────────────────────────────────────┘Integration Points
Depends On
| Contract | Purpose |
|---|---|
| DiffusalOptionsPositionManager | Position updates and signer verification |
| DiffusalOptionsSeriesRegistry | Series validation/creation |
| DiffusalCollateralVault | Premium transfers |
| DiffusalMarginCalculator | Post-trade margin health checks |
| USDT (ERC20) | Collateral token |
Used By
| Contract | Purpose |
|---|---|
Configured feeCollector address | Receives RFQ fees |
| Off-chain MM systems | Quote generation |
Security Considerations
MM Authorization
Only registered isMmm signers 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
Taker portfolio must pass health checks after the trade (via MarginCalculator):
IDiffusalMarginCalculator calc = IDiffusalMarginCalculator(s.marginCalculator);
if (!calc.isPortfolioHealthy(msg.sender, takerPortfolioId)) {
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
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 — Privileged RFQ signer role and responsibilities