Skip to main content

ADR-002: Pyth Network as Exclusive Oracle

Status

Accepted

Context

Prediction markets require reliable, manipulation-resistant price data for settlement. The oracle is critical infrastructure - incorrect prices lead to incorrect settlements and potential loss of user funds.

Requirements

  1. Accuracy - Prices must reflect true market values
  2. Timeliness - Prices must be fresh (low latency)
  3. Availability - Prices must be consistently available
  4. Manipulation Resistance - Prices must resist attacks
  5. Decentralization - No single point of failure or trust

Options Evaluated

OracleLatencyCoverageDecentralizationSolana Native
Pyth Network~400ms350+ assets90+ publishersYes
Chainlink~1s100+ assets15+ nodesVia bridges
Switchboard~1s100+ assetsVariableYes
Custom TWAPVariableAnySingle pointYes

Decision

Use Pyth Network as the exclusive oracle provider for all price data.

Sampling Rule

Use "First valid price at or after boundary" (Sampling Rule A):

Start Price: First Pyth price where publish_time >= t_start
End Price: First Pyth price where publish_time >= t_end

Resolution Logic

if end_price >= start_price:
    outcome = UP
else:
    outcome = DOWN

Note: Equality resolves to UP

Consequences

Positive

  1. Proven Infrastructure

    • Battle-tested on Solana mainnet
    • Used by major DeFi protocols (Jupiter, Drift, etc.)
    • Continuous uptime track record
  2. High Decentralization

    • 90+ independent data publishers
    • Aggregation prevents single-source manipulation
    • No trusted intermediary
  3. Native Solana Integration

    • No bridge risk
    • Low latency (~400ms)
    • Direct account access
  4. Rich Asset Coverage

    • 350+ price feeds
    • Crypto, forex, commodities, equities
    • Easy to add new markets
  5. Confidence Intervals

    • Each price includes confidence metric
    • Can gate on confidence for safety
    • Transparent uncertainty

Negative

  1. Single Oracle Dependency

    • No fallback if Pyth fails
    • Protocol stops if Pyth stops
    • Concentrated trust
  2. External Risk

    • Pyth bugs affect Seesaw
    • Pyth governance changes affect Seesaw
    • Cannot control oracle evolution
  3. Price Timing

    • Must wait for Pyth to publish after boundary
    • Some latency between boundary and snapshot
    • Predictable timing could enable MEV

Mitigations

  1. Idempotent Snapshots

    • Safe to retry if first attempt fails
    • No penalty for late snapshot (within reason)
  2. Immutable Snapshots

    • Once captured, prices cannot change
    • Eliminates re-org manipulation
  3. Confidence Gating (Optional)

    • Can reject prices with wide confidence
    • Protects against uncertain prices
  4. Multiple Crank Operators

    • Anyone can capture snapshots
    • Redundancy prevents single-point failure

Alternatives Considered

Alternative 1: Multi-Oracle Aggregation

Use multiple oracles (Pyth + Chainlink + Switchboard) and aggregate.

Rejected because:

  • Increased complexity
  • Different update frequencies cause inconsistency
  • Bridges introduce additional risk for non-native oracles
  • Higher gas costs for multiple reads

Alternative 2: Custom TWAP

Calculate time-weighted average price from on-chain DEX trades.

Rejected because:

  • Manipulable with large trades
  • Requires sufficient on-chain liquidity
  • Complex implementation
  • Single source of truth (one DEX)

Alternative 3: Chainlink (via Bridge)

Use Chainlink through Wormhole or similar bridge.

Rejected because:

  • Bridge introduces trust assumptions
  • Higher latency
  • Less Solana ecosystem integration
  • Additional failure modes

Alternative 4: Dispute Windows

Allow users to dispute oracle prices within a window.

Rejected because:

  • Delays settlement
  • Complex dispute resolution
  • Creates attack surface for denial-of-service
  • Inconsistent with "instant settlement" goal

Implementation Notes

Price Validation

pub fn validate_pyth_price(
    price: &PythPrice,
    boundary_time: i64,
    clock: &Clock,
) -> Result<()> {
    // Price must be positive
    require!(price.price > 0, Error::InvalidPrice);

    // Must be at or after boundary
    require!(price.publish_time >= boundary_time, Error::StaleOracle);

    // Not too far in future (clock skew protection)
    require!(
        price.publish_time <= clock.unix_timestamp + 60,
        Error::FutureOracle
    );

    Ok(())
}

Feed Verification

pub fn validate_pyth_feed(
    feed_account: &AccountInfo,
    expected_feed_id: &[u8; 32],
) -> Result<()> {
    // Must be owned by Pyth program
    require!(
        feed_account.owner == &PYTH_PROGRAM_ID,
        Error::InvalidOracleOwner
    );

    // Feed ID must match market configuration
    let feed_data = feed_account.try_borrow_data()?;
    let feed_id = &feed_data[..32];
    require!(
        feed_id == expected_feed_id,
        Error::FeedIdMismatch
    );

    Ok(())
}

Snapshot Immutability

pub fn capture_start_snapshot(
    market: &mut MarketAccount,
    price: i64,
    timestamp: i64,
) -> Result<()> {
    // Idempotency: already captured
    if market.start_price != 0 {
        return Ok(());  // No-op
    }

    // Capture immutably
    market.start_price = price;
    market.start_price_timestamp = timestamp;

    Ok(())
}

Security Considerations

Oracle Manipulation

AttackMitigation
Flash loan price manipulationPyth aggregates from multiple sources
Stale price replayTimestamp validation
Future price injectionClock skew bounds
Feed substitutionFeed ID verification

Confidence Gating

Optional feature to reject uncertain prices:

const MAX_CONFIDENCE_RATIO: u64 = 100; // 1%

if price.conf * 10000 / price.price.abs() > MAX_CONFIDENCE_RATIO {
    return Err(Error::ConfidenceTooWide);
}

References

Changelog

  • 2026-01: Initial decision
  • 2026-01: Added confidence gating option