All articles
Risk·March 26, 2026·7 min read

Pyth vs Chainlink vs Switchboard: Oracle Risk on Solana Perps

How Pyth, Chainlink, and Switchboard differ on staleness and manipulation risk, and what a Solana perp-DEX oracle audit must check for each.

A perp DEX on Solana is only as solvent as its last price update. Get the oracle wrong — stale, manipulated, or just misread by the integrating program — and liquidations either fire on garbage data or don't fire at all while a position bleeds the insurance fund dry. Mango Markets lost $114M in October 2022 not because Pyth lied to it, but because the market Pyth was pricing (MNGO-PERP itself, thinly traded) could be pushed by a single actor with enough capital. The oracle worked exactly as designed. The protocol's risk parameters didn't.

That distinction — oracle correctness versus oracle appropriateness — is where most Solana perp audits go wrong. Pyth, Chainlink, and Switchboard all solve "get a price on-chain" differently, and each solution creates a different failure mode you have to check for explicitly, not assume away.

How each oracle actually gets a price on-chain

Pyth Network is pull-based. Roughly 100+ first-party publishers (exchanges, market makers) push signed price observations to Pythnet, an app-chain that aggregates them into a price plus a confidence interval every ~400ms. Your Solana program doesn't get this automatically — a crank or the user's own transaction has to pull the latest update from Hermes and post it on-chain via postUpdateAtomic or similar, usually in the same transaction as the trade. This is why Pyth feels fast: the price you read is whatever the taker's own transaction bothered to fetch, which is good for freshness and bad if nobody incentivizes anyone to keep pulling during low-volume periods.

Chainlink on Solana runs push-based aggregation through off-chain reporting (OCR): a decentralized set of node operators sign an aggregated report off-chain, and it's written on-chain either on a heartbeat interval (commonly 3600s for slower pairs, much tighter for majors) or when price deviates past a threshold (often 0.5%–1%). You read a stored account, no pull step required. Chainlink also ships Data Streams now, a lower-latency pull variant closer to Pyth's model, but the classic feed-account pattern is still what most Solana integrations use.

Switchboard (particularly Switchboard On-Demand) uses a permissionless oracle queue where operators run jobs inside TEEs (trusted execution environments, typically Intel SGX) and attest to the result. You define the data sources and aggregation logic yourself as a "job" — which is powerful for exotic assets with no Pyth/Chainlink feed, and dangerous if you configure that job badly, because now the oracle is only as honest as the queue you picked and the attestation chain you trust.

The staleness problem, concretely

Every one of these oracles can hand you an old price if you don't check for it. Pyth exposes publish_time right on the price account — if Clock::get()?.unix_timestamp - publish_time > threshold, reject the trade. Chainlink's aggregator has latestRoundData equivalents with updatedAt. Switchboard has its own staleness slot. The bug isn't that these fields don't exist — it's that a shocking number of Solana perp programs never check them, or check them against a threshold so loose (60+ seconds during a fast market) that it's meaningless.

// Pyth staleness + confidence check, roughly what we look for in audit
let price_feed = load_price_feed_from_account(&pyth_account)?;
let price = price_feed.get_price_no_older_than(clock.unix_timestamp, MAX_STALENESS_SECS)
    .ok_or(ErrorCode::StaleOracle)?;

// confidence interval too wide relative to price => volatile/thin market, don't trust it
let conf_ratio = (price.conf as f64) / (price.price.unsigned_abs() as f64);
require!(conf_ratio < MAX_CONF_RATIO, ErrorCode::ConfidenceTooWide);

That second check matters more than most teams realize. Pyth's confidence interval widens automatically during volatility or thin publisher agreement — it's the market telling you it's unsure. Ignoring it and taking price.price at face value is how you get liquidated at a print that reversed two slots later.

Manipulation surface by oracle

Pyth's attack surface is mostly about the underlying market, not the oracle mechanism — see Mango. If the asset itself is thin and publishers source from a handful of venues, an attacker can move the real price, and Pyth will faithfully (and briefly) report the manipulated number. Your circuit breaker needs to live in your protocol, not wait for Pyth to fix it.

