Skip to content

Latest commit

 

History

History
1260 lines (1022 loc) · 41.9 KB

File metadata and controls

1260 lines (1022 loc) · 41.9 KB

Aptos Options Protocol Architecture

Executive Summary

An orderbook-based options protocol on Aptos, leveraging Hyperion for price discovery, Pyth for spot oracles, and Move's resource model for position safety. Targeting European-style BTC/ETH options for MVP with 3-6 month solo development timeline.


Table of Contents

  1. System Overview
  2. Core Components
  3. Move Module Architecture
  4. Oracle Strategy
  5. Hyperion Integration
  6. Options Pricing
  7. Settlement & Exercise
  8. Collateral & Margin
  9. Risk Management
  10. Phased Development Plan
  11. Technical Risks & Mitigations

System Overview

High-Level Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                         USER INTERFACE                               │
│   (Web App: React + Aptos Wallet Adapter)                           │
└─────────────────────────────────┬───────────────────────────────────┘
                                  │
┌─────────────────────────────────▼───────────────────────────────────┐
│                      PROTOCOL LAYER (Move)                           │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌────────────┐ │
│  │   Options   │  │  Orderbook  │  │  Collateral │  │ Settlement │ │
│  │   Engine    │  │  Interface  │  │   Vault     │  │   Engine   │ │
│  └─────────────┘  └─────────────┘  └─────────────┘  └────────────┘ │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐                 │
│  │   Oracle    │  │    Risk     │  │   Events    │                 │
│  │  Adapter    │  │   Manager   │  │   Emitter   │                 │
│  └─────────────┘  └─────────────┘  └─────────────┘                 │
└─────────────────────────────────────────────────────────────────────┘
                    │                               │
        ┌───────────▼───────────┐     ┌────────────▼────────────┐
        │   HYPERION ORDERBOOK  │     │      PYTH ORACLE        │
        │   (Price Discovery)   │     │    (Spot + Vol Data)    │
        └───────────────────────┘     └─────────────────────────┘

Design Principles

  1. Safety First: Move resources prevent double-exercise, position manipulation
  2. Capital Efficiency: Collateral requirements based on real-time risk
  3. Composability: Standard interfaces for DeFi integration
  4. Simplicity for MVP: European options only, limited strike/expiry grid

Scope for 3-6 Month MVP

In Scope:

  • European-style call and put options
  • BTC/USDC and ETH/USDC pairs
  • Weekly and monthly expiries
  • Cash-settled options
  • Basic orderbook integration via Hyperion
  • Pyth oracle for spot prices
  • Simple collateral system (full collateral initially)

Out of Scope (Phase 2+):

  • American-style options
  • Portfolio margin
  • Exotic options (barriers, binaries)
  • Cross-margin between positions
  • Implied volatility oracle (use historical vol initially)

Core Components

1. Options Engine

Manages option series creation, position tracking, and lifecycle.

Responsibilities:

  • Create option series (underlying, strike, expiry, type)
  • Mint/burn option tokens
  • Track open interest
  • Manage exercise window

2. Orderbook Interface

Adapter layer for Hyperion orderbook integration.

Responsibilities:

  • Create markets for option series
  • Route orders to Hyperion
  • Handle fills and partial fills
  • Manage order cancellation

3. Collateral Vault

Holds and manages collateral for writers (sellers).

Responsibilities:

  • Accept collateral deposits
  • Lock collateral against positions
  • Release collateral on close/expiry
  • Handle liquidations

4. Settlement Engine

Handles option exercise and settlement at expiry.

Responsibilities:

  • Fetch settlement price from oracle
  • Calculate payoffs
  • Distribute funds to ITM holders
  • Return collateral to OTM writers

5. Oracle Adapter

Abstracts oracle integration for price feeds.

Responsibilities:

  • Fetch spot prices (Pyth)
  • Calculate historical volatility
  • Provide settlement prices
  • Handle oracle failures gracefully

6. Risk Manager

Monitors positions and triggers liquidations.

Responsibilities:

  • Calculate margin requirements
  • Monitor collateral ratios
  • Trigger liquidations when undercollateralized
  • Manage protocol risk parameters

Move Module Architecture

Module Dependency Graph

                    ┌──────────────┐
                    │    errors    │
                    └──────────────┘
                           ▲
        ┌──────────────────┼──────────────────┐
        │                  │                  │
