DiffusalOptionsSeriesRegistry
Registry for option series lifecycle management
The DiffusalOptionsSeriesRegistry contract manages the lifecycle of option series from creation through settlement. It handles admin-controlled series creation, validates series parameters, and coordinates TWAP-based settlement when options expire.
Overview
An option series is defined by four parameters:
| Parameter | Description |
|---|---|
pairId | Trading pair (e.g., keccak256("ETH-USDT")) |
strike | Strike price (WAD) |
expiry | Option expiry timestamp |
isCall | Call (true) or put (false) |
Series Lifecycle
┌───────────────────────────────────────────────────────┐
│ 1. CREATION (admin-only) │
│ Created via createSeries() by admin wallet │
└───────────────────────────┬───────────────────────────┘
▼
┌───────────────────────────────────────────────────────┐
│ 2. ACTIVE │
│ Trading allowed until expiry │
└───────────────────────────┬───────────────────────────┘
▼
┌───────────────────────────────────────────────────────┐
│ 3. EXPIRED │
│ Trading stops, waiting for settlement │
└───────────────────────────┬───────────────────────────┘
▼
┌───────────────────────────────────────────────────────┐
│ 4. SETTLED │
│ Settlement price set, positions can settle │
└───────────────────────────────────────────────────────┘Key Concepts
Admin-Controlled Series Creation
Series are created by admin wallets via createSeries(). Only admins can register new series with canonical strikes from the strike ladder algorithm. Operator contracts (OrderBook, RFQ) use getSeries() to look up existing series during trades.
// Admin creates a new series
SERIES_REGISTRY.createSeries(seriesId, seriesParams);
// OrderBook/RFQ look up existing series for trading
SERIES_REGISTRY.getSeries(seriesId);This approach:
- Ensures only canonical strikes from the strike ladder are listed
- Reduces costs (only create series that are actually traded)
- Trading remains permissionless once a series exists
Series ID Generation
The series ID is a deterministic hash of the series parameters:
seriesId = keccak256(abi.encodePacked(pairId, strike, expiry, isCall));TWAP Settlement
When a series expires, it's settled using a 1-hour Time-Weighted Average Price (TWAP) from the PriceHistory contract:
(settlementPrice, snapshotCount) = priceHistory.getTwap(pairId, expiry);This prevents settlement price manipulation via single-block price attacks.
Open Interest Tracking
The registry tracks aggregate open interest (OI) at the pair level, not per-series:
- Separate buckets: Calls and puts are tracked independently
- Long positions only: Only positive option balances count toward OI
- Conservation law: Long OI always equals Short OI (every long has a counterparty short)
- Configurable caps: Owner can set maximum OI per pair to limit exposure
- Cap of 0: Means unlimited (no cap enforced)
This design prevents unbounded market maker exposure from P2P trading while allowing flexible risk management.
Storage & State
/// @custom:storage-location erc7201:diffusal.storage.SeriesRegistry
struct SeriesRegistryStorage {
address owner;
address pendingOwner; // Two-step ownership transfer
mapping(address => bool) operators; // Authorized operators
mapping(bytes32 => SeriesInfo) series; // Series ID → info
uint256 minTimeToExpiry; // Min time for new series
address priceHistory; // TWAP provider
mapping(bytes32 => PairOpenInterest) pairOpenInterest; // Pair ID → OI
mapping(bytes32 => PairCaps) pairCaps; // Pair ID → caps
}SeriesInfo Struct
struct SeriesInfo {
bytes32 pairId; // Trading pair identifier
uint256 strike; // Strike price (WAD)
uint256 expiry; // Option expiry timestamp
bool isCall; // true = call, false = put
bool isSettled; // Whether settled
uint256 settlementPrice; // Settlement price (WAD, 0 until settled)
}SeriesParams Struct
struct SeriesParams {
bytes32 pairId;
uint256 strike;
uint256 optionExpiry;
bool isCall;
}PairOpenInterest Struct
struct PairOpenInterest {
uint256 callOI; // Total call contracts (sum of positive call balances)
uint256 putOI; // Total put contracts (sum of positive put balances)
}PairCaps Struct
struct PairCaps {
uint256 maxCallOI; // Maximum call OI allowed (0 = unlimited)
uint256 maxPutOI; // Maximum put OI allowed (0 = unlimited)
}External Functions
Series Management
createSeries
Creates a new series. Admin only.
function createSeries(bytes32 seriesId, SeriesParams calldata p)
external onlyAdmin returns (SeriesInfo memory info)| Parameter | Type | Description |
|---|---|---|
seriesId | bytes32 | Expected series identifier (must match hash of params) |
p | SeriesParams | Series parameters for validation/creation |
Behavior:
- Reverts if the series already exists
- Validates
seriesIdmatches computed hash of params - Validates strike > 0
- Validates expiry > now + minTimeToExpiry
- Creates and returns new series
Emits: SeriesCreated
getSeries
Looks up an existing series. Operator only. Validates the series is still tradeable (not expired, not settled).
function getSeries(bytes32 seriesId)
external view onlyOperator returns (SeriesInfo memory info)Reverts: SeriesNotFound if series doesn't exist, or if series is expired/settled.
settle
Settles an expired series using TWAP.
function settle(bytes32 seriesId) externalRequirements:
- Series must exist
- Series must be expired (
block.timestamp >= expiry) - Series must not already be settled
Process:
- Query TWAP from PriceHistory contract
- Set settlement price
- Mark series as settled
Emits: SeriesSettled, optionally SettlementFallbackUsed
Note: This function is permissionless—anyone can trigger settlement.
View Functions
seriesExists
Checks if a series has been created.
function seriesExists(bytes32 seriesId) external view returns (bool)getSeriesInfo
Returns full series information.
function getSeriesInfo(bytes32 seriesId) external view returns (SeriesInfo memory info)Reverts: SeriesNotFound if series doesn't exist.
isExpired
Checks if a series has expired.
function isExpired(bytes32 seriesId) external view returns (bool)isSettled
Checks if a series has been settled.
function isSettled(bytes32 seriesId) external view returns (bool)canTrade
Checks if a series can be traded.
function canTrade(bytes32 seriesId) external view returns (bool)Returns true if:
- Series exists AND not expired AND not settled
Returns false if the series doesn't exist (expiry == 0).
computeSeriesId
Computes the series ID from parameters.
function computeSeriesId(bytes32 pairId, uint256 strike, uint256 expiry, bool isCall)
external pure returns (bytes32)validateSeriesId
Validates that a series ID matches its parameters.
function validateSeriesId(bytes32 seriesId, SeriesParams calldata params)
external pure returns (bool)Open Interest Functions
getPairOpenInterest
Returns current open interest for a trading pair.
function getPairOpenInterest(bytes32 pairId)
external view returns (PairOpenInterest memory)getPairCaps
Returns OI caps for a trading pair.
function getPairCaps(bytes32 pairId)
external view returns (PairCaps memory)getPairRemainingCapacity
Returns remaining capacity before hitting OI caps.
function getPairRemainingCapacity(bytes32 pairId)
external view returns (uint256 callsRemaining, uint256 putsRemaining)Returns type(uint256).max if cap is 0 (unlimited).
updateOpenInterest
Updates open interest when positions change. Operator only.
function updateOpenInterest(bytes32 pairId, bool isCall, int256 delta) external| Parameter | Type | Description |
|---|---|---|
pairId | bytes32 | Trading pair identifier |
isCall | bool | true for call OI, false for put OI |
delta | int256 | Signed change (positive = increase, negative = decrease) |
Behavior:
- Called by PositionManager when positions change
- Enforces OI caps on increases
- Reverts with
OpenInterestCapExceededif cap would be exceeded
Emits: OpenInterestUpdated
Owner Functions
setOperator
Authorizes or deauthorizes an operator.
function setOperator(address operator, bool authorized) external onlyAdminEmits: OperatorUpdated
setMinTimeToExpiry
Sets the minimum time to expiry for new series.
function setMinTimeToExpiry(uint256 _minTimeToExpiry) external onlyAdminDefault: MIN_TIME_TO_EXPIRY from Constants (1 hour)
setPriceHistory
Sets the price history contract for TWAP settlement.
function setPriceHistory(address priceHistory_) external onlyAdminEmits: PriceHistoryUpdated
transferOwnership
Initiates ownership transfer (two-step pattern).
function transferOwnership(address newOwner) external onlyOwnerEmits: OwnershipTransferStarted
Note: Setting newOwner to zero cancels any pending transfer.
acceptOwnership
Accepts ownership transfer. Pending owner only.
function acceptOwnership() externalEmits: OwnershipTransferred
pendingOwner
Returns the pending owner address.
function pendingOwner() external view returns (address)setPairCaps
Sets OI caps for a trading pair.
function setPairCaps(bytes32 pairId, uint256 maxCallOI, uint256 maxPutOI)
external onlyOwner| Parameter | Type | Description |
|---|---|---|
pairId | bytes32 | Trading pair identifier |
maxCallOI | uint256 | Maximum call OI (0 = unlimited) |
maxPutOI | uint256 | Maximum put OI (0 = unlimited) |
Emits: PairCapsSet
Events
| Event | Parameters | Description |
|---|---|---|
SeriesCreated | seriesId, pairId, strike, expiry, isCall | New series created |
SeriesSettled | seriesId, settlementPrice, snapshotCount | Series settlement completed |
OperatorUpdated | operator, authorized | Operator status changed |
PriceHistoryUpdated | oldPriceHistory, newPriceHistory | Price history contract changed |
MinTimeToExpiryUpdated | oldMinTime, newMinTime | Minimum time to expiry changed |
OwnershipTransferStarted | previousOwner, newOwner | Ownership transfer initiated |
OwnershipTransferred | previousOwner, newOwner | Ownership transfer completed |
OpenInterestUpdated | pairId, isCall, oldOI, newOI | Open interest changed for pair |
PairCapsSet | pairId, maxCallOI, maxPutOI | OI caps configured for pair |
Note: Settlement requires TWAP snapshot data. If no snapshots exist for the settlement window, the
settleSeries()function will revert withInsufficientSnapshots(). There is no fallback mechanism.
Integration Points
Depends On
| Contract | Purpose |
|---|---|
| DiffusalPriceHistory | TWAP for settlement |
Used By
| Contract | Purpose |
|---|---|
| DiffusalOrderBookCoordinator | Series validation on order flow |
| DiffusalRfqExecutor | Series validation on RFQ fill |
| DiffusalOptionsPositionManager | OI updates when positions change |
| DiffusalSettlementEngine | Settlement price lookup |
| DiffusalCollateralVault | Series info for margin calculations |
Security Considerations
Series ID Validation
Every trade validates that the provided seriesId matches the computed hash:
if (_computeSeriesIdFromParams(p) != seriesId) revert Errors.InvalidSeriesId();This prevents:
- Trading non-existent series with arbitrary IDs
- Parameter mismatches between order and series
Minimum Time to Expiry
New series must have sufficient time until expiry:
if (p.optionExpiry < block.timestamp + $.minTimeToExpiry) {
revert Errors.OptionExpiryTooSoon();
}This ensures:
- Adequate time for position management
- Prevention of near-expiry manipulation
Settlement Security
TWAP-based settlement provides:
- Resistance to single-block price manipulation
- Fair settlement price based on historical data
- Permissionless triggering (anyone can call
settle()after expiry)
Open Interest Caps
OI caps protect against unbounded exposure:
- Caps are enforced at the pair level, not per-series
- Users cannot bypass caps by using different strikes or expiries
- Cap enforcement happens atomically with position updates
- Reverts with
OpenInterestCapExceededif a trade would exceed limits
Code Reference
Source: packages/contracts/src/DiffusalOptionsSeriesRegistry.sol
Interface: packages/contracts/src/interfaces/IDiffusalOptionsSeriesRegistry.sol
Key Constants
// From Constants.sol
uint256 constant MIN_TIME_TO_EXPIRY = 1 hours;Related
- Options Creation (Protocol) — Series creation mechanics
- Options Settlement (Protocol) — Settlement process
- DiffusalPriceHistory — TWAP data source
- DiffusalSettlementEngine — Position settlement