Diffusal

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:

PrincipleDescription
TWAP SettlementSettlement uses a 1-hour time-weighted average price
Shared Price HistoryRolling price buffer maintained per trading pair
PermissionlessAnyone can trigger snapshots and settlements
Manipulation Resistant1-hour TWAP window prevents price manipulation
Net SettlementOptions and premiums settle together with fixed directions

Settlement Phase

At or after expiry (block.timestamp >= expiry), the settlement process begins:

  1. Anyone calls settle(seriesId)
  2. Query PriceHistory for 1-hour TWAP ending at expiry
  3. Calculate intrinsic value: Call = max(0, settlementPrice - strike), Put = max(0, strike - settlementPrice)
  4. Mark series as settled
  5. 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 TypeSettlement DirectionWhat Happens
Option Long (optionBalance > 0)ALWAYS receivesReceives intrinsic (or nothing if OTM)
Option Short (optionBalance < 0)ALWAYS paysPays intrinsic obligation (or nothing if OTM)
Premium Receiver (premiumBalance > 0)ALWAYS receivesReceives their fixed premium amount
Premium Payer (premiumBalance < 0)ALWAYS paysPays their fixed premium obligation

Net Settlement Formula:

netSettlement=(intrinsic×optionBalance)+premiumBalance\text{netSettlement} = (\text{intrinsic} \times \text{optionBalance}) + \text{premiumBalance}

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
UserOption StatusOption PositionOption SettlementPremium PositionPremium SettlementNet
AliceITMLong +10+$800 (receives)Payer -$500-$500 (pays)+$300
BobITMShort -10-$800 (pays)Receiver +$500+$500 (receives)-$300
CarolOTMLong +10$0Payer -$500-$500 (pays)-$500
DaveOTMShort -10$0Receiver +$500+$500 (receives)+$500
EveNone (closed)$0Receiver +$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:

ScenarioAction
Collection = payout owedNo insurance needed
Collection < payout, insurance sufficientInsurance covers full shortfall
Collection < payout, insurance insufficientPartial coverage, recipients prorated

Proration Example

When insurance can't cover the full shortfall:

ItemAmount
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 factor80% (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

ParameterValueDescription
BUFFER_DURATION2 hoursTotal history retained
TWAP_WINDOW1 hourWindow for TWAP calculation
MIN_SNAPSHOT_INTERVAL30 secondsMinimum time between snapshots
BUFFER_SIZE240Number 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]:

TWAP=i=1npricein\text{TWAP} = \frac{\sum_{i=1}^{n} \text{price}_i}{n}

where nn 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:

Intrinsic Value=max(0,settlementPricestrike)\text{Intrinsic Value} = \max(0, \text{settlementPrice} - \text{strike})

For puts:

Intrinsic Value=max(0,strikesettlementPrice)\text{Intrinsic Value} = \max(0, \text{strike} - \text{settlementPrice})

Net Settlement Calculation (Four-Instrument Model)

For each position, the net settlement is calculated by combining option and premium settlements:

netSettlement=(intrinsicValue×optionBalance)+premiumBalance\text{netSettlement} = (\text{intrinsicValue} \times \text{optionBalance}) + \text{premiumBalance}

Settlement by Position Type (Fixed Directions):

Position TypeSettlement FormulaDirection
Option Longintrinsic × (+optionBalance)RECEIVES (≥ 0)
Option Shortintrinsic × (-optionBalance)PAYS (≤ 0)
Premium Receiver+premiumBalanceRECEIVES
Premium Payer-premiumBalancePAYS

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:

JobActionFrequency
Price SnapshotstakeSnapshot(pairId)Every ~30 seconds per pair
Settlementsettle(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 users
  • calculateIntrinsicValue(bytes32 seriesId) — View function to calculate intrinsic value
  • calculateSettlementAmount(bytes32 seriesId, int256 positionSize) — View function to calculate settlement amount
  • isPositionSettled(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

PhaseTimingActions
Price SnapshotsContinuousKeeper takes snapshots per-pair every ~30s
SettlementAt/after expiry (within 1hr)Query TWAP, calculate intrinsic value, mark settled
Net SettlementAfter settlementCalculate netSettlement = (intrinsic × optionBalance) + premiumBalance, collect from payers, pay receivers

Net Settlement Formula:

netSettlement=(intrinsic×optionBalance)+premiumBalance\text{netSettlement} = (\text{intrinsic} \times \text{optionBalance}) + \text{premiumBalance}

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

ContractRole
DiffusalSettlementEngineCore settlement logic and payout calculation
DiffusalPriceHistoryRolling price snapshots and TWAP computation
DiffusalOptionsSeriesRegistrySeries lifecycle and settlement price storage
DiffusalCollateralVaultCollateral transfers during settlement

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

On this page