┌───────┴──────┐  ┌────────┴───────┐  ┌──────┴───────┐
│    types     │  │     events     │  │    config    │
└──────────────┘  └────────────────┘  └──────────────┘
        ▲                  ▲                  ▲
        │                  │                  │
        └──────────────────┼──────────────────┘
                           │
                    ┌──────┴──────┐
                    │   oracle    │
                    └─────────────┘
                           ▲
                           │
        ┌──────────────────┼──────────────────┐
        │                  │                  │
┌───────┴──────┐  ┌────────┴───────┐  ┌──────┴───────┐
│  collateral  │  │    series      │  │    margin    │
└──────────────┘  └────────────────┘  └──────────────┘
        ▲                  ▲                  ▲
        │                  │                  │
        └──────────────────┼──────────────────┘
                           │
                    ┌──────┴──────┐
                    │   options   │ (main entry point)
                    └─────────────┘
                           ▲
                           │
                    ┌──────┴──────┐
                    │  orderbook  │ (Hyperion integration)
                    └─────────────┘

Module Descriptions

sources/
├── errors.move        # Error codes
├── types.move         # Core type definitions
├── events.move        # Event structs
├── config.move        # Protocol parameters
├── oracle.move        # Pyth adapter
├── collateral.move    # Vault management
├── series.move        # Option series management
├── margin.move        # Margin calculations
├── options.move       # Main protocol logic
└── orderbook.move     # Hyperion integration

Core Type Definitions

// types.move
module options_protocol::types {
    use std::string::String;

    /// Option type enum
    const CALL: u8 = 1;
    const PUT: u8 = 2;

    /// Option style enum
    const EUROPEAN: u8 = 1;
    const AMERICAN: u8 = 2;  // Future

    /// Represents an option series (e.g., BTC-30000-CALL-2025-03-28)
    struct OptionSeries has copy, drop, store {
        underlying: String,      // "BTC", "ETH"
        quote: String,           // "USDC"
        strike: u64,             // Strike price (6 decimals)
        expiry: u64,             // Unix timestamp
        option_type: u8,         // CALL or PUT
        style: u8,               // EUROPEAN or AMERICAN
    }

    /// Unique identifier for a series
    struct SeriesId has copy, drop, store {
        id: u256,
    }

    /// Long position (buyer)
    struct LongPosition has key, store {
        series_id: SeriesId,
        amount: u64,             // Number of contracts (8 decimals)
        entry_price: u64,        // Average entry premium (6 decimals)
        created_at: u64,
    }

    /// Short position (writer/seller)
    struct ShortPosition has key, store {
        series_id: SeriesId,
        amount: u64,             // Number of contracts
        collateral_locked: u64,  // USDC locked
        entry_price: u64,        // Premium received
        created_at: u64,
    }

    /// Settlement result
    struct SettlementResult has copy, drop, store {
        series_id: SeriesId,
        settlement_price: u64,   // Spot at expiry
        payout_per_contract: u64,
        is_itm: bool,
    }
}

Main Options Module

// options.move
module options_protocol::options {
    use std::signer;
    use aptos_framework::coin::{Self, Coin};
    use aptos_framework::timestamp;
    use options_protocol::types::{Self, OptionSeries, SeriesId, LongPosition, ShortPosition};
    use options_protocol::oracle;
    use options_protocol::collateral;
    use options_protocol::series;
    use options_protocol::margin;
    use options_protocol::events;

    /// Errors
    const E_SERIES_NOT_FOUND: u64 = 1;
    const E_EXPIRED: u64 = 2;
    const E_NOT_EXPIRED: u64 = 3;
    const E_INSUFFICIENT_COLLATERAL: u64 = 4;
    const E_NOT_POSITION_OWNER: u64 = 5;
    const E_INVALID_AMOUNT: u64 = 6;

    /// Protocol configuration stored at module address
    struct ProtocolConfig has key {
        admin: address,
        fee_bps: u64,            // Trading fee in basis points
        liquidation_threshold: u64, // 80% = 8000
        min_collateral_ratio: u64,  // 125% = 12500
        paused: bool,
    }

    /// Global state tracking all series
    struct ProtocolState has key {
        next_series_id: u256,
        total_open_interest: u64,
        total_volume: u64,
    }

    // ============ Writer Functions (Sell Options) ============

