Switchboard

Switchboard adalah protokol Oracle yang memungkinkan pengembang untuk menarik data on-chain untuk berbagai kasus penggunaan seperti feed harga, harga dasar NFT, statistik olahraga, atau bahkan keacakan yang dapat diverifikasi. Secara umum, Switchboard adalah sumber daya off-chain yang dapat digunakan untuk menjembatani data dengan integritas tinggi secara on-chain dan memberi daya pada web3 dan DeFi.

Data Feeds

Switchboard menyediakan librari JavaScript/TypeScript yang disebut @switchboard-xyz/switchboard-v2 . Librari ini bisa digunakan untuk mendapatkan on-chain data dari feeds yang sudah ada atau bisa juga untuk mempublikasikan feeds kustom. Pelajari lebih lanjut hereopen in new window

Cara Baca Data Dari Aggregator Feeds

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());
};

Membuat Aggregator Feed Baru

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);

Baca data dari aggregator feeds dalam sebuah program

Switchboard menyediakan sebuah crate yang dinamakan switchboard_v2 Pelajari lebih lanjut hereopen 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
}

Cara Membuat a Feeds Dari Publisher

Dokumentasi Switchboard resmi memiliki panduan mendalam tentang cara membuat feeds dari penerbit. Cek lebih lanjut hereopen in new window.

Oracles

Fitur unik Switchboard adalah memungkinkan Anda membuat oracle Anda sendiri dan menjalankannya secara lokal.

Create an 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,
});

Menjalan Oracle di lokal

Anda dapat menjalankan oracle secara lokal dan menetapkannya ke antrian oracle Anda sendiri untuk menguji bagaimana program Anda dapat beroperasi dalam environment production. Oracle Mainnet harus selalu dijalankan di lingkungan ketersediaan tinggi dengan beberapa kemampuan pemantauan.

Yang Dibutuhkan

  • Docker-compose

Buat sebuah file docker-compose.yml dengan environment variable di 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

Jalankan container dengan docker-compose up

Konfigurasi Oracle

Env VariableDefinisi
ORACLE_KEYDibutuhkan
Tipe - Public Key
Deskripsi - Public key dari sebuah oracle account yang sudah mendapatkan izin untuk menggunakan oracle queue
HEARTBEAT_INTERVALOpsional
Tipe - Number (detik)
Default - 30
Deskripsi - detik antara oracle heartbeats. Queues bisa memiliki oracle heartbeat yang berbeda. Nilai yang direkomendasikan adalah 15
GCP_CONFIG_BUCKETOpsional
Tipe - GCP Resource Path
Default - Cari file configs.json di folder, jika tidak ditemukan maka tidak ada config yang di load.
Deskripsi - Mengandung API keys untuk private API endpoints
UNWRAP_STAKE_THRESHOLDOptsonal
Tipe - Number (jumlah SOL, Contoh. 1.55)
Default - 0, disabled.
Deskripsi - Jumlah saldo Solana untuk memicu tindakan pasak yang dibuka (_unwrap stake action_). Ketika balance Solana oracle turun di bawah ambang batas yang ditetapkan, node akan secara otomatis membuka dana dari dompet staking oracle, menyisakan setidaknya 0,1 wSOL atau 10% lebih banyak dari persyaratan stake minimum antrian.

Fungsi Random yang Terverifikasi - Verifiable Random Function(VRF)

Verifiable Random Function (VRF) adalah fungsi pseudorandom dari public-key yang menyediakan bukti bahwa output telah di kalkukasi secara benar.

Membaca akun VRF

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;

Membuat Sebuah Akun VRF

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,
});

Request Randomness dari akun vrf

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(())
    }
}

Sumber

API dan Librari

Contoh

Informasi Lebih Lanjut

Last Updated:
Contributors: akangaziz