Switchboard

Switchboard est un Oracle qui permet aux développeurs de s'approvisionner en données on-chain pour une variété de cas d'utilisation tels que l'obtention du prix de jetons, le prix plancher (floor price) de NFTs, les statistiques sportives ou même le caractère vérifiable du hasard. D'une manière générale, Switchboard est une ressource hors chaîne que les développeurs peuvent utiliser pour relier des données on-chain de haute qualité et alimenter la prochaine génération du Web3 et de la DeFi.

Flux de Données

Switchboard fournit une bibliothèque JavaScript/TypeScript appelée @switchboard-xyz/switchboard-v2. Cette bibliothèque peut être utilisée pour accéder aux données On-chain à partir de flux de données existants ou pour publier vos propres flux personnalisés. Plus d'informations à ce sujet iciopen in new window

Lire les données d'un flux d'agrégation

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

Créer un nouveau flux d'agrégation

Press </> button to view full source
import * as anchor from "@project-serum/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);

Lire les données d'un flux d'agrégation dans un programme

Switchboard fournit une crate appelée switchboard_v2. Plus d'informations à ce sujet iciopen 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
}

Comment Créer un Flux à partir du Publisher

La documentation officielle de Switchboard explique en détail comment créer un flux à partir du Publisher. Découvrez-la iciopen in new window.

Oracles

La caractéristique unique de Switchboard est qu'il vous permet de créer votre propre oracle et de l'exécuter localement.

Créer un oracle

Press </> button to view full source
import * as anchor from "@project-serum/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,
});

Exécuter un oracle localement

Vous pouvez exécuter un oracle localement et l'affecter à votre propre file d'attente d'oracles pour tester comment votre programme peut fonctionner en production. Les oracles du Mainnet doivent toujours être exécutés dans des environnements à haute disponibilité avec un certain nombre de fonctionnalités de contrôle.

Conditions Requises

  • Docker-compose

Créez un fichier docker-compose.yml avec les variables d'environnement dans 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

Exécutez le conteneur en utilisant docker-compose up

Configuration de l'Oracle

Variable EnvDéfinition
ORACLE_KEYObligatoire
Type - Clé Publique
Description - Clé publique du compte de l'oracle qui a reçu les permissions d'utiliser une file d'attente oracle
HEARTBEAT_INTERVALFacultatif
Type - Nombre (secondes)
Par Défaut - 30
Description - Secondes entre les battements de cœur de l'oracle. Les files d'attente ont différentes exigences en matière de battement de cœur de l'oracle. La valeur recommandée est de 15
GCP_CONFIG_BUCKETFacultatif
Type - GCP Resource Path
Par Défaut - Recherche le fichier configs.json dans le répertoire de travail actuel. Si elle n'est pas trouvée, aucune configuration n'est chargée.
Description - Contient les clés API pour les points de terminaison API privés
UNWRAP_STAKE_THRESHOLDFacultatif
Type - Nombre (montant de SOL amount, Ex. 1.55)
Par Défaut - 0, désactivé.
Description - Le montant de la balance Solana pour déclencher une action de déblocage de la mise. Lorsque le solde de Solana d'un oracle tombe sous le seuil fixé, le nœud débloque automatiquement les fonds du portefeuille de staking de l'oracle, laissant au moins 0,1 wSOL ou 10 % de plus que l'exigence de mise minimale de la file.

Fonction Aléatoire Vérifiable(VRF)

Une Fonction Aléatoire Vérifiable (VRF) est une fonction pseudo-aléatoire à clé publique qui fournit des preuves que ses sorties ont été calculées correctement.

Lire un compte VRF

Press </> button to view full source
import * as anchor from "@project-serum/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;

Créer un compte VRF

Press </> button to view full source
import * as anchor from "@project-serum/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,
});

Demande de Hasard à partir d'un compte VRF

Press </> button to view full source
import * as anchor from "@project-serum/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(())
    }
}

Ressources

API et Bibliothèques

Exemples

Plus d'Informations

Last Updated:
Contributors: cryptoloutre