    /// Write (sell) options - requires collateral
    public entry fun write_options<CollateralCoin>(
        writer: &signer,
        series_id: SeriesId,
        amount: u64,
        collateral: Coin<CollateralCoin>,
    ) acquires ProtocolConfig, ProtocolState {
        let writer_addr = signer::address_of(writer);

        // Validate series exists and not expired
        let series = series::get_series(series_id);
        assert!(timestamp::now_seconds() < series.expiry, E_EXPIRED);

        // Calculate required collateral
        let spot_price = oracle::get_spot_price(series.underlying);
        let required = margin::calculate_writer_collateral(
            &series,
            amount,
            spot_price
        );
        assert!(coin::value(&collateral) >= required, E_INSUFFICIENT_COLLATERAL);

        // Lock collateral in vault
        collateral::deposit(writer_addr, series_id, collateral);

        // Create short position
        let position = ShortPosition {
            series_id,
            amount,
            collateral_locked: coin::value(&collateral),
            entry_price: 0, // Set when matched
            created_at: timestamp::now_seconds(),
        };

        // Store position under writer's account
        move_to(writer, position);

        // Emit event
        events::emit_position_opened(writer_addr, series_id, amount, false);
    }

    // ============ Settlement Functions ============

    /// Settle position at expiry (European style)
    public entry fun settle<CollateralCoin>(
        holder: &signer,
        position: LongPosition,
    ) acquires ProtocolConfig {
        let holder_addr = signer::address_of(holder);
        let series = series::get_series(position.series_id);

        // Must be at or after expiry
        assert!(timestamp::now_seconds() >= series.expiry, E_NOT_EXPIRED);

        // Get settlement price
        let settlement_price = oracle::get_settlement_price(
            series.underlying,
            series.expiry
        );

        // Calculate payout
        let payout = calculate_payout(&series, settlement_price, position.amount);

        // Consume position resource (Move guarantees single settlement)
        let LongPosition { series_id: _, amount: _, entry_price: _, created_at: _ } = position;

        // Transfer payout from collateral vault
        if (payout > 0) {
            let coins = collateral::withdraw_for_settlement<CollateralCoin>(
                position.series_id,
                payout
            );
            coin::deposit(holder_addr, coins);
        }

        events::emit_position_settled(holder_addr, position.series_id, payout);
    }

    /// Calculate option payout at settlement
    fun calculate_payout(
        series: &OptionSeries,
        settlement_price: u64,
        amount: u64
    ): u64 {
        let strike = series.strike;

        if (series.option_type == types::CALL) {
            // Call: max(0, spot - strike)
            if (settlement_price > strike) {
                ((settlement_price - strike) * amount) / PRECISION
            } else {
                0
            }
        } else {
            // Put: max(0, strike - spot)
            if (strike > settlement_price) {
                ((strike - settlement_price) * amount) / PRECISION
            } else {
                0
            }
        }
    }

    // ============ View Functions ============

    #[view]
    public fun get_series_info(series_id: SeriesId): OptionSeries {
        series::get_series(series_id)
    }

    #[view]
    public fun get_required_collateral(
        series_id: SeriesId,
        amount: u64
    ): u64 {
        let series = series::get_series(series_id);
        let spot_price = oracle::get_spot_price(series.underlying);
        margin::calculate_writer_collateral(&series, amount, spot_price)
    }

    #[view]
    public fun calculate_theoretical_price(
        series_id: SeriesId
    ): u64 {
        let series = series::get_series(series_id);
        let spot = oracle::get_spot_price(series.underlying);
        let vol = oracle::get_volatility(series.underlying);
        let time_to_expiry = series.expiry - timestamp::now_seconds();

        pricing::black_scholes(
            spot,
            series.strike,
            time_to_expiry,
            vol,
            series.option_type
        )
    }
}

Oracle Strategy

The IV Oracle Problem

The Gap: Implied volatility (IV) is critical for options pricing, but no oracle currently provides on-chain IV for Aptos.

Solutions (in order of feasibility for MVP):

Option 1: Historical Volatility (MVP)

Use realized volatility as a proxy. Simpler but less accurate.

module options_protocol::oracle {
    use pyth::pyth;
    use pyth::price_identifier;

    /// Calculate 30-day historical volatility from Pyth price feeds
    /// Store rolling window of prices, compute std dev
    struct VolatilityState has key {
        prices: vector<PricePoint>,  // Last 30 daily prices
        last_update: u64,
    }

    struct PricePoint has store, drop {
        price: u64,
        timestamp: u64,
    }

