Options Settlement
How option series are settled at expiry using manipulation-resistant TWAP pricing
This document explains how option series are settled at expiry using TWAP pricing and how payouts are calculated and distributed. Options are created via lazy registration—see Options Creation for series mechanics.
Design Principles
The options settlement system follows these core principles:
| Principle | Description |
|---|---|
| TWAP Settlement | Settlement uses a 1-hour time-weighted average price |
| Shared Price History | Rolling price buffer maintained per trading pair |
| Permissionless | Anyone can trigger snapshots and settlements |
| Manipulation Resistant | 1-hour TWAP window prevents price manipulation |
| Net Settlement | Options and premiums settle together with fixed directions |
Settlement Phase
At or after expiry (block.timestamp >= expiry), the settlement process begins:
- Anyone calls
settle(seriesId) - Query PriceHistory for 1-hour TWAP ending at expiry
- Calculate intrinsic value: Call = max(0, settlementPrice - strike), Put = max(0, strike - settlementPrice)
- Mark series as settled
- Trading disabled for this series
Settlement Window: Settlement must occur within 1 hour after expiry (while price history data is still available).
Net Settlement Model (Four-Instrument)
Options settlement uses the Net Settlement Model where options and premiums are tracked as separate instruments with fixed settlement directions:
| Position Type | Settlement Direction | What Happens |
|---|---|---|
| Option Long (optionBalance > 0) | ALWAYS receives | Receives intrinsic (or nothing if OTM) |
| Option Short (optionBalance < 0) | ALWAYS pays | Pays intrinsic obligation (or nothing if OTM) |
| Premium Receiver (premiumBalance > 0) | ALWAYS receives | Receives their fixed premium amount |
| Premium Payer (premiumBalance < 0) | ALWAYS pays | Pays their fixed premium obligation |
Net Settlement Formula:
Where:
- optionBalance: Signed position size (+ = long, - = short)
- premiumBalance: Signed premium position (+ = receivable, - = payable)
- intrinsic: Settlement price intrinsic value (≥ 0)
Result interpretation:
- netSettlement > 0: User receives funds
- netSettlement < 0: User pays funds
Critical Invariants:
- The protocol takes no position
- Sum of all option settlements = 0 (longs receive, shorts pay)
- Sum of all premium settlements = 0 (receivers receive, payers pay)
Pre-Settlement Protection: Before settlement, the Settlement Readiness Liquidation mechanism ensures that users with expiring obligations have sufficient deposited cash (not just equity) to cover them. This happens 1 day before expiry—liquidators sell non-expiring long positions into actual cash if needed.
Settlement Examples (Four-Instrument)
Scenario: ETH-USDC call options with strike price $3,000. Settlement price is $3,080.
- ITM (In-The-Money): Settlement price ($3,080) > Strike ($3,000) → Intrinsic = $80 per contract
- OTM (Out-of-The-Money): If settlement price were below strike → Intrinsic = $0
| User | Option Status | Option Position | Option Settlement | Premium Position | Premium Settlement | Net |
|---|---|---|---|---|---|---|
| Alice | ITM | Long +10 | +$800 (receives) | Payer -$500 | -$500 (pays) | +$300 |
| Bob | ITM | Short -10 | -$800 (pays) | Receiver +$500 | +$500 (receives) | -$300 |
| Carol | OTM | Long +10 | $0 | Payer -$500 | -$500 (pays) | -$500 |
| Dave | OTM | Short -10 | $0 | Receiver +$500 | +$500 (receives) | +$500 |
| Eve | — | None (closed) | $0 | Receiver +$200 | +$200 (receives) | +$200 |
Key insights:
- Carol pays at settlement because her Premium Payer position owes premium (her Option Long expired worthless, contributing nothing)
- Dave receives at settlement because his Premium Receiver position receives premium (his Option Short expired worthless, owing nothing)
- Eve (closed) has no option position but still receives from her Premium Receiver position (locked-in profit)
Settlement Flow Diagram
SETTLEMENT EXECUTION FLOW
─────────────────────────
┌─────────────────────────────────────────────────────────────────────────┐
│ Step 1: CALCULATE NET SETTLEMENT FOR EACH USER │
├─────────────────────────────────────────────────────────────────────────┤
│ For each user with positions in this series: │
│ │
│ optionSettlement = intrinsic × optionBalance │
│ premiumSettlement = premiumBalance │
│ netSettlement = optionSettlement + premiumSettlement │
│ │
│ Example (intrinsic = $80): │
│ Alice: ($80 × +10) + (-$500) = +$300 (receives) │
│ Bob: ($80 × -10) + (+$500) = -$300 (pays) │
│ │
│ Example (intrinsic = $0, OTM): │
│ Carol: ($0 × +10) + (-$500) = -$500 (PAYS!) │
│ Dave: ($0 × -10) + (+$500) = +$500 (RECEIVES!) │
│ │
│ Note: Σ(all netSettlement) = 0 by invariant │
└────────────────────────────────────┬────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Step 2: COLLECT FROM USERS WITH NEGATIVE NET SETTLEMENT │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ User A ──debit $500 (full)────►┐ │
│ │ Settlement Pool │
│ User B ──debit $300 (partial)──►│ Collected: $800 │
│ └───────────────────────────────────────┘
│ │
│ If deposit < |netSettlement|, take what is available │
│ (could be option long OR short with insufficient collateral) │
└────────────────────────────────────┬────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Step 3: COVER SHORTFALL (if any) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Shortfall = totalReceiving - collectedFromPaying │
│ │
│ Insurance Fund ──cover() $200──► Settlement Pool │
│ │
│ If shortfall > insurance balance: partial coverage only │
└────────────────────────────────────┬────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Step 4: PAY USERS WITH POSITIVE NET SETTLEMENT │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Settlement Pool ──credit $600──► User C │
│ (Pool: $1000) ──credit $400──► User D │
│ │
│ (could be option long OR short with positive netSettlement) │
│ │
│ If pool < totalReceiving: │
│ actualPayout = expected × (pool / totalReceiving) │
└─────────────────────────────────────────────────────────────────────────┘Settlement Steps
Step 1: Calculate net settlement for each user
- Intrinsic value = max(0, settlementPrice - strike) for calls, or max(0, strike - settlementPrice) for puts
- For each position:
netSettlement = (intrinsic × optionBalance) + premiumBalance - netSettlement > 0 = user receives funds; netSettlement < 0 = user pays funds
- Note: Σ(all netSettlement) = 0 (by invariant)
Step 2: Collect from users with negative net settlement
- For each user with netSettlement < 0, deduct |netSettlement| from collateral
- Users may have negative net settlement due to: Premium Payer obligations (fixed amount owed) or Option Short obligations (intrinsic payout owed if ITM)
- If a user has insufficient collateral, take what's available and record shortfall
- Accumulate into settlement pool
Step 3: Cover shortfall from insurance fund (if any)
- Shortfall = totalReceiving - collectedFromPaying
- If shortfall > 0, the insurance fund covers it (up to available balance)
Step 4: Pay users with positive net settlement
- For each user with netSettlement > 0, credit netSettlement to collateral
- Users may have positive net settlement due to: Option Long intrinsic payout exceeds Premium Payer obligation, or Premium Receiver balance exceeds Option Short obligation
- Proration: If pool < totalReceiving, each recipient receives proportional share
- Zero out all option and premium positions for this series
Insurance Fund Coverage
The DiffusalInsuranceFund provides shortfall coverage when parties with negative P&L can't fully cover their obligations:
| Scenario | Action |
|---|---|
| Collection = payout owed | No insurance needed |
| Collection < payout, insurance sufficient | Insurance covers full shortfall |
| Collection < payout, insurance insufficient | Partial coverage, recipients prorated |
Proration Example
When insurance can't cover the full shortfall:
| Item | Amount |
|---|---|
| Total positive P&L owed | $10,000 |
| Collected from negative P&L | $7,000 |
| Shortfall | $3,000 |
| Insurance fund balance | $1,000 |
| Payout pool | $8,000 (7k + 1k) |
| Proration factor | 80% (8k / 10k) |
Each recipient (positive P&L) receives 80% of their expected payout. This ensures fair distribution of available funds.
See DiffusalSettlementEngine for implementation details.
Price History (TWAP Data)
Instead of accumulating TWAP per-series, a shared PriceHistory contract maintains rolling price snapshots per-pair. This is more efficient:
- One keeper job per pair (not per series)
- Shared storage — all ETH-USDC options use the same price buffer
- Guaranteed data — as long as the pair is active, price history exists
Buffer Configuration
| Parameter | Value | Description |
|---|---|---|
BUFFER_DURATION | 2 hours | Total history retained |
TWAP_WINDOW | 1 hour | Window for TWAP calculation |
MIN_SNAPSHOT_INTERVAL | 30 seconds | Minimum time between snapshots |
BUFFER_SIZE | 240 | Number of snapshots in buffer |
Key insight: With 2 hours of data and a 1-hour TWAP window, there is a 1-hour buffer after expiry to settle. As long as settlement happens within 1 hour of expiry, the required price data is available.
TWAP Calculation
At settlement, the TWAP is calculated from price snapshots in the window [expiry - 1 hour, expiry]:
where is the number of snapshots in the 1-hour window.
If no snapshots exist in the window (fallback case), the current spot price is used.
Intrinsic Value Calculation
At Settlement
For calls:
For puts:
Net Settlement Calculation (Four-Instrument Model)
For each position, the net settlement is calculated by combining option and premium settlements:
Settlement by Position Type (Fixed Directions):
| Position Type | Settlement Formula | Direction |
|---|---|---|
| Option Long | intrinsic × (+optionBalance) | RECEIVES (≥ 0) |
| Option Short | intrinsic × (-optionBalance) | PAYS (≤ 0) |
| Premium Receiver | +premiumBalance | RECEIVES |
| Premium Payer | -premiumBalance | PAYS |
Key insight: Each position type has a FIXED direction:
- Option longs ALWAYS receive (or receive nothing if OTM)
- Option shorts ALWAYS pay (or pay nothing if OTM)
- Premium receivers ALWAYS receive their fixed amount
- Premium payers ALWAYS pay their fixed amount
The NET settlement combines these fixed-direction flows. While the individual components never reverse direction, the net result determines whether a user pays or receives overall.
Keeper Operations
Keepers are anyone who calls the protocol's permissionless maintenance functions. For settlement, two keeper operations are essential:
| Job | Action | Frequency |
|---|---|---|
| Price Snapshots | takeSnapshot(pairId) | Every ~30 seconds per pair |
| Settlement | settle(seriesId) | After expiry (within 1hr) |
Keeper Operations Timeline
KEEPER OPERATIONS TIMELINE
──────────────────────────────────────────────────────────────────────────────
Time: t-90m t-60m t-30m EXPIRY t+30m t+60m
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
Price [S1] [S2] [S3] [S4] [S5]
History ────────────────────────────────────────────────────
├────────── Buffer retained (2 hours) ──────────────┤
TWAP ├─────── 1-hour TWAP window ───────┤
Window (used for settlement)
Settlement ★ EXPIRY
│
├────── Settlement window ──────►
(keeper calls settle() after expiry)
Legend:
[Sn] = Price snapshot
★ = Milestone (option expiry)Timeline explanation:
- Price Snapshots: Continuous, every ~30 seconds per trading pair
- Buffer Duration: 2 hours of price history retained
- TWAP Window: 1-hour window ending at expiry used for settlement price
- Settlement Window: Must settle within 1 hour after expiry (while TWAP data available)
All functions are permissionless and self-validating—they revert if called too early or already completed. Multiple keepers can operate in parallel without conflict.
For batch operations (takeSnapshotBatch, settleBatch) and comprehensive keeper documentation, see Keepers.
Security Considerations
TWAP Manipulation Resistance
- 1-hour window requires sustained price manipulation
- Minimum snapshot interval prevents spam/gaming
- Multiple snapshots (~120) smooth out outliers
Oracle Failures
- Fallback to spot price if TWAP data is insufficient
- Staleness checks on oracle reads
- Events emitted when fallback is used (for monitoring)
Keeper Failures
- All functions are permissionless (anyone can call)
- Multiple keepers can operate in parallel without conflict
- Protocol may run a backup keeper for reliability
See Keepers for error handling and competitive dynamics.
Front-running
- TWAP accumulation is rate-limited
- Settlement can only happen after expiry
- No advantage to front-running settlement (price is already fixed by TWAP)
Settlement Engine Contract
The DiffusalSettlementEngine contract implements the settlement execution logic. It is an operator on both PositionManager and CollateralVault.
Key Functions
settlePosition(address user, bytes32 seriesId)— Settle a single user's position (operator only)settlePositionBatch(address[] users, bytes32 seriesId)— Batch settle multiple userscalculateIntrinsicValue(bytes32 seriesId)— View function to calculate intrinsic valuecalculateSettlementAmount(bytes32 seriesId, int256 positionSize)— View function to calculate settlement amountisPositionSettled(address user, bytes32 seriesId)— View function to check if position is already settled
Settlement Result
Each settlement returns a result containing: user address, settled status, seriesId, original position size (+ = long, - = short), intrinsic value per contract (WAD), and settlement amount (USDC, 6 decimals, signed).
Integration
The Settlement Engine must be registered as an operator on both PositionManager and CollateralVault during deployment.
Summary
| Phase | Timing | Actions |
|---|---|---|
| Price Snapshots | Continuous | Keeper takes snapshots per-pair every ~30s |
| Settlement | At/after expiry (within 1hr) | Query TWAP, calculate intrinsic value, mark settled |
| Net Settlement | After settlement | Calculate netSettlement = (intrinsic × optionBalance) + premiumBalance, collect from payers, pay receivers |
Net Settlement Formula:
Fixed Settlement Directions (Four-Instrument Model):
- Option Long → ALWAYS receives (intrinsic ≥ 0)
- Option Short → ALWAYS pays (intrinsic obligation)
- Premium Receiver → ALWAYS receives (fixed amount)
- Premium Payer → ALWAYS pays (fixed amount)
The system is:
- Net Settlement — Options and premiums combine into single net payment per user
- Fixed Directions — Each position type has deterministic settlement direction
- Efficient — Shared price history per-pair (not per-series)
- Permissionless — Anyone can take snapshots or settle
- Manipulation-resistant — 1-hour TWAP prevents price manipulation
- Fallback-safe — Current spot price fallback if no snapshots available
Contract Implementation
| Contract | Role |
|---|---|
| DiffusalSettlementEngine | Core settlement logic and payout calculation |
| DiffusalPriceHistory | Rolling price snapshots and TWAP computation |
| DiffusalOptionsSeriesRegistry | Series lifecycle and settlement price storage |
| DiffusalCollateralVault | Collateral transfers during settlement |
Related
Protocol Documentation
- Keepers — Price snapshots, settlement triggers, and liquidation operations
- Options Creation — How series are created via lazy registration
- Margin System — Collateral requirements before settlement
- Liquidation — What happens if users can't cover obligations, settlement readiness liquidation
Contract Documentation
- DiffusalSettlementEngine — Core settlement logic, batch settlement with proration
- DiffusalPriceHistory — Rolling price snapshots, TWAP computation
- DiffusalOptionsSeriesRegistry — Series lifecycle,
settle()function - DiffusalInsuranceFund —
cover()function for shortfall coverage - DiffusalCollateralVault —
creditCollateral()anddebitCollateral()functions