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 hier
Daten aus einem Aggregator-Feed lesen
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());
};
let aggregatorKey: PublicKey;
let program: Program<Idl>;
const aggregatorAccount = new switchboard.AggregatorAccount({
program: program,
publicKey: aggregatorKey,
});
const result: any = await aggregatorAccount.getLatestValue();
Erstellen eines neuen Aggregator-Feed
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);
const queueAccount = new OracleQueueAccount({
program,
publicKey: queuePubkey,
});
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,
});
console.log(aggregatorAccount.publicKey.toString());
Lesen Sie Daten aus einem Aggregator-Feed-in-Programm
Switchboard bietet eine Kiste namens switchboard_v2 Erfahren Sie mehr darüber hier
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
}
let aggregator = &ctx.accounts.aggregator_feed.load()?;
let val:f64 = aggregator
.get_result()?
.try_into()?;
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 hier an .
Oracles
Die einzigartige Funktion von Switchboard besteht darin, dass Sie Ihr eigenes Orakel erstellen und lokal ausführen können.
Erstelen eines Oracle
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,
});
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
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
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 Variable | Definition |
---|---|
ORACLE_KEY | Required Type - Public Key Description - Public key of the oracle account that has been granted permissions to use an oracle queue |
HEARTBEAT_INTERVAL | Optional Type - Number (seconds) Default - 30 Description - Seconds between oracle heartbeats. Queues have different oracle heartbeat requirements. Recommended value is 15 |
GCP_CONFIG_BUCKET | Optional 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_THRESHOLD | Optional 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
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);
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;
let vrf = VrfAccountData::new(vrf_account_info)?;
let result_buffer = vrf.get_result()?;
Erstellen eines VRF-Kontos
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,
});
const vrfAccount = await VrfAccount.create(program, {
queue: queueAccount,
callback: vrfClientCallback,
authority: vrfClientKey, // vrf authority
keypair: vrfSecret,
});
Fordern von Zufälligkeit vom vrf-Konto
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,
});
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(())
}
}
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,
)?;