    /// Get current spot price from Pyth
    public fun get_spot_price(underlying: String): u64 {
        let price_id = get_price_id(underlying);
        let price = pyth::get_price(price_id);
        // Convert Pyth price to our format (6 decimals)
        normalize_pyth_price(price)
    }

    /// Calculate historical volatility (annualized)
    public fun get_volatility(underlying: String): u64 acquires VolatilityState {
        let state = borrow_global<VolatilityState>(@options_protocol);

        // Calculate daily returns
        let returns = calculate_log_returns(&state.prices);

        // Standard deviation of returns
        let daily_vol = std_deviation(&returns);

        // Annualize: daily_vol * sqrt(365)
        (daily_vol * 191) / 10  // sqrt(365) ≈ 19.1
    }

    /// Get settlement price (uses Pyth with specific timestamp)
    public fun get_settlement_price(underlying: String, expiry: u64): u64 {
        // For settlement, we need price at exact expiry time
        // Pyth provides historical prices
        let price_id = get_price_id(underlying);
        pyth::get_price_at_timestamp(price_id, expiry)
    }
}

Pros: Simple, no external dependencies beyond Pyth Cons: Historical vol ≠ implied vol, mispricing vs market

Option 2: Off-Chain IV Feed (Recommended for Production)

Run an off-chain service that calculates IV from Deribit/CEX data, submits on-chain.

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   Deribit API   │────▶│  IV Calculator  │────▶│  Aptos Oracle   │
│  (options data) │     │   (off-chain)   │     │    Contract     │
└─────────────────┘     └─────────────────┘     └─────────────────┘
/// Admin-controlled IV oracle (trusted feed)
struct IVOracle has key {
    admin: address,
    iv_surface: Table<IVKey, u64>,  // strike/expiry -> IV
    last_update: u64,
}

struct IVKey has copy, drop, store {
    underlying: String,
    strike: u64,
    expiry: u64,
}

/// Only admin can update IV (from off-chain calculation)
public entry fun update_iv(
    admin: &signer,
    underlying: String,
    strikes: vector<u64>,
    expiries: vector<u64>,
    ivs: vector<u64>,
) acquires IVOracle {
    let oracle = borrow_global_mut<IVOracle>(@options_protocol);
    assert!(signer::address_of(admin) == oracle.admin, E_NOT_ADMIN);

    // Update IV surface
    let i = 0;
    while (i < vector::length(&strikes)) {
        let key = IVKey {
            underlying,
            strike: *vector::borrow(&strikes, i),
            expiry: *vector::borrow(&expiries, i),
        };
        table::upsert(&mut oracle.iv_surface, key, *vector::borrow(&ivs, i));
        i = i + 1;
    };

    oracle.last_update = timestamp::now_seconds();
}

Option 3: Chainlink Volatility Feeds (Future)

Chainlink is launching realized volatility feeds. When available:

  • 24h, 7d, 30d rolling realized volatility
  • More decentralized than custom oracle
  • Still not IV, but better than nothing

Recommended Approach

Phase 1 (MVP): Historical volatility from Pyth price feeds Phase 2: Off-chain IV calculator submitting to trusted oracle Phase 3: Decentralized IV oracle (if ecosystem develops)


Hyperion Integration

Overview

Hyperion is Aptos's leading DEX with orderbook functionality. We'll create options markets on Hyperion rather than building custom orderbook infrastructure.

Integration Architecture

┌─────────────────────────────────────────────────────────────┐
│                    Options Protocol                          │
│  ┌─────────────┐                      ┌─────────────────┐   │
│  │   Series    │                      │   Position      │   │
│  │  Manager    │                      │   Tracker       │   │
│  └──────┬──────┘                      └────────▲────────┘   │
│         │                                      │            │
│         │ Create Market                        │ Record     │
│         ▼                                      │ Position   │
│  ┌──────────────────────────────────────────────────────┐  │
│  │              Hyperion Adapter                         │  │
│  │  - Market creation                                    │  │
│  │  - Order routing                                      │  │
│  │  - Fill callbacks                                     │  │
│  └──────────────────────────┬───────────────────────────┘  │
└─────────────────────────────┼───────────────────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │    HYPERION     │
                    │   ORDERBOOK     │
                    │                 │
                    │  BTC-CALL-30K   │
                    │  ETH-PUT-2000   │
                    │       ...       │
                    └─────────────────┘

Market Creation

For each option series, create a Hyperion market:

