Diffusal

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:

FeatureDescription
Professional PricingRegistered MM signers provide tight spreads and 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
CounterpartyRegistered RFQ market-maker signerAny user
Price DiscoveryMarket-maker quotes / auction responsesMarket-driven bids/asks
Order CreationMM 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 GetsMM Signer GetsPremium Flow
trueLong (+size)Short (-size)Taker → MM
falseShort (-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)
ParameterTypeDescription
quoteRfqQuoteThe quote to fill
signaturebytesEIP-712 signature from the MM signer
fillAmountuint256Amount of contracts to fill
portfolioIduint256Taker 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 isMmm address
  • 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() external

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

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

Emits: FeeCollectorUpdated


setCollateralVault

Sets the collateral vault address (for premium transfers).

function setCollateralVault(address _collateralVault) external

Emits: CollateralVaultUpdated


setMarginCalculator

Sets the margin calculator address (for health checks after trades).

function setMarginCalculator(address _marginCalculator) external

Emits: MarginCalculatorUpdated


setExecutor

Sets the delegated RFQ execution helper.

function setExecutor(address _executor) external

Emits: ExecutorUpdated


setLiquidationEngine

Sets the liquidation engine allowed to call liquidation fills.

function setLiquidationEngine(address _liquidationEngine) external

Emits: LiquidationEngineUpdated


addAdmin / removeAdmin

Owner-managed admin set for non-owner operational controls.

function addAdmin(address admin) external
function removeAdmin(address admin) external

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, newNonceRegistered signer nonce increased
RFQFeeUpdatedoldFeeBps, newFeeBpsFee rate changed
FeeCollectorUpdatedoldCollector, newCollectorFee collector changed
CollateralVaultUpdatedoldVault, newVaultVault address changed
MarginCalculatorUpdatedoldCalculator, newCalculatorMargin calculator changed
ExecutorUpdatedoldExecutor, newExecutorExecutor changed
LiquidationEngineUpdatedoldLiquidationEngine, newLiquidationEngineLiquidation engine changed
AdminAddedadminAdmin added
AdminRemovedadminAdmin removed
OwnershipTransferredpreviousOwner, newOwnerOwnership changed
OwnershipTransferStartedpreviousOwner, newOwnerOwnership 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

ContractPurpose
DiffusalOptionsPositionManagerPosition updates and signer verification
DiffusalOptionsSeriesRegistrySeries validation/creation
DiffusalCollateralVaultPremium transfers
DiffusalMarginCalculatorPost-trade margin health checks
USDT (ERC20)Collateral token

Used By

ContractPurpose
Configured feeCollector addressReceives RFQ fees
Off-chain MM systemsQuote 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";

On this page