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:
| Phase | Location | Gas Cost |
|---|---|---|
| Order creation | Off-chain (signed message) | Free |
| Order storage | Off-chain (relayer/indexer) | Free |
| Order matching | Off-chain | Free |
| Execution & settlement | On-chain | Gas 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 Direction | isBuy | Maker Gets | Taker Gets | Premium Flow |
|---|---|---|---|---|
| Bid | true | Long (+size) | Short (-size) | Maker → Taker |
| Ask | false | Short (-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 Type | Can Be Negative? | Description |
|---|---|---|
makerFeeBps | Yes (rebates) | Fee paid by order creator |
takerFeeBps | No | Fee 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)| Parameter | Type | Description |
|---|---|---|
order | Order | The order to fill |
signature | bytes | EIP-712 signature from maker |
fillAmount | uint256 | Amount of contracts to fill |
seriesParams | SeriesParams | Parameters 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) externalRequirements:
- 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) externalincrementNonce
Invalidates all pending orders for the caller.
function incrementNonce() externalEffect: 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) externalConstraint: takerFeeBps + makerFeeBps > 0 (net positive fees)
Emits: FeesUpdated
setFeeRecipient
Sets the address that receives protocol fees.
function setFeeRecipient(address _feeRecipient) externalEmits: FeeRecipientUpdated
setCollateralVault
Sets the collateral vault for margin checks.
function setCollateralVault(address _collateralVault) externaltransferOwnership
Transfers contract ownership.
function transferOwnership(address newOwner) externalEmits: OwnershipTransferred
Events
| Event | Parameters | Description |
|---|---|---|
OrderFilled | orderHash, maker, taker, seriesId, isBuy, price, fillAmount, premium | Order fill executed |
OrderCancelled | orderHash, maker | Order cancelled |
NonceIncremented | user, newNonce | User nonce increased |
FeesCollected | orderHash, maker, taker, makerFee, takerFee, recipient | Fees collected |
FeesUpdated | makerFeeBps, takerFeeBps, rfqFeeBps | Fee rates changed |
FeeRecipientUpdated | oldRecipient, newRecipient | Fee recipient changed |
CollateralVaultUpdated | oldVault, newVault | Collateral vault address changed |
OwnershipTransferred | previousOwner, newOwner | Ownership 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 EventsIntegration Points
Depends On
| Contract | Purpose |
|---|---|
| DiffusalOptionsPositionManager | Position updates |
| DiffusalOptionsSeriesRegistry | Series validation/creation |
| DiffusalCollateralVault | Premium transfers, margin checks |
| USDC (ERC20) | Collateral token |
Used By
| Contract | Purpose |
|---|---|
| DiffusalInsuranceFund | Fee recipient |
| Off-chain relayers | Order 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
| Protection | Mechanism |
|---|---|
| EIP-712 domain | Includes chainId and contract address |
| Nonce | Order nonce must match user's current nonce |
| Fill tracking | orderFilled[orderHash] prevents double-filling |
| Expiry | Orders 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)"
);Related
- Order Book (Protocol) — High-level order book mechanics
- DiffusalOptionsRFQ — Alternative trading via MMM quotes
- DiffusalCollateralVault — Margin enforcement
- Protocol Design — Fee system overview