module options_protocol::orderbook {
    use hyperion::market;
    use hyperion::order;

    /// Create Hyperion market for an option series
    public fun create_options_market(
        admin: &signer,
        series_id: SeriesId,
    ) {
        let series = series::get_series(series_id);

        // Market name: "BTC-30000-CALL-20250328"
        let market_name = format_market_name(&series);

        // Options trade in USDC (premium currency)
        // The "base" is the option contract itself
        hyperion::create_market(
            admin,
            market_name,
            @options_protocol,  // Option token address
            @usdc,              // Quote currency
            6,                  // Price decimals
            8,                  // Size decimals
        );
    }

    /// Place limit order for options
    public entry fun place_order(
        trader: &signer,
        series_id: SeriesId,
        side: u8,           // BID or ASK
        price: u64,         // Premium in USDC (6 decimals)
        size: u64,          // Number of contracts (8 decimals)
    ) {
        let market_id = get_market_id(series_id);

        if (side == ASK) {
            // Selling options requires collateral check
            let required = margin::calculate_writer_collateral(...);
            assert!(collateral::available(trader) >= required, E_INSUFFICIENT_COLLATERAL);
            collateral::reserve(trader, required);
        }

        hyperion::place_limit_order(
            trader,
            market_id,
            side,
            price,
            size,
        );
    }

    /// Handle order fill (callback from Hyperion)
    public fun on_fill(
        market_id: u64,
        maker: address,
        taker: address,
        price: u64,
        size: u64,
        maker_side: u8,
    ) {
        let series_id = get_series_from_market(market_id);

        // Determine buyer/seller
        let (buyer, seller) = if (maker_side == ASK) {
            (taker, maker)
        } else {
            (maker, taker)
        };

        // Create positions
        create_long_position(buyer, series_id, size, price);
        create_short_position(seller, series_id, size, price);

        // Lock seller's collateral
        collateral::lock(seller, series_id, size);

        // Transfer premium from buyer to seller
        let premium = (price * size) / PRECISION;
        transfer_premium(buyer, seller, premium);
    }
}

Alternative: Custom Orderbook (If Hyperion Integration Blocked)

If Hyperion integration proves difficult, fallback to simple on-chain orderbook:

struct OrderBook has key {
    bids: vector<Order>,  // Sorted by price desc
    asks: vector<Order>,  // Sorted by price asc
}

struct Order has store, drop {
    trader: address,
    price: u64,
    size: u64,
    timestamp: u64,
}

Tradeoffs:

  • Simpler to implement
  • Less capital efficient (no integration with Hyperion liquidity)
  • Higher gas for matching
  • Recommendation: Try Hyperion first, fallback if needed

Options Pricing

Black-Scholes Implementation

For European options, Black-Scholes provides theoretical pricing:

module options_protocol::pricing {
    /// Fixed-point precision (6 decimals)
    const PRECISION: u64 = 1_000_000;

    /// Black-Scholes call price
    /// C = S*N(d1) - K*e^(-rT)*N(d2)
    ///
    /// Where:
    /// d1 = (ln(S/K) + (r + σ²/2)T) / (σ√T)
    /// d2 = d1 - σ√T
    public fun black_scholes_call(
        spot: u64,           // Current price (6 dec)
        strike: u64,         // Strike price (6 dec)
        time_to_expiry: u64, // Seconds until expiry
        volatility: u64,     // Annualized vol (6 dec, e.g., 500000 = 50%)
        risk_free_rate: u64, // Annual rate (6 dec, e.g., 50000 = 5%)
    ): u64 {
        // Convert time to years (6 decimal precision)
        let t = (time_to_expiry * PRECISION) / SECONDS_PER_YEAR;

        // Calculate d1 and d2
        let (d1, d2) = calculate_d1_d2(spot, strike, t, volatility, risk_free_rate);

        // N(d1) and N(d2) - cumulative normal distribution
        let n_d1 = cumulative_normal(d1);
        let n_d2 = cumulative_normal(d2);

        // e^(-rT)
        let discount = exp_negative((risk_free_rate * t) / PRECISION);

        // C = S*N(d1) - K*e^(-rT)*N(d2)
        let term1 = (spot * n_d1) / PRECISION;
        let term2 = (strike * discount * n_d2) / (PRECISION * PRECISION);

        if (term1 > term2) {
            term1 - term2
        } else {
            0
        }
    }

