Switchboard

Switchboard ist ein Oracle-Protokoll, das es Entwicklern ermöglicht, Daten für eine Vielzahl von Anwendungsfällen wie Preis-Feeds, NFT-Mindestpreise, Sportstatistiken oder sogar überprüfbare Zufälligkeiten on-chain zu beziehen. Im Allgemeinen ist Switchboard eine Off-Chain-Ressource, auf die sich Entwickler berufen können, um hochintegrierte Daten on-Chain zu überbrücken und die nächste Generation von Web3 und DeFi voranzutreiben.

Daten Feeds

Switchboard bietet eine JavaScript/TypeScript-Bibliothek namens @switchboard-xyz/switchboard-v2 . Diese Bibliothek kann verwendet werden, um On-Chain-Daten aus vorhandenen Datenfeeds zu erreichen oder Ihre eigenen benutzerdefinierten Feeds zu veröffentlichen. Erfahren Sie mehr darüber hieropen in new window

Daten aus einem Aggregator-Feed lesen

Press </> button to view full source
import {
  clusterApiUrl,
  Connection,
  Keypair,
  PublicKey,
  LAMPORTS_PER_SOL,
} from "@solana/web3.js";
import * as switchboard from "@switchboard-xyz/switchboard-v2";
async () => {
  const payer = Keypair.generate();
  const connection = new Connection(clusterApiUrl("devnet"));
  const program = await switchboard.loadSwitchboardProgram(
    "devnet",
    connection,
    payer
  );
  const airdropSignature = await connection.requestAirdrop(
    payer.publicKey,
    LAMPORTS_PER_SOL
  );

  await connection.confirmTransaction(airdropSignature);
  console.log("here");
  const aggregatorAccount = new switchboard.AggregatorAccount({
    program: program,
    publicKey: aggregatorKey,
  });
  const result: any = await aggregatorAccount.getLatestValue();

  console.log(result.toNumber());
};

Erstellen eines neuen Aggregator-Feed

Press </> button to view full source
import * as anchor from "@project-OpenBook/anchor";
import { Keypair } from "@solana/web3.js";
import {
  AggregatorAccount,
  loadSwitchboardProgram,
  LeaseAccount,
  OracleQueueAccount,
  SwitchboardPermission,
} from "@switchboard-xyz/switchboard-v2";

let payer: Keypair;
let authority: Keypair; // queue authority
const program = await loadSwitchboardProgram("devnet", undefined, payer);
const queueAccount = new OracleQueueAccount({
  program,
  publicKey: queuePubkey,
});

// aggregator
const aggregatorAccount = await AggregatorAccount.create(program, {
  name: Buffer.from("MY SOL/USD Feed"),
  batchSize: 1,
  minRequiredOracleResults: 1,
  minRequiredJobResults: 1,
  minUpdateDelaySeconds: 10,
  queueAccount,
  authority: authority.publicKey,
});

// permission
const permissionAccount = await PermissionAccount.create(program, {
  authority: authority.publicKey,
  granter: queueAccount.publicKey,
  grantee: aggregatorAccount.publicKey,
});
await aggregatorPermission.set({
  authority,
  permission: SwitchboardPermission.PERMIT_ORACLE_QUEUE_USAGE,
  enable: true,
});

// lease
const leaseContract = await LeaseAccount.create(program, {
  loadAmount: new anchor.BN(0),
  funder: tokenAccount,
  funderAuthority: authority,
  oracleQueueAccount: queueAccount,
  aggregatorAccount,
});

// job
const tasks: OracleJob.Task[] = [
  OracleJob.Task.create({
    httpTask: OracleJob.HttpTask.create({
      url: `https://ftx.us/api/markets/SOL_USD`,
    }),
  }),
  OracleJob.Task.create({
    jsonParseTask: OracleJob.JsonParseTask.create({ path: "$.result.price" }),
  }),
];
const jobData = Buffer.from(
  OracleJob.encodeDelimited(
    OracleJob.create({
      tasks,
    })
  ).finish()
);
const jobKeypair = anchor.web3.Keypair.generate();
const jobAccount = await JobAccount.create(program, {
  data: jobData,
  keypair: jobKeypair,
  authority: authority.publicKey,
});

// add job to aggregator
await aggregatorAccount.addJob(jobAccount, authority);

Lesen Sie Daten aus einem Aggregator-Feed-in-Programm

