Diffusal
Contracts

DiffusalOptionsOrderBook

On-chain settlement for EIP-712 signed limit orders

The DiffusalOptionsOrderBook contract provides peer-to-peer limit order trading for options. Unlike RFQ trades against the Main Market Maker, the order book allows any user to place and fill orders against each other using EIP-712 signed messages.


Overview

The order book uses a hybrid architecture:

PhaseLocationGas Cost
Order creationOff-chain (signed message)Free
Order storageOff-chain (relayer/indexer)Free
Order matchingOff-chainFree
Execution & settlementOn-chainGas required

Key Features

  • Gas-free order placement — Orders are EIP-712 signed messages
  • Partial fills — Orders can be filled incrementally
  • Maker rebates — Negative maker fees incentivize liquidity providers
  • Margin enforcement — Post-trade health checks via CollateralVault
  • Lazy series creation — Series registered on first trade

Key Concepts

Order Types

Order DirectionisBuyMaker GetsTaker GetsPremium Flow
BidtrueLong (+size)Short (-size)Maker → Taker
AskfalseShort (-size)Long (+size)Taker → Maker

Key insight: The buyer always gets long positions, the seller gets short. The isBuy flag determines which party is the buyer.

Fee Model

Fee TypeCan Be Negative?Description
makerFeeBpsYes (rebates)Fee paid by order creator
takerFeeBpsNoFee paid by order filler

Fee invariant: takerFeeBps + makerFeeBps > 0 ensures net positive protocol revenue even with maker rebates.

Nonce Management

Each user has an independent nonce counter for replay protection:

  • Order nonce must match maker's current nonce
  • incrementNonce() invalidates all pending orders
  • Individual orders can be cancelled separately

Storage & State

/// @custom:storage-location erc7201:diffusal.storage.DiffusalOptionsOrderBook
struct DiffusalOptionsOrderBookStorage {
    address owner;
    mapping(bytes32 => uint256) orderFilled;     // Order hash → filled amount
    mapping(bytes32 => bool) orderCancelled;     // Order hash → cancelled flag
    mapping(address => uint256) userNonce;       // User → current nonce
    int256 makerFeeBps;                          // Maker fee (can be negative)
    uint256 takerFeeBps;                         // Taker fee
    uint256 rfqFeeBps;                           // RFQ fee (unused here)
    address feeRecipient;                        // Fee collector address
    address collateralVault;                     // Vault for margin checks
}

Order Struct

struct Order {
    address maker;      // Order creator
    bytes32 seriesId;   // Option series identifier
    bool isBuy;         // true = bid (buy), false = ask (sell)
    uint256 price;      // Premium per contract (WAD)
    uint256 size;       // Number of contracts
    uint256 nonce;      // Must match maker's current nonce
    uint256 expiry;     // Order validity deadline (unix timestamp)
}

Series ID Generation

seriesId = keccak256(abi.encodePacked(pairId, strike, expiry, isCall));

External Functions

Order Filling

fillOrder

Fills a single order.

function fillOrder(
    OrderTypes.Order calldata order,
    bytes calldata signature,
    uint256 fillAmount,
    IDiffusalOptionsSeriesRegistry.SeriesParams calldata seriesParams
) external nonReentrant returns (FillResult memory result)
ParameterTypeDescription
orderOrderThe order to fill
signaturebytesEIP-712 signature from maker
fillAmountuint256Amount of contracts to fill
seriesParamsSeriesParamsParameters for series validation/creation

Returns: FillResult with order hash, filled amount, premium, and fees.

Validation checks:

  • fillAmount > 0
  • Order not expired (block.timestamp < order.expiry)
  • No self-trade (msg.sender != order.maker)
  • Order not cancelled
  • Valid signature from maker
  • Nonce matches maker's current nonce
  • Fill amount ≤ remaining size
  • Series ID matches provided parameters
  • Post-trade: both parties pass margin health check

Emits: OrderFilled, FeesCollected (if fees configured)


fillOrderBatch

Fills multiple orders in a single transaction.

function fillOrderBatch(
    OrderTypes.Order[] calldata orders,
    bytes[] calldata signatures,
    uint256[] calldata fillAmounts,
    IDiffusalOptionsSeriesRegistry.SeriesParams calldata seriesParams
) external nonReentrant returns (FillResult[] memory results)

Use cases:

  • Market orders consuming multiple price levels
  • Arbitrage across multiple orders
  • Reduced gas per fill

Order Cancellation

cancelOrder

Cancels a specific order.

function cancelOrder(OrderTypes.Order calldata order, bytes calldata signature) external

Requirements:

  • Caller must be the order maker
  • Valid signature
  • Order not already cancelled

Emits: OrderCancelled


cancelOrderBatch

Cancels multiple orders in one transaction.

function cancelOrderBatch(OrderTypes.Order[] calldata orders, bytes[] calldata signatures) external

incrementNonce

Invalidates all pending orders for the caller.

function incrementNonce() external

Effect: All orders with nonce < newNonce become invalid.

Emits: NonceIncremented

Example: If Alice has 50 pending orders with nonce = 7, calling incrementNonce() changes her nonce to 8, instantly invalidating all 50 orders.


View Functions

getOrderHash

Computes the EIP-712 hash of an order.

function getOrderHash(OrderTypes.Order calldata order) public view returns (bytes32)

getOrderStatus

Returns the complete status of an order.