    /// Black-Scholes put price using put-call parity
    /// P = C - S + K*e^(-rT)
    public fun black_scholes_put(
        spot: u64,
        strike: u64,
        time_to_expiry: u64,
        volatility: u64,
        risk_free_rate: u64,
    ): u64 {
        let call_price = black_scholes_call(spot, strike, time_to_expiry, volatility, risk_free_rate);
        let t = (time_to_expiry * PRECISION) / SECONDS_PER_YEAR;
        let discount = exp_negative((risk_free_rate * t) / PRECISION);
        let pv_strike = (strike * discount) / PRECISION;

        // P = C - S + K*e^(-rT)
        call_price + pv_strike - spot
    }

    /// Approximate cumulative normal distribution
    /// Using Abramowitz and Stegun approximation
    fun cumulative_normal(x: i64): u64 {
        // Implementation using polynomial approximation
        // Accurate to ~10^-7
        // ...
    }

    /// Calculate Greeks for risk management
    public fun calculate_delta(
        spot: u64,
        strike: u64,
        time_to_expiry: u64,
        volatility: u64,
        option_type: u8,
    ): i64 {
        let (d1, _) = calculate_d1_d2(spot, strike, time_to_expiry, volatility, 0);
        let n_d1 = cumulative_normal(d1);

        if (option_type == CALL) {
            (n_d1 as i64)
        } else {
            (n_d1 as i64) - (PRECISION as i64)
        }
    }
}

Pricing Considerations

  1. Risk-Free Rate: Use USDC lending rate from Aries as proxy
  2. Volatility: Historical vol initially, IV oracle later
  3. Gas Optimization: Pre-compute common exp/log values
  4. Accuracy vs Gas: Fixed-point math limits precision

Settlement & Exercise

European Settlement Flow

                    BEFORE EXPIRY                    AT/AFTER EXPIRY

    ┌─────────┐                               ┌─────────────────┐
    │  LONG   │ ◄── Can trade ──────────────► │  LONG holder    │
    │ POSITION│     (buy/sell)                │  calls settle() │
    └─────────┘                               └────────┬────────┘
                                                       │
                                              ┌────────▼────────┐
    ┌─────────┐                               │ Oracle provides │
    │  SHORT  │ ◄── Collateral locked ──────► │ settlement price│
    │ POSITION│                               └────────┬────────┘
    └─────────┘                                        │
                                              ┌────────▼────────┐
                                              │ Calculate payout│
                                              │ ITM: holder paid│
                                              │ OTM: writer keeps│
                                              │     collateral   │
                                              └────────┬────────┘
                                                       │
                                              ┌────────▼────────┐
                                              │ Positions closed│
                                              │ Resources burned│
                                              └─────────────────┘

Settlement Implementation

module options_protocol::settlement {

    /// Settlement window: 24 hours after expiry
    const SETTLEMENT_WINDOW: u64 = 86400;

    /// Settle a long position
    public entry fun settle_long<CollateralCoin>(
        holder: &signer,
    ) acquires LongPosition {
        let holder_addr = signer::address_of(holder);

        // Move position out (consumed after this)
        let position = move_from<LongPosition>(holder_addr);
        let series = series::get_series(position.series_id);

        // Validate timing
        let now = timestamp::now_seconds();
        assert!(now >= series.expiry, E_NOT_EXPIRED);
        assert!(now <= series.expiry + SETTLEMENT_WINDOW, E_SETTLEMENT_EXPIRED);

        // Get settlement price
        let settlement_price = oracle::get_settlement_price(
            series.underlying,
            series.expiry
        );

        // Calculate payout
        let payout = calculate_payout(&series, settlement_price, position.amount);

        // Position resource is consumed here - cannot settle twice!
        let LongPosition {
            series_id: _,
            amount: _,
            entry_price: _,
            created_at: _
        } = position;

        // Distribute payout
        if (payout > 0) {
            let coins = collateral::withdraw_for_settlement<CollateralCoin>(
                series_id,
                payout
            );
            coin::deposit(holder_addr, coins);
        }

        events::emit_settlement(holder_addr, series_id, settlement_price, payout);
    }