Chainlink's surface is more about staleness under gas/fee spikes on the reporting side and deviation-threshold gaps — if the threshold is 1% and price moves 0.9% five times in a row without crossing it, you can sit on a price that's 4.5% stale until the heartbeat fires. Also check whether the specific feed you're using has a documented minimum answer / max answer floor and ceiling (older aggregators had these; a flash crash below the floor freezes the reported price at the floor, which has caused real liquidation cascades on EVM chains and is worth checking Solana deployments for too).

Switchboard's surface is configuration risk. Because you (or the feed you're consuming) define the job, an audit has to actually read the job definition: which exchanges does it query, how many, what's the aggregation (median vs mean — mean is trivially manipulable with one bad source), and crucially, is the queue authority upgradeable by someone other than a multisig you trust. A permissionless queue sounds decentralized until you check who can add oracles to it.

Dimension Pyth Chainlink Switchboard
Update model Pull, publisher-aggregated Push, OCR heartbeat/deviation Push/pull, TEE-attested jobs
Native staleness signal publish_time + confidence interval updatedAt + heartbeat config Slot + attestation timestamp
Manipulation risk source Underlying thin market Stale reports under deviation gaps Misconfigured job / queue authority
Solana perp adoption Highest (Drift, Zeta, Flash) Moderate, growing via Data Streams Niche/long-tail assets, custom feeds
Operator decentralization ~100+ first-party publishers Independent node network Permissionless but queue-dependent
Audit focus Confidence interval enforcement Deviation threshold + floor/ceiling checks Job definition + queue authority review

What we actually check in a perp-DEX audit

Beyond staleness thresholds, every audit we run against oracle integrations checks: the feed account pubkey is hardcoded or governance-gated, never passed as an unchecked instruction argument (a classic Solana account-substitution bug — swap in a stale or attacker-controlled feed account and the program happily reads it); decimal/exponent handling matches across the feed and the market config; and there's a secondary source or hard circuit breaker for divergence beyond some sane percentage, so one oracle's bad day doesn't cascade into bad liquidations. If you're building or hardening a perp engine, that oracle-integration review is exactly the kind of thing we cover in a full code review and audit engagement — it's a small surface area with outsized blast radius when it's wrong.

None of this is a one-time check either. Feed configs get migrated, queues get re-pointed, thresholds get "temporarily" loosened during an incident and never tightened back — which is the argument for ongoing support and maintenance rather than a single audit and walking away. If you're weighing custody risk in the same review pass, it's worth reading how multisig and MPC wallets compare on custody risk for the keys that control your oracle config in the first place. And if your liquidation engine needs a model for absorbing bad-debt risk when an oracle does misfire, how the Hyperliquid HLP vault absorbs liquidation risk is a useful reference architecture even off Hyperliquid.

The verdict

For a Solana perp DEX trading liquid majors, use Pyth as primary — the pull model gives you the freshest possible price at trade time, and the confidence interval is a genuinely useful signal most competitors don't give you. Enforce a tight staleness window (under 5 seconds for majors) and a hard confidence-ratio cap, and treat any breach as "halt trading on this market," not "use the price anyway." Add Chainlink as a secondary/fallback feed for cross-checking on your top pairs — its push model means it's already sitting on-chain when you need a sanity check, no extra pull latency. Reach for Switchboard specifically for the long tail: assets Pyth and Chainlink don't cover, where you need a custom job. Just budget real audit time for reading that job definition line by line, because that's where the actual risk lives, not in the oracle protocol itself. If you're shipping the front end alongside this, our trading dashboard build work bakes these same staleness and confidence checks into the UI so traders see a stale-price warning before they get liquidated by one.

Need a bot like this built?

We design, build and run trading bots on Solana, Hyperliquid and Polymarket.

Start a project
#oracles#Solana#perpetuals#security audit#risk