DiffusalOptionsSeriesRegistry
Registry for option series lifecycle management
The DiffusalOptionsSeriesRegistry contract manages the lifecycle of option series from creation through settlement. It handles lazy series creation on first trade, 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-USDC")) |
strike | Strike price (WAD) |
expiry | Option expiry timestamp |
isCall | Call (true) or put (false) |
Series Lifecycle
┌───────────────────────────────────────────────────────┐
│ 1. CREATION (lazy) │
│ Created on first trade via getOrCreateSeries() │
└───────────────────────────┬───────────────────────────┘
▼
┌───────────────────────────────────────────────────────┐
│ 2. ACTIVE │
│ Trading allowed until expiry │
└───────────────────────────┬───────────────────────────┘
▼
┌───────────────────────────────────────────────────────┐
│ 3. EXPIRED │
│ Trading stops, waiting for settlement │
└───────────────────────────┬───────────────────────────┘
▼
┌───────────────────────────────────────────────────────┐
│ 4. SETTLED │
│ Settlement price set, positions can settle │
└───────────────────────────────────────────────────────┘Key Concepts
Lazy Series Creation
Series are not created in advance. Instead, they're created on-demand when the first trade occurs:
// Called by OrderBook or RFQ on every trade
SERIES_REGISTRY.getOrCreateSeries(seriesId, seriesParams);This approach:
- Eliminates need for admin intervention to list new options
- Reduces costs (only create series that are actually traded)
- Allows infinite series combinations
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
getOrCreateSeries
Gets existing series or creates a new one.
function getOrCreateSeries(bytes32 seriesId, SeriesParams calldata params)
external returns (SeriesInfo memory info)| Parameter | Type | Description |
|---|---|---|
seriesId | bytes32 | Expected series identifier |
params | SeriesParams | Series parameters for validation/creation |
If series exists:
- Validates series is still tradeable (not expired, not settled)
- Returns existing SeriesInfo
If series doesn't exist:
- Validates
seriesIdmatches computed hash of params - Validates strike > 0
- Validates expiry > now + minTimeToExpiry
- Creates and returns new series
Emits: SeriesCreated (on new series only)
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 onlyOwnerEmits: OperatorUpdated
setMinTimeToExpiry
Sets the minimum time to expiry for new series.
function setMinTimeToExpiry(uint256 _minTimeToExpiry) external onlyOwnerDefault: MIN_TIME_TO_EXPIRY from Constants (1 hour)
setPriceHistory
Sets the price history contract for TWAP settlement.
function setPriceHistory(address priceHistory_) external onlyOwnerEmits: 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 |
|---|---|
| DiffusalOptionsOrderBook | Series validation on order fill |
| DiffusalOptionsRFQ | 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 (no admin bottleneck)
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