    /// Settle a short position (writer claims remaining collateral)
    public entry fun settle_short<CollateralCoin>(
        writer: &signer,
    ) acquires ShortPosition {
        let writer_addr = signer::address_of(writer);
        let position = move_from<ShortPosition>(writer_addr);
        let series = series::get_series(position.series_id);

        // Validate timing
        assert!(timestamp::now_seconds() >= series.expiry, E_NOT_EXPIRED);

        let settlement_price = oracle::get_settlement_price(
            series.underlying,
            series.expiry
        );

        // Calculate what writer owes
        let liability = calculate_payout(&series, settlement_price, position.amount);

        // Return excess collateral
        let return_amount = if (position.collateral_locked > liability) {
            position.collateral_locked - liability
        } else {
            0
        };

        // Consume position
        let ShortPosition {
            series_id: _,
            amount: _,
            collateral_locked: _,
            entry_price: _,
            created_at: _
        } = position;

        if (return_amount > 0) {
            let coins = collateral::release<CollateralCoin>(writer_addr, return_amount);
            coin::deposit(writer_addr, coins);
        }
    }

    /// Auto-settle expired positions (can be called by anyone, e.g., keeper bot)
    public entry fun auto_settle_batch<CollateralCoin>(
        series_id: SeriesId,
        holders: vector<address>,
    ) {
        let i = 0;
        while (i < vector::length(&holders)) {
            let holder = *vector::borrow(&holders, i);
            // Settle if position exists
            if (exists<LongPosition>(holder)) {
                settle_for_holder<CollateralCoin>(holder);
            }
            i = i + 1;
        }
    }
}

Collateral & Margin

Collateral Requirements

For MVP, use simple full-collateral model:

Position Type Collateral Required
Short Call max(strike, spot) * contracts
Short Put strike * contracts
Long Premium only (paid upfront)
module options_protocol::margin {

    /// Calculate collateral required to write options
    public fun calculate_writer_collateral(
        series: &OptionSeries,
        amount: u64,
        spot_price: u64,
    ): u64 {
        if (series.option_type == CALL) {
            // Call writer: obligated to deliver underlying at strike
            // Max loss is unlimited, but practical collateral:
            // max(spot, strike) * 1.25 (safety buffer)
            let base = if (spot_price > series.strike) {
                spot_price
            } else {
                series.strike
            };
            (base * amount * 125) / (PRECISION * 100)
        } else {
            // Put writer: obligated to buy at strike
            // Max loss = strike (if underlying goes to 0)
            (series.strike * amount * 125) / (PRECISION * 100)
        }
    }

    /// Check if position is undercollateralized
    public fun is_liquidatable(
        position: &ShortPosition,
        spot_price: u64,
    ): bool {
        let series = series::get_series(position.series_id);
        let required = calculate_writer_collateral(&series, position.amount, spot_price);
        let threshold = (required * LIQUIDATION_THRESHOLD) / 10000;  // 80%

        position.collateral_locked < threshold
    }
}

Future: Portfolio Margin (Phase 2+)

Portfolio margin nets positions for capital efficiency:

Example:
- Long 1 BTC Call @ 30K
- Short 1 BTC Call @ 35K
= Bull Call Spread

Without portfolio margin: Full collateral for short leg
With portfolio margin: Max loss = $5K (spread width)

This requires:

  • Position tracking by account
  • Risk engine calculating net Greeks
  • More complex liquidation logic

Risk Management

Liquidation Engine

module options_protocol::liquidation {

    /// Liquidate undercollateralized position
    public entry fun liquidate<CollateralCoin>(
        liquidator: &signer,
        position_owner: address,
    ) acquires ShortPosition {
        let position = borrow_global<ShortPosition>(position_owner);
        let spot = oracle::get_spot_price(series::get_underlying(position.series_id));

        // Verify position is liquidatable
        assert!(margin::is_liquidatable(position, spot), E_NOT_LIQUIDATABLE);

        // Calculate liquidation amounts
        let debt = margin::calculate_writer_collateral(
            &series::get_series(position.series_id),
            position.amount,
            spot
        );

        // Liquidator receives bonus (5% of collateral)
        let bonus = (position.collateral_locked * 500) / 10000;
        let liquidator_reward = bonus;

        // Close position
        let position = move_from<ShortPosition>(position_owner);
        let ShortPosition {
            series_id,
            amount,
            collateral_locked,
            entry_price: _,
            created_at: _
        } = position;

        // Pay liquidator
        let reward_coins = collateral::withdraw<CollateralCoin>(
            position_owner,
            liquidator_reward
        );
        coin::deposit(signer::address_of(liquidator), reward_coins);

        // Remaining collateral goes to insurance fund / covers debt
        let remaining = collateral_locked - liquidator_reward;
        collateral::transfer_to_insurance<CollateralCoin>(remaining);

        events::emit_liquidation(position_owner, series_id, amount, liquidator_reward);
    }
}

