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
- Accuracy - Prices must reflect true market values
- Timeliness - Prices must be fresh (low latency)
- Availability - Prices must be consistently available
- Manipulation Resistance - Prices must resist attacks
- Decentralization - No single point of failure or trust
Options Evaluated
| Oracle | Latency | Coverage | Decentralization | Solana Native |
|---|---|---|---|---|
| Pyth Network | ~400ms | 350+ assets | 90+ publishers | Yes |
| Chainlink | ~1s | 100+ assets | 15+ nodes | Via bridges |
| Switchboard | ~1s | 100+ assets | Variable | Yes |
| Custom TWAP | Variable | Any | Single point | Yes |
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
-
Proven Infrastructure
- Battle-tested on Solana mainnet
- Used by major DeFi protocols (Jupiter, Drift, etc.)
- Continuous uptime track record
-
High Decentralization
- 90+ independent data publishers
- Aggregation prevents single-source manipulation
- No trusted intermediary
-
Native Solana Integration
- No bridge risk
- Low latency (~400ms)
- Direct account access
-
Rich Asset Coverage
- 350+ price feeds
- Crypto, forex, commodities, equities
- Easy to add new markets
-
Confidence Intervals
- Each price includes confidence metric
- Can gate on confidence for safety
- Transparent uncertainty
Negative
-
Single Oracle Dependency
- No fallback if Pyth fails
- Protocol stops if Pyth stops
- Concentrated trust
-
External Risk
- Pyth bugs affect Seesaw
- Pyth governance changes affect Seesaw
- Cannot control oracle evolution
-
Price Timing
- Must wait for Pyth to publish after boundary
- Some latency between boundary and snapshot
- Predictable timing could enable MEV
Mitigations
-
Idempotent Snapshots
- Safe to retry if first attempt fails
- No penalty for late snapshot (within reason)
-
Immutable Snapshots
- Once captured, prices cannot change
- Eliminates re-org manipulation
-
Confidence Gating (Optional)
- Can reject prices with wide confidence
- Protects against uncertain prices
-
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
| Attack | Mitigation |
|---|---|
| Flash loan price manipulation | Pyth aggregates from multiple sources |
| Stale price replay | Timestamp validation |
| Future price injection | Clock skew bounds |
| Feed substitution | Feed 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
- Pyth Network Documentation
- ORACLE.md - Full oracle specification
- Oracle manipulation research papers
Changelog
- 2026-01: Initial decision
- 2026-01: Added confidence gating option