Switchboard bietet eine Kiste namens switchboard_v2 Erfahren Sie mehr darüber hieropen in new window

Press </> button to view full source
use anchor_lang::prelude::*;

use switchboard_v2::AggregatorAccountData;

declare_id!("HDa6A4wLjEymb8Cv3UGeGBnFUNCUEMpoiVBbkTKAkfrt");

#[program]
pub mod get_result {
    use super::*;
   
    pub fn get_result(ctx: Context<GetResult>) -> Result<()> {
        let aggregator = &ctx.accounts.aggregator_feed.load()?;
        let val:f64 = aggregator
            .get_result()?
            .try_into()?;

        msg!("Current feed result is {}!", val);
        Ok(())
    }
   
}

#[derive(Accounts)]
pub struct GetResult<'info> {
    pub authority: Signer<'info>,
    /// CHECK: field is unsafe
    pub aggregator_feed: AccountLoader<'info, AggregatorAccountData>, // pass aggregator key
}

So erstellen Sie einen Feed vom Publisher

Die offizielle Switchboard-Dokumentation enthält eine ausführliche Anleitung zum Erstellen eines Feeds vom Herausgeber. Sehen Sie es sich hieropen in new window an .

Oracles

Die einzigartige Funktion von Switchboard besteht darin, dass Sie Ihr eigenes Orakel erstellen und lokal ausführen können.

Erstelen eines Oracle

Press </> button to view full source
import * as anchor from "@project-OpenBook/anchor";
import { Keypair } from "@solana/web3.js";
import {
  loadSwitchboardProgram,
  OracleAccount,
  OracleQueueAccount,
} from "@switchboard-xyz/switchboard-v2";

let payer: Keypair;
const program = await loadSwitchboardProgram("devnet", undefined, payer);

const queueAccount = new OracleQueueAccount({
  program,
  publicKey: queuePubkey,
});

// Create oracle
const oracleAccount = await OracleAccount.create(program, {
  name: Buffer.from("My Oracle"),
  queueAccount,
});

Ausführen eines lokalen Oracle

Sie können ein Orakel lokal ausführen und es Ihrer eigenen Orakelwarteschlange zuweisen, um zu testen, wie Ihr Programm in der Produktion funktionieren kann. Mainnet-Orakel sollten immer in Hochverfügbarkeitsumgebungen mit einigen Überwachungsfunktionen ausgeführt werden.

Anforderungen

  • Docker-compose

Erstellen Sie eine docker-compose.yml-Datei mit den Umgebungsvariablen in Oracle Config

Press </> button to view full source
version: "3.3"
services:
  switchboard:
    image: "switchboardlabs/node:dev-v2-5-28-22a"
    network_mode: host
    restart: always
    environment:
      - LIVE=1
      - CLUSTER=devnet
      - RPC_URL=${RPC_URL}
      - ORACLE_KEY=${ORACLE_KEY}
      - HEARTBEAT_INTERVAL=15
    volumes:
      - ./configs.json:/configs.json
secrets:
  PAYER_SECRETS:
    file: /filesystem/path/to/keypair.json

Führen Sie den Container mit „docker-compose up“ aus

Oracle Config

Env VariableDefinition
ORACLE_KEYRequired
Type - Public Key
Description - Public key of the oracle account that has been granted permissions to use an oracle queue
HEARTBEAT_INTERVALOptional
Type - Number (seconds)
Default - 30
Description - Seconds between oracle heartbeats. Queues have different oracle heartbeat requirements. Recommended value is 15
GCP_CONFIG_BUCKETOptional
Type - GCP Resource Path
Default - Looks for configs.json in the current working directory. If not found, no config is loaded.
Description - Contains API keys for private API endpoints
UNWRAP_STAKE_THRESHOLDOptional
Type - Number (SOL amount, Ex. 1.55)
Default - 0, disabled.
Description - The Solana balance amount to trigger an unwrap stake action. When an oracle's Solana balance falls below the set threshold, the node will automatically unwrap funds from the oracle's staking wallet, leaving at least 0.1 wSOL or 10% more than the queue's minimum stake requirement.

Verifizierbare Zufallsfunktion (VRF)

Eine verifizierbare Zufallsfunktion (VRF) ist eine Pseudozufallsfunktion mit öffentlichem Schlüssel, die beweist, dass ihre Ausgaben korrekt berechnet wurden

