Read Data Feed On-Chain
This guide will show you how to read a Switchboard data feed.
In order to read a Switchboard data feed using Anchor, we will need to:
- Add the switchboard-solana dependency
- Create the
ReadResult
Accounts context containing the Switchboard data feed - Create the
read_result
instruction with theReadResult
Accounts context - Submit a transaction on-chain with the
read_result
instruction
View the 01_feed_client example on Github.
Not using Anchor?
View the 02_spl_native example on Github.
1. Add switchboard-solanaโ
Add the switchboard-solana
crate to your Cargo.toml file:
[dependencies]
switchboard-solana = "0.9"
2. ReadResult
Contextโ
Anchor provides the anchor-lang AccountLoader trait to:
- verify the account has the correct discriminator (all AggregatorAccounts share the same first 8 bytes)
- the account is owned by the program ID defined in the switchboard-solana crate
The ReadResult
Accounts context would look like:
use anchor_lang::prelude::*;
use switchboard_solana::{AggregatorAccountData};
#[derive(Accounts)]
pub struct ReadResult<'info> {
pub switchboard_aggregator: AccountLoader<'info, AggregatorAccountData>,
}
You should store the aggregator's pubkey somewhere in your program state and verify the expected aggregator was passed into your instruction.
If you are building a DeFi market you may have a MyMarket account type storing
the SOL/USD Switchboard feed address in the field switchboard_aggregator
. You
can then verify this pubkey in the Accounts context like the following:
use anchor_lang::prelude::*;
use switchboard_solana::{AggregatorAccountData};
#[account(zero_copy)]
pub struct MyMarket {
pub bump: u8,
pub switchboard_aggregator: Pubkey,
}
#[derive(Accounts)]
pub struct ReadResult<'info> {
#[account(
has_one = switchboard_aggregator
)]
pub market: AccountLoader<'info, MyMarket>,
pub switchboard_aggregator: AccountLoader<'info, AggregatorAccountData>,
}
3. read_result
Instructionโ
Now lets add a read_result
instruction to our program and pass in the
ReadResult
context.
First, we will deserialize the account data into the AggregatorAccountData (docs.rs) struct.
Next, we will use the TryInto trait to convert the
SwitchboardDecimal (docs.rs)
into the f64
primitive because it's easier to work with.
The SwitchboardDecimal
struct is a basic wrapper around the
rust-decimal (docs.rs)
implementation.
Finally, we will verify the feed was updated in the last 300 seconds so we aren't consuming a stale value.
use anchor_lang::prelude::*;
use anchor_lang::solana_program::clock;
use std::convert::TryInto;
#[program]
pub mod anchor_feed_parser {
use super::*;
pub fn read_result(
ctx: Context<ReadResult>
) -> anchor_lang::Result<()> {
let feed = &ctx.accounts.switchboard_aggregator.load()?;
// get result
let val: f64 = feed.get_result()?.try_into()?;
// check whether the feed has been updated in the last 300 seconds
feed.check_staleness(clock::Clock::get().unwrap().unix_timestamp, 300)
.map_err(|_| error!(FeedErrorCode::StaleFeed))?;
msg!("Current feed result is {}!", val);
// Your custom logic here
Ok(())
}
}
#[error_code]
#[derive(Eq, PartialEq)]
pub enum FeedErrorCode {
#[msg("Switchboard feed has not been updated in 5 minutes")]
StaleFeed,
}
4. Off-Chain: Read the result!โ
We will need to build a Solana transaction that contains our aggregator address to read.
import * as anchor from "@coral-xyz/anchor";
import { sleep } from "@switchboard-xyz/common";
import {
AggregatorAccount,
SwitchboardProgram,
} from "@switchboard-xyz/solana.js";
import assert from "assert";
import { AnchorFeedParser } from "../target/types/anchor_feed_parser";
describe("anchor-feed-parser test", () => {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const feedParserProgram: anchor.Program<AnchorFeedParser> =
anchor.workspace.AnchorFeedParser;
let switchboard: SwitchboardProgram;
let aggregatorAccount: AggregatorAccount;
it("Reads a Switchboard data feed", async () => {
const signature = await feedParserProgram.methods
.readResult()
.accounts({ switchboardAggregator: aggregatorAccount.publicKey })
.rpc();
// wait for RPC
await sleep(2000);
const logs = await provider.connection.getParsedTransaction(
signature,
"confirmed"
);
console.log(JSON.stringify(logs?.meta?.logMessages, undefined, 2));
const match = JSON.stringify(logs?.meta?.logMessages ?? []).match(
new RegExp(/Current feed result is (?<feed_result>\d+)/)
);
const feedResult = Number(match?.groups?.feed_result ?? null);
console.log(`Feed Result: ${feedResult}`);
});
});