Switchboard: the customisable oracle, queues, jobs, and aggregators
Switchboard is Solana's customisable oracle network. Queues, oracles, jobs, aggregators, randomness — here's the on-chain shape and how to wire each up.
Switchboard is Solana's other major oracle. Pyth handles a curated list of canonical asset prices; Switchboard lets you publish a feed of anything by writing a job definition. Sports scores, custom CEX prices, weather data, election results, on-chain randomness — same primitive across all of them.
The four account types
Queue → the oracle network that processes update requests
Oracle → a participant in a queue, runs the off-chain worker
Aggregator → the on-chain feed account holding the latest aggregated value
Job → a definition of how to fetch + transform raw data
(referenced by Aggregators; each Aggregator can have N Jobs)
Crank → a permissionless turn-the-handle account that triggers
due updatesSwitchboard's permissionless design: anyone can create an Aggregator with their own Jobs, pay into a queue, and start receiving updates from the queue's oracles. The on-chain program enforces validation; the off-chain oracles compete to publish.
What a Job looks like
Jobs are serialized protobuf describing a pipeline of tasks: HTTP fetch → JSON path extract → math transform. Each oracle in the queue runs the same job, posts its result, and the aggregator takes the median (with outlier rejection).
{
"tasks": [
{
"httpTask": {
"url": "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=solana"
}
},
{
"jsonParseTask": {
"path": "$[0].current_price"
}
}
]
}That's the entire definition for a SOL/USD feed. Switch solana to bitcoin and you have BTC/USD. Replace the URL with a sports API and JSON path with a score field, and you have a live game score feed.
The Aggregator account
pub struct AggregatorAccountData {
pub name: [u8; 32],
pub metadata: [u8; 128],
pub author_wallet: Pubkey,
pub queue_pubkey: Pubkey, // which queue this feed lives in
pub oracle_request_batch_size: u32, // how many oracles must respond
pub min_oracle_results: u32, // minimum valid responses for an update
pub min_job_results: u32,
pub min_update_delay_seconds: u32, // throttle — minimum seconds between updates
pub start_after: i64,
pub variance_threshold: SwitchboardDecimal, // % change required to trigger an update
pub force_report_period: i64,
pub expiration: i64,
pub consecutive_failure_count: u64,
pub next_allowed_update_time: i64,
pub is_locked: bool,
pub crank_pubkey: Pubkey,
pub latest_confirmed_round: AggregatorRound {
num_success: u32,
num_error: u32,
is_closed: bool,
round_open_slot: u64,
round_open_timestamp: i64,
result: SwitchboardDecimal, // the actual aggregated value
std_deviation: SwitchboardDecimal,
min_response: SwitchboardDecimal,
max_response: SwitchboardDecimal,
oracle_pubkeys_data: [Pubkey; 16],
medians_data: [SwitchboardDecimal; 16],
current_payout: [i64; 16],
errors_fulfilled: [bool; 16],
medians_fulfilled: [bool; 16],
},
pub current_round: AggregatorRound,
pub job_pubkeys_data: [Pubkey; 16],
pub job_hashes: [Hash; 16],
pub job_pubkeys_size: u32,
pub jobs_checksum: [u8; 32],
pub authority: Pubkey,
pub history_buffer: Pubkey, // optional ring-buffer of past results
pub previous_confirmed_round_result: SwitchboardDecimal,
pub previous_confirmed_round_slot: u64,
pub disable_crank: bool,
pub job_weights: [u8; 16],
pub creation_timestamp: i64,
pub _ebuf: [u8; 130],
}Reading a Switchboard feed
use anchor_lang::prelude::*;
use switchboard_solana::AggregatorAccountData;
#[derive(Accounts)]
pub struct UseSwitchboard<'info> {
/// CHECK: validated by Switchboard
pub aggregator: AccountLoader<'info, AggregatorAccountData>,
}
pub fn read_price(ctx: Context<UseSwitchboard>) -> Result<()> {
let agg = ctx.accounts.aggregator.load()?;
// Returns SwitchboardDecimal { mantissa: i128, scale: u32 }
let result = agg.get_result()?;
let price_f64: f64 = result.try_into()?;
// Reject if stale (>5 minutes old)
agg.check_staleness(Clock::get()?.unix_timestamp, 300)?;
// Reject if confidence too wide (variance > 1%)
agg.check_confidence_interval(SwitchboardDecimal::from_f64(0.01))?;
msg!("price: {}", price_f64);
Ok(())
}Switchboard randomness (VRF)
Beyond price feeds, Switchboard publishes a verifiable random function — on-chain randomness backed by an oracle's cryptographic proof. Useful for games, fair draws, mint ordering.
use switchboard_solana::VrfAccountData;
pub fn use_random(ctx: Context<UseVrf>) -> Result<()> {
let vrf = ctx.accounts.vrf_account.load()?;
let randomness = vrf.get_result()?; // [u8; 32]
// Use first 8 bytes as u64
let n = u64::from_le_bytes(randomness[..8].try_into().unwrap());
let dice = (n % 6) + 1;
msg!("rolled: {}", dice);
Ok(())
}Switchboard vs Pyth
Pyth Switchboard
─────────────────────────────────────────────────────────────────
Model Push (account) Push (account)
Custom feeds No — curated symbols Yes — any job definition
Setup cost Free (read the account) Pay queue lease + per-update
Update cadence Every slot Configurable (variance + delay)
Confidence First-class (1-sigma) Variance + std deviation
Randomness No Yes (VRF)
Best for Canonical asset prices Custom data, long-tail symbolsReferences
Pyth for canonical asset prices, Switchboard for everything else. Both run in production on every major Solana DeFi protocol's read path.