Lesen eines VRF-Kontos

Press </> button to view full source
import * as anchor from "@project-OpenBook/anchor";
import { Keypair } from "@solana/web3.js";
import {
  loadSwitchboardProgram,
  VrfAccount,
} from "@switchboard-xyz/switchboard-v2";

let payer: Keypair;
const program = await loadSwitchboardProgram("devnet", undefined, payer);

const vrfAccount = new VrfAccount({
  program,
  publicKey: vrfKey,
});
const vrf = await vrfAccount.loadData();
console.log(vrf.currentRound.result);
use switchboard_v2::VrfAccountData;

let vrf = VrfAccountData::new(vrf_account_info)?;
let result_buffer = vrf.get_result()?;
if result_buffer == [0u8; 32] {
    msg!("vrf buffer empty");
    return Ok(());
}

let value: &[u128] = bytemuck::cast_slice(&result_buffer[..]);
let result = value[0] % 256000 as u128;

Erstellen eines VRF-Kontos

Press </> button to view full source
import * as anchor from "@project-OpenBook/anchor";
import { Keypair } from "@solana/web3.js";
import {
  loadSwitchboardProgram,
  OracleQueueAccount,
  PermissionAccount,
  SwitchboardPermission,
  VrfAccount,
} from "@switchboard-xyz/switchboard-v2";

let payer: Keypair;
const program = await loadSwitchboardProgram("devnet", undefined, payer);
const queueAccount = new queueAccount({ program, publicKey: queueKey });
const queue = await queueAccount.loadData();

// load client program used for callback
const vrfClientProgram = anchor.workspace
  .AnchorVrfParser as anchor.Program<AnchorVrfParser>;
const vrfSecret = anchor.web3.Keypair.generate();

const vrfIxCoder = new anchor.BorshInstructionCoder(vrfClientProgram.idl);
const vrfClientCallback: Callback = {
  programId: vrfClientProgram.programId,
  accounts: [
    // ensure all accounts in updateResult are populated
    { pubkey: vrfClientKey, isSigner: false, isWritable: true },
    { pubkey: vrfSecret.publicKey, isSigner: false, isWritable: false },
  ],
  ixData: vrfIxCoder.encode("updateResult", ""), // pass any params for instruction here
};

// create VRF
const vrfAccount = await VrfAccount.create(program, {
  queue: queueAccount,
  callback: vrfClientCallback,
  authority: vrfClientKey, // vrf authority
  keypair: vrfSecret,
});

// create permission
const permissionAccount = await PermissionAccount.create(program, {
  authority: queue.authority,
  granter: queue.publicKey,
  grantee: vrfAccount.publicKey,
});

// if queue has not enabled unpermissionedVrfEnabled, queue will need to grant permission
let queueAuthority: Keypair;
await permissionAccount.set({
  authority: queueAuthority,
  permission: SwitchboardPermission.PERMIT_VRF_REQUESTS,
  enable: true,
});

Fordern von Zufälligkeit vom vrf-Konto

Press </> button to view full source
import * as anchor from "@project-OpenBook/anchor";
import { Keypair } from "@solana/web3.js";
import {
  loadSwitchboardProgram,
  VrfAccount,
} from "@switchboard-xyz/switchboard-v2";

let payer: Keypair;
let authority: Keypair;
const program = await loadSwitchboardProgram("devnet", undefined, payer);

const vrfAccount = new VrfAccount({
  program,
  publicKey: vrfKey,
});
const vrf = await vrfAccount.loadData();

const queueAccount = new OracleQueueAccount({
  program,
  publicKey: vrf.queuePubkey,
});
const queue = await queueAccount.loadData();
const mint = await queueAccount.loadMint();

const payerTokenWallet = (
  await mint.getOrCreateAssociatedAccountInfo(payer.publicKey)
).address;

const signature = await vrfAccount.requestRandomness({
  authority,
  payer: payerTokenWallet,
  payerAuthority: payer,
});
use crate::*;
use anchor_lang::prelude::*;
pub use switchboard_v2::{VrfAccountData, VrfRequestRandomness};
use anchor_spl::token::Token;
use anchor_lang::solana_program::clock;

