Diffusal
Protocol

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

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).


Settlement Execution

After the settlement price is finalized, the protocol collects from shorts and pays longs.

Critical Invariant: The protocol takes no position. Shorts pay longs. The protocol is only the intermediary.

Step 1: Calculate total obligations

  • Intrinsic value = max(0, settlementPrice - strike) for calls, or max(0, strike - settlementPrice) for puts
  • Total long payout = Σ(longPosition × intrinsicValue)
  • Total short obligation = Σ(|shortPosition| × intrinsicValue)
  • Note: totalLongPayout == totalShortObligation (by invariant)

Step 2: Collect from shorts

  • For each short holder, deduct |position| × intrinsicValue from collateral
  • If a short has insufficient collateral, take what's available and record shortfall
  • Accumulate into settlement pool

Step 3: Cover shortfall from insurance fund (if any)

  • Shortfall = totalLongPayout - collectedFromShorts
  • If shortfall > 0, the insurance fund covers it

Step 4: Pay longs

  • For each long holder, credit position × intrinsicValue to collateral
  • Zero out all positions for this series

Settlement Flow

Funds flow from short holders to the settlement pool, then to long holders. If there's a shortfall (shorts couldn't cover full obligations), the insurance fund provides coverage.


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-USD 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_INTERVAL15 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})

Payout Calculation

For each user:

Payout=position×intrinsicValue\text{Payout} = \text{position} \times \text{intrinsicValue}
  • Positive position (long) → positive payout if ITM
  • Negative position (short) → negative payout (obligation) if ITM

Keeper Operations

Keepers automate two simple jobs:

JobActionFrequency
Price SnapshotstakeSnapshot(pairId)Every ~30 seconds per pair
Settlementsettle(seriesId)After expiry for each series

All functions are permissionless and self-validating—they revert if called too early or already completed.

Batch Operations

For gas efficiency, keepers can batch multiple operations:

  • takeSnapshotBatch(pairIds[]) — Snapshot multiple pairs in one transaction
  • settleBatch(seriesIds[]) — Settle multiple series in one transaction

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 can run backup keepers

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
ExecutionAfter settlementCollect from shorts, pay longs, zero positions

The system is:

  • 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

On this page