function getOrderStatus(OrderTypes.Order calldata order, bytes calldata signature)
    external view returns (OrderStatus memory status)

Returns:

struct OrderStatus {
    bool isValid;      // Signature valid
    bool isCancelled;  // Order cancelled
    bool isExpired;    // Order expired
    uint256 filled;    // Amount filled
    uint256 remaining; // Amount remaining
}

verifySignature

Verifies an order signature.

function verifySignature(OrderTypes.Order calldata order, bytes calldata signature)
    public view returns (bool)

orderFilled

Returns the filled amount for an order hash.

function orderFilled(bytes32 orderHash) external view returns (uint256)

orderCancelled

Returns whether an order is cancelled.

function orderCancelled(bytes32 orderHash) external view returns (bool)

userNonce

Returns a user's current nonce.

function userNonce(address user) external view returns (uint256)

domainSeparator

Returns the EIP-712 domain separator.

function domainSeparator() public view returns (bytes32)

Owner Functions

setFees

Configures maker, taker, and RFQ fees.

function setFees(int256 _makerFeeBps, uint256 _takerFeeBps, uint256 _rfqFeeBps) external

Constraint: takerFeeBps + makerFeeBps > 0 (net positive fees)

Emits: FeesUpdated


setFeeRecipient

Sets the address that receives protocol fees.

function setFeeRecipient(address _feeRecipient) external

Emits: FeeRecipientUpdated


setCollateralVault

Sets the collateral vault for margin checks.

function setCollateralVault(address _collateralVault) external

transferOwnership

Transfers contract ownership.

function transferOwnership(address newOwner) external

Emits: OwnershipTransferred


Events

EventParametersDescription
OrderFilledorderHash, maker, taker, seriesId, isBuy, price, fillAmount, premiumOrder fill executed
OrderCancelledorderHash, makerOrder cancelled
NonceIncrementeduser, newNonceUser nonce increased
FeesCollectedorderHash, maker, taker, makerFee, takerFee, recipientFees collected
FeesUpdatedmakerFeeBps, takerFeeBps, rfqFeeBpsFee rates changed
FeeRecipientUpdatedoldRecipient, newRecipientFee recipient changed
CollateralVaultUpdatedoldVault, newVaultCollateral vault address changed
OwnershipTransferredpreviousOwner, newOwnerOwnership changed

Execution Flow

Fill Order Sequence

1. Taker calls fillOrder(order, signature, fillAmount, seriesParams)

2. Validation
   ├─ Check fillAmount > 0
   ├─ Check order not expired
   ├─ Check no self-trade
   ├─ Check order not cancelled
   ├─ Verify EIP-712 signature
   ├─ Check nonce matches
   └─ Check fillAmount ≤ remaining

3. Series Registration (lazy)
   └─ SeriesRegistry.getOrCreateSeries()

4. Calculate Premium & Fees
   ├─ premium = (price × fillAmount) / 1e18
   ├─ makerFee = (premium × makerFeeBps) / 10000
   └─ takerFee = (premium × takerFeeBps) / 10000

5. Execute Transfer
   ├─ If isBuy (maker buying):
   │   ├─ Transfer premium: maker → taker
   │   ├─ Position maker: +fillAmount (long)
   │   └─ Position taker: -fillAmount (short)
   └─ If !isBuy (maker selling):
       ├─ Transfer premium: taker → maker
       ├─ Position maker: -fillAmount (short)
       └─ Position taker: +fillAmount (long)

6. Collect Fees
   ├─ Taker fee: taker → feeRecipient
   └─ Maker fee: maker → feeRecipient (or reverse if rebate)

7. Margin Check
   ├─ Check maker isHealthy()
   └─ Check taker isHealthy()

8. Emit Events

Integration Points

Depends On

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

Used By

ContractPurpose
DiffusalInsuranceFundFee recipient
Off-chain relayersOrder indexing and matching

Security Considerations

Reentrancy Protection

All fill operations use nonReentrant modifier to prevent callback attacks during token transfers.

Self-Trade Prevention

if (msg.sender == order.maker) revert Errors.SelfTrade();

Signature Security

ProtectionMechanism
EIP-712 domainIncludes chainId and contract address
NonceOrder nonce must match user's current nonce
Fill trackingorderFilled[orderHash] prevents double-filling
ExpiryOrders have limited validity

Margin Enforcement

Post-trade health checks ensure neither party becomes undercollateralized:

if (!vault.isHealthy(order.maker)) revert Errors.InsufficientMargin();
if (!vault.isHealthy(msg.sender)) revert Errors.InsufficientMargin();

Fee Invariant

The constraint takerFeeBps + makerFeeBps > 0 prevents protocol from paying out more in rebates than it collects:

if (int256(_takerFeeBps) + _makerFeeBps < 1) revert Errors.InvalidFeeConfiguration();

Code Reference

Source: packages/contracts/src/DiffusalOptionsOrderBook.sol

Interface: packages/contracts/src/interfaces/IDiffusalOptionsOrderBook.sol

Order Types: packages/contracts/src/utils/OptionsOrderTypes.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 = "DiffusalOptionsOrderBook";
string private constant VERSION = "1";

Order Type Hash

bytes32 public constant ORDER_TYPEHASH = keccak256(
    "Order(address maker,bytes32 seriesId,bool isBuy,uint256 price,uint256 size,uint256 nonce,uint256 expiry)"
);

On this page