#[derive(Accounts)]
#[instruction(params: RequestResultParams)] // rpc parameters hint
pub struct RequestResult<'info> {
    #[account(
        mut,
        seeds = [
            STATE_SEED,
            vrf.key().as_ref(),
            authority.key().as_ref(),
        ],
        bump = state.load()?.bump,
        has_one = vrf,
        has_one = authority
    )]
    pub state: AccountLoader<'info, VrfClient>,
    #[account(signer)]
    pub authority: AccountInfo<'info>,
    #[account(constraint = switchboard_program.executable == true)]
    pub switchboard_program: AccountInfo<'info>,
    #[account(mut, constraint = vrf.owner.as_ref() == switchboard_program.key().as_ref())]
    pub vrf: AccountInfo<'info>,
    #[account(mut, constraint = oracle_queue.owner.as_ref() == switchboard_program.key().as_ref())]
    pub oracle_queue: AccountInfo<'info>,
    pub queue_authority: UncheckedAccount<'info>,
    #[account(constraint = data_buffer.owner.as_ref() == switchboard_program.key().as_ref())]
    pub data_buffer: AccountInfo<'info>,
    #[account(mut, constraint = permission.owner.as_ref() == switchboard_program.key().as_ref())]
    pub permission: AccountInfo<'info>,
    #[account(mut, constraint = escrow.owner == program_state.key())]
    pub escrow: Account<'info, TokenAccount>,
    #[account(mut, constraint = payer_wallet.owner == payer_authority.key())]
    pub payer_wallet: Account<'info, TokenAccount>,
    #[account(signer)]
    pub payer_authority: AccountInfo<'info>,
    #[account(address = solana_program::sysvar::recent_blockhashes::ID)]
    pub recent_blockhashes: AccountInfo<'info>,
    #[account(constraint = program_state.owner.as_ref() == switchboard_program.key().as_ref())]
    pub program_state: AccountInfo<'info>,
    #[account(address = anchor_spl::token::ID)]
    pub token_program: Program<'info, Token>,
}

#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
pub struct RequestResultParams {
    pub permission_bump: u8,
    pub switchboard_state_bump: u8,
}

impl RequestResult<'_> {
    pub fn validate(&self, _ctx: &Context<Self>, _params: &RequestResultParams) -> Result<()> {
        Ok(())
    }

    pub fn actuate(ctx: &Context<Self>, params: &RequestResultParams) -> Result<()> {
        let client_state = ctx.accounts.state.load()?;
        let bump = client_state.bump.clone();
        let max_result = client_state.max_result.clone();
        drop(client_state);

        let switchboard_program = ctx.accounts.switchboard_program.to_account_info();

        let vrf_request_randomness = VrfRequestRandomness {
            authority: ctx.accounts.state.to_account_info(),
            vrf: ctx.accounts.vrf.to_account_info(),
            oracle_queue: ctx.accounts.oracle_queue.to_account_info(),
            queue_authority: ctx.accounts.queue_authority.to_account_info(),
            data_buffer: ctx.accounts.data_buffer.to_account_info(),
            permission: ctx.accounts.permission.to_account_info(),
            escrow: ctx.accounts.escrow.clone(),
            payer_wallet: ctx.accounts.payer_wallet.clone(),
            payer_authority: ctx.accounts.payer_authority.to_account_info(),
            recent_blockhashes: ctx.accounts.recent_blockhashes.to_account_info(),
            program_state: ctx.accounts.program_state.to_account_info(),
            token_program: ctx.accounts.token_program.to_account_info(),
        };

        let vrf_key = ctx.accounts.vrf.key.clone();
        let authority_key = ctx.accounts.authority.key.clone();

        msg!("bump: {}", bump);
        msg!("authority: {}", authority_key);
        msg!("vrf: {}", vrf_key);

        let state_seeds: &[&[&[u8]]] = &[&[
            &STATE_SEED,
            vrf_key.as_ref(),
            authority_key.as_ref(),
            &[bump],
        ]];
        msg!("requesting randomness");
        vrf_request_randomness.invoke_signed(
            switchboard_program,
            params.switchboard_state_bump,
            params.permission_bump,
            state_seeds,
        )?;

        emit!(RequestingRandomness{
            vrf_client: ctx.accounts.state.key(),
            max_result: max_result,
            timestamp: clock::Clock::get().unwrap().unix_timestamp
        });

        msg!("randomness requested successfully");
        Ok(())
    }
}

Resources

APIs and Libraries

Examples

More Information

Last Updated:
Contributors: nyk