added buffer relayer impl
[RS] Added buffer relayer structs [RS] Added check_staleness and check_confidence_interval to accounts [CLI] Added buffer relayer commands [Anchor] Added better data feed validation to examples [TS] Added enableBufferRelayers to oracleQueueInit
This commit is contained in:
parent
2f99623c3a
commit
32c9f86b24
|
@ -2,6 +2,7 @@
|
|||
members = [
|
||||
"programs/anchor-feed-parser",
|
||||
"programs/anchor-vrf-parser",
|
||||
"programs/anchor-buffer-parser",
|
||||
"programs/native-feed-parser"
|
||||
]
|
||||
|
||||
|
@ -14,6 +15,7 @@ wallet = "../payer-keypair.json"
|
|||
[programs.localnet]
|
||||
anchor_feed_parser = "FnsPs665aBSwJRu2A8wGv6ZT76ipR41kHm4hoA3B1QGh"
|
||||
anchor_vrf_parser = "HjjRFjCyQH3ne6Gg8Yn3TQafrrYecRrphwLwnh2A26vM"
|
||||
anchor_buffer_parser = "96punQGZDShZGkzsBa3SsfTxfUnwu4XGpzXbhF7NTgcP"
|
||||
|
||||
[registry]
|
||||
url = "https://anchor.projectserum.com"
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*",
|
||||
]
|
||||
exclude = [
|
||||
"libraries/rs"
|
||||
]
|
|
@ -3,7 +3,9 @@ import * as anchor from "@project-serum/anchor";
|
|||
import { ACCOUNT_DISCRIMINATOR_SIZE } from "@project-serum/anchor/dist/cjs/coder";
|
||||
import { clusterApiUrl, Connection, Keypair, PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
DEFAULT_KEYPAIR,
|
||||
prettyPrintAggregator,
|
||||
prettyPrintBufferRelayer,
|
||||
prettyPrintCrank,
|
||||
prettyPrintJob,
|
||||
prettyPrintLease,
|
||||
|
@ -17,6 +19,7 @@ import {
|
|||
} from "@switchboard-xyz/sbv2-utils";
|
||||
import {
|
||||
AggregatorAccount,
|
||||
BufferRelayerAccount,
|
||||
CrankAccount,
|
||||
JobAccount,
|
||||
LeaseAccount,
|
||||
|
@ -159,7 +162,14 @@ abstract class PrintBaseCommand extends Command {
|
|||
this.logger.log(await prettyPrintVrf(vrfAccount));
|
||||
break;
|
||||
}
|
||||
|
||||
case "BufferRelayerAccountData": {
|
||||
const bufferRelayerAccount = new BufferRelayerAccount({
|
||||
program,
|
||||
publicKey,
|
||||
});
|
||||
this.logger.log(await prettyPrintBufferRelayer(bufferRelayerAccount));
|
||||
break;
|
||||
}
|
||||
case "BUFFERxx": {
|
||||
console.log(`Found buffer account but dont know which one`);
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
import { Flags } from "@oclif/core";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { prettyPrintBufferRelayer } from "@switchboard-xyz/sbv2-utils";
|
||||
import {
|
||||
BufferRelayerAccount,
|
||||
JobAccount,
|
||||
OracleJob,
|
||||
OracleQueueAccount,
|
||||
programWallet,
|
||||
} from "@switchboard-xyz/switchboard-v2";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import BaseCommand from "../../BaseCommand";
|
||||
import { verifyProgramHasPayer } from "../../utils";
|
||||
|
||||
export default class BufferCreate extends BaseCommand {
|
||||
static description = "create a buffer relayer account";
|
||||
|
||||
static flags = {
|
||||
...BaseCommand.flags,
|
||||
authority: Flags.string({
|
||||
char: "a",
|
||||
description: "alternate keypair that will be the aggregator authority",
|
||||
}),
|
||||
name: Flags.string({
|
||||
char: "n",
|
||||
description: "name of the buffer account",
|
||||
}),
|
||||
minUpdateDelaySeconds: Flags.integer({
|
||||
description: "minimum number of seconds between update calls",
|
||||
default: 30,
|
||||
}),
|
||||
jobDefinition: Flags.string({
|
||||
description: "filesystem path to job definition",
|
||||
exclusive: ["jobKey"],
|
||||
}),
|
||||
jobKey: Flags.string({
|
||||
description: "public key of existing job account",
|
||||
exclusive: ["jobDefinition"],
|
||||
}),
|
||||
};
|
||||
|
||||
static args = [
|
||||
{
|
||||
name: "queueKey",
|
||||
description: "oracle queue to create BufferRelayer account on",
|
||||
},
|
||||
];
|
||||
|
||||
async run() {
|
||||
verifyProgramHasPayer(this.program);
|
||||
const { args, flags } = await this.parse(BufferCreate);
|
||||
const payerKeypair = programWallet(this.program);
|
||||
const authority = await this.loadAuthority(flags.authority);
|
||||
|
||||
let jobAccount: JobAccount;
|
||||
if (flags.jobDefinition) {
|
||||
const jobDefinitionPath = flags.jobDefinition.startsWith("/")
|
||||
? flags.jobDefinition
|
||||
: path.join(process.cwd(), flags.jobDefinition);
|
||||
if (!fs.existsSync(jobDefinitionPath)) {
|
||||
throw new Error(
|
||||
`jobDefinitionPath does not exist, ${jobDefinitionPath}`
|
||||
);
|
||||
}
|
||||
|
||||
const oracleJob = OracleJob.create(
|
||||
JSON.parse(fs.readFileSync(jobDefinitionPath, "utf-8"))
|
||||
);
|
||||
jobAccount = await JobAccount.create(this.program, {
|
||||
authority: authority.publicKey,
|
||||
name: flags.name ? Buffer.from(flags.name) : Buffer.from(""),
|
||||
data: Buffer.from(OracleJob.encodeDelimited(oracleJob).finish()),
|
||||
});
|
||||
} else if (flags.jobKey) {
|
||||
jobAccount = new JobAccount({
|
||||
program: this.program,
|
||||
publicKey: new PublicKey(flags.jobKey),
|
||||
});
|
||||
} else {
|
||||
throw new Error(`Need to provide --jobDefinition or --jobKey flag`);
|
||||
}
|
||||
|
||||
const queueAccount = new OracleQueueAccount({
|
||||
program: this.program,
|
||||
publicKey: new PublicKey(args.queueKey),
|
||||
});
|
||||
|
||||
const bufferRelayerAccount = await BufferRelayerAccount.create(
|
||||
this.program,
|
||||
{
|
||||
authority: authority.publicKey,
|
||||
name: flags.name ? Buffer.from(flags.name) : Buffer.from(""),
|
||||
minUpdateDelaySeconds: flags.minUpdateDelaySeconds,
|
||||
queueAccount,
|
||||
jobAccount,
|
||||
}
|
||||
);
|
||||
if (this.silent) {
|
||||
this.logger.info(bufferRelayerAccount.publicKey.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.info(await prettyPrintBufferRelayer(bufferRelayerAccount));
|
||||
}
|
||||
|
||||
async catch(error) {
|
||||
super.catch(error, "failed to create buffer relayer account");
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ export default class QueueAddCrank extends BaseCommand {
|
|||
}),
|
||||
maxRows: Flags.integer({
|
||||
char: "r",
|
||||
default: 100,
|
||||
description: "maximum number of rows a crank can support",
|
||||
}),
|
||||
queueAuthority: Flags.string({
|
||||
|
|
|
@ -11,13 +11,13 @@ import BaseCommand from "../../../BaseCommand";
|
|||
import { verifyProgramHasPayer } from "../../../utils";
|
||||
|
||||
export default class JobCreate extends BaseCommand {
|
||||
static description = "create a buffer relayer account";
|
||||
static description = "create a job account";
|
||||
|
||||
static flags = {
|
||||
...BaseCommand.flags,
|
||||
authority: Flags.string({
|
||||
char: "a",
|
||||
description: "alternate keypair that will be the aggregator authority",
|
||||
description: "alternate keypair that will be the account authority",
|
||||
}),
|
||||
name: Flags.string({
|
||||
char: "n",
|
||||
|
@ -49,22 +49,10 @@ export default class JobCreate extends BaseCommand {
|
|||
const oracleJob = OracleJob.create(
|
||||
JSON.parse(fs.readFileSync(jobDefinitionPath, "utf-8"))
|
||||
);
|
||||
if (!("tasks" in oracleJob)) {
|
||||
throw new Error("Must provide tasks in job definition");
|
||||
}
|
||||
|
||||
const data = Buffer.from(
|
||||
OracleJob.encodeDelimited(
|
||||
OracleJob.create({
|
||||
tasks: oracleJob.tasks,
|
||||
})
|
||||
).finish()
|
||||
);
|
||||
console.log(`DATA: [${data.join(",")}]`);
|
||||
const jobAccount = await JobAccount.create(this.program, {
|
||||
authority: authority.publicKey,
|
||||
name: flags.name ? Buffer.from(flags.name) : Buffer.from(""),
|
||||
data,
|
||||
data: Buffer.from(OracleJob.encodeDelimited(oracleJob).finish()),
|
||||
});
|
||||
|
||||
if (this.silent) {
|
||||
|
@ -72,13 +60,11 @@ export default class JobCreate extends BaseCommand {
|
|||
return;
|
||||
}
|
||||
|
||||
const job = await jobAccount.loadData();
|
||||
this.logger.info(await prettyPrintJob(jobAccount, job));
|
||||
|
||||
console.log(`DATA: [${job.data.join(",")}]`);
|
||||
const jobData = await jobAccount.loadData();
|
||||
this.logger.info(await prettyPrintJob(jobAccount, jobData));
|
||||
}
|
||||
|
||||
async catch(error) {
|
||||
super.catch(error, "failed to create buffer relayer account");
|
||||
super.catch(error, "failed to create job account");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/* eslint-disable unicorn/import-style */
|
||||
import { Flags } from "@oclif/core";
|
||||
import { prettyPrintBufferRelayer } from "@switchboard-xyz/sbv2-utils";
|
||||
import { BufferRelayerAccount } from "@switchboard-xyz/switchboard-v2";
|
||||
import BaseCommand from "../../BaseCommand";
|
||||
|
||||
export default class BufferPrint extends BaseCommand {
|
||||
static description =
|
||||
"Print the deserialized Switchboard buffer relayer account";
|
||||
|
||||
static aliases = ["buffer:print"];
|
||||
|
||||
static flags = {
|
||||
...BaseCommand.flags,
|
||||
job: Flags.boolean({
|
||||
description: "output job definitions",
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
static args = [
|
||||
{
|
||||
name: "bufferRelayerKey",
|
||||
description: "public key of the buffer relayer account to deserialize",
|
||||
},
|
||||
];
|
||||
|
||||
static examples = [
|
||||
"$ sbv2 buffer:print 23GvzENjwgqqaLejsAtAWgTkSzWjSMo2LUYTAETT8URp",
|
||||
];
|
||||
|
||||
async run() {
|
||||
const { args, flags } = await this.parse(BufferPrint);
|
||||
|
||||
const bufferAccount = new BufferRelayerAccount({
|
||||
program: this.program,
|
||||
publicKey: args.bufferRelayerKey,
|
||||
});
|
||||
const bufferAccountData = await bufferAccount.loadData();
|
||||
|
||||
this.logger.log(
|
||||
await prettyPrintBufferRelayer(
|
||||
bufferAccount,
|
||||
bufferAccountData,
|
||||
flags.job
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async catch(error) {
|
||||
super.catch(error, "failed to print buffer relayer account");
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "switchboard-v2"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
edition = "2021"
|
||||
description = "A Rust library to interact with Switchboard V2 data feeds."
|
||||
readme = "README.md"
|
||||
|
@ -13,6 +13,7 @@ documentation = "https://docs.rs/switchboard-v2/"
|
|||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "switchboard_v2"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
default = ["cpi"]
|
||||
|
|
|
@ -19,12 +19,27 @@ from a provided data feed AccountInfo.
|
|||
### Aggregator
|
||||
|
||||
```rust
|
||||
use switchboard_v2::AggregatorAccountData;
|
||||
use anchor_lang::solana_program::clock;
|
||||
use std::convert::TryInto;
|
||||
use switchboard_v2::{AggregatorAccountData, SWITCHBOARD_V2_DEVNET, SWITCHBOARD_V2_MAINNET};
|
||||
|
||||
let feed_result = AggregatorAccountData::new(feed_account_info)?.get_result()?;
|
||||
// check feed owner
|
||||
let owner = *aggregator.owner;
|
||||
if owner != SWITCHBOARD_V2_DEVNET && owner != SWITCHBOARD_V2_MAINNET {
|
||||
return Err(error!(ErrorCode::InvalidSwitchboardAccount));
|
||||
}
|
||||
|
||||
let decimal: f64 = feed_result.try_into()?;
|
||||
// deserialize account info
|
||||
let feed = AggregatorAccountData::new(feed_account_info)?;
|
||||
|
||||
// get result
|
||||
let decimal: f64 = feed.get_result()?.try_into()?;
|
||||
|
||||
// check if feed has been updated in the last 5 minutes
|
||||
feed.check_staleness(clock::Clock::get().unwrap().unix_timestamp, 300)?;
|
||||
|
||||
// check if feed exceeds a confidence interval of +/i $0.80
|
||||
feed.check_confidence_interval(0.80)?;
|
||||
```
|
||||
|
||||
### Aggregator History
|
||||
|
@ -45,11 +60,34 @@ 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;
|
||||
```
|
||||
|
||||
### Buffer Relayer Account
|
||||
|
||||
```rust
|
||||
use anchor_lang::solana_program::clock;
|
||||
use std::convert::TryInto;
|
||||
use switchboard_v2::{BufferRelayerAccountData, SWITCHBOARD_V2_DEVNET, SWITCHBOARD_V2_MAINNET};
|
||||
|
||||
// check feed owner
|
||||
let owner = *aggregator.owner;
|
||||
if owner != SWITCHBOARD_V2_DEVNET && owner != SWITCHBOARD_V2_MAINNET {
|
||||
return Err(error!(ErrorCode::InvalidSwitchboardAccount));
|
||||
}
|
||||
|
||||
// deserialize account info
|
||||
let buffer = BufferRelayerAccountData::new(feed_account_info)?;
|
||||
|
||||
// get result
|
||||
let buffer_result = buffer.get_result();
|
||||
|
||||
// check if feed has been updated in the last 5 minutes
|
||||
buffer.check_staleness(clock::Clock::get().unwrap().unix_timestamp, 300)?;
|
||||
|
||||
// convert buffer to a string
|
||||
let result_string = String::from_utf8(buffer.result)
|
||||
.map_err(|_| error!(ErrorCode::StringConversionFailed))?;
|
||||
msg!("Buffer string {:?}!", result_string);
|
||||
```
|
||||
|
|
|
@ -34,10 +34,10 @@ pub struct AggregatorRound {
|
|||
pub min_response: SwitchboardDecimal,
|
||||
// Maintains the maximum node response this round.
|
||||
pub max_response: SwitchboardDecimal,
|
||||
// pub lease_key: Option<Pubkey>,
|
||||
// pub lease_key: Pubkey,
|
||||
// Pubkeys of the oracles fulfilling this round.
|
||||
pub oracle_pubkeys_data: [Pubkey; 16],
|
||||
// pub oracle_pubkeys_size: Option<u32>, IMPLIED BY ORACLE_REQUEST_BATCH_SIZE
|
||||
// pub oracle_pubkeys_size: u32, IMPLIED BY ORACLE_REQUEST_BATCH_SIZE
|
||||
// Represents all successful node responses this round. `NaN` if empty.
|
||||
pub medians_data: [SwitchboardDecimal; 16],
|
||||
// Current rewards/slashes oracles have received this round.
|
||||
|
@ -55,39 +55,54 @@ pub struct AggregatorRound {
|
|||
pub struct AggregatorAccountData {
|
||||
pub name: [u8; 32],
|
||||
pub metadata: [u8; 128],
|
||||
pub author_wallet: Pubkey,
|
||||
pub _reserved1: [u8; 32],
|
||||
pub queue_pubkey: Pubkey,
|
||||
// CONFIGS
|
||||
// affects update price, shouldnt be changeable
|
||||
pub oracle_request_batch_size: u32,
|
||||
pub min_oracle_results: u32,
|
||||
pub min_job_results: u32,
|
||||
// affects update price, shouldnt be changeable
|
||||
pub min_update_delay_seconds: u32,
|
||||
// timestamp to start feed updates at
|
||||
pub start_after: i64,
|
||||
pub start_after: i64, // timestamp to start feed updates at
|
||||
pub variance_threshold: SwitchboardDecimal,
|
||||
// If no feed results after this period, trigger nodes to report
|
||||
pub force_report_period: i64,
|
||||
pub force_report_period: i64, // If no feed results after this period, trigger nodes to report
|
||||
pub expiration: i64,
|
||||
//
|
||||
pub consecutive_failure_count: u64,
|
||||
pub next_allowed_update_time: i64,
|
||||
pub is_locked: bool,
|
||||
pub _schedule: [u8; 32],
|
||||
pub crank_pubkey: Pubkey,
|
||||
pub latest_confirmed_round: AggregatorRound,
|
||||
pub current_round: AggregatorRound,
|
||||
pub job_pubkeys_data: [Pubkey; 16],
|
||||
pub job_hashes: [Hash; 16],
|
||||
pub job_pubkeys_size: u32,
|
||||
// Used to confirm with oracles they are answering what they think theyre answering
|
||||
pub jobs_checksum: [u8; 32],
|
||||
pub jobs_checksum: [u8; 32], // Used to confirm with oracles they are answering what they think theyre answering
|
||||
//
|
||||
pub authority: Pubkey,
|
||||
pub _ebuf: [u8; 224], // Buffer for future info
|
||||
pub history_buffer: Pubkey,
|
||||
pub previous_confirmed_round_result: SwitchboardDecimal,
|
||||
pub previous_confirmed_round_slot: u64,
|
||||
pub disable_crank: bool,
|
||||
pub job_weights: [u8; 16],
|
||||
pub _ebuf: [u8; 147], // Buffer for future info
|
||||
}
|
||||
unsafe impl Pod for AggregatorAccountData {}
|
||||
unsafe impl Zeroable for AggregatorAccountData {}
|
||||
|
||||
impl AggregatorAccountData {
|
||||
/// Returns the deserialized Switchboard Aggregator account
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `switchboard_feed` - A Solana AccountInfo referencing an existing Switchboard Aggregator
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use switchboard_v2::AggregatorAccountData;
|
||||
///
|
||||
/// let data_feed = AggregatorAccountData::new(feed_account_info)?;
|
||||
/// ```
|
||||
pub fn new<'info>(
|
||||
switchboard_feed: &'info AccountInfo,
|
||||
) -> anchor_lang::Result<Ref<'info, AggregatorAccountData>> {
|
||||
|
@ -96,13 +111,22 @@ impl AggregatorAccountData {
|
|||
let mut disc_bytes = [0u8; 8];
|
||||
disc_bytes.copy_from_slice(&data[..8]);
|
||||
if disc_bytes != AggregatorAccountData::discriminator() {
|
||||
msg!("{:?}", disc_bytes);
|
||||
return Err(SwitchboardError::AccountDiscriminatorMismatch.into());
|
||||
}
|
||||
|
||||
Ok(Ref::map(data, |data| bytemuck::from_bytes(&data[8..])))
|
||||
}
|
||||
|
||||
/// If sufficient oracle responses, returns the latest on-chain result in SwitchboardDecimal format
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use switchboard_v2::AggregatorAccountData;
|
||||
/// use std::convert::TryInto;
|
||||
///
|
||||
/// let feed_result = AggregatorAccountData::new(feed_account_info)?.get_result()?;
|
||||
/// let decimal: f64 = feed_result.try_into()?;
|
||||
/// ```
|
||||
pub fn get_result(&self) -> anchor_lang::Result<SwitchboardDecimal> {
|
||||
if self.min_oracle_results > self.latest_confirmed_round.num_success {
|
||||
return Err(SwitchboardError::InvalidAggregatorRound.into());
|
||||
|
@ -110,12 +134,53 @@ impl AggregatorAccountData {
|
|||
Ok(self.latest_confirmed_round.result)
|
||||
}
|
||||
|
||||
/// Check whether the confidence interval exceeds a given threshold
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use switchboard_v2::{AggregatorAccountData, SwitchboardDecimal};
|
||||
///
|
||||
/// let feed = AggregatorAccountData::new(feed_account_info)?;
|
||||
/// feed.check_confidence_interval(SwitchboardDecimal::from_f64(0.80))?;
|
||||
/// ```
|
||||
pub fn check_confidence_interval(
|
||||
&self,
|
||||
max_confidence_interval: SwitchboardDecimal,
|
||||
) -> anchor_lang::Result<()> {
|
||||
if self.latest_confirmed_round.std_deviation > max_confidence_interval {
|
||||
return Err(SwitchboardError::ConfidenceIntervalExceeded.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check whether the feed has been updated in the last max_staleness seconds
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use switchboard_v2::AggregatorAccountData;
|
||||
///
|
||||
/// let feed = AggregatorAccountData::new(feed_account_info)?;
|
||||
/// feed.check_staleness(clock::Clock::get().unwrap().unix_timestamp, 300)?;
|
||||
/// ```
|
||||
pub fn check_staleness(
|
||||
&self,
|
||||
unix_timestamp: i64,
|
||||
max_staleness: i64,
|
||||
) -> anchor_lang::Result<()> {
|
||||
let staleness = unix_timestamp - self.latest_confirmed_round.round_open_timestamp;
|
||||
if staleness > max_staleness {
|
||||
msg!("Feed has not been updated in {} seconds!", staleness);
|
||||
return Err(SwitchboardError::StaleFeed.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn discriminator() -> [u8; 8] {
|
||||
return [217, 230, 65, 101, 201, 162, 27, 125];
|
||||
}
|
||||
}
|
||||
unsafe impl Pod for AggregatorAccountData {}
|
||||
unsafe impl Zeroable for AggregatorAccountData {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
use super::error::SwitchboardError;
|
||||
use anchor_lang::prelude::*;
|
||||
use solana_program::pubkey::Pubkey;
|
||||
|
||||
#[derive(AnchorDeserialize, Default, Debug)]
|
||||
pub struct BufferRelayerAccountData {
|
||||
pub name: [u8; 32],
|
||||
pub queue_pubkey: Pubkey,
|
||||
pub escrow: Pubkey,
|
||||
pub authority: Pubkey,
|
||||
pub job_pubkey: Pubkey,
|
||||
pub job_hash: [u8; 32],
|
||||
pub min_update_delay_seconds: u32,
|
||||
pub is_locked: bool,
|
||||
pub current_round: BufferRelayerRound,
|
||||
pub latest_confirmed_round: BufferRelayerRound,
|
||||
pub result: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct BufferRelayerRound {
|
||||
pub num_success: u32,
|
||||
pub num_error: u32,
|
||||
pub round_open_slot: u64,
|
||||
pub round_open_timestamp: i64,
|
||||
pub oracle_pubkey: Pubkey,
|
||||
}
|
||||
|
||||
impl BufferRelayerAccountData {
|
||||
/// Returns the deserialized Switchboard Buffer Relayer account
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `switchboard_buffer` - A Solana AccountInfo referencing an existing Switchboard BufferRelayer
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use switchboard_v2::BufferRelayerAccountData;
|
||||
///
|
||||
/// let buffer_account = BufferRelayerAccountData::new(buffer_account_info)?;
|
||||
/// ```
|
||||
pub fn new<'a>(
|
||||
switchboard_buffer: &'a AccountInfo,
|
||||
) -> anchor_lang::Result<Box<BufferRelayerAccountData>> {
|
||||
let data = switchboard_buffer.try_borrow_data()?;
|
||||
|
||||
let mut disc_bytes = [0u8; 8];
|
||||
disc_bytes.copy_from_slice(&data[..8]);
|
||||
if disc_bytes != BufferRelayerAccountData::discriminator() {
|
||||
return Err(SwitchboardError::AccountDiscriminatorMismatch.into());
|
||||
}
|
||||
|
||||
let mut v_mut = &data[8..];
|
||||
Ok(Box::new(BufferRelayerAccountData::deserialize(&mut v_mut)?))
|
||||
}
|
||||
|
||||
pub fn get_result(&self) -> &Vec<u8> {
|
||||
return &self.result;
|
||||
}
|
||||
|
||||
/// Check whether the buffer relayer has been updated in the last max_staleness seconds
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use switchboard_v2::BufferRelayerAccountData;
|
||||
///
|
||||
/// let buffer = BufferRelayerAccountData::new(buffer_account_info)?;
|
||||
/// buffer.check_staleness(clock::Clock::get().unwrap().unix_timestamp, 300)?;
|
||||
/// ```
|
||||
pub fn check_staleness(
|
||||
&self,
|
||||
unix_timestamp: i64,
|
||||
max_staleness: i64,
|
||||
) -> anchor_lang::Result<()> {
|
||||
let staleness = unix_timestamp - self.latest_confirmed_round.round_open_timestamp;
|
||||
if staleness > max_staleness {
|
||||
msg!("Feed has not been updated in {} seconds!", staleness);
|
||||
return Err(SwitchboardError::StaleFeed.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn discriminator() -> [u8; 8] {
|
||||
return [50, 35, 51, 115, 169, 219, 158, 52];
|
||||
}
|
||||
}
|
|
@ -3,20 +3,26 @@ use anchor_lang::prelude::*;
|
|||
#[error_code]
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub enum SwitchboardError {
|
||||
#[msg("Aggregator is not currently populated with a valid round.")]
|
||||
#[msg("Aggregator is not currently populated with a valid round")]
|
||||
InvalidAggregatorRound,
|
||||
#[msg("Failed to convert string to decimal format.")]
|
||||
#[msg("Failed to convert string to decimal format")]
|
||||
InvalidStrDecimalConversion,
|
||||
#[msg("Decimal conversion method failed.")]
|
||||
#[msg("Decimal conversion method failed")]
|
||||
DecimalConversionError,
|
||||
#[msg("An integer overflow occurred.")]
|
||||
#[msg("An integer overflow occurred")]
|
||||
IntegerOverflowError,
|
||||
#[msg("Account discriminator did not match.")]
|
||||
#[msg("Account discriminator did not match")]
|
||||
AccountDiscriminatorMismatch,
|
||||
#[msg("Vrf value is empty.")]
|
||||
#[msg("Vrf value is empty")]
|
||||
VrfEmptyError,
|
||||
#[msg("Failed to send requestRandomness instruction")]
|
||||
VrfCpiError,
|
||||
#[msg("Failed to send signed requestRandomness instruction")]
|
||||
VrfCpiSignedError,
|
||||
#[msg("Failed to deserialize account")]
|
||||
AccountDeserializationError,
|
||||
#[msg("Switchboard feed exceeded the staleness threshold")]
|
||||
StaleFeed,
|
||||
#[msg("Switchboard feed exceeded the confidence interval threshold")]
|
||||
ConfidenceIntervalExceeded,
|
||||
}
|
||||
|
|
|
@ -23,6 +23,11 @@ pub struct AggregatorHistoryBuffer<'a> {
|
|||
pub rows: Ref<'a, [AggregatorHistoryRow]>,
|
||||
}
|
||||
impl<'a> AggregatorHistoryBuffer<'a> {
|
||||
/// Returns the deserialized Switchboard history buffer account
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `history_buffer` - A Solana AccountInfo referencing an existing Switchboard history buffer account
|
||||
pub fn new(
|
||||
history_buffer: &'a AccountInfo,
|
||||
) -> anchor_lang::Result<AggregatorHistoryBuffer<'a>> {
|
||||
|
@ -41,7 +46,11 @@ impl<'a> AggregatorHistoryBuffer<'a> {
|
|||
});
|
||||
}
|
||||
|
||||
/// return the previous row in the history buffer for a given timestamp
|
||||
/// Return the previous row in the history buffer for a given timestamp
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `timestamp` - A unix timestamp to search in the history buffer
|
||||
pub fn lower_bound(&self, timestamp: i64) -> Option<AggregatorHistoryRow> {
|
||||
if self.rows[self.insertion_idx].timestamp == 0 {
|
||||
return None;
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
use anchor_spl::token::TokenAccount;
|
||||
|
||||
pub mod aggregator;
|
||||
pub mod buffer_relayer;
|
||||
pub mod decimal;
|
||||
pub mod error;
|
||||
pub mod history_buffer;
|
||||
pub mod vrf;
|
||||
|
||||
pub use aggregator::AggregatorAccountData;
|
||||
pub use history_buffer::AggregatorHistoryBuffer;
|
||||
pub use vrf::VrfAccountData;
|
||||
pub use vrf::VrfRequestRandomness;
|
||||
pub use aggregator::{AggregatorAccountData, AggregatorRound};
|
||||
pub use buffer_relayer::{BufferRelayerAccountData, BufferRelayerRound};
|
||||
pub use decimal::SwitchboardDecimal;
|
||||
pub use error::SwitchboardError;
|
||||
pub use history_buffer::{AggregatorHistoryBuffer, AggregatorHistoryRow};
|
||||
pub use vrf::{VrfAccountData, VrfRequestRandomness, VrfRound, VrfStatus};
|
||||
|
||||
/// Mainnet program id for Switchboard v2. Prints out as "SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f"
|
||||
pub const SWITCHBOARD_V2_MAINNET: solana_program::pubkey::Pubkey =
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#![allow(non_snake_case)]
|
||||
use super::error::SwitchboardError;
|
||||
#[allow(unaligned_references)]
|
||||
use crate::*;
|
||||
use super::error::SwitchboardError;
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token::TokenAccount;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use solana_program::account_info::AccountInfo;
|
||||
use solana_program::instruction::Instruction;
|
||||
|
@ -316,6 +316,8 @@ pub struct Callback {
|
|||
#[zero_copy]
|
||||
#[repr(packed)]
|
||||
pub struct VrfRound {
|
||||
// pub producer_pubkeys: [Pubkey; 16],
|
||||
// pub producer_pubkeys_len: u32,
|
||||
pub alpha: [u8; 256],
|
||||
pub alpha_len: u32,
|
||||
pub request_slot: u64,
|
||||
|
@ -356,7 +358,6 @@ pub struct VrfAccountData {
|
|||
pub test_mode: bool,
|
||||
// pub last_verified_round: VrfRound,
|
||||
pub current_round: VrfRound,
|
||||
//
|
||||
pub _ebuf: [u8; 1024], // Buffer for future info
|
||||
}
|
||||
impl Default for VrfAccountData {
|
||||
|
@ -366,6 +367,19 @@ impl Default for VrfAccountData {
|
|||
}
|
||||
|
||||
impl VrfAccountData {
|
||||
/// Returns the deserialized Switchboard VRF account
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `switchboard_vrf` - A Solana AccountInfo referencing an existing Switchboard VRF account
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use switchboard_v2::VrfAccountData;
|
||||
///
|
||||
/// let vrf = VrfAccountData::new(vrf_account_info)?;
|
||||
/// ```
|
||||
pub fn new<'info>(
|
||||
switchboard_vrf: &'info AccountInfo,
|
||||
) -> anchor_lang::Result<Ref<'info, VrfAccountData>> {
|
||||
|
@ -374,17 +388,27 @@ impl VrfAccountData {
|
|||
let mut disc_bytes = [0u8; 8];
|
||||
disc_bytes.copy_from_slice(&data[..8]);
|
||||
if disc_bytes != VrfAccountData::discriminator() {
|
||||
msg!("{:?}", disc_bytes);
|
||||
return Err(error!(SwitchboardError::AccountDiscriminatorMismatch));
|
||||
}
|
||||
|
||||
Ok(Ref::map(data, |data| bytemuck::from_bytes(&data[8..])))
|
||||
}
|
||||
|
||||
/// Returns the current VRF round ID
|
||||
pub fn get_current_randomness_round_id(&self) -> u128 {
|
||||
self.counter
|
||||
}
|
||||
|
||||
/// If sufficient oracle responses, returns the latest on-chain result in SwitchboardDecimal format
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use switchboard_v2::AggregatorAccountData;
|
||||
/// use std::convert::TryInto;
|
||||
///
|
||||
/// let feed_result = AggregatorAccountData::new(feed_account_info)?.get_result()?;
|
||||
/// let decimal: f64 = feed_result.try_into()?;
|
||||
/// ```
|
||||
pub fn get_result(&self) -> anchor_lang::Result<[u8; 32]> {
|
||||
if self.current_round.result == [0u8; 32] {
|
||||
return Err(error!(SwitchboardError::VrfEmptyError));
|
||||
|
@ -1628,4 +1652,4 @@ mod tests {
|
|||
assert_eq!(vrf.get_result().unwrap(), VRF_RESULT);
|
||||
assert_eq!(vrf.status, VrfStatus::StatusCallbackSuccess);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
"@project-serum/anchor": "^0.24.2",
|
||||
"@saberhq/token-utils": "^1.13.32",
|
||||
"@solana/spl-token": "^0.2.0",
|
||||
"@solana/web3.js": "^1.42.0",
|
||||
"@solana/web3.js": "^1.47.3",
|
||||
"@switchboard-xyz/switchboard-v2": "^0.0.121",
|
||||
"big.js": "^6.1.1",
|
||||
"bn.js": "^5.2.1",
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as anchor from "@project-serum/anchor";
|
|||
import { AccountMeta, PublicKey, TokenAmount } from "@solana/web3.js";
|
||||
import {
|
||||
AggregatorAccount,
|
||||
BufferRelayerAccount,
|
||||
CrankAccount,
|
||||
CrankRow,
|
||||
JobAccount,
|
||||
|
@ -15,7 +16,7 @@ import {
|
|||
SwitchboardPermissionValue,
|
||||
VrfAccount,
|
||||
} from "@switchboard-xyz/switchboard-v2";
|
||||
import type Big from "big.js";
|
||||
import Big from "big.js";
|
||||
import chalk from "chalk";
|
||||
import { getIdlAddress, getProgramDataAddress } from "./anchor.js";
|
||||
import { anchorBNtoDateTimeString } from "./date.js";
|
||||
|
@ -47,6 +48,28 @@ export const chalkString = (
|
|||
)}`;
|
||||
};
|
||||
|
||||
// JSON.stringify: Object => String
|
||||
export const pubKeyConverter = (key: any, value: any): any => {
|
||||
if (value instanceof PublicKey || key.toLowerCase().endsWith("publickey")) {
|
||||
return value.toString() ?? "";
|
||||
}
|
||||
if (value instanceof Uint8Array) {
|
||||
return `[${value.toString()}]`;
|
||||
}
|
||||
if (value instanceof anchor.BN) {
|
||||
return value.toString();
|
||||
}
|
||||
if (value instanceof Big) {
|
||||
return value.toString();
|
||||
}
|
||||
if (value instanceof SwitchboardDecimal) {
|
||||
return new Big(value.mantissa.toString())
|
||||
.div(new Big(10).pow(value.scale))
|
||||
.toString();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
export const tokenAmountString = (value: TokenAmount): string => {
|
||||
return `${value.uiAmountString ?? ""} (${value.amount})`;
|
||||
};
|
||||
|
@ -379,8 +402,7 @@ export async function prettyPrintJob(
|
|||
outputString +=
|
||||
chalkString("metadata", buffer2string(data.metadata as any), SPACING) +
|
||||
"\r\n";
|
||||
outputString +=
|
||||
chalkString("authorWallet", data.authorWallet, SPACING) + "\r\n";
|
||||
outputString += chalkString("authority", data.authority, SPACING) + "\r\n";
|
||||
outputString += chalkString("expiration", data.expiration, SPACING) + "\r\n";
|
||||
outputString += chalkString(
|
||||
"tasks",
|
||||
|
@ -416,7 +438,11 @@ export async function prettyPrintAggregator(
|
|||
|
||||
let outputString = "";
|
||||
outputString += chalk.underline(
|
||||
chalkString("## Aggregator", aggregatorAccount.publicKey!, SPACING) + "\r\n"
|
||||
chalkString(
|
||||
"## Aggregator",
|
||||
aggregatorAccount.publicKey ?? PublicKey.default,
|
||||
SPACING
|
||||
) + "\r\n"
|
||||
);
|
||||
|
||||
outputString +=
|
||||
|
@ -477,7 +503,7 @@ export async function prettyPrintAggregator(
|
|||
aggregatorAccount.program,
|
||||
queue.authority,
|
||||
queueAccount.publicKey,
|
||||
aggregatorAccount.publicKey!
|
||||
aggregatorAccount.publicKey ?? PublicKey.default
|
||||
);
|
||||
const permissionData = await permissionAccount.loadData();
|
||||
outputString +=
|
||||
|
@ -702,6 +728,56 @@ export async function prettyPrintCrank(
|
|||
return outputString;
|
||||
}
|
||||
|
||||
export async function prettyPrintBufferRelayer(
|
||||
bufferRelayerAccount: BufferRelayerAccount,
|
||||
accountData?: any,
|
||||
printJob = false,
|
||||
SPACING = 24
|
||||
): Promise<string> {
|
||||
const data = accountData ?? (await bufferRelayerAccount.loadData());
|
||||
|
||||
let outputString = "";
|
||||
|
||||
outputString += chalk.underline(
|
||||
chalkString("## BufferRelayer", bufferRelayerAccount.publicKey, SPACING) +
|
||||
"\r\n"
|
||||
);
|
||||
outputString +=
|
||||
chalkString("name", buffer2string(data.name as any), SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("queuePubkey", data.queuePubkey, SPACING) + "\r\n";
|
||||
outputString += chalkString("escrow", data.escrow, SPACING) + "\r\n";
|
||||
outputString += chalkString("authority", data.authority, SPACING) + "\r\n";
|
||||
outputString += chalkString("jobPubkey", data.jobPubkey, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("minUpdateDelaySeconds", data.minUpdateDelaySeconds, SPACING) +
|
||||
"\r\n";
|
||||
|
||||
const result = data.result as number[];
|
||||
outputString +=
|
||||
chalkString(
|
||||
"result",
|
||||
`[${result.map((r) => r.toString()).join(",")}]`,
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
outputString +=
|
||||
chalkString(
|
||||
"currentRound",
|
||||
JSON.stringify(data.currentRound, pubKeyConverter, 2),
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
|
||||
if (printJob) {
|
||||
const jobAccount = new JobAccount({
|
||||
program: bufferRelayerAccount.program,
|
||||
publicKey: data.jobPubkey,
|
||||
});
|
||||
outputString += "\r\n" + (await prettyPrintJob(jobAccount));
|
||||
}
|
||||
|
||||
return outputString;
|
||||
}
|
||||
|
||||
export async function prettyPrintSwitchboardAccount(
|
||||
program: anchor.Program,
|
||||
publicKey: PublicKey,
|
||||
|
@ -745,6 +821,13 @@ export async function prettyPrintSwitchboardAccount(
|
|||
const vrfAccount = new VrfAccount({ program, publicKey });
|
||||
return prettyPrintVrf(vrfAccount, undefined);
|
||||
}
|
||||
case "BufferRelayerAccountData": {
|
||||
const bufferRelayerAccount = new BufferRelayerAccount({
|
||||
program,
|
||||
publicKey,
|
||||
});
|
||||
return prettyPrintBufferRelayer(bufferRelayerAccount, undefined);
|
||||
}
|
||||
case "BUFFERxx": {
|
||||
return `Found buffer account but dont know which one`;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ export interface CreateQueueParams {
|
|||
numOracles?: number;
|
||||
unpermissionedFeeds?: boolean;
|
||||
unpermissionedVrf?: boolean;
|
||||
enableBufferRelayers?: boolean;
|
||||
}
|
||||
|
||||
export interface CreateQueueResponse {
|
||||
|
@ -152,6 +153,7 @@ export async function createQueue(
|
|||
queueSize: queueSize,
|
||||
unpermissionedFeeds: params.unpermissionedFeeds ?? false,
|
||||
unpermissionedVrf: params.unpermissionedVrf ?? false,
|
||||
enableBufferRelayers: params.enableBufferRelayers ?? false,
|
||||
})
|
||||
.accounts({
|
||||
oracleQueue: queueKeypair.publicKey,
|
||||
|
@ -282,16 +284,16 @@ export async function createQueue(
|
|||
})
|
||||
);
|
||||
|
||||
const createAccountSignatures = packAndSend(
|
||||
const createAccountSignatures = await packAndSend(
|
||||
program,
|
||||
[ixns, finalTransactions],
|
||||
signers,
|
||||
payerKeypair.publicKey
|
||||
);
|
||||
|
||||
const result = await program.provider.connection.confirmTransaction(
|
||||
createAccountSignatures[-1]
|
||||
);
|
||||
// const result = await program.provider.connection.confirmTransaction(
|
||||
// createAccountSignatures[-1]
|
||||
// );
|
||||
|
||||
return {
|
||||
queueAccount,
|
||||
|
|
|
@ -3,6 +3,7 @@ import { ACCOUNT_DISCRIMINATOR_SIZE } from "@project-serum/anchor";
|
|||
import type { PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
AggregatorAccount,
|
||||
BufferRelayerAccount,
|
||||
CrankAccount,
|
||||
JobAccount,
|
||||
LeaseAccount,
|
||||
|
@ -26,6 +27,7 @@ export const SWITCHBOARD_ACCOUNT_TYPES = [
|
|||
"SbState",
|
||||
"BUFFERxx",
|
||||
"CrankAccountData",
|
||||
"BufferRelayerAccountData",
|
||||
] as const;
|
||||
|
||||
export type SwitchboardAccount =
|
||||
|
@ -37,7 +39,8 @@ export type SwitchboardAccount =
|
|||
| LeaseAccount
|
||||
| ProgramStateAccount
|
||||
| VrfAccount
|
||||
| CrankAccount;
|
||||
| CrankAccount
|
||||
| BufferRelayerAccount;
|
||||
|
||||
export type SwitchboardAccountType = typeof SWITCHBOARD_ACCOUNT_TYPES[number];
|
||||
|
||||
|
@ -109,6 +112,9 @@ export const loadSwitchboardAccount = async (
|
|||
case "VrfAccountData": {
|
||||
return [accountType, new VrfAccount({ program, publicKey })];
|
||||
}
|
||||
case "BufferRelayerAccountData": {
|
||||
return [accountType, new BufferRelayerAccount({ program, publicKey })];
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidSwitchboardAccount();
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
import * as anchor from "@project-serum/anchor";
|
||||
import * as spl from "@solana/spl-token";
|
||||
import { clusterApiUrl, Connection, Keypair, PublicKey } from "@solana/web3.js";
|
||||
import * as sbv2 from "@switchboard-xyz/switchboard-v2";
|
||||
import {
|
||||
CrankAccount,
|
||||
OracleAccount,
|
||||
PermissionAccount,
|
||||
ProgramStateAccount,
|
||||
} from "@switchboard-xyz/switchboard-v2";
|
||||
import chalk from "chalk";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { getIdlAddress, getProgramDataAddress } from "../anchor.js";
|
||||
import { anchorBNtoDateString } from "../date.js";
|
||||
import { createQueue } from "../queue.js";
|
||||
import { getOrCreateSwitchboardTokenAccount } from "../state.js";
|
||||
|
||||
const LATEST_DOCKER_VERSION = "dev-v2-07-11-22";
|
||||
|
||||
|
@ -315,95 +323,150 @@ secrets:
|
|||
);
|
||||
const idlAddress = await getIdlAddress(switchboardProgram.programId);
|
||||
|
||||
const [switchboardProgramState] =
|
||||
sbv2.ProgramStateAccount.fromSeed(switchboardProgram);
|
||||
let programState: any;
|
||||
try {
|
||||
programState = await switchboardProgramState.loadData();
|
||||
} catch {
|
||||
await sbv2.ProgramStateAccount.create(switchboardProgram, {
|
||||
mint: spl.NATIVE_MINT,
|
||||
daoMint: spl.NATIVE_MINT,
|
||||
});
|
||||
programState = await switchboardProgramState.loadData();
|
||||
}
|
||||
// const [switchboardProgramState] =
|
||||
// sbv2.ProgramStateAccount.fromSeed(switchboardProgram);
|
||||
// let programState: any;
|
||||
// try {
|
||||
// programState = await switchboardProgramState.loadData();
|
||||
// } catch {
|
||||
// await sbv2.ProgramStateAccount.create(switchboardProgram, {
|
||||
// mint: spl.NATIVE_MINT,
|
||||
// daoMint: spl.NATIVE_MINT,
|
||||
// });
|
||||
// programState = await switchboardProgramState.loadData();
|
||||
// }
|
||||
|
||||
const mint = await switchboardProgramState.getTokenMint();
|
||||
// const mint = await switchboardProgramState.getTokenMint();
|
||||
|
||||
const payerSwitchboardWallet = (
|
||||
await spl.getOrCreateAssociatedTokenAccount(
|
||||
connection,
|
||||
payerKeypair,
|
||||
mint.address,
|
||||
payerKeypair.publicKey,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
spl.TOKEN_PROGRAM_ID,
|
||||
spl.ASSOCIATED_TOKEN_PROGRAM_ID
|
||||
)
|
||||
).address;
|
||||
// const payerSwitchboardWallet = (
|
||||
// await spl.getOrCreateAssociatedTokenAccount(
|
||||
// connection,
|
||||
// payerKeypair,
|
||||
// mint.address,
|
||||
// payerKeypair.publicKey,
|
||||
// undefined,
|
||||
// undefined,
|
||||
// undefined,
|
||||
// spl.TOKEN_PROGRAM_ID,
|
||||
// spl.ASSOCIATED_TOKEN_PROGRAM_ID
|
||||
// )
|
||||
// ).address;
|
||||
|
||||
// create queue with unpermissioned VRF accounts enabled
|
||||
const queueAccount = await sbv2.OracleQueueAccount.create(
|
||||
// // create queue with unpermissioned VRF accounts enabled
|
||||
// const queueAccount = await sbv2.OracleQueueAccount.create(
|
||||
// switchboardProgram,
|
||||
// {
|
||||
// name: Buffer.from("My Test Queue"),
|
||||
// mint: spl.NATIVE_MINT,
|
||||
// authority: payerKeypair.publicKey, // Approve new participants
|
||||
// minStake: new anchor.BN(0), // Oracle minStake to heartbeat
|
||||
// reward: new anchor.BN(0), // Oracle rewards per request (non-VRF)
|
||||
// queueSize: 10, // Number of active oracles a queue can support
|
||||
// unpermissionedFeeds: true, // Whether feeds need PERMIT_ORACLE_QUEUE_USAGE permissions
|
||||
// unpermissionedVrf: true, // Whether VRF accounts need PERMIT_VRF_REQUESTS permissions
|
||||
// enableBufferRelayers: true,
|
||||
// }
|
||||
// );
|
||||
// await queueAccount.setVrfSettings({
|
||||
// authority: payerKeypair,
|
||||
// unpermissionedVrf: true,
|
||||
// });
|
||||
// const queue = await queueAccount.loadData();
|
||||
|
||||
// // create a crank for the queue
|
||||
// const crankAccount = await sbv2.CrankAccount.create(switchboardProgram, {
|
||||
// name: Buffer.from("My Crank"),
|
||||
// maxRows: 100,
|
||||
// queueAccount,
|
||||
// });
|
||||
// const crank = await crankAccount.loadData();
|
||||
|
||||
// // create oracle to run locally
|
||||
// const oracleAccount = await sbv2.OracleAccount.create(switchboardProgram, {
|
||||
// name: Buffer.from("My Oracle"),
|
||||
// oracleAuthority: payerKeypair,
|
||||
// queueAccount,
|
||||
// });
|
||||
// const oracle = await oracleAccount.loadData();
|
||||
|
||||
// // grant oracle heartbeat permissions
|
||||
// const oraclePermissionAccount = await sbv2.PermissionAccount.create(
|
||||
// switchboardProgram,
|
||||
// {
|
||||
// authority: queue.authority,
|
||||
// granter: queueAccount.publicKey,
|
||||
// grantee: oracleAccount.publicKey,
|
||||
// }
|
||||
// );
|
||||
// await oraclePermissionAccount.set({
|
||||
// authority: payerKeypair,
|
||||
// enable: true,
|
||||
// permission: sbv2.SwitchboardPermission.PERMIT_ORACLE_HEARTBEAT,
|
||||
// });
|
||||
|
||||
const queueResponse = await createQueue(
|
||||
switchboardProgram,
|
||||
{
|
||||
name: Buffer.from("My Test Queue"),
|
||||
mint: spl.NATIVE_MINT,
|
||||
authority: payerKeypair.publicKey, // Approve new participants
|
||||
minStake: new anchor.BN(0), // Oracle minStake to heartbeat
|
||||
reward: new anchor.BN(0), // Oracle rewards per request (non-VRF)
|
||||
queueSize: 10, // Number of active oracles a queue can support
|
||||
unpermissionedFeeds: true, // Whether feeds need PERMIT_ORACLE_QUEUE_USAGE permissions
|
||||
unpermissionedVrf: true, // Whether VRF accounts need PERMIT_VRF_REQUESTS permissions
|
||||
}
|
||||
authority: payerKeypair.publicKey,
|
||||
name: "Test Queue",
|
||||
metadata: `created ${anchorBNtoDateString(
|
||||
new anchor.BN(Math.floor(Date.now() / 1000))
|
||||
)}`,
|
||||
minStake: new anchor.BN(0),
|
||||
reward: new anchor.BN(0),
|
||||
crankSize: 10,
|
||||
oracleTimeout: 180,
|
||||
numOracles: 1,
|
||||
unpermissionedFeeds: true,
|
||||
unpermissionedVrf: true,
|
||||
enableBufferRelayers: true,
|
||||
},
|
||||
10
|
||||
);
|
||||
await queueAccount.setVrfSettings({
|
||||
authority: payerKeypair,
|
||||
unpermissionedVrf: true,
|
||||
});
|
||||
|
||||
const queueAccount = queueResponse.queueAccount;
|
||||
const queue = await queueAccount.loadData();
|
||||
|
||||
// create a crank for the queue
|
||||
const crankAccount = await sbv2.CrankAccount.create(switchboardProgram, {
|
||||
name: Buffer.from("My Crank"),
|
||||
maxRows: 100,
|
||||
queueAccount,
|
||||
const [programStateAccount, stateBump] =
|
||||
ProgramStateAccount.fromSeed(switchboardProgram);
|
||||
const programState = await programStateAccount.loadData();
|
||||
|
||||
const mint = await queueAccount.loadMint();
|
||||
|
||||
const payerSwitchboardWallet = await getOrCreateSwitchboardTokenAccount(
|
||||
switchboardProgram,
|
||||
mint
|
||||
);
|
||||
|
||||
const crankAccount = new CrankAccount({
|
||||
program: switchboardProgram,
|
||||
publicKey: queueResponse.crankPubkey,
|
||||
});
|
||||
const crank = await crankAccount.loadData();
|
||||
|
||||
// create oracle to run locally
|
||||
const oracleAccount = await sbv2.OracleAccount.create(switchboardProgram, {
|
||||
name: Buffer.from("My Oracle"),
|
||||
oracleAuthority: payerKeypair,
|
||||
queueAccount,
|
||||
const oracleAccount = new OracleAccount({
|
||||
program: switchboardProgram,
|
||||
publicKey: queueResponse.oracles[0],
|
||||
});
|
||||
const oracle = await oracleAccount.loadData();
|
||||
|
||||
// grant oracle heartbeat permissions
|
||||
const oraclePermissionAccount = await sbv2.PermissionAccount.create(
|
||||
const [permissionAccount] = PermissionAccount.fromSeed(
|
||||
switchboardProgram,
|
||||
{
|
||||
authority: queue.authority,
|
||||
granter: queueAccount.publicKey,
|
||||
grantee: oracleAccount.publicKey,
|
||||
}
|
||||
queue.authority,
|
||||
queueAccount.publicKey,
|
||||
oracleAccount.publicKey
|
||||
);
|
||||
await oraclePermissionAccount.set({
|
||||
authority: payerKeypair,
|
||||
enable: true,
|
||||
permission: sbv2.SwitchboardPermission.PERMIT_ORACLE_HEARTBEAT,
|
||||
});
|
||||
const permission = await permissionAccount.loadData();
|
||||
|
||||
const ctx: ISwitchboardTestEnvironment = {
|
||||
programId: switchboardProgram.programId,
|
||||
programDataAddress,
|
||||
idlAddress,
|
||||
programState: switchboardProgramState.publicKey,
|
||||
programState: programStateAccount.publicKey,
|
||||
switchboardVault: programState.tokenVault,
|
||||
switchboardMint: mint.address,
|
||||
tokenWallet: payerSwitchboardWallet,
|
||||
queue: queueAccount.publicKey,
|
||||
queue: queueResponse.queueAccount.publicKey,
|
||||
queueAuthority: queue.authority,
|
||||
queueBuffer: queue.dataBuffer,
|
||||
crank: crankAccount.publicKey,
|
||||
|
@ -411,7 +474,7 @@ secrets:
|
|||
oracle: oracleAccount.publicKey,
|
||||
oracleAuthority: oracle.oracleAuthority,
|
||||
oracleEscrow: oracle.tokenAccount,
|
||||
oraclePermissions: oraclePermissionAccount.publicKey,
|
||||
oraclePermissions: permissionAccount.publicKey,
|
||||
payerKeypairPath: fullKeypairPath,
|
||||
additionalClonedAccounts,
|
||||
};
|
||||
|
|
|
@ -37,14 +37,10 @@ export async function packAndSend(
|
|||
sendAndConfirmRawTransaction(program.provider.connection, rawTx, {
|
||||
maxRetries: 10,
|
||||
commitment: "processed",
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
throw error;
|
||||
})
|
||||
.then((sig) => {
|
||||
return sig;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
throw error;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -2092,15 +2092,19 @@ export interface OracleQueueInitParams {
|
|||
*/
|
||||
queueSize?: number;
|
||||
/**
|
||||
* Eanbling this setting means data feeds do not need explicit permission
|
||||
* Enabling this setting means data feeds do not need explicit permission
|
||||
* to join the queue.
|
||||
*/
|
||||
unpermissionedFeeds?: boolean;
|
||||
/**
|
||||
* Eanbling this setting means data feeds do not need explicit permission
|
||||
* Enabling this setting means data feeds do not need explicit permission
|
||||
* to request VRF proofs and verifications from this queue.
|
||||
*/
|
||||
unpermissionedVrf?: boolean;
|
||||
/**
|
||||
* Enabling this setting will allow buffer relayer accounts to call openRound.
|
||||
*/
|
||||
enableBufferRelayers?: boolean;
|
||||
mint: PublicKey;
|
||||
}
|
||||
|
||||
|
@ -2236,6 +2240,7 @@ export class OracleQueueAccount {
|
|||
minimumDelaySeconds: params.minimumDelaySeconds ?? 5,
|
||||
queueSize: params.queueSize,
|
||||
unpermissionedFeeds: params.unpermissionedFeeds ?? false,
|
||||
enableBufferRelayers: params.enableBufferRelayers ?? false,
|
||||
})
|
||||
.accounts({
|
||||
oracleQueue: oracleQueueAccount.publicKey,
|
||||
|
@ -3217,25 +3222,13 @@ export class OracleAccount {
|
|||
ProgramStateAccount.fromSeed(program);
|
||||
|
||||
const mint = await params.queueAccount.loadMint();
|
||||
const wallet = await spl.createAccount(
|
||||
program.provider.connection,
|
||||
payerKeypair,
|
||||
mint.address,
|
||||
programWallet(program).publicKey,
|
||||
Keypair.generate()
|
||||
);
|
||||
await spl.setAuthority(
|
||||
program.provider.connection,
|
||||
programWallet(program),
|
||||
wallet,
|
||||
programWallet(program),
|
||||
spl.AuthorityType.AccountOwner,
|
||||
programStateAccount.publicKey
|
||||
);
|
||||
|
||||
const walletKeypair = Keypair.generate();
|
||||
|
||||
const [oracleAccount, oracleBump] = OracleAccount.fromSeed(
|
||||
program,
|
||||
params.queueAccount,
|
||||
wallet
|
||||
walletKeypair.publicKey
|
||||
);
|
||||
|
||||
await program.methods
|
||||
|
@ -3249,11 +3242,28 @@ export class OracleAccount {
|
|||
oracle: oracleAccount.publicKey,
|
||||
oracleAuthority: authorityKeypair.publicKey,
|
||||
queue: params.queueAccount.publicKey,
|
||||
wallet,
|
||||
wallet: walletKeypair.publicKey,
|
||||
programState: programStateAccount.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
payer: programWallet(program).publicKey,
|
||||
})
|
||||
.preInstructions([
|
||||
spl.createInitializeAccountInstruction(
|
||||
walletKeypair.publicKey,
|
||||
mint.address,
|
||||
programWallet(program).publicKey,
|
||||
spl.TOKEN_PROGRAM_ID
|
||||
),
|
||||
spl.createSetAuthorityInstruction(
|
||||
walletKeypair.publicKey,
|
||||
programWallet(program).publicKey,
|
||||
spl.AuthorityType.AccountOwner,
|
||||
programStateAccount.publicKey,
|
||||
[programWallet(program), walletKeypair],
|
||||
spl.TOKEN_PROGRAM_ID
|
||||
),
|
||||
])
|
||||
.signers([walletKeypair])
|
||||
.rpc();
|
||||
|
||||
return new OracleAccount({ program, publicKey: oracleAccount.publicKey });
|
||||
|
@ -3583,24 +3593,15 @@ export class VrfAccount {
|
|||
const keypair = params.keypair;
|
||||
const size = program.account.vrfAccountData.size;
|
||||
const switchTokenMint = await params.queue.loadMint();
|
||||
const escrow = (
|
||||
await spl.getOrCreateAssociatedTokenAccount(
|
||||
program.provider.connection,
|
||||
programWallet(program),
|
||||
switchTokenMint.address,
|
||||
keypair.publicKey,
|
||||
true
|
||||
)
|
||||
).address;
|
||||
|
||||
await spl.setAuthority(
|
||||
program.provider.connection,
|
||||
programWallet(program),
|
||||
escrow,
|
||||
keypair,
|
||||
spl.AuthorityType.AccountOwner,
|
||||
programStateAccount.publicKey
|
||||
const escrow = await spl.getAssociatedTokenAddress(
|
||||
switchTokenMint.address,
|
||||
keypair.publicKey,
|
||||
true,
|
||||
spl.TOKEN_PROGRAM_ID,
|
||||
spl.ASSOCIATED_TOKEN_PROGRAM_ID
|
||||
);
|
||||
|
||||
await program.methods
|
||||
.vrfInit({
|
||||
stateBump,
|
||||
|
@ -3615,6 +3616,22 @@ export class VrfAccount {
|
|||
tokenProgram: spl.TOKEN_PROGRAM_ID,
|
||||
})
|
||||
.preInstructions([
|
||||
spl.createAssociatedTokenAccountInstruction(
|
||||
programWallet(program).publicKey,
|
||||
escrow,
|
||||
keypair.publicKey,
|
||||
switchTokenMint.address,
|
||||
spl.TOKEN_PROGRAM_ID,
|
||||
spl.ASSOCIATED_TOKEN_PROGRAM_ID
|
||||
),
|
||||
spl.createSetAuthorityInstruction(
|
||||
escrow,
|
||||
keypair.publicKey,
|
||||
spl.AuthorityType.AccountOwner,
|
||||
programStateAccount.publicKey,
|
||||
[programWallet(program), keypair],
|
||||
spl.TOKEN_PROGRAM_ID
|
||||
),
|
||||
anchor.web3.SystemProgram.createAccount({
|
||||
fromPubkey: programWallet(program).publicKey,
|
||||
newAccountPubkey: keypair.publicKey,
|
||||
|
@ -3632,21 +3649,6 @@ export class VrfAccount {
|
|||
return new VrfAccount({ program, keypair, publicKey: keypair.publicKey });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the callback CPI when vrf verification is successful.
|
||||
*/
|
||||
// async setCallback(
|
||||
// params: VrfSetCallbackParams
|
||||
// ): Promise<TransactionSignature> {
|
||||
// return await this.program.rpc.vrfSetCallback(params, {
|
||||
// accounts: {
|
||||
// vrf: this.publicKey,
|
||||
// authority: params.authority.publicKey,
|
||||
// },
|
||||
// signers: [params.authority],
|
||||
// });
|
||||
// }
|
||||
|
||||
/**
|
||||
* Trigger new randomness production on the vrf account
|
||||
*/
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
"docs:deploy": "yarn workspace website deploy",
|
||||
"gen:idl": "rawrtools gen:anchor SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f -o website/idl -p /idl",
|
||||
"gen:idl:devnet": "rawrtools gen:anchor --devnet 2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG -o website/idl -p /idl",
|
||||
"nuke": "shx rm -rf {./programs/*,./packages/*,./website,./libraries/*,.}/{node_modules,yarn*.log,build,dist,lib,.anchor,Cargo.lock,.docusaurus,target/release,target/rls, target/bpfel-unknown-unknown}"
|
||||
"nuke": "shx rm -rf {./programs/*,./packages/*,./website,./libraries/*,.}/{node_modules,yarn*.log,build,dist,lib,.anchor,Cargo.lock,.docusaurus,target/release,target/rls,target/bpfel-unknown-unknown}"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@gallynaut/rawrtools": "^0.0.1",
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"start": "ts-node src/main",
|
||||
"build": "rimraf dist && ./esbuild.js",
|
||||
"build": "shx rm -rf dist && ./esbuild.js",
|
||||
"test": "echo \"No test script for @switchboard-xyz/v2-feed-parser\" && exit 0"
|
||||
},
|
||||
"author": "",
|
||||
|
@ -27,8 +27,8 @@
|
|||
"@types/big.js": "^6.1.3",
|
||||
"esbuild-node-externals": "^1.4.1",
|
||||
"estrella": "^1.4.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-node": "^10.7.0",
|
||||
"typescript": "^4.6.3"
|
||||
"typescript": "^4.6.3",
|
||||
"shx": "^0.3.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "anchor-buffer-parser"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "anchor_buffer_parser"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
switchboard-v2 = { path = "../../libraries/rs" }
|
||||
# switchboard-v2 = "^0.1.11"
|
||||
anchor-lang = "^0.24.2"
|
||||
solana-program = "~1.9.13"
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -0,0 +1,67 @@
|
|||
import * as borsh from "@project-serum/borsh" // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
import { Connection, PublicKey } from "@solana/web3.js"
|
||||
import { PROGRAM_ID } from "../programId"
|
||||
|
||||
export interface BufferClientFields {}
|
||||
|
||||
export interface BufferClientJSON {}
|
||||
|
||||
export class BufferClient {
|
||||
static readonly discriminator = Buffer.from([142, 182, 89, 69, 9, 66, 10, 86])
|
||||
|
||||
static readonly layout = borsh.struct([])
|
||||
|
||||
constructor(fields: BufferClientFields) {}
|
||||
|
||||
static async fetch(
|
||||
c: Connection,
|
||||
address: PublicKey
|
||||
): Promise<BufferClient | null> {
|
||||
const info = await c.getAccountInfo(address)
|
||||
|
||||
if (info === null) {
|
||||
return null
|
||||
}
|
||||
if (!info.owner.equals(PROGRAM_ID)) {
|
||||
throw new Error("account doesn't belong to this program")
|
||||
}
|
||||
|
||||
return this.decode(info.data)
|
||||
}
|
||||
|
||||
static async fetchMultiple(
|
||||
c: Connection,
|
||||
addresses: PublicKey[]
|
||||
): Promise<Array<BufferClient | null>> {
|
||||
const infos = await c.getMultipleAccountsInfo(addresses)
|
||||
|
||||
return infos.map((info) => {
|
||||
if (info === null) {
|
||||
return null
|
||||
}
|
||||
if (!info.owner.equals(PROGRAM_ID)) {
|
||||
throw new Error("account doesn't belong to this program")
|
||||
}
|
||||
|
||||
return this.decode(info.data)
|
||||
})
|
||||
}
|
||||
|
||||
static decode(data: Buffer): BufferClient {
|
||||
if (!data.slice(0, 8).equals(BufferClient.discriminator)) {
|
||||
throw new Error("invalid account discriminator")
|
||||
}
|
||||
|
||||
const dec = BufferClient.layout.decode(data.slice(8))
|
||||
|
||||
return new BufferClient({})
|
||||
}
|
||||
|
||||
toJSON(): BufferClientJSON {
|
||||
return {}
|
||||
}
|
||||
|
||||
static fromJSON(obj: BufferClientJSON): BufferClient {
|
||||
return new BufferClient({})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export {
|
||||
BufferClient,
|
||||
BufferClientFields,
|
||||
BufferClientJSON,
|
||||
} from "./BufferClient"
|
|
@ -0,0 +1,690 @@
|
|||
export type AnchorError =
|
||||
| InstructionMissing
|
||||
| InstructionFallbackNotFound
|
||||
| InstructionDidNotDeserialize
|
||||
| InstructionDidNotSerialize
|
||||
| IdlInstructionStub
|
||||
| IdlInstructionInvalidProgram
|
||||
| ConstraintMut
|
||||
| ConstraintHasOne
|
||||
| ConstraintSigner
|
||||
| ConstraintRaw
|
||||
| ConstraintOwner
|
||||
| ConstraintRentExempt
|
||||
| ConstraintSeeds
|
||||
| ConstraintExecutable
|
||||
| ConstraintState
|
||||
| ConstraintAssociated
|
||||
| ConstraintAssociatedInit
|
||||
| ConstraintClose
|
||||
| ConstraintAddress
|
||||
| ConstraintZero
|
||||
| ConstraintTokenMint
|
||||
| ConstraintTokenOwner
|
||||
| ConstraintMintMintAuthority
|
||||
| ConstraintMintFreezeAuthority
|
||||
| ConstraintMintDecimals
|
||||
| ConstraintSpace
|
||||
| RequireViolated
|
||||
| RequireEqViolated
|
||||
| RequireKeysEqViolated
|
||||
| RequireNeqViolated
|
||||
| RequireKeysNeqViolated
|
||||
| RequireGtViolated
|
||||
| RequireGteViolated
|
||||
| AccountDiscriminatorAlreadySet
|
||||
| AccountDiscriminatorNotFound
|
||||
| AccountDiscriminatorMismatch
|
||||
| AccountDidNotDeserialize
|
||||
| AccountDidNotSerialize
|
||||
| AccountNotEnoughKeys
|
||||
| AccountNotMutable
|
||||
| AccountOwnedByWrongProgram
|
||||
| InvalidProgramId
|
||||
| InvalidProgramExecutable
|
||||
| AccountNotSigner
|
||||
| AccountNotSystemOwned
|
||||
| AccountNotInitialized
|
||||
| AccountNotProgramData
|
||||
| AccountNotAssociatedTokenAccount
|
||||
| AccountSysvarMismatch
|
||||
| StateInvalidAddress
|
||||
| DeclaredProgramIdMismatch
|
||||
| Deprecated
|
||||
|
||||
export class InstructionMissing extends Error {
|
||||
readonly code = 100
|
||||
readonly name = "InstructionMissing"
|
||||
readonly msg = "8 byte instruction identifier not provided"
|
||||
|
||||
constructor() {
|
||||
super("100: 8 byte instruction identifier not provided")
|
||||
}
|
||||
}
|
||||
|
||||
export class InstructionFallbackNotFound extends Error {
|
||||
readonly code = 101
|
||||
readonly name = "InstructionFallbackNotFound"
|
||||
readonly msg = "Fallback functions are not supported"
|
||||
|
||||
constructor() {
|
||||
super("101: Fallback functions are not supported")
|
||||
}
|
||||
}
|
||||
|
||||
export class InstructionDidNotDeserialize extends Error {
|
||||
readonly code = 102
|
||||
readonly name = "InstructionDidNotDeserialize"
|
||||
readonly msg = "The program could not deserialize the given instruction"
|
||||
|
||||
constructor() {
|
||||
super("102: The program could not deserialize the given instruction")
|
||||
}
|
||||
}
|
||||
|
||||
export class InstructionDidNotSerialize extends Error {
|
||||
readonly code = 103
|
||||
readonly name = "InstructionDidNotSerialize"
|
||||
readonly msg = "The program could not serialize the given instruction"
|
||||
|
||||
constructor() {
|
||||
super("103: The program could not serialize the given instruction")
|
||||
}
|
||||
}
|
||||
|
||||
export class IdlInstructionStub extends Error {
|
||||
readonly code = 1000
|
||||
readonly name = "IdlInstructionStub"
|
||||
readonly msg = "The program was compiled without idl instructions"
|
||||
|
||||
constructor() {
|
||||
super("1000: The program was compiled without idl instructions")
|
||||
}
|
||||
}
|
||||
|
||||
export class IdlInstructionInvalidProgram extends Error {
|
||||
readonly code = 1001
|
||||
readonly name = "IdlInstructionInvalidProgram"
|
||||
readonly msg =
|
||||
"The transaction was given an invalid program for the IDL instruction"
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
"1001: The transaction was given an invalid program for the IDL instruction"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintMut extends Error {
|
||||
readonly code = 2000
|
||||
readonly name = "ConstraintMut"
|
||||
readonly msg = "A mut constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2000: A mut constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintHasOne extends Error {
|
||||
readonly code = 2001
|
||||
readonly name = "ConstraintHasOne"
|
||||
readonly msg = "A has_one constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2001: A has_one constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintSigner extends Error {
|
||||
readonly code = 2002
|
||||
readonly name = "ConstraintSigner"
|
||||
readonly msg = "A signer constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2002: A signer constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintRaw extends Error {
|
||||
readonly code = 2003
|
||||
readonly name = "ConstraintRaw"
|
||||
readonly msg = "A raw constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2003: A raw constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintOwner extends Error {
|
||||
readonly code = 2004
|
||||
readonly name = "ConstraintOwner"
|
||||
readonly msg = "An owner constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2004: An owner constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintRentExempt extends Error {
|
||||
readonly code = 2005
|
||||
readonly name = "ConstraintRentExempt"
|
||||
readonly msg = "A rent exemption constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2005: A rent exemption constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintSeeds extends Error {
|
||||
readonly code = 2006
|
||||
readonly name = "ConstraintSeeds"
|
||||
readonly msg = "A seeds constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2006: A seeds constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintExecutable extends Error {
|
||||
readonly code = 2007
|
||||
readonly name = "ConstraintExecutable"
|
||||
readonly msg = "An executable constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2007: An executable constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintState extends Error {
|
||||
readonly code = 2008
|
||||
readonly name = "ConstraintState"
|
||||
readonly msg = "A state constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2008: A state constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintAssociated extends Error {
|
||||
readonly code = 2009
|
||||
readonly name = "ConstraintAssociated"
|
||||
readonly msg = "An associated constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2009: An associated constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintAssociatedInit extends Error {
|
||||
readonly code = 2010
|
||||
readonly name = "ConstraintAssociatedInit"
|
||||
readonly msg = "An associated init constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2010: An associated init constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintClose extends Error {
|
||||
readonly code = 2011
|
||||
readonly name = "ConstraintClose"
|
||||
readonly msg = "A close constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2011: A close constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintAddress extends Error {
|
||||
readonly code = 2012
|
||||
readonly name = "ConstraintAddress"
|
||||
readonly msg = "An address constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2012: An address constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintZero extends Error {
|
||||
readonly code = 2013
|
||||
readonly name = "ConstraintZero"
|
||||
readonly msg = "Expected zero account discriminant"
|
||||
|
||||
constructor() {
|
||||
super("2013: Expected zero account discriminant")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintTokenMint extends Error {
|
||||
readonly code = 2014
|
||||
readonly name = "ConstraintTokenMint"
|
||||
readonly msg = "A token mint constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2014: A token mint constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintTokenOwner extends Error {
|
||||
readonly code = 2015
|
||||
readonly name = "ConstraintTokenOwner"
|
||||
readonly msg = "A token owner constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2015: A token owner constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintMintMintAuthority extends Error {
|
||||
readonly code = 2016
|
||||
readonly name = "ConstraintMintMintAuthority"
|
||||
readonly msg = "A mint mint authority constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2016: A mint mint authority constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintMintFreezeAuthority extends Error {
|
||||
readonly code = 2017
|
||||
readonly name = "ConstraintMintFreezeAuthority"
|
||||
readonly msg = "A mint freeze authority constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2017: A mint freeze authority constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintMintDecimals extends Error {
|
||||
readonly code = 2018
|
||||
readonly name = "ConstraintMintDecimals"
|
||||
readonly msg = "A mint decimals constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2018: A mint decimals constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstraintSpace extends Error {
|
||||
readonly code = 2019
|
||||
readonly name = "ConstraintSpace"
|
||||
readonly msg = "A space constraint was violated"
|
||||
|
||||
constructor() {
|
||||
super("2019: A space constraint was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class RequireViolated extends Error {
|
||||
readonly code = 2500
|
||||
readonly name = "RequireViolated"
|
||||
readonly msg = "A require expression was violated"
|
||||
|
||||
constructor() {
|
||||
super("2500: A require expression was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class RequireEqViolated extends Error {
|
||||
readonly code = 2501
|
||||
readonly name = "RequireEqViolated"
|
||||
readonly msg = "A require_eq expression was violated"
|
||||
|
||||
constructor() {
|
||||
super("2501: A require_eq expression was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class RequireKeysEqViolated extends Error {
|
||||
readonly code = 2502
|
||||
readonly name = "RequireKeysEqViolated"
|
||||
readonly msg = "A require_keys_eq expression was violated"
|
||||
|
||||
constructor() {
|
||||
super("2502: A require_keys_eq expression was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class RequireNeqViolated extends Error {
|
||||
readonly code = 2503
|
||||
readonly name = "RequireNeqViolated"
|
||||
readonly msg = "A require_neq expression was violated"
|
||||
|
||||
constructor() {
|
||||
super("2503: A require_neq expression was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class RequireKeysNeqViolated extends Error {
|
||||
readonly code = 2504
|
||||
readonly name = "RequireKeysNeqViolated"
|
||||
readonly msg = "A require_keys_neq expression was violated"
|
||||
|
||||
constructor() {
|
||||
super("2504: A require_keys_neq expression was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class RequireGtViolated extends Error {
|
||||
readonly code = 2505
|
||||
readonly name = "RequireGtViolated"
|
||||
readonly msg = "A require_gt expression was violated"
|
||||
|
||||
constructor() {
|
||||
super("2505: A require_gt expression was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class RequireGteViolated extends Error {
|
||||
readonly code = 2506
|
||||
readonly name = "RequireGteViolated"
|
||||
readonly msg = "A require_gte expression was violated"
|
||||
|
||||
constructor() {
|
||||
super("2506: A require_gte expression was violated")
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountDiscriminatorAlreadySet extends Error {
|
||||
readonly code = 3000
|
||||
readonly name = "AccountDiscriminatorAlreadySet"
|
||||
readonly msg = "The account discriminator was already set on this account"
|
||||
|
||||
constructor() {
|
||||
super("3000: The account discriminator was already set on this account")
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountDiscriminatorNotFound extends Error {
|
||||
readonly code = 3001
|
||||
readonly name = "AccountDiscriminatorNotFound"
|
||||
readonly msg = "No 8 byte discriminator was found on the account"
|
||||
|
||||
constructor() {
|
||||
super("3001: No 8 byte discriminator was found on the account")
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountDiscriminatorMismatch extends Error {
|
||||
readonly code = 3002
|
||||
readonly name = "AccountDiscriminatorMismatch"
|
||||
readonly msg = "8 byte discriminator did not match what was expected"
|
||||
|
||||
constructor() {
|
||||
super("3002: 8 byte discriminator did not match what was expected")
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountDidNotDeserialize extends Error {
|
||||
readonly code = 3003
|
||||
readonly name = "AccountDidNotDeserialize"
|
||||
readonly msg = "Failed to deserialize the account"
|
||||
|
||||
constructor() {
|
||||
super("3003: Failed to deserialize the account")
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountDidNotSerialize extends Error {
|
||||
readonly code = 3004
|
||||
readonly name = "AccountDidNotSerialize"
|
||||
readonly msg = "Failed to serialize the account"
|
||||
|
||||
constructor() {
|
||||
super("3004: Failed to serialize the account")
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountNotEnoughKeys extends Error {
|
||||
readonly code = 3005
|
||||
readonly name = "AccountNotEnoughKeys"
|
||||
readonly msg = "Not enough account keys given to the instruction"
|
||||
|
||||
constructor() {
|
||||
super("3005: Not enough account keys given to the instruction")
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountNotMutable extends Error {
|
||||
readonly code = 3006
|
||||
readonly name = "AccountNotMutable"
|
||||
readonly msg = "The given account is not mutable"
|
||||
|
||||
constructor() {
|
||||
super("3006: The given account is not mutable")
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountOwnedByWrongProgram extends Error {
|
||||
readonly code = 3007
|
||||
readonly name = "AccountOwnedByWrongProgram"
|
||||
readonly msg =
|
||||
"The given account is owned by a different program than expected"
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
"3007: The given account is owned by a different program than expected"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidProgramId extends Error {
|
||||
readonly code = 3008
|
||||
readonly name = "InvalidProgramId"
|
||||
readonly msg = "Program ID was not as expected"
|
||||
|
||||
constructor() {
|
||||
super("3008: Program ID was not as expected")
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidProgramExecutable extends Error {
|
||||
readonly code = 3009
|
||||
readonly name = "InvalidProgramExecutable"
|
||||
readonly msg = "Program account is not executable"
|
||||
|
||||
constructor() {
|
||||
super("3009: Program account is not executable")
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountNotSigner extends Error {
|
||||
readonly code = 3010
|
||||
readonly name = "AccountNotSigner"
|
||||
readonly msg = "The given account did not sign"
|
||||
|
||||
constructor() {
|
||||
super("3010: The given account did not sign")
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountNotSystemOwned extends Error {
|
||||
readonly code = 3011
|
||||
readonly name = "AccountNotSystemOwned"
|
||||
readonly msg = "The given account is not owned by the system program"
|
||||
|
||||
constructor() {
|
||||
super("3011: The given account is not owned by the system program")
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountNotInitialized extends Error {
|
||||
readonly code = 3012
|
||||
readonly name = "AccountNotInitialized"
|
||||
readonly msg = "The program expected this account to be already initialized"
|
||||
|
||||
constructor() {
|
||||
super("3012: The program expected this account to be already initialized")
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountNotProgramData extends Error {
|
||||
readonly code = 3013
|
||||
readonly name = "AccountNotProgramData"
|
||||
readonly msg = "The given account is not a program data account"
|
||||
|
||||
constructor() {
|
||||
super("3013: The given account is not a program data account")
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountNotAssociatedTokenAccount extends Error {
|
||||
readonly code = 3014
|
||||
readonly name = "AccountNotAssociatedTokenAccount"
|
||||
readonly msg = "The given account is not the associated token account"
|
||||
|
||||
constructor() {
|
||||
super("3014: The given account is not the associated token account")
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountSysvarMismatch extends Error {
|
||||
readonly code = 3015
|
||||
readonly name = "AccountSysvarMismatch"
|
||||
readonly msg = "The given public key does not match the required sysvar"
|
||||
|
||||
constructor() {
|
||||
super("3015: The given public key does not match the required sysvar")
|
||||
}
|
||||
}
|
||||
|
||||
export class StateInvalidAddress extends Error {
|
||||
readonly code = 4000
|
||||
readonly name = "StateInvalidAddress"
|
||||
readonly msg = "The given state account does not have the correct address"
|
||||
|
||||
constructor() {
|
||||
super("4000: The given state account does not have the correct address")
|
||||
}
|
||||
}
|
||||
|
||||
export class DeclaredProgramIdMismatch extends Error {
|
||||
readonly code = 4100
|
||||
readonly name = "DeclaredProgramIdMismatch"
|
||||
readonly msg = "The declared program id does not match the actual program id"
|
||||
|
||||
constructor() {
|
||||
super("4100: The declared program id does not match the actual program id")
|
||||
}
|
||||
}
|
||||
|
||||
export class Deprecated extends Error {
|
||||
readonly code = 5000
|
||||
readonly name = "Deprecated"
|
||||
readonly msg = "The API being used is deprecated and should no longer be used"
|
||||
|
||||
constructor() {
|
||||
super("5000: The API being used is deprecated and should no longer be used")
|
||||
}
|
||||
}
|
||||
|
||||
export function fromCode(code: number): AnchorError | null {
|
||||
switch (code) {
|
||||
case 100:
|
||||
return new InstructionMissing()
|
||||
case 101:
|
||||
return new InstructionFallbackNotFound()
|
||||
case 102:
|
||||
return new InstructionDidNotDeserialize()
|
||||
case 103:
|
||||
return new InstructionDidNotSerialize()
|
||||
case 1000:
|
||||
return new IdlInstructionStub()
|
||||
case 1001:
|
||||
return new IdlInstructionInvalidProgram()
|
||||
case 2000:
|
||||
return new ConstraintMut()
|
||||
case 2001:
|
||||
return new ConstraintHasOne()
|
||||
case 2002:
|
||||
return new ConstraintSigner()
|
||||
case 2003:
|
||||
return new ConstraintRaw()
|
||||
case 2004:
|
||||
return new ConstraintOwner()
|
||||
case 2005:
|
||||
return new ConstraintRentExempt()
|
||||
case 2006:
|
||||
return new ConstraintSeeds()
|
||||
case 2007:
|
||||
return new ConstraintExecutable()
|
||||
case 2008:
|
||||
return new ConstraintState()
|
||||
case 2009:
|
||||
return new ConstraintAssociated()
|
||||
case 2010:
|
||||
return new ConstraintAssociatedInit()
|
||||
case 2011:
|
||||
return new ConstraintClose()
|
||||
case 2012:
|
||||
return new ConstraintAddress()
|
||||
case 2013:
|
||||
return new ConstraintZero()
|
||||
case 2014:
|
||||
return new ConstraintTokenMint()
|
||||
case 2015:
|
||||
return new ConstraintTokenOwner()
|
||||
case 2016:
|
||||
return new ConstraintMintMintAuthority()
|
||||
case 2017:
|
||||
return new ConstraintMintFreezeAuthority()
|
||||
case 2018:
|
||||
return new ConstraintMintDecimals()
|
||||
case 2019:
|
||||
return new ConstraintSpace()
|
||||
case 2500:
|
||||
return new RequireViolated()
|
||||
case 2501:
|
||||
return new RequireEqViolated()
|
||||
case 2502:
|
||||
return new RequireKeysEqViolated()
|
||||
case 2503:
|
||||
return new RequireNeqViolated()
|
||||
case 2504:
|
||||
return new RequireKeysNeqViolated()
|
||||
case 2505:
|
||||
return new RequireGtViolated()
|
||||
case 2506:
|
||||
return new RequireGteViolated()
|
||||
case 3000:
|
||||
return new AccountDiscriminatorAlreadySet()
|
||||
case 3001:
|
||||
return new AccountDiscriminatorNotFound()
|
||||
case 3002:
|
||||
return new AccountDiscriminatorMismatch()
|
||||
case 3003:
|
||||
return new AccountDidNotDeserialize()
|
||||
case 3004:
|
||||
return new AccountDidNotSerialize()
|
||||
case 3005:
|
||||
return new AccountNotEnoughKeys()
|
||||
case 3006:
|
||||
return new AccountNotMutable()
|
||||
case 3007:
|
||||
return new AccountOwnedByWrongProgram()
|
||||
case 3008:
|
||||
return new InvalidProgramId()
|
||||
case 3009:
|
||||
return new InvalidProgramExecutable()
|
||||
case 3010:
|
||||
return new AccountNotSigner()
|
||||
case 3011:
|
||||
return new AccountNotSystemOwned()
|
||||
case 3012:
|
||||
return new AccountNotInitialized()
|
||||
case 3013:
|
||||
return new AccountNotProgramData()
|
||||
case 3014:
|
||||
return new AccountNotAssociatedTokenAccount()
|
||||
case 3015:
|
||||
return new AccountSysvarMismatch()
|
||||
case 4000:
|
||||
return new StateInvalidAddress()
|
||||
case 4100:
|
||||
return new DeclaredProgramIdMismatch()
|
||||
case 5000:
|
||||
return new Deprecated()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
export type CustomError =
|
||||
| InvalidSwitchboardAccount
|
||||
| ExpectedResultMismatch
|
||||
| StaleBuffer
|
||||
| StringConversionFailed
|
||||
|
||||
export class InvalidSwitchboardAccount extends Error {
|
||||
readonly code = 6000
|
||||
readonly name = "InvalidSwitchboardAccount"
|
||||
readonly msg = "Not a valid Switchboard account"
|
||||
|
||||
constructor() {
|
||||
super("6000: Not a valid Switchboard account")
|
||||
}
|
||||
}
|
||||
|
||||
export class ExpectedResultMismatch extends Error {
|
||||
readonly code = 6001
|
||||
readonly name = "ExpectedResultMismatch"
|
||||
readonly msg = "Switchboard buffer does not match provided expected_result!"
|
||||
|
||||
constructor() {
|
||||
super("6001: Switchboard buffer does not match provided expected_result!")
|
||||
}
|
||||
}
|
||||
|
||||
export class StaleBuffer extends Error {
|
||||
readonly code = 6002
|
||||
readonly name = "StaleBuffer"
|
||||
readonly msg =
|
||||
"Switchboard buffer has not been updated in the last 5 minutes!"
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
"6002: Switchboard buffer has not been updated in the last 5 minutes!"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class StringConversionFailed extends Error {
|
||||
readonly code = 6003
|
||||
readonly name = "StringConversionFailed"
|
||||
readonly msg = "Failed to convert the buffer to a string!"
|
||||
|
||||
constructor() {
|
||||
super("6003: Failed to convert the buffer to a string!")
|
||||
}
|
||||
}
|
||||
|
||||
export function fromCode(code: number): CustomError | null {
|
||||
switch (code) {
|
||||
case 6000:
|
||||
return new InvalidSwitchboardAccount()
|
||||
case 6001:
|
||||
return new ExpectedResultMismatch()
|
||||
case 6002:
|
||||
return new StaleBuffer()
|
||||
case 6003:
|
||||
return new StringConversionFailed()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import { PROGRAM_ID } from "../programId"
|
||||
import * as anchor from "./anchor"
|
||||
import * as custom from "./custom"
|
||||
|
||||
export function fromCode(
|
||||
code: number
|
||||
): custom.CustomError | anchor.AnchorError | null {
|
||||
return code >= 6000 ? custom.fromCode(code) : anchor.fromCode(code)
|
||||
}
|
||||
|
||||
function hasOwnProperty<X extends object, Y extends PropertyKey>(
|
||||
obj: X,
|
||||
prop: Y
|
||||
): obj is X & Record<Y, unknown> {
|
||||
return Object.hasOwnProperty.call(obj, prop)
|
||||
}
|
||||
|
||||
const errorRe = /Program (\w+) failed: custom program error: (\w+)/
|
||||
|
||||
export function fromTxError(
|
||||
err: unknown
|
||||
): custom.CustomError | anchor.AnchorError | null {
|
||||
if (
|
||||
typeof err !== "object" ||
|
||||
err === null ||
|
||||
!hasOwnProperty(err, "logs") ||
|
||||
!Array.isArray(err.logs)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
let firstMatch: RegExpExecArray | null = null
|
||||
for (const logLine of err.logs) {
|
||||
firstMatch = errorRe.exec(logLine)
|
||||
if (firstMatch !== null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (firstMatch === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const [programIdRaw, codeRaw] = firstMatch.slice(1)
|
||||
if (programIdRaw !== PROGRAM_ID.toString()) {
|
||||
return null
|
||||
}
|
||||
|
||||
let errorCode: number
|
||||
try {
|
||||
errorCode = parseInt(codeRaw, 16)
|
||||
} catch (parseErr) {
|
||||
return null
|
||||
}
|
||||
|
||||
return fromCode(errorCode)
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { readResult, ReadResultAccounts, ReadResultArgs } from "./readResult"
|
|
@ -0,0 +1,29 @@
|
|||
import * as borsh from "@project-serum/borsh" // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
import { PublicKey, TransactionInstruction } from "@solana/web3.js" // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
import { PROGRAM_ID } from "../programId"
|
||||
import * as types from "../types" // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
|
||||
export interface ReadResultArgs {
|
||||
params: types.ReadResultParamsFields
|
||||
}
|
||||
|
||||
export interface ReadResultAccounts {
|
||||
buffer: PublicKey
|
||||
}
|
||||
|
||||
export const layout = borsh.struct([types.ReadResultParams.layout("params")])
|
||||
|
||||
export function readResult(args: ReadResultArgs, accounts: ReadResultAccounts) {
|
||||
const keys = [{ pubkey: accounts.buffer, isSigner: false, isWritable: false }]
|
||||
const identifier = Buffer.from([130, 229, 115, 203, 180, 191, 240, 90])
|
||||
const buffer = Buffer.alloc(1000)
|
||||
const len = layout.encode(
|
||||
{
|
||||
params: types.ReadResultParams.toEncodable(args.params),
|
||||
},
|
||||
buffer
|
||||
)
|
||||
const data = Buffer.concat([identifier, buffer]).slice(0, 8 + len)
|
||||
const ix = new TransactionInstruction({ keys, programId: PROGRAM_ID, data })
|
||||
return ix
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { PublicKey } from "@solana/web3.js"
|
||||
|
||||
// Program ID passed with the cli --program-id flag when running the code generator. Do not edit, it will get overwritten.
|
||||
export const PROGRAM_ID_CLI = new PublicKey(
|
||||
"96punQGZDShZGkzsBa3SsfTxfUnwu4XGpzXbhF7NTgcP"
|
||||
)
|
||||
|
||||
// This constant will not get overwritten on subsequent code generations and it's safe to modify it's value.
|
||||
export const PROGRAM_ID: PublicKey = PROGRAM_ID_CLI
|
|
@ -0,0 +1,55 @@
|
|||
import * as borsh from "@project-serum/borsh"
|
||||
|
||||
export interface ReadResultParamsFields {
|
||||
expectedResult: Array<number> | null
|
||||
}
|
||||
|
||||
export interface ReadResultParamsJSON {
|
||||
expectedResult: Array<number> | null
|
||||
}
|
||||
|
||||
export class ReadResultParams {
|
||||
readonly expectedResult: Array<number> | null
|
||||
|
||||
constructor(fields: ReadResultParamsFields) {
|
||||
this.expectedResult = fields.expectedResult
|
||||
}
|
||||
|
||||
static layout(property?: string) {
|
||||
return borsh.struct(
|
||||
[borsh.option(borsh.vecU8(), "expectedResult")],
|
||||
property
|
||||
)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
static fromDecoded(obj: any) {
|
||||
return new ReadResultParams({
|
||||
expectedResult:
|
||||
(obj.expectedResult && Array.from(obj.expectedResult)) || null,
|
||||
})
|
||||
}
|
||||
|
||||
static toEncodable(fields: ReadResultParamsFields) {
|
||||
return {
|
||||
expectedResult:
|
||||
(fields.expectedResult && Buffer.from(fields.expectedResult)) || null,
|
||||
}
|
||||
}
|
||||
|
||||
toJSON(): ReadResultParamsJSON {
|
||||
return {
|
||||
expectedResult: this.expectedResult,
|
||||
}
|
||||
}
|
||||
|
||||
static fromJSON(obj: ReadResultParamsJSON): ReadResultParams {
|
||||
return new ReadResultParams({
|
||||
expectedResult: obj.expectedResult,
|
||||
})
|
||||
}
|
||||
|
||||
toEncodable() {
|
||||
return ReadResultParams.toEncodable(this)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export {
|
||||
ReadResultParams,
|
||||
ReadResultParamsFields,
|
||||
ReadResultParamsJSON,
|
||||
} from "./ReadResultParams"
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "anchor-buffer-parser",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/switchboard-xyz/switchboard-v2",
|
||||
"directory": "programs/anchor-buffer-parser"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "echo \"For workspace anchor-buffer-parser, run 'anchor build' from the project root\" && exit 0",
|
||||
"lint": "eslint --ext .js,.json,.ts 'src/**' --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@project-serum/anchor": "^0.24.2",
|
||||
"@solana/web3.js": "^1.42.0",
|
||||
"@switchboard-xyz/sbv2-utils": "^0.1.33",
|
||||
"@switchboard-xyz/switchboard-v2": "^0.0.121",
|
||||
"node-fetch": "^2.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.0",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/node": "^17.0.45",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"chai": "^4.3.6",
|
||||
"mocha": "^9.0.3",
|
||||
"ts-mocha": "^9.0.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"typescript": "^4.3.5"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
#[allow(unaligned_references)]
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_lang::solana_program::clock;
|
||||
pub use switchboard_v2::{BufferRelayerAccountData, SWITCHBOARD_V2_DEVNET, SWITCHBOARD_V2_MAINNET};
|
||||
|
||||
declare_id!("96punQGZDShZGkzsBa3SsfTxfUnwu4XGpzXbhF7NTgcP");
|
||||
|
||||
#[account(zero_copy)]
|
||||
#[derive(AnchorDeserialize, Debug)]
|
||||
pub struct BufferClient {}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(params: ReadResultParams)]
|
||||
pub struct ReadResult<'info> {
|
||||
/// CHECK:
|
||||
pub buffer: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct ReadResultParams {
|
||||
pub expected_result: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[program]
|
||||
pub mod anchor_buffer_parser {
|
||||
use super::*;
|
||||
|
||||
pub fn read_result(
|
||||
ctx: Context<ReadResult>,
|
||||
params: ReadResultParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
let buffer_account = &ctx.accounts.buffer;
|
||||
|
||||
// check buffer owner
|
||||
let owner = *buffer_account.owner;
|
||||
if owner != SWITCHBOARD_V2_DEVNET && owner != SWITCHBOARD_V2_MAINNET {
|
||||
msg!("Feed owner = {:?}", owner);
|
||||
return Err(error!(BufferErrorCode::InvalidSwitchboardAccount));
|
||||
}
|
||||
|
||||
// load and deserialize buffer
|
||||
let buffer = BufferRelayerAccountData::new(buffer_account)?;
|
||||
|
||||
msg!("Buffer account loaded!");
|
||||
|
||||
let buffer_result = buffer.get_result();
|
||||
|
||||
// get result
|
||||
msg!("Current buffer result is {:?}!", buffer_result);
|
||||
|
||||
// check whether the buffer has been updated in the last 300 seconds
|
||||
buffer
|
||||
.check_staleness(clock::Clock::get().unwrap().unix_timestamp, 300)
|
||||
.map_err(|_| error!(BufferErrorCode::StaleBuffer))?;
|
||||
|
||||
// compare buffer with expected result
|
||||
if let Some(expected_result) = params.expected_result {
|
||||
if expected_result != *buffer_result {
|
||||
msg!(
|
||||
"Buffer mismatch, expected {:?}, actual {:?}",
|
||||
expected_result,
|
||||
buffer_result
|
||||
);
|
||||
return Err(error!(BufferErrorCode::ExpectedResultMismatch));
|
||||
}
|
||||
}
|
||||
|
||||
let result_string = String::from_utf8(buffer.result)
|
||||
.map_err(|_| error!(BufferErrorCode::StringConversionFailed))?;
|
||||
|
||||
msg!("Buffer string {:?}!", result_string);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[error_code]
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub enum BufferErrorCode {
|
||||
#[msg("Not a valid Switchboard account")]
|
||||
InvalidSwitchboardAccount,
|
||||
#[msg("Switchboard buffer does not match provided expected_result!")]
|
||||
ExpectedResultMismatch,
|
||||
#[msg("Switchboard buffer has not been updated in the last 5 minutes!")]
|
||||
StaleBuffer,
|
||||
#[msg("Failed to convert the buffer to a string!")]
|
||||
StringConversionFailed,
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
import * as anchor from "@project-serum/anchor";
|
||||
import {
|
||||
promiseWithTimeout,
|
||||
SwitchboardTestContext,
|
||||
} from "@switchboard-xyz/sbv2-utils";
|
||||
import {
|
||||
AnchorWallet,
|
||||
BufferRelayerAccount,
|
||||
JobAccount,
|
||||
OracleJob,
|
||||
PermissionAccount,
|
||||
} from "@switchboard-xyz/switchboard-v2";
|
||||
import fetch from "node-fetch";
|
||||
import {
|
||||
AnchorBufferParser,
|
||||
IDL,
|
||||
} from "../../../target/types/anchor_buffer_parser";
|
||||
import { PROGRAM_ID } from "../client/programId";
|
||||
|
||||
const sleep = (ms: number): Promise<any> =>
|
||||
new Promise((s) => setTimeout(s, ms));
|
||||
|
||||
// Anchor.toml will copy this to localnet when we start our tests
|
||||
|
||||
describe("anchor-buffer-parser test", () => {
|
||||
const provider = anchor.AnchorProvider.env();
|
||||
anchor.setProvider(provider);
|
||||
|
||||
// const bufferParserProgram = anchor.workspace
|
||||
// .AnchorBufferParser as Program<AnchorBufferParser>;
|
||||
|
||||
const bufferParserProgram = new anchor.Program(
|
||||
IDL,
|
||||
PROGRAM_ID,
|
||||
provider,
|
||||
new anchor.BorshCoder(IDL)
|
||||
) as anchor.Program<AnchorBufferParser>;
|
||||
|
||||
const payer = (provider.wallet as AnchorWallet).payer;
|
||||
|
||||
let switchboard: SwitchboardTestContext;
|
||||
let localnet = false;
|
||||
|
||||
before(async () => {
|
||||
// First, attempt to load the switchboard devnet PID
|
||||
try {
|
||||
switchboard = await SwitchboardTestContext.loadDevnetQueue(
|
||||
provider,
|
||||
"9e8rQPoyVZeGA1mD2BBVqDthbFwircnfLTB5JE6yUFQR"
|
||||
);
|
||||
console.log("devnet detected");
|
||||
return;
|
||||
} catch (error: any) {
|
||||
console.log(`Error: SBV2 Devnet - ${error.message}`);
|
||||
}
|
||||
// If fails, fallback to looking for a local env file
|
||||
try {
|
||||
switchboard = await SwitchboardTestContext.loadFromEnv(provider);
|
||||
localnet = true;
|
||||
console.log("localnet detected");
|
||||
return;
|
||||
} catch (error: any) {
|
||||
console.log(`Error: SBV2 Localnet - ${error.message}`);
|
||||
}
|
||||
// If fails, throw error
|
||||
throw new Error(
|
||||
`Failed to load the SwitchboardTestContext from devnet or from a switchboard.env file`
|
||||
);
|
||||
});
|
||||
|
||||
it("Create and read buffer account", async () => {
|
||||
const queue = await switchboard.queue.loadData();
|
||||
if (!queue.enableBufferRelayers) {
|
||||
throw new Error(`Queue has buffer relayers disabled!`);
|
||||
}
|
||||
|
||||
const url = "https://jsonplaceholder.typicode.com/todos/1";
|
||||
const expectedResult = Buffer.from(await (await fetch(url)).text());
|
||||
|
||||
const jobData = Buffer.from(
|
||||
OracleJob.encodeDelimited(
|
||||
OracleJob.create({
|
||||
tasks: [
|
||||
OracleJob.Task.create({
|
||||
httpTask: OracleJob.HttpTask.create({
|
||||
url,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
})
|
||||
).finish()
|
||||
);
|
||||
const jobKeypair = anchor.web3.Keypair.generate();
|
||||
const jobAccount = await JobAccount.create(switchboard.program, {
|
||||
data: jobData,
|
||||
keypair: jobKeypair,
|
||||
authority: payer.publicKey,
|
||||
});
|
||||
|
||||
const bufferAccount = await BufferRelayerAccount.create(
|
||||
switchboard.program,
|
||||
{
|
||||
name: Buffer.from("My Buffer").slice(0, 32),
|
||||
minUpdateDelaySeconds: 30,
|
||||
queueAccount: switchboard.queue,
|
||||
authority: payer.publicKey,
|
||||
jobAccount,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(`BufferRelayer ${bufferAccount.publicKey}`);
|
||||
|
||||
const permissionAccount = await PermissionAccount.create(
|
||||
switchboard.program,
|
||||
{
|
||||
granter: switchboard.queue.publicKey,
|
||||
grantee: bufferAccount.publicKey,
|
||||
authority: queue.authority,
|
||||
}
|
||||
);
|
||||
|
||||
// call openRound
|
||||
bufferAccount
|
||||
.openRound()
|
||||
.then((sig) => console.log(`OpenRound Txn: ${sig}`));
|
||||
|
||||
const buf = await awaitCallback(bufferAccount, 30_000);
|
||||
|
||||
console.log(`Current Buffer Result: [${new Uint8Array(buf).toString()}]`);
|
||||
|
||||
const signature = await bufferParserProgram.methods
|
||||
.readResult({ expectedResult: expectedResult })
|
||||
.accounts({ buffer: bufferAccount.publicKey })
|
||||
.rpc()
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
throw error;
|
||||
});
|
||||
|
||||
await sleep(2000);
|
||||
|
||||
const logs = await provider.connection.getParsedTransaction(
|
||||
signature,
|
||||
"confirmed"
|
||||
);
|
||||
|
||||
console.log(JSON.stringify(logs?.meta?.logMessages, undefined, 2));
|
||||
});
|
||||
});
|
||||
|
||||
async function awaitCallback(
|
||||
bufferAccount: BufferRelayerAccount,
|
||||
timeoutInterval: number,
|
||||
errorMsg = "Timed out waiting for Buffer Relayer open round."
|
||||
) {
|
||||
const acctCoder = new anchor.BorshAccountsCoder(bufferAccount.program.idl);
|
||||
let ws: number | undefined = undefined;
|
||||
const result: Buffer = await promiseWithTimeout(
|
||||
timeoutInterval,
|
||||
new Promise(
|
||||
(resolve: (result: Buffer) => void, reject: (reason: string) => void) => {
|
||||
ws = bufferAccount.program.provider.connection.onAccountChange(
|
||||
bufferAccount.publicKey,
|
||||
async (
|
||||
accountInfo: anchor.web3.AccountInfo<Buffer>,
|
||||
context: anchor.web3.Context
|
||||
) => {
|
||||
const buf = acctCoder.decode(
|
||||
"BufferRelayerAccountData",
|
||||
accountInfo.data
|
||||
);
|
||||
const bufResult = buf.result as Buffer;
|
||||
if (bufResult.byteLength > 0) {
|
||||
resolve(bufResult);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
).finally(async () => {
|
||||
if (ws) {
|
||||
await bufferAccount.program.provider.connection.removeAccountChangeListener(
|
||||
ws
|
||||
);
|
||||
}
|
||||
ws = undefined;
|
||||
}),
|
||||
new Error(errorMsg)
|
||||
).finally(async () => {
|
||||
if (ws) {
|
||||
await bufferAccount.program.provider.connection.removeAccountChangeListener(
|
||||
ws
|
||||
);
|
||||
}
|
||||
ws = undefined;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["mocha", "chai"],
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"module": "CommonJS",
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"paths": {
|
||||
"@switchboard-xyz/switchboard-v2": ["../../libraries/ts"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"tests/**/*",
|
||||
"client/**/*",
|
||||
"../../target/types/anchor_feed_parser"
|
||||
],
|
||||
"references": [{ "path": "../../libraries/ts" }]
|
||||
}
|
|
@ -17,6 +17,6 @@ default = []
|
|||
|
||||
[dependencies]
|
||||
switchboard-v2 = { path = "../../libraries/rs" }
|
||||
# switchboard-v2 = "^0.1.10"
|
||||
# switchboard-v2 = "^0.1.11"
|
||||
anchor-lang = "^0.24.2"
|
||||
solana-program = "~1.9.13"
|
|
@ -0,0 +1,69 @@
|
|||
import * as borsh from "@project-serum/borsh" // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
import { Connection, PublicKey } from "@solana/web3.js"
|
||||
import { PROGRAM_ID } from "../programId"
|
||||
|
||||
export interface FeedClientFields {}
|
||||
|
||||
export interface FeedClientJSON {}
|
||||
|
||||
export class FeedClient {
|
||||
static readonly discriminator = Buffer.from([
|
||||
30, 15, 152, 236, 85, 180, 84, 151,
|
||||
])
|
||||
|
||||
static readonly layout = borsh.struct([])
|
||||
|
||||
constructor(fields: FeedClientFields) {}
|
||||
|
||||
static async fetch(
|
||||
c: Connection,
|
||||
address: PublicKey
|
||||
): Promise<FeedClient | null> {
|
||||
const info = await c.getAccountInfo(address)
|
||||
|
||||
if (info === null) {
|
||||
return null
|
||||
}
|
||||
if (!info.owner.equals(PROGRAM_ID)) {
|
||||
throw new Error("account doesn't belong to this program")
|
||||
}
|
||||
|
||||
return this.decode(info.data)
|
||||
}
|
||||
|
||||
static async fetchMultiple(
|
||||
c: Connection,
|
||||
addresses: PublicKey[]
|
||||
): Promise<Array<FeedClient | null>> {
|
||||
const infos = await c.getMultipleAccountsInfo(addresses)
|
||||
|
||||
return infos.map((info) => {
|
||||
if (info === null) {
|
||||
return null
|
||||
}
|
||||
if (!info.owner.equals(PROGRAM_ID)) {
|
||||
throw new Error("account doesn't belong to this program")
|
||||
}
|
||||
|
||||
return this.decode(info.data)
|
||||
})
|
||||
}
|
||||
|
||||
static decode(data: Buffer): FeedClient {
|
||||
if (!data.slice(0, 8).equals(FeedClient.discriminator)) {
|
||||
throw new Error("invalid account discriminator")
|
||||
}
|
||||
|
||||
const dec = FeedClient.layout.decode(data.slice(8))
|
||||
|
||||
return new FeedClient({})
|
||||
}
|
||||
|
||||
toJSON(): FeedClientJSON {
|
||||
return {}
|
||||
}
|
||||
|
||||
static fromJSON(obj: FeedClientJSON): FeedClient {
|
||||
return new FeedClient({})
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { FeedClient, FeedClientFields, FeedClientJSON } from "./FeedClient"
|
|
@ -0,0 +1,47 @@
|
|||
export type CustomError =
|
||||
| InvalidSwitchboardAccount
|
||||
| StaleFeed
|
||||
| ConfidenceIntervalExceeded
|
||||
|
||||
export class InvalidSwitchboardAccount extends Error {
|
||||
readonly code = 6000
|
||||
readonly name = "InvalidSwitchboardAccount"
|
||||
readonly msg = "Not a valid Switchboard account"
|
||||
|
||||
constructor() {
|
||||
super("6000: Not a valid Switchboard account")
|
||||
}
|
||||
}
|
||||
|
||||
export class StaleFeed extends Error {
|
||||
readonly code = 6001
|
||||
readonly name = "StaleFeed"
|
||||
readonly msg = "Switchboard feed has not been updated in 5 minutes"
|
||||
|
||||
constructor() {
|
||||
super("6001: Switchboard feed has not been updated in 5 minutes")
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfidenceIntervalExceeded extends Error {
|
||||
readonly code = 6002
|
||||
readonly name = "ConfidenceIntervalExceeded"
|
||||
readonly msg = "Switchboard feed exceeded provided confidence interval"
|
||||
|
||||
constructor() {
|
||||
super("6002: Switchboard feed exceeded provided confidence interval")
|
||||
}
|
||||
}
|
||||
|
||||
export function fromCode(code: number): CustomError | null {
|
||||
switch (code) {
|
||||
case 6000:
|
||||
return new InvalidSwitchboardAccount()
|
||||
case 6001:
|
||||
return new StaleFeed()
|
||||
case 6002:
|
||||
return new ConfidenceIntervalExceeded()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
import { PROGRAM_ID } from "../programId"
|
||||
import * as anchor from "./anchor"
|
||||
import * as custom from "./custom"
|
||||
|
||||
export function fromCode(code: number): anchor.AnchorError | null {
|
||||
return anchor.fromCode(code)
|
||||
export function fromCode(
|
||||
code: number
|
||||
): custom.CustomError | anchor.AnchorError | null {
|
||||
return code >= 6000 ? custom.fromCode(code) : anchor.fromCode(code)
|
||||
}
|
||||
|
||||
function hasOwnProperty<X extends object, Y extends PropertyKey>(
|
||||
|
@ -14,7 +17,9 @@ function hasOwnProperty<X extends object, Y extends PropertyKey>(
|
|||
|
||||
const errorRe = /Program (\w+) failed: custom program error: (\w+)/
|
||||
|
||||
export function fromTxError(err: unknown): anchor.AnchorError | null {
|
||||
export function fromTxError(
|
||||
err: unknown
|
||||
): custom.CustomError | anchor.AnchorError | null {
|
||||
if (
|
||||
typeof err !== "object" ||
|
||||
err === null ||
|
||||
|
|
|
@ -1 +1 @@
|
|||
export { readResult, ReadResultAccounts } from "./readResult"
|
||||
export { readResult, ReadResultAccounts, ReadResultArgs } from "./readResult"
|
||||
|
|
|
@ -1,16 +1,31 @@
|
|||
import * as borsh from "@project-serum/borsh" // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
import { PublicKey, TransactionInstruction } from "@solana/web3.js" // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
import { PROGRAM_ID } from "../programId"
|
||||
import * as types from "../types" // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
|
||||
export interface ReadResultArgs {
|
||||
params: types.ReadResultParamsFields
|
||||
}
|
||||
|
||||
export interface ReadResultAccounts {
|
||||
aggregator: PublicKey
|
||||
}
|
||||
|
||||
export function readResult(accounts: ReadResultAccounts) {
|
||||
export const layout = borsh.struct([types.ReadResultParams.layout("params")])
|
||||
|
||||
export function readResult(args: ReadResultArgs, accounts: ReadResultAccounts) {
|
||||
const keys = [
|
||||
{ pubkey: accounts.aggregator, isSigner: false, isWritable: false },
|
||||
]
|
||||
const identifier = Buffer.from([130, 229, 115, 203, 180, 191, 240, 90])
|
||||
const data = identifier
|
||||
const buffer = Buffer.alloc(1000)
|
||||
const len = layout.encode(
|
||||
{
|
||||
params: types.ReadResultParams.toEncodable(args.params),
|
||||
},
|
||||
buffer
|
||||
)
|
||||
const data = Buffer.concat([identifier, buffer]).slice(0, 8 + len)
|
||||
const ix = new TransactionInstruction({ keys, programId: PROGRAM_ID, data })
|
||||
return ix
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { PublicKey } from "@solana/web3.js"
|
||||
|
||||
// Program ID defined in the provided IDL. Do not edit, it will get overwritten.
|
||||
export const PROGRAM_ID_IDL = new PublicKey(
|
||||
"FnsPs665aBSwJRu2A8wGv6ZT76ipR41kHm4hoA3B1QGh"
|
||||
)
|
||||
|
||||
// Program ID passed with the cli --program-id flag when running the code generator. Do not edit, it will get overwritten.
|
||||
export const PROGRAM_ID_CLI = new PublicKey(
|
||||
"FnsPs665aBSwJRu2A8wGv6ZT76ipR41kHm4hoA3B1QGh"
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import * as borsh from "@project-serum/borsh"
|
||||
|
||||
export interface ReadResultParamsFields {
|
||||
maxConfidenceInterval: number | null
|
||||
}
|
||||
|
||||
export interface ReadResultParamsJSON {
|
||||
maxConfidenceInterval: number | null
|
||||
}
|
||||
|
||||
export class ReadResultParams {
|
||||
readonly maxConfidenceInterval: number | null
|
||||
|
||||
constructor(fields: ReadResultParamsFields) {
|
||||
this.maxConfidenceInterval = fields.maxConfidenceInterval
|
||||
}
|
||||
|
||||
static layout(property?: string) {
|
||||
return borsh.struct(
|
||||
[borsh.option(borsh.f64(), "maxConfidenceInterval")],
|
||||
property
|
||||
)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
static fromDecoded(obj: any) {
|
||||
return new ReadResultParams({
|
||||
maxConfidenceInterval: obj.maxConfidenceInterval,
|
||||
})
|
||||
}
|
||||
|
||||
static toEncodable(fields: ReadResultParamsFields) {
|
||||
return {
|
||||
maxConfidenceInterval: fields.maxConfidenceInterval,
|
||||
}
|
||||
}
|
||||
|
||||
toJSON(): ReadResultParamsJSON {
|
||||
return {
|
||||
maxConfidenceInterval: this.maxConfidenceInterval,
|
||||
}
|
||||
}
|
||||
|
||||
static fromJSON(obj: ReadResultParamsJSON): ReadResultParams {
|
||||
return new ReadResultParams({
|
||||
maxConfidenceInterval: obj.maxConfidenceInterval,
|
||||
})
|
||||
}
|
||||
|
||||
toEncodable() {
|
||||
return ReadResultParams.toEncodable(this)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export {
|
||||
ReadResultParams,
|
||||
ReadResultParamsFields,
|
||||
ReadResultParamsJSON,
|
||||
} from "./ReadResultParams"
|
|
@ -14,8 +14,8 @@
|
|||
"dependencies": {
|
||||
"@project-serum/anchor": "^0.24.2",
|
||||
"@solana/web3.js": "^1.42.0",
|
||||
"@switchboard-xyz/sbv2-utils": "^0.1.32",
|
||||
"@switchboard-xyz/switchboard-v2": "^0.0.120"
|
||||
"@switchboard-xyz/sbv2-utils": "^0.1.33",
|
||||
"@switchboard-xyz/switchboard-v2": "^0.0.121"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.0",
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_lang::solana_program::clock;
|
||||
use std::convert::TryInto;
|
||||
pub use switchboard_v2::{AggregatorAccountData, SWITCHBOARD_V2_DEVNET, SWITCHBOARD_V2_MAINNET};
|
||||
pub use switchboard_v2::{
|
||||
AggregatorAccountData, SwitchboardDecimal, SWITCHBOARD_V2_DEVNET, SWITCHBOARD_V2_MAINNET,
|
||||
};
|
||||
|
||||
declare_id!("FnsPs665aBSwJRu2A8wGv6ZT76ipR41kHm4hoA3B1QGh");
|
||||
|
||||
|
@ -11,37 +13,49 @@ declare_id!("FnsPs665aBSwJRu2A8wGv6ZT76ipR41kHm4hoA3B1QGh");
|
|||
pub struct FeedClient {}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(params: ReadResultParams)]
|
||||
pub struct ReadResult<'info> {
|
||||
/// CHECK:
|
||||
pub aggregator: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct ReadResultParams {
|
||||
pub max_confidence_interval: Option<f64>,
|
||||
}
|
||||
|
||||
#[program]
|
||||
pub mod anchor_feed_parser {
|
||||
use super::*;
|
||||
|
||||
pub fn read_result(ctx: Context<ReadResult>) -> anchor_lang::Result<()> {
|
||||
pub fn read_result(
|
||||
ctx: Context<ReadResult>,
|
||||
params: ReadResultParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
let aggregator = &ctx.accounts.aggregator;
|
||||
|
||||
// check feed owner
|
||||
let owner = *aggregator.owner;
|
||||
if owner != SWITCHBOARD_V2_DEVNET && owner != SWITCHBOARD_V2_MAINNET {
|
||||
return Err(error!(FeedErrorCode::InvalidSwitchboardVrfAccount));
|
||||
return Err(error!(FeedErrorCode::InvalidSwitchboardAccount));
|
||||
}
|
||||
|
||||
// load and deserialize feed
|
||||
let feed = AggregatorAccountData::new(aggregator)?;
|
||||
|
||||
// check if feed has updated in the last 5 minutes
|
||||
let staleness = clock::Clock::get().unwrap().unix_timestamp
|
||||
- feed.latest_confirmed_round.round_open_timestamp;
|
||||
if staleness > 300 {
|
||||
msg!("Feed has not been updated in {} seconds!", staleness);
|
||||
return Err(error!(FeedErrorCode::StaleFeed));
|
||||
}
|
||||
|
||||
// get result
|
||||
let val: f64 = feed.get_result()?.try_into()?;
|
||||
|
||||
// check whether the feed has been updated in the last 300 seconds
|
||||
feed.check_staleness(clock::Clock::get().unwrap().unix_timestamp, 300)
|
||||
.map_err(|_| error!(FeedErrorCode::StaleFeed))?;
|
||||
|
||||
// check feed does not exceed max_confidence_interval
|
||||
if let Some(max_confidence_interval) = params.max_confidence_interval {
|
||||
feed.check_confidence_interval(SwitchboardDecimal::from_f64(max_confidence_interval))
|
||||
.map_err(|_| error!(FeedErrorCode::ConfidenceIntervalExceeded))?;
|
||||
}
|
||||
|
||||
msg!("Current feed result is {}!", val);
|
||||
|
||||
Ok(())
|
||||
|
@ -51,8 +65,10 @@ pub mod anchor_feed_parser {
|
|||
#[error_code]
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub enum FeedErrorCode {
|
||||
#[msg("Not a valid Switchboard VRF account")]
|
||||
InvalidSwitchboardVrfAccount,
|
||||
#[msg("Not a valid Switchboard account")]
|
||||
InvalidSwitchboardAccount,
|
||||
#[msg("Switchboard feed has not been updated in 5 minutes")]
|
||||
StaleFeed,
|
||||
#[msg("Switchboard feed exceeded provided confidence interval")]
|
||||
ConfidenceIntervalExceeded,
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as anchor from "@project-serum/anchor";
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { SwitchboardTestContext } from "@switchboard-xyz/sbv2-utils";
|
||||
import type { AnchorWallet } from "@switchboard-xyz/switchboard-v2";
|
||||
import assert from "assert";
|
||||
import {
|
||||
AnchorFeedParser,
|
||||
IDL,
|
||||
|
@ -65,7 +66,7 @@ describe("anchor-feed-parser test", () => {
|
|||
|
||||
it("Read SOL/USD Feed", async () => {
|
||||
const signature = await feedParserProgram.methods
|
||||
.readResult()
|
||||
.readResult({ maxConfidenceInterval: 0.25 })
|
||||
.accounts({ aggregator: aggregatorKey })
|
||||
.rpc();
|
||||
|
||||
|
@ -79,4 +80,18 @@ describe("anchor-feed-parser test", () => {
|
|||
|
||||
console.log(JSON.stringify(logs?.meta?.logMessages, undefined, 2));
|
||||
});
|
||||
|
||||
it("Fails to read feed if confidence interval is exceeded", async () => {
|
||||
await assert
|
||||
.rejects(async function () {
|
||||
await feedParserProgram.methods
|
||||
.readResult({ maxConfidenceInterval: 0.0000000001 })
|
||||
.accounts({ aggregator: aggregatorKey })
|
||||
.rpc();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@ default = []
|
|||
|
||||
[dependencies]
|
||||
switchboard-v2 = { path = "../../libraries/rs" }
|
||||
# switchboard-v2 = "^0.1.10"
|
||||
# switchboard-v2 = "^0.1.11"
|
||||
anchor-lang = "^0.24.2"
|
||||
anchor-spl = "^0.24.2"
|
||||
solana-program = "~1.9.13"
|
||||
|
|
|
@ -1,46 +1,40 @@
|
|||
import * as borsh from "@project-serum/borsh"; // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
import BN from "bn.js"; // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
import { PROGRAM_ID } from "../programId";
|
||||
import * as borsh from "@project-serum/borsh" // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
import { Connection, PublicKey } from "@solana/web3.js"
|
||||
import BN from "bn.js" // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
import { PROGRAM_ID } from "../programId"
|
||||
|
||||
export interface VrfClientFields {
|
||||
bump: number;
|
||||
maxResult: BN;
|
||||
resultBuffer: Array<number>;
|
||||
result: BN;
|
||||
lastTimestamp: BN;
|
||||
authority: PublicKey;
|
||||
vrf: PublicKey;
|
||||
bump: number
|
||||
maxResult: BN
|
||||
resultBuffer: Array<number>
|
||||
result: BN
|
||||
lastTimestamp: BN
|
||||
authority: PublicKey
|
||||
vrf: PublicKey
|
||||
}
|
||||
|
||||
export interface VrfClientJSON {
|
||||
bump: number;
|
||||
maxResult: string;
|
||||
resultBuffer: Array<number>;
|
||||
result: string;
|
||||
lastTimestamp: string;
|
||||
authority: string;
|
||||
vrf: string;
|
||||
bump: number
|
||||
maxResult: string
|
||||
resultBuffer: Array<number>
|
||||
result: string
|
||||
lastTimestamp: string
|
||||
authority: string
|
||||
vrf: string
|
||||
}
|
||||
|
||||
export class VrfClient {
|
||||
readonly bump: number;
|
||||
|
||||
readonly maxResult: BN;
|
||||
|
||||
readonly resultBuffer: Array<number>;
|
||||
|
||||
readonly result: BN;
|
||||
|
||||
readonly lastTimestamp: BN;
|
||||
|
||||
readonly authority: PublicKey;
|
||||
|
||||
readonly vrf: PublicKey;
|
||||
readonly bump: number
|
||||
readonly maxResult: BN
|
||||
readonly resultBuffer: Array<number>
|
||||
readonly result: BN
|
||||
readonly lastTimestamp: BN
|
||||
readonly authority: PublicKey
|
||||
readonly vrf: PublicKey
|
||||
|
||||
static readonly discriminator = Buffer.from([
|
||||
230, 174, 157, 153, 51, 18, 230, 163,
|
||||
]);
|
||||
])
|
||||
|
||||
static readonly layout = borsh.struct([
|
||||
borsh.u8("bump"),
|
||||
|
@ -50,58 +44,58 @@ export class VrfClient {
|
|||
borsh.i64("lastTimestamp"),
|
||||
borsh.publicKey("authority"),
|
||||
borsh.publicKey("vrf"),
|
||||
]);
|
||||
])
|
||||
|
||||
constructor(fields: VrfClientFields) {
|
||||
this.bump = fields.bump;
|
||||
this.maxResult = fields.maxResult;
|
||||
this.resultBuffer = fields.resultBuffer;
|
||||
this.result = fields.result;
|
||||
this.lastTimestamp = fields.lastTimestamp;
|
||||
this.authority = fields.authority;
|
||||
this.vrf = fields.vrf;
|
||||
this.bump = fields.bump
|
||||
this.maxResult = fields.maxResult
|
||||
this.resultBuffer = fields.resultBuffer
|
||||
this.result = fields.result
|
||||
this.lastTimestamp = fields.lastTimestamp
|
||||
this.authority = fields.authority
|
||||
this.vrf = fields.vrf
|
||||
}
|
||||
|
||||
static async fetch(
|
||||
c: Connection,
|
||||
address: PublicKey
|
||||
): Promise<VrfClient | null> {
|
||||
const info = await c.getAccountInfo(address);
|
||||
const info = await c.getAccountInfo(address)
|
||||
|
||||
if (info === null) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
if (!info.owner.equals(PROGRAM_ID)) {
|
||||
throw new Error("account doesn't belong to this program");
|
||||
throw new Error("account doesn't belong to this program")
|
||||
}
|
||||
|
||||
return this.decode(info.data);
|
||||
return this.decode(info.data)
|
||||
}
|
||||
|
||||
static async fetchMultiple(
|
||||
c: Connection,
|
||||
addresses: PublicKey[]
|
||||
): Promise<Array<VrfClient | null>> {
|
||||
const infos = await c.getMultipleAccountsInfo(addresses);
|
||||
const infos = await c.getMultipleAccountsInfo(addresses)
|
||||
|
||||
return infos.map((info) => {
|
||||
if (info === null) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
if (!info.owner.equals(PROGRAM_ID)) {
|
||||
throw new Error("account doesn't belong to this program");
|
||||
throw new Error("account doesn't belong to this program")
|
||||
}
|
||||
|
||||
return this.decode(info.data);
|
||||
});
|
||||
return this.decode(info.data)
|
||||
})
|
||||
}
|
||||
|
||||
static decode(data: Buffer): VrfClient {
|
||||
if (!data.slice(0, 8).equals(VrfClient.discriminator)) {
|
||||
throw new Error("invalid account discriminator");
|
||||
throw new Error("invalid account discriminator")
|
||||
}
|
||||
|
||||
const dec = VrfClient.layout.decode(data.slice(8));
|
||||
const dec = VrfClient.layout.decode(data.slice(8))
|
||||
|
||||
return new VrfClient({
|
||||
bump: dec.bump,
|
||||
|
@ -111,7 +105,7 @@ export class VrfClient {
|
|||
lastTimestamp: dec.lastTimestamp,
|
||||
authority: dec.authority,
|
||||
vrf: dec.vrf,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
toJSON(): VrfClientJSON {
|
||||
|
@ -123,7 +117,7 @@ export class VrfClient {
|
|||
lastTimestamp: this.lastTimestamp.toString(),
|
||||
authority: this.authority.toString(),
|
||||
vrf: this.vrf.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static fromJSON(obj: VrfClientJSON): VrfClient {
|
||||
|
@ -135,6 +129,6 @@ export class VrfClient {
|
|||
lastTimestamp: new BN(obj.lastTimestamp),
|
||||
authority: new PublicKey(obj.authority),
|
||||
vrf: new PublicKey(obj.vrf),
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { PublicKey } from "@solana/web3.js"
|
||||
|
||||
// Program ID passed with the cli --program-id flag when running the code generator. Do not edit, it will get overwritten.
|
||||
export const PROGRAM_ID_CLI = new PublicKey(
|
||||
"HjjRFjCyQH3ne6Gg8Yn3TQafrrYecRrphwLwnh2A26vM"
|
||||
);
|
||||
)
|
||||
|
||||
// This constant will not get overwritten on subsequent code generations and it's safe to modify it's value.
|
||||
export const PROGRAM_ID: PublicKey = PROGRAM_ID_CLI;
|
||||
export const PROGRAM_ID: PublicKey = PROGRAM_ID_CLI
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
"@project-serum/borsh": "^0.2.5",
|
||||
"@solana/spl-token": "^0.2.0",
|
||||
"@solana/web3.js": "^1.42.0",
|
||||
"@switchboard-xyz/sbv2-utils": "^0.1.32",
|
||||
"@switchboard-xyz/switchboard-v2": "^0.0.120",
|
||||
"@switchboard-xyz/sbv2-utils": "^0.1.33",
|
||||
"@switchboard-xyz/switchboard-v2": "^0.0.121",
|
||||
"chalk": "^4.1.2",
|
||||
"child_process": "^1.0.2",
|
||||
"dotenv": "^16.0.1",
|
||||
|
|
|
@ -66,7 +66,10 @@ describe("anchor-vrf-parser test", () => {
|
|||
before(async () => {
|
||||
// First, attempt to load the switchboard devnet PID
|
||||
try {
|
||||
switchboard = await SwitchboardTestContext.loadDevnetQueue(provider);
|
||||
switchboard = await SwitchboardTestContext.loadDevnetQueue(
|
||||
provider,
|
||||
"9e8rQPoyVZeGA1mD2BBVqDthbFwircnfLTB5JE6yUFQR"
|
||||
);
|
||||
console.log("devnet detected");
|
||||
return;
|
||||
} catch (error: any) {
|
||||
|
@ -98,7 +101,7 @@ describe("anchor-vrf-parser test", () => {
|
|||
authority: vrfClientKey, // vrf authority
|
||||
keypair: vrfSecret,
|
||||
});
|
||||
const { escrow } = await vrfAccount.loadData();
|
||||
|
||||
console.log(`Created VRF Account: ${vrfAccount.publicKey}`);
|
||||
|
||||
const permissionAccount = await PermissionAccount.create(
|
||||
|
@ -164,9 +167,10 @@ describe("anchor-vrf-parser test", () => {
|
|||
spl.ASSOCIATED_TOKEN_PROGRAM_ID
|
||||
);
|
||||
|
||||
const { escrow } = await vrfAccount.loadData();
|
||||
|
||||
// Request randomness
|
||||
console.log(`Sending RequestRandomness instruction`);
|
||||
const requestTxn = vrfClientProgram.methods.requestResult!({
|
||||
vrfClientProgram.methods.requestResult!({
|
||||
switchboardStateBump: programStateBump,
|
||||
permissionBump,
|
||||
})
|
||||
|
@ -187,7 +191,10 @@ describe("anchor-vrf-parser test", () => {
|
|||
tokenProgram: spl.TOKEN_PROGRAM_ID,
|
||||
})
|
||||
.signers([payer, payer])
|
||||
.rpc();
|
||||
.rpc()
|
||||
.then((sig) => {
|
||||
console.log(`RequestRandomness Txn: ${sig}`);
|
||||
});
|
||||
|
||||
const result = await awaitCallback(
|
||||
vrfClientProgram.provider.connection,
|
||||
|
|
|
@ -12,6 +12,6 @@ no-entrypoint = []
|
|||
|
||||
[dependencies]
|
||||
switchboard-v2 = { path = "../../libraries/rs" }
|
||||
# switchboard-v2 = "^0.1.10"
|
||||
# switchboard-v2 = "^0.1.11"
|
||||
solana-program = "1.9.29"
|
||||
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
"dependencies": {
|
||||
"@project-serum/anchor": "^0.24.2",
|
||||
"@solana/web3.js": "^1.42.0",
|
||||
"@switchboard-xyz/sbv2-utils": "^0.1.32",
|
||||
"@switchboard-xyz/switchboard-v2": "^0.0.120"
|
||||
"@switchboard-xyz/sbv2-utils": "^0.1.33",
|
||||
"@switchboard-xyz/switchboard-v2": "^0.0.121"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.0",
|
||||
|
|
|
@ -40,7 +40,13 @@ const anchorFeedKeypairPath = path.join(
|
|||
"anchor_feed_parser-keypair.json"
|
||||
);
|
||||
|
||||
const splFeedKeypairPath = path.join(
|
||||
const anchorBufferKeypairPath = path.join(
|
||||
targetDir,
|
||||
"deploy",
|
||||
"anchor_buffer_parser-keypair.json"
|
||||
);
|
||||
|
||||
const nativeFeedKeypairPath = path.join(
|
||||
targetDir,
|
||||
"deploy",
|
||||
"native_feed_parser-keypair.json"
|
||||
|
@ -80,8 +86,11 @@ async function main() {
|
|||
const anchorFeedParserPid = web3.Keypair.fromSecretKey(
|
||||
new Uint8Array(JSON.parse(fs.readFileSync(anchorFeedKeypairPath, "utf8")))
|
||||
).publicKey;
|
||||
const splFeedParserPid = web3.Keypair.fromSecretKey(
|
||||
new Uint8Array(JSON.parse(fs.readFileSync(splFeedKeypairPath, "utf8")))
|
||||
const anchorBufferParserPid = web3.Keypair.fromSecretKey(
|
||||
new Uint8Array(JSON.parse(fs.readFileSync(anchorBufferKeypairPath, "utf8")))
|
||||
).publicKey;
|
||||
const nativeFeedParserPid = web3.Keypair.fromSecretKey(
|
||||
new Uint8Array(JSON.parse(fs.readFileSync(nativeFeedKeypairPath, "utf8")))
|
||||
).publicKey;
|
||||
|
||||
// REPLACE ANCHOR-VRF-PROGRAM IDS
|
||||
|
@ -113,17 +122,31 @@ async function main() {
|
|||
anchorToml
|
||||
);
|
||||
|
||||
console.log(`SPL Feed Parser PID: ${splFeedParserPid}`);
|
||||
console.log(`Anchor Buffer Parser PID: ${anchorBufferParserPid}`);
|
||||
shell.sed(
|
||||
"-i",
|
||||
/declare_id!(.*);/,
|
||||
`declare_id!("${splFeedParserPid.toString()}");`,
|
||||
`declare_id!("${anchorBufferParserPid.toString()}");`,
|
||||
path.join(projectRoot, "programs", "anchor-buffer-parser", "src", "lib.rs")
|
||||
);
|
||||
shell.sed(
|
||||
"-i",
|
||||
/anchor_buffer_parser = "(.*)"/,
|
||||
`anchor_buffer_parser = "${anchorBufferParserPid.toString()}"`,
|
||||
anchorToml
|
||||
);
|
||||
|
||||
console.log(`Native Feed Parser PID: ${nativeFeedParserPid}`);
|
||||
shell.sed(
|
||||
"-i",
|
||||
/declare_id!(.*);/,
|
||||
`declare_id!("${nativeFeedParserPid.toString()}");`,
|
||||
path.join(projectRoot, "programs", "native-feed-parser", "src", "lib.rs")
|
||||
);
|
||||
shell.sed(
|
||||
"-i",
|
||||
/native_feed_parser = "(.*)"/,
|
||||
`native_feed_parser = "${splFeedParserPid.toString()}"`,
|
||||
`native_feed_parser = "${nativeFeedParserPid.toString()}"`,
|
||||
anchorToml
|
||||
);
|
||||
|
||||
|
@ -142,6 +165,7 @@ async function main() {
|
|||
"anchor_vrf_parser.json"
|
||||
)} ${vrfClientPath} --program-id ${anchorVrfParserPid.toString()}`
|
||||
);
|
||||
|
||||
const feedClientPath = path.join(
|
||||
projectRoot,
|
||||
"programs",
|
||||
|
@ -156,6 +180,21 @@ async function main() {
|
|||
"anchor_feed_parser.json"
|
||||
)} ${feedClientPath} --program-id ${anchorFeedParserPid.toString()}`
|
||||
);
|
||||
|
||||
const bufferClientPath = path.join(
|
||||
projectRoot,
|
||||
"programs",
|
||||
"anchor-buffer-parser",
|
||||
"client"
|
||||
);
|
||||
shell.rm("-rf", bufferClientPath);
|
||||
fs.mkdirSync(bufferClientPath, { recursive: true });
|
||||
execSync(
|
||||
`node ${anchorClientGen} ${path.join(
|
||||
idlDir,
|
||||
"anchor_buffer_parser.json"
|
||||
)} ${bufferClientPath} --program-id ${anchorBufferParserPid.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
main()
|
||||
|
|
108
yarn.lock
108
yarn.lock
|
@ -3942,6 +3942,29 @@
|
|||
superstruct "^0.14.2"
|
||||
tweetnacl "^1.0.0"
|
||||
|
||||
"@solana/web3.js@^1.47.3":
|
||||
version "1.48.0"
|
||||
resolved "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.48.0.tgz#331281b2d80640431fb3b6fdc6b704ec325917aa"
|
||||
integrity sha512-Gb6XvdhGjGI7CdAXLmlMIEvEYvrwqc78JOtwCsSrTqzz7Ek/BhJpZ/Cv89gxRDrWxf6kHegAfaN2FxwuYMmDZQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
"@ethersproject/sha2" "^5.5.0"
|
||||
"@solana/buffer-layout" "^4.0.0"
|
||||
bigint-buffer "^1.1.5"
|
||||
bn.js "^5.0.0"
|
||||
borsh "^0.7.0"
|
||||
bs58 "^4.0.1"
|
||||
buffer "6.0.1"
|
||||
fast-stable-stringify "^1.0.0"
|
||||
jayson "^3.4.4"
|
||||
js-sha3 "^0.8.0"
|
||||
node-fetch "2"
|
||||
react-native-url-polyfill "^1.3.0"
|
||||
rpc-websockets "^7.5.0"
|
||||
secp256k1 "^4.0.2"
|
||||
superstruct "^0.14.2"
|
||||
tweetnacl "^1.0.0"
|
||||
|
||||
"@svgr/babel-plugin-add-jsx-attribute@^5.4.0":
|
||||
version "5.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906"
|
||||
|
@ -4149,27 +4172,6 @@
|
|||
"@svgr/plugin-jsx" "^6.2.1"
|
||||
"@svgr/plugin-svgo" "^6.2.0"
|
||||
|
||||
"@switchboard-xyz/switchboard-v2@^0.0.115":
|
||||
version "0.0.115"
|
||||
resolved "https://registry.npmjs.org/@switchboard-xyz/switchboard-v2/-/switchboard-v2-0.0.115.tgz#9450357d8341b0432975363716ce44954408d83a"
|
||||
integrity sha512-eM9CGs1HaKpExPtb56IQG0+j+Oy6nXM+5g4U7JK6DOR3SwbevCHoTjZt8EIgMuWr1M5NnGK9c0aRMygow+lFIg==
|
||||
dependencies:
|
||||
"@project-serum/anchor" "^0.24.2"
|
||||
"@solana/spl-governance" "^0.0.34"
|
||||
"@solana/spl-token" "^0.2.0"
|
||||
"@solana/web3.js" "^1.44.3"
|
||||
"@types/big.js" "^6.1.4"
|
||||
assert "^2.0.0"
|
||||
big.js "^6.2.0"
|
||||
bs58 "^4.0.1"
|
||||
chan "^0.6.1"
|
||||
crypto-js "^4.0.0"
|
||||
glob "^8.0.3"
|
||||
long "^4.0.0"
|
||||
mocha "^9.1.1"
|
||||
node-fetch "^3.2.3"
|
||||
protobufjs "^6.11.3"
|
||||
|
||||
"@switchboard-xyz/switchboard-v2@^0.0.120":
|
||||
version "0.0.120"
|
||||
resolved "https://registry.npmjs.org/@switchboard-xyz/switchboard-v2/-/switchboard-v2-0.0.120.tgz#b48d2292489771535dc4e9c6b5241115096291ec"
|
||||
|
@ -4450,6 +4452,14 @@
|
|||
resolved "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz"
|
||||
integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==
|
||||
|
||||
"@types/node-fetch@^2.6.2":
|
||||
version "2.6.2"
|
||||
resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da"
|
||||
integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
form-data "^3.0.0"
|
||||
|
||||
"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0":
|
||||
version "18.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.0.tgz#67c7b724e1bcdd7a8821ce0d5ee184d3b4dd525a"
|
||||
|
@ -6546,7 +6556,7 @@ combine-promises@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/combine-promises/-/combine-promises-1.1.0.tgz#72db90743c0ca7aab7d0d8d2052fd7b0f674de71"
|
||||
integrity sha512-ZI9jvcLDxqwaXEixOhArm3r7ReIivsXkpbyEWyeOhzz1QS0iSgBPnWvEqvIQtYyamGCYA88gFhmUrs9hrrQ0pg==
|
||||
|
||||
combined-stream@^1.0.6, combined-stream@~1.0.6:
|
||||
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||
|
@ -8931,6 +8941,15 @@ fork-ts-checker-webpack-plugin@^6.5.0:
|
|||
semver "^7.3.2"
|
||||
tapable "^1.0.0"
|
||||
|
||||
form-data@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
|
||||
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
||||
|
@ -12068,22 +12087,13 @@ node-emoji@^1.10.0:
|
|||
dependencies:
|
||||
lodash "^4.17.21"
|
||||
|
||||
node-fetch@2, node-fetch@2.6.7, node-fetch@^2.2.0, node-fetch@^2.6.1, node-fetch@^2.6.6, node-fetch@^2.6.7:
|
||||
node-fetch@2, node-fetch@2.6.7, node-fetch@^2.2.0, node-fetch@^2.6, node-fetch@^2.6.1, node-fetch@^2.6.6, node-fetch@^2.6.7:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz"
|
||||
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-fetch@^3.2.3:
|
||||
version "3.2.8"
|
||||
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.8.tgz#0d9516dcf43a758d78d6dbe538adf0b1f6a4944e"
|
||||
integrity sha512-KtpD1YhGszhntMpBDyp5lyagk8KIMopC1LEb7cQUAh7zcosaX5uK8HnbNb2i3NTQK3sIawCItS0uFC3QzcLHdg==
|
||||
dependencies:
|
||||
data-uri-to-buffer "^4.0.0"
|
||||
fetch-blob "^3.1.4"
|
||||
formdata-polyfill "^4.0.10"
|
||||
|
||||
node-fetch@^3.2.6:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.6.tgz#6d4627181697a9d9674aae0d61548e0d629b31b9"
|
||||
|
@ -13964,6 +13974,13 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1:
|
|||
dependencies:
|
||||
"@babel/runtime" "^7.10.3"
|
||||
|
||||
react-native-url-polyfill@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-1.3.0.tgz#c1763de0f2a8c22cc3e959b654c8790622b6ef6a"
|
||||
integrity sha512-w9JfSkvpqqlix9UjDvJjm1EjSt652zVQ6iwCIj1cVVkwXf4jQhQgTNXY6EVTwuAmUjg6BC6k9RHCBynoLFo3IQ==
|
||||
dependencies:
|
||||
whatwg-url-without-unicode "8.0.0-3"
|
||||
|
||||
react-player@^2.10.1:
|
||||
version "2.10.1"
|
||||
resolved "https://registry.yarnpkg.com/react-player/-/react-player-2.10.1.tgz#f2ee3ec31393d7042f727737545414b951ffc7e4"
|
||||
|
@ -14640,6 +14657,19 @@ rpc-websockets@^7.4.2:
|
|||
bufferutil "^4.0.1"
|
||||
utf-8-validate "^5.0.2"
|
||||
|
||||
rpc-websockets@^7.5.0:
|
||||
version "7.5.0"
|
||||
resolved "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.0.tgz#bbeb87572e66703ff151e50af1658f98098e2748"
|
||||
integrity sha512-9tIRi1uZGy7YmDjErf1Ax3wtqdSSLIlnmL5OtOzgd5eqPKbsPpwDP5whUDO2LQay3Xp0CcHlcNSGzacNRluBaQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.17.2"
|
||||
eventemitter3 "^4.0.7"
|
||||
uuid "^8.3.2"
|
||||
ws "^8.5.0"
|
||||
optionalDependencies:
|
||||
bufferutil "^4.0.1"
|
||||
utf-8-validate "^5.0.2"
|
||||
|
||||
rtl-detect@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.0.4.tgz#40ae0ea7302a150b96bc75af7d749607392ecac6"
|
||||
|
@ -16700,6 +16730,11 @@ webidl-conversions@^3.0.0:
|
|||
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
|
||||
integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
|
||||
|
||||
webidl-conversions@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
|
||||
integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==
|
||||
|
||||
webidl-conversions@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
|
||||
|
@ -16833,6 +16868,15 @@ websocket-extensions@>=0.1.1:
|
|||
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
|
||||
integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
|
||||
|
||||
whatwg-url-without-unicode@8.0.0-3:
|
||||
version "8.0.0-3"
|
||||
resolved "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz#ab6df4bf6caaa6c85a59f6e82c026151d4bb376b"
|
||||
integrity sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==
|
||||
dependencies:
|
||||
buffer "^5.4.3"
|
||||
punycode "^2.1.1"
|
||||
webidl-conversions "^5.0.0"
|
||||
|
||||
whatwg-url@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz"
|
||||
|
|
Loading…
Reference in New Issue