Diffusal
Contracts

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:

FeatureDescription
Professional PricingMMMs provide tight spreads and deep liquidity
Instant ExecutionNo order matching needed—quotes are pre-agreed
Large OrdersBetter for size without moving the market
Partial FillsQuotes can be filled incrementally

RFQ vs Order Book

AspectRFQOrder Book
CounterpartyMain Market Maker onlyAny user
Price DiscoveryMMM quotesMarket-driven bids/asks
Order CreationMMM signs quote off-chainMaker signs order off-chain
FeesRFQ fee (taker only)Maker + taker fees
Best ForLarge orders, professional pricingPrice discovery, liquidity provision

Key Concepts

Trade Direction

The takerIsBuying flag determines position assignment:

takerIsBuyingTaker GetsMMM GetsPremium Flow
trueLong (+size)Short (-size)Taker → MMM
falseShort (-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)
ParameterTypeDescription
quoteRfqQuoteThe quote to fill
signaturebytesEIP-712 signature from MMM
fillAmountuint256Amount 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() external

Requirements: 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) external

Emits: RFQFeeUpdated


setFeeRecipient

Sets the fee recipient address.

function setFeeRecipient(address _feeRecipient) external

Emits: FeeRecipientUpdated


setCollateralVault

Sets the collateral vault address.

function setCollateralVault(address _collateralVault) external

Emits: CollateralVaultUpdated


transferOwnership

Transfers contract ownership.

function transferOwnership(address newOwner) external

Emits: OwnershipTransferred


Events

EventParametersDescription
RfqQuoteFilledquoteHash, mmm, taker, seriesId, takerIsBuying, price, fillAmount, premium, feeAmountQuote fill executed
MmmNonceIncrementedmmm, newNonceMMM nonce increased
RFQFeeUpdatedoldFeeBps, newFeeBpsFee rate changed
FeeRecipientUpdatedoldRecipient, newRecipientFee recipient changed
CollateralVaultUpdatedoldVault, newVaultVault address changed
OwnershipTransferredpreviousOwner, newOwnerOwnership 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 RfqQuoteFilled

Integration Points

Depends On

ContractPurpose
DiffusalOptionsPositionManagerPosition updates, MMM verification
DiffusalOptionsSeriesRegistrySeries validation/creation
DiffusalCollateralVaultPremium transfers, margin checks
USDC (ERC20)Collateral token

Used By

ContractPurpose
DiffusalInsuranceFundFee recipient
Off-chain MMM systemsQuote 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";

On this page