If you're using the builder, you'll be confronted with a few inputs configured to some defaults, and an empty name field.
The required fields are:
Name: The identifier for the feed in the Switchboard UI.
Authority: The address with authority to modify this feed. Feeds are always initialized with the creator as authority, but they can later be set to something other than the creator. This feature can be useful for DAO controlled feeds.
Max Variance: The maximum allowed variance of the job results (as a percentage) for an update to be accepted on-chain.
Min Responses: The minimum number of successful job responses required for an update to be accepted on-chain.
Sample Size: The number of samples that will be considered when reading a feed.
Max Staleness: The maximum staleness a sample is allowed when reading a feed on-chain.
Configuring Feeds in the Builder
Setting these configs in the builder is as simple as filling in the inputs and clicking "Create Account" to make the feed:
Switchboard Feed Page
Once you create the feed, you'll be taken to a page where you can see the current value for the feed (waiting to be populated on-chain).
Since this is an On-Demand feed, updates will be read in only when they're needed (alternatively with a pusher service).
Another important component is Switchboard's instance of Crossbar, a convenience server for using on-demand updates.
Creating a Feed in Typescript
This section will build on Designing a Feed. Before continuing, ensure you have a funded Solana Keypair file. The easiest way to create a new solana-keypair is using the solana-keygen cli tool.
In this case you can create a local wallet with:
solana-keygen new --outfile path/to/solana-keypair.json
If you don't feel like dealing with this tool, remember that the solana keypair file is just the representation of the private key in the format of an array of numbers:
Get an instance of the Switchboard queue (after the simulation success). By default, Bun enables top-level async-await which is useful in this instance.
Note: Queues in the context of Switchboard are subnets of Oracles that respond to feed updates.
Call getDefaultQueue(solanaRPCUrl: string) to pull the Switchboard Mainnet Queue. Calling getDefaultDevnetQueue(solanaRPCUrl: string) will pull the Switchboard Devnet queue.
// Get the queue for the network you're deploying onlet queue =awaitgetDefaultQueue(); // or `getDefaultDevnetQueue()` for devnet,
For ease of use, and displaying your Jobs on the Explorer, you should store your feeds with CrossBar. This is a service that will store valid Job definitions on IPFS.
// Get the default crossbar server clientconstcrossbarClient=CrossbarClient.default();// Upload jobs to Crossbar, which pins valid feeds on ipfs// Feeds are associated with a specific queue, which is why we need to pass it inconst { feedHash } =awaitcrossbarClient.store(queue.pubkey.toBase58(), jobs);
Load your Solana payer's keypair from your keypair file. Log the payer just to be certain that it's the right account. It must be funded on the target Solana network for pull feed creation to work.
// Get the payer keypairconstpayer=awaitAnchorUtils.initKeypairFromFile("path/to/solana-keypair.json");console.log("Using Payer:",payer.publicKey.toBase58(),"\n");
Generate a new pull feed object, this is just generating a new keypair associated with the PullFeedAccountData, and creating a wrapper object:
// Get the initialization for the pull feedsconstix=awaitpullFeed.initIx({ name:"BTC Price Feed",// the feed name (max 32 bytes) queue:queue.pubkey,// the queue of oracles to bind to maxVariance:1.0,// the maximum variance allowed for the feed results minResponses:1,// minimum number of responses of jobs to allow feedHash:Buffer.from(feedHash.slice(2),"hex"),// the feed hash minSampleSize:1; // The minimum number of samples required for setting feed value maxStaleness: 60; // The maximum number of slots that can pass before a feed value is considered stale. payer: payer.publicKey,// the payer of the feed});
Create the transaction and configure it so it can be sent to a Solana validator. This example uses the asV0Tx, a convenience function that's not necessary (but can be useful) for building transactions with priority fees.
The assembled contract should go through the steps we've built:
Configure the OracleJobs and Simulate
Pick the queue for the target network (Mainnet or Devnet)
Store with Crossbar so it's visible in the explorer
Build the PullFeed's initIx instruction
Build and send the transaction on Solana
index.ts
import { AnchorUtils, PullFeed, CrossbarClient, OracleJob, getDefaultQueue, getDefaultDevnetQueue, asV0Tx,} from"@switchboard-xyz/on-demand";constjobs:OracleJob[] = [newOracleJob({ tasks: [ { httpTask: { url:"https://binance.com/api/v3/ticker/price", }, }, { jsonParseTask: { path:"$[?(@.symbol == 'BTCUSDT')].price", }, }, ], }),];console.log("Running simulation...\n");// Print the jobs that are being run.constjobJson=JSON.stringify({ jobs:jobs.map((job) =>job.toJSON()) });console.log(jobJson);console.log();// Serialize the jobs to base64 strings.constserializedJobs=jobs.map((oracleJob) => {constencoded=OracleJob.encodeDelimited(oracleJob).finish();constbase64=Buffer.from(encoded).toString("base64");return base64;});// Call the simulation server.constresponse=awaitfetch("https://api.switchboard.xyz/api/simulate", { method:"POST", headers: [["Content-Type","application/json"]], body:JSON.stringify({ cluster:"Mainnet", jobs: serializedJobs }),});// Check response.if (response.ok) {constdata=awaitresponse.json();console.log(`Response is good (${response.status})`);console.log(JSON.stringify(data,null,2));} else {console.log(`Response is bad (${response.status})`);throwawaitresponse.text();}console.log("Storing and creating the feed...\n");// Get the queue for the network you're deploying onlet queue =awaitgetDefaultQueue(); // or `getDefaultDevnetQueue()` for devnet,// Get the crossbar server clientconstcrossbarClient=CrossbarClient.default();// Get the payer keypairconstpayer=awaitAnchorUtils.initKeypairFromFile("path/to/solana-keypair.json");console.log("Using Payer:",payer.publicKey.toBase58(),"\n");// Upload jobs to Crossbar, which pins valid feeds on ipfsconst { feedHash } =awaitcrossbarClient.store(queue.pubkey.toBase58(), jobs);const [pullFeed,feedKeypair] =PullFeed.generate(queue.program);constix=awaitpullFeed.initIx({ name:"BTC Price Feed",// the feed name (max 32 bytes) queue:queue.pubkey,// the queue of oracles to bind to maxVariance:1.0,// the maximum variance allowed for the feed results minResponses:1,// minimum number of responses of jobs to allow feedHash:Buffer.from(feedHash.slice(2),"hex"),// the feed hash minSampleSize:1; // The minimum number of samples required for setting feed value maxStaleness: 300; // The maximum number of slots that can pass before a feed value is considered stale. payer: payer.publicKey,// the payer of the feed});consttx=awaitasV0Tx({ connection:queue.program.provider.connection, ixs: [ix], payer:payer.publicKey, signers: [payer, feedKeypair], computeUnitPrice:200_000, computeUnitLimitMultiple:1.5,});// simulate the transactionconstsimulateResult=awaitqueue.program.provider.connection.simulateTransaction(tx, { commitment:"processed", });console.log(simulateResult);constsig=awaitqueue.program.provider.connection.sendTransaction(tx, { preflightCommitment:"processed", skipPreflight:false,});console.log(`Feed ${feedKeypair.publicKey} initialized: ${sig}`);
Example output:
If it's successful, you should see a successful simulation in the console. Just to verify that the account was initialized correctly, search for the printed key or signature on a Solana Explorer.