Circuit Breakers

/// Emergency pause
public entry fun pause(admin: &signer) acquires ProtocolConfig {
    let config = borrow_global_mut<ProtocolConfig>(@options_protocol);
    assert!(signer::address_of(admin) == config.admin, E_NOT_ADMIN);
    config.paused = true;
}

/// Max position size per account
const MAX_POSITION_SIZE: u64 = 1000_00000000;  // 1000 contracts

/// Max open interest per series
const MAX_OPEN_INTEREST: u64 = 100000_00000000;  // 100K contracts

Phased Development Plan

Phase 1: Core MVP (Month 1-2)

Goal: Basic options that can be written, traded, and settled

Deliverables:

  • Core type definitions
  • Option series management
  • Pyth oracle integration (spot prices)
  • Historical volatility calculation
  • Full-collateral margin system
  • European settlement logic
  • Basic frontend (write, settle)

Out of Scope:

  • Orderbook integration
  • Greeks display
  • Advanced margin

Milestones:

  • Week 2: Types, errors, config modules complete
  • Week 4: Oracle adapter working with Pyth testnet
  • Week 6: Write and settle functions working on testnet
  • Week 8: Basic frontend deployed

Phase 2: Orderbook Integration (Month 3-4)

Goal: Real price discovery through Hyperion

Deliverables:

  • Hyperion market creation for options
  • Order placement/cancellation
  • Fill handling and position creation
  • Collateral locking on order placement
  • Premium transfers

Milestones:

  • Week 10: Hyperion testnet integration
  • Week 12: End-to-end trade flow working
  • Week 14: Frontend orderbook UI
  • Week 16: Testnet launch with community testing

Phase 3: Production Hardening (Month 5-6)

Goal: Mainnet-ready protocol

Deliverables:

  • Security audit
  • Liquidation engine
  • Keeper bot for settlements
  • IV oracle (off-chain feed)
  • Frontend polish
  • Documentation

Milestones:

  • Week 18: Audit started
  • Week 20: Audit findings fixed
  • Week 22: Mainnet soft launch (limited series)
  • Week 24: Full mainnet launch

Future Phases

Phase 4: Capital Efficiency

  • Portfolio margin
  • Cross-collateral
  • Partial collateralization

Phase 5: Product Expansion

  • American options
  • Weekly expiries
  • More underlyings (SOL, etc.)
  • Structured products

Technical Risks & Mitigations

Risk Impact Likelihood Mitigation
Pyth oracle manipulation High Low Use TWAP, multiple sources
Hyperion integration blocked Medium Medium Fallback to custom orderbook
IV oracle inaccuracy Medium High Start with historical vol, iterate
Smart contract bug Critical Medium Audit, formal verification
Low liquidity High High Market maker partnerships, incentives
Regulatory issues High Medium Focus on non-US initially

Audit Strategy

  1. Internal review: Code review before each phase
  2. Formal verification: Use Move Prover for critical invariants
  3. External audit: Budget $30-50K for professional audit
  4. Bug bounty: Launch with Immunefi program

Testing Strategy

Unit Tests (Move)
├── types_test.move
├── pricing_test.move
├── margin_test.move
├── settlement_test.move
└── integration_test.move

Integration Tests
├── Full trade lifecycle
├── Settlement at various prices
├── Liquidation scenarios
└── Oracle failure handling

Testnet Testing
├── Community beta
├── Stress testing
└── Edge case exploration

Appendix: Key Addresses & Resources

Aptos Ecosystem

Resource Link
Pyth Aptos https://docs.pyth.network/price-feeds/contract-addresses/aptos
Hyperion https://hyperion.xyz
Aries Markets https://ariesmarkets.xyz
Aptos Explorer https://explorer.aptoslabs.com

Reference Implementations

Protocol Chain Architecture Study For
Derive V2 Ethereum L2 Hybrid orderbook Capital efficiency
Aevo OP Stack Off-chain matching Performance
Lyra V1 Optimism AMM Simpler model
Zeta Solana Orderbook Similar performance profile

Contact for Partnerships

  • Hyperion: Discord for integration support
  • Pyth: Apply for price feed sponsorship
  • Aptos Foundation: Grant application for audit funding