Merge pull request #42 from switchboard-xyz/vrf-example-cli
programs: added sbv2-vrf-example CLI script
This commit is contained in:
commit
2f99623c3a
|
@ -4,5 +4,6 @@
|
||||||
},
|
},
|
||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.wordWrap": "on"
|
"editor.wordWrap": "on",
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib"
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,9 +196,7 @@ export default class AggregatorCreateCopy extends BaseCommand {
|
||||||
const leaseEscrow = await spl.getAssociatedTokenAddress(
|
const leaseEscrow = await spl.getAssociatedTokenAddress(
|
||||||
mint.address,
|
mint.address,
|
||||||
leaseAccount.publicKey,
|
leaseAccount.publicKey,
|
||||||
true,
|
true
|
||||||
spl.TOKEN_PROGRAM_ID,
|
|
||||||
spl.ASSOCIATED_TOKEN_PROGRAM_ID
|
|
||||||
);
|
);
|
||||||
|
|
||||||
createAccountInstructions.push(
|
createAccountInstructions.push(
|
||||||
|
@ -310,6 +308,8 @@ export default class AggregatorCreateCopy extends BaseCommand {
|
||||||
.crankPush({
|
.crankPush({
|
||||||
stateBump,
|
stateBump,
|
||||||
permissionBump,
|
permissionBump,
|
||||||
|
nofitiRef: null,
|
||||||
|
notifiRef: null,
|
||||||
})
|
})
|
||||||
.accounts({
|
.accounts({
|
||||||
crank: new PublicKey(flags.crankKey),
|
crank: new PublicKey(flags.crankKey),
|
||||||
|
|
|
@ -341,6 +341,8 @@ export default class AggregatorCreate extends BaseCommand {
|
||||||
.crankPush({
|
.crankPush({
|
||||||
stateBump,
|
stateBump,
|
||||||
permissionBump,
|
permissionBump,
|
||||||
|
nofitiRef: null,
|
||||||
|
notifiRef: null,
|
||||||
})
|
})
|
||||||
.accounts({
|
.accounts({
|
||||||
crank: new PublicKey(flags.crankKey),
|
crank: new PublicKey(flags.crankKey),
|
||||||
|
|
|
@ -6,6 +6,8 @@ import BaseCommand from "../../BaseCommand";
|
||||||
export default class VrfPrint extends BaseCommand {
|
export default class VrfPrint extends BaseCommand {
|
||||||
outputFile?: string;
|
outputFile?: string;
|
||||||
|
|
||||||
|
static enableJsonFlag = true;
|
||||||
|
|
||||||
static description = "Print the deserialized Switchboard VRF account";
|
static description = "Print the deserialized Switchboard VRF account";
|
||||||
|
|
||||||
static aliases = ["vrf:print"];
|
static aliases = ["vrf:print"];
|
||||||
|
|
|
@ -38,7 +38,7 @@ export default class VrfVerify extends BaseCommand {
|
||||||
const vrf = await vrfAccount.loadData();
|
const vrf = await vrfAccount.loadData();
|
||||||
|
|
||||||
const status = toVrfStatusString(vrf.status);
|
const status = toVrfStatusString(vrf.status);
|
||||||
if (status !== "statusVerifying") {
|
if (status !== "StatusVerifying") {
|
||||||
throw new Error(`Vrf not ready to be verified, current status ${status}`);
|
throw new Error(`Vrf not ready to be verified, current status ${status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,10 +73,18 @@ export const toPermissionString = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toVrfStatusString = (status: Record<string, unknown>): string => {
|
export type VrfStatusString =
|
||||||
if (status === undefined) {
|
| ""
|
||||||
return "";
|
| "StatusNone"
|
||||||
}
|
| "StatusRequesting"
|
||||||
|
| "StatusVerifying"
|
||||||
|
| "StatusVerified"
|
||||||
|
| "StatusCallbackSuccess"
|
||||||
|
| "StatusVerifyFailure";
|
||||||
|
|
||||||
|
export const toVrfStatusString = (
|
||||||
|
status: Record<string, unknown>
|
||||||
|
): VrfStatusString => {
|
||||||
if ("statusNone" in status) {
|
if ("statusNone" in status) {
|
||||||
return "StatusNone";
|
return "StatusNone";
|
||||||
}
|
}
|
||||||
|
@ -95,7 +103,7 @@ export const toVrfStatusString = (status: Record<string, unknown>): string => {
|
||||||
if ("statusVerifyFailure" in status) {
|
if ("statusVerifyFailure" in status) {
|
||||||
return "StatusVerifyFailure";
|
return "StatusVerifyFailure";
|
||||||
}
|
}
|
||||||
return "Unknown";
|
return "";
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function prettyPrintProgramState(
|
export async function prettyPrintProgramState(
|
||||||
|
@ -582,10 +590,14 @@ export async function prettyPrintVrf(
|
||||||
"latestResult",
|
"latestResult",
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
{
|
{
|
||||||
producer: data.builders[0]?.producer.toString() ?? "",
|
|
||||||
status: toVrfStatusString(data.builders[0]?.status) ?? "",
|
status: toVrfStatusString(data.builders[0]?.status) ?? "",
|
||||||
verified: data.builders[0]?.verified ?? "",
|
verified: data.builders[0]?.verified ?? "",
|
||||||
txRemaining: data.builders[0]?.txRemaining ?? "",
|
txRemaining: data.builders[0]?.txRemaining ?? "",
|
||||||
|
producer: data.builders[0]?.producer.toString() ?? "",
|
||||||
|
reprProof: `[${data.builders[0].reprProof.map((value) =>
|
||||||
|
value.toString()
|
||||||
|
)}]`,
|
||||||
|
reprProofHex: Buffer.from(data.builders[0].reprProof).toString("hex"),
|
||||||
currentRound: {
|
currentRound: {
|
||||||
result: `[${data.currentRound.result.map((value) =>
|
result: `[${data.currentRound.result.map((value) =>
|
||||||
value.toString()
|
value.toString()
|
||||||
|
@ -593,6 +605,7 @@ export async function prettyPrintVrf(
|
||||||
alpha: `[${data.currentRound.alpha.map((value) =>
|
alpha: `[${data.currentRound.alpha.map((value) =>
|
||||||
value.toString()
|
value.toString()
|
||||||
)}]`,
|
)}]`,
|
||||||
|
alphaHex: Buffer.from(data.currentRound.alpha).toString("hex"),
|
||||||
requestSlot: data.currentRound?.requestSlot?.toString() ?? "",
|
requestSlot: data.currentRound?.requestSlot?.toString() ?? "",
|
||||||
requestTimestamp: anchorBNtoDateTimeString(
|
requestTimestamp: anchorBNtoDateTimeString(
|
||||||
data.currentRound.requestTimestamp
|
data.currentRound.requestTimestamp
|
||||||
|
|
|
@ -0,0 +1,763 @@
|
||||||
|
#!/usr/bin/env ts-node-esm
|
||||||
|
/* eslint-disable @typescript-eslint/no-loop-func */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
import * as anchor from "@project-serum/anchor";
|
||||||
|
import * as spl from "@solana/spl-token";
|
||||||
|
import type {
|
||||||
|
Connection,
|
||||||
|
ParsedTransactionWithMeta,
|
||||||
|
PublicKey,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import * as sbv2Utils from "@switchboard-xyz/sbv2-utils";
|
||||||
|
import { toVrfStatusString } from "@switchboard-xyz/sbv2-utils";
|
||||||
|
import * as sbv2 from "@switchboard-xyz/switchboard-v2";
|
||||||
|
import chalk from "chalk";
|
||||||
|
const yargs = require("yargs");
|
||||||
|
const { hideBin } = require("yargs/helpers");
|
||||||
|
// import yargs from "yargs";
|
||||||
|
// import { hideBin } from "yargs/helpers";
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import { AnchorVrfParser, IDL } from "../../target/types/anchor_vrf_parser";
|
||||||
|
import { VrfClient } from "./client/accounts";
|
||||||
|
import { PROGRAM_ID } from "./client/programId";
|
||||||
|
|
||||||
|
const DEFAULT_MAINNET_RPC = "https://ssc-dao.genesysgo.net";
|
||||||
|
const DEFAULT_DEVNET_RPC = "https://devnet.genesysgo.net";
|
||||||
|
const DEFAULT_LOCALNET_RPC = "http://localhost:8899";
|
||||||
|
|
||||||
|
const DEFAULT_COMMITMENT = "confirmed";
|
||||||
|
|
||||||
|
const VRF_REQUEST_AMOUNT = BigInt(2_000_000);
|
||||||
|
|
||||||
|
interface RequestRandomnessResult {
|
||||||
|
success: boolean;
|
||||||
|
status: string;
|
||||||
|
counter: number;
|
||||||
|
txRemaining: number;
|
||||||
|
producer: string;
|
||||||
|
alpha: string;
|
||||||
|
alphaHex: string;
|
||||||
|
proof?: string;
|
||||||
|
proofHex?: string;
|
||||||
|
proofBase64?: string;
|
||||||
|
result: string;
|
||||||
|
stage?: number;
|
||||||
|
txs?: ParsedTransactionWithMeta[] | string[] | any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
yargs(hideBin(process.argv))
|
||||||
|
.scriptName("sbv2-vrf-example")
|
||||||
|
.command(
|
||||||
|
"create [queueKey]",
|
||||||
|
"create a VRF client",
|
||||||
|
(y: any) => {
|
||||||
|
return y
|
||||||
|
.positional("queueKey", {
|
||||||
|
type: "string",
|
||||||
|
describe: "publicKey of the oracle queue to create a VRF for",
|
||||||
|
default: "F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy",
|
||||||
|
})
|
||||||
|
.option("option", {
|
||||||
|
description: "test",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async function (argv: any) {
|
||||||
|
const { queueKey, rpcUrl, cluster } = argv;
|
||||||
|
|
||||||
|
const { vrfClientProgram, switchboardProgram, payer, provider } =
|
||||||
|
await loadCli(rpcUrl, cluster);
|
||||||
|
|
||||||
|
const vrfSecret = anchor.web3.Keypair.generate();
|
||||||
|
|
||||||
|
const [vrfClientKey, vrfClientBump] =
|
||||||
|
anchor.utils.publicKey.findProgramAddressSync(
|
||||||
|
[
|
||||||
|
Buffer.from("STATE"),
|
||||||
|
vrfSecret.publicKey.toBytes(),
|
||||||
|
payer.publicKey.toBytes(),
|
||||||
|
],
|
||||||
|
vrfClientProgram.programId
|
||||||
|
);
|
||||||
|
|
||||||
|
const vrfIxCoder = new anchor.BorshInstructionCoder(vrfClientProgram.idl);
|
||||||
|
const vrfClientCallback = {
|
||||||
|
programId: vrfClientProgram.programId,
|
||||||
|
accounts: [
|
||||||
|
// ensure all accounts in updateResult are populated
|
||||||
|
{ pubkey: vrfClientKey, isSigner: false, isWritable: true },
|
||||||
|
{ pubkey: vrfSecret.publicKey, isSigner: false, isWritable: false },
|
||||||
|
],
|
||||||
|
ixData: vrfIxCoder.encode("updateResult", ""), // pass any params for instruction here
|
||||||
|
};
|
||||||
|
|
||||||
|
const queueAccount = new sbv2.OracleQueueAccount({
|
||||||
|
program: switchboardProgram,
|
||||||
|
publicKey: new anchor.web3.PublicKey(queueKey),
|
||||||
|
});
|
||||||
|
const { unpermissionedVrfEnabled, authority, dataBuffer } =
|
||||||
|
await queueAccount.loadData();
|
||||||
|
|
||||||
|
// Create Switchboard VRF and Permission account
|
||||||
|
const vrfAccount = await sbv2.VrfAccount.create(switchboardProgram, {
|
||||||
|
queue: queueAccount,
|
||||||
|
callback: vrfClientCallback,
|
||||||
|
authority: vrfClientKey, // vrf authority
|
||||||
|
keypair: vrfSecret,
|
||||||
|
});
|
||||||
|
await sbv2Utils.sleep(2000);
|
||||||
|
const { escrow } = await vrfAccount.loadData();
|
||||||
|
console.log(sbv2Utils.chalkString("VRF Account", vrfAccount.publicKey));
|
||||||
|
|
||||||
|
const permissionAccount = await sbv2.PermissionAccount.create(
|
||||||
|
switchboardProgram,
|
||||||
|
{
|
||||||
|
authority,
|
||||||
|
granter: queueAccount.publicKey,
|
||||||
|
grantee: vrfAccount.publicKey,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// console.log(`Created Permission Account: ${permissionAccount.publicKey}`);
|
||||||
|
|
||||||
|
// If queue requires permissions to use VRF, check the correct authority was provided
|
||||||
|
if (!unpermissionedVrfEnabled) {
|
||||||
|
if (!payer.publicKey.equals(authority)) {
|
||||||
|
throw new Error(
|
||||||
|
`queue requires PERMIT_VRF_REQUESTS and wrong queue authority provided`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await permissionAccount.set({
|
||||||
|
authority: payer,
|
||||||
|
permission: sbv2.SwitchboardPermission.PERMIT_VRF_REQUESTS,
|
||||||
|
enable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create VRF Client account
|
||||||
|
await vrfClientProgram.methods
|
||||||
|
.initState({
|
||||||
|
maxResult: new anchor.BN(1),
|
||||||
|
})
|
||||||
|
.accounts({
|
||||||
|
state: vrfClientKey,
|
||||||
|
vrf: vrfAccount.publicKey,
|
||||||
|
payer: payer.publicKey,
|
||||||
|
authority: payer.publicKey,
|
||||||
|
systemProgram: anchor.web3.SystemProgram.programId,
|
||||||
|
})
|
||||||
|
.rpc();
|
||||||
|
|
||||||
|
console.log(sbv2Utils.chalkString("VRF Client", vrfClientKey));
|
||||||
|
|
||||||
|
console.log(`\nRun the following command to request randomness`);
|
||||||
|
console.log(
|
||||||
|
`\t${chalk.yellow(
|
||||||
|
"npx sbv2-vrf-example request",
|
||||||
|
vrfClientKey.toString()
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.command(
|
||||||
|
"request [vrfClientKey]",
|
||||||
|
"request randomness for a VRF client",
|
||||||
|
(y: any) => {
|
||||||
|
return y.positional("vrfClientKey", {
|
||||||
|
type: "string",
|
||||||
|
describe: "publicKey of the VRF client to request randomness for",
|
||||||
|
demand: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async function (argv: any) {
|
||||||
|
const { vrfClientKey, rpcUrl, cluster } = argv;
|
||||||
|
if (!vrfClientKey) {
|
||||||
|
throw new Error(`Must provide vrfClientKey arguement`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { vrfClientProgram, switchboardProgram, payer, provider } =
|
||||||
|
await loadCli(rpcUrl as string, cluster as string);
|
||||||
|
|
||||||
|
const vrfClient = await VrfClient.fetch(
|
||||||
|
provider.connection,
|
||||||
|
new anchor.web3.PublicKey(vrfClientKey)
|
||||||
|
);
|
||||||
|
if (!vrfClient) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to fetch VrfClient for account ${vrfClientKey}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const vrfAccount = new sbv2.VrfAccount({
|
||||||
|
program: switchboardProgram,
|
||||||
|
publicKey: vrfClient.vrf,
|
||||||
|
});
|
||||||
|
const vrfData = await vrfAccount.loadData();
|
||||||
|
|
||||||
|
const vrfEscrow = await spl.getAccount(
|
||||||
|
provider.connection,
|
||||||
|
vrfData.escrow,
|
||||||
|
DEFAULT_COMMITMENT,
|
||||||
|
spl.TOKEN_PROGRAM_ID
|
||||||
|
);
|
||||||
|
|
||||||
|
const queueAccount = new sbv2.OracleQueueAccount({
|
||||||
|
program: switchboardProgram,
|
||||||
|
publicKey: vrfData.oracleQueue,
|
||||||
|
});
|
||||||
|
const queueData = await queueAccount.loadData();
|
||||||
|
const mint = await queueAccount.loadMint();
|
||||||
|
|
||||||
|
const payerTokenAccount = await spl.getOrCreateAssociatedTokenAccount(
|
||||||
|
provider.connection,
|
||||||
|
payer,
|
||||||
|
mint.address,
|
||||||
|
payer.publicKey,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
spl.TOKEN_PROGRAM_ID,
|
||||||
|
spl.ASSOCIATED_TOKEN_PROGRAM_ID
|
||||||
|
);
|
||||||
|
|
||||||
|
const tokensNeeded = VRF_REQUEST_AMOUNT - vrfEscrow.amount;
|
||||||
|
|
||||||
|
if (tokensNeeded > 0 && payerTokenAccount.amount < tokensNeeded) {
|
||||||
|
throw new Error(
|
||||||
|
`Payer token account has insufficient funds, need 2_000_000, current ${payerTokenAccount.amount}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [programStateAccount, programStateBump] =
|
||||||
|
sbv2.ProgramStateAccount.fromSeed(switchboardProgram);
|
||||||
|
|
||||||
|
const [permissionAccount, permissionBump] =
|
||||||
|
sbv2.PermissionAccount.fromSeed(
|
||||||
|
switchboardProgram,
|
||||||
|
queueData.authority,
|
||||||
|
queueAccount.publicKey,
|
||||||
|
vrfAccount.publicKey
|
||||||
|
);
|
||||||
|
|
||||||
|
let ws: number | undefined = undefined;
|
||||||
|
const waitForResultPromise = new Promise(
|
||||||
|
(
|
||||||
|
resolve: (result: anchor.BN) => void,
|
||||||
|
reject: (reason: string) => void
|
||||||
|
) => {
|
||||||
|
ws = vrfClientProgram.provider.connection.onAccountChange(
|
||||||
|
new anchor.web3.PublicKey(vrfClientKey),
|
||||||
|
async (
|
||||||
|
accountInfo: anchor.web3.AccountInfo<Buffer>,
|
||||||
|
context: anchor.web3.Context
|
||||||
|
) => {
|
||||||
|
const clientState = VrfClient.decode(accountInfo.data);
|
||||||
|
if (clientState.result.gt(new anchor.BN(0))) {
|
||||||
|
resolve(clientState.result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
).then(async (result) => {
|
||||||
|
console.log(
|
||||||
|
sbv2Utils.chalkString("Client Result", result.toString(10))
|
||||||
|
);
|
||||||
|
if (ws) {
|
||||||
|
await vrfClientProgram.provider.connection.removeAccountChangeListener(
|
||||||
|
ws
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ws = undefined;
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
const requestTxnPromise = vrfClientProgram.methods
|
||||||
|
.requestResult({
|
||||||
|
switchboardStateBump: programStateBump,
|
||||||
|
permissionBump,
|
||||||
|
})
|
||||||
|
.accounts({
|
||||||
|
state: vrfClientKey,
|
||||||
|
authority: payer.publicKey,
|
||||||
|
switchboardProgram: switchboardProgram.programId,
|
||||||
|
vrf: vrfAccount.publicKey,
|
||||||
|
oracleQueue: queueAccount.publicKey,
|
||||||
|
queueAuthority: queueData.authority,
|
||||||
|
dataBuffer: queueData.dataBuffer,
|
||||||
|
permission: permissionAccount.publicKey,
|
||||||
|
escrow: vrfData.escrow,
|
||||||
|
payerWallet: payerTokenAccount.address,
|
||||||
|
payerAuthority: payer.publicKey,
|
||||||
|
recentBlockhashes: anchor.web3.SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
|
||||||
|
programState: programStateAccount.publicKey,
|
||||||
|
tokenProgram: spl.TOKEN_PROGRAM_ID,
|
||||||
|
})
|
||||||
|
.signers([payer, payer])
|
||||||
|
.rpc()
|
||||||
|
.then((txnSignature) => {
|
||||||
|
console.log(
|
||||||
|
`${chalk.yellow("Randomness Requested")}\t${txnSignature}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let result: anchor.BN;
|
||||||
|
try {
|
||||||
|
result = await sbv2Utils.promiseWithTimeout(
|
||||||
|
45_000,
|
||||||
|
waitForResultPromise,
|
||||||
|
new Error(`Timed out waiting for VRF Client callback`)
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
if (ws) {
|
||||||
|
await vrfClientProgram.provider.connection.removeAccountChangeListener(
|
||||||
|
ws
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.command(
|
||||||
|
"loop [vrfClientKey] [numLoops]",
|
||||||
|
"request randomness for a VRF client N times and record the results",
|
||||||
|
(y: any) => {
|
||||||
|
return y
|
||||||
|
.positional("vrfClientKey", {
|
||||||
|
type: "string",
|
||||||
|
describe: "publicKey of the VRF client to request randomness for",
|
||||||
|
demand: true,
|
||||||
|
})
|
||||||
|
.positional("numLoops", {
|
||||||
|
type: "number",
|
||||||
|
describe: "number of times to request randomness",
|
||||||
|
default: 100,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async function (argv: any) {
|
||||||
|
const { vrfClientKey, rpcUrl, cluster, numLoops } = argv;
|
||||||
|
if (!vrfClientKey) {
|
||||||
|
throw new Error(`Must provide vrfClientKey arguement`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
const vrfClientPubkey = new anchor.web3.PublicKey(vrfClientKey);
|
||||||
|
|
||||||
|
const { vrfClientProgram, switchboardProgram, payer, provider } =
|
||||||
|
await loadCli(rpcUrl as string, cluster as string);
|
||||||
|
|
||||||
|
const vrfClient = await VrfClient.fetch(
|
||||||
|
provider.connection,
|
||||||
|
vrfClientPubkey
|
||||||
|
);
|
||||||
|
if (!vrfClient) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to fetch VrfClient for account ${vrfClientKey}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const vrfAccount = new sbv2.VrfAccount({
|
||||||
|
program: switchboardProgram,
|
||||||
|
publicKey: vrfClient.vrf,
|
||||||
|
});
|
||||||
|
const vrfData = await vrfAccount.loadData();
|
||||||
|
|
||||||
|
const vrfEscrow = await spl.getAccount(
|
||||||
|
provider.connection,
|
||||||
|
vrfData.escrow,
|
||||||
|
DEFAULT_COMMITMENT,
|
||||||
|
spl.TOKEN_PROGRAM_ID
|
||||||
|
);
|
||||||
|
|
||||||
|
const queueAccount = new sbv2.OracleQueueAccount({
|
||||||
|
program: switchboardProgram,
|
||||||
|
publicKey: vrfData.oracleQueue,
|
||||||
|
});
|
||||||
|
const queueData = await queueAccount.loadData();
|
||||||
|
const mint = await queueAccount.loadMint();
|
||||||
|
|
||||||
|
const payerTokenAccount = await spl.getOrCreateAssociatedTokenAccount(
|
||||||
|
provider.connection,
|
||||||
|
payer,
|
||||||
|
mint.address,
|
||||||
|
payer.publicKey,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
spl.TOKEN_PROGRAM_ID,
|
||||||
|
spl.ASSOCIATED_TOKEN_PROGRAM_ID
|
||||||
|
);
|
||||||
|
|
||||||
|
const tokensNeeded = VRF_REQUEST_AMOUNT - vrfEscrow.amount;
|
||||||
|
|
||||||
|
if (tokensNeeded > 0 && payerTokenAccount.amount < tokensNeeded) {
|
||||||
|
throw new Error(
|
||||||
|
`Payer token account has insufficient funds, need 2_000_000, current ${payerTokenAccount.amount}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [programStateAccount, programStateBump] =
|
||||||
|
sbv2.ProgramStateAccount.fromSeed(switchboardProgram);
|
||||||
|
|
||||||
|
const [permissionAccount, permissionBump] =
|
||||||
|
sbv2.PermissionAccount.fromSeed(
|
||||||
|
switchboardProgram,
|
||||||
|
queueData.authority,
|
||||||
|
queueAccount.publicKey,
|
||||||
|
vrfAccount.publicKey
|
||||||
|
);
|
||||||
|
|
||||||
|
const requestInstruction = await vrfClientProgram.methods
|
||||||
|
.requestResult({
|
||||||
|
switchboardStateBump: programStateBump,
|
||||||
|
permissionBump,
|
||||||
|
})
|
||||||
|
.accounts({
|
||||||
|
state: vrfClientPubkey,
|
||||||
|
authority: payer.publicKey,
|
||||||
|
switchboardProgram: switchboardProgram.programId,
|
||||||
|
vrf: vrfAccount.publicKey,
|
||||||
|
oracleQueue: queueAccount.publicKey,
|
||||||
|
queueAuthority: queueData.authority,
|
||||||
|
dataBuffer: queueData.dataBuffer,
|
||||||
|
permission: permissionAccount.publicKey,
|
||||||
|
escrow: vrfData.escrow,
|
||||||
|
payerWallet: payerTokenAccount.address,
|
||||||
|
payerAuthority: payer.publicKey,
|
||||||
|
recentBlockhashes: anchor.web3.SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
|
||||||
|
programState: programStateAccount.publicKey,
|
||||||
|
tokenProgram: spl.TOKEN_PROGRAM_ID,
|
||||||
|
})
|
||||||
|
.signers([payer])
|
||||||
|
.instruction();
|
||||||
|
|
||||||
|
const results: RequestRandomnessResult[] = [];
|
||||||
|
let successes = 0;
|
||||||
|
let failures = 0;
|
||||||
|
try {
|
||||||
|
for await (const i of [...Array(numLoops).keys()]) {
|
||||||
|
writeStatus(i, numLoops, successes, failures);
|
||||||
|
let success = false;
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
await provider.sendAndConfirm(
|
||||||
|
new anchor.web3.Transaction().add(requestInstruction),
|
||||||
|
[payer]
|
||||||
|
);
|
||||||
|
// console.log("sent");
|
||||||
|
break;
|
||||||
|
} catch (error) {
|
||||||
|
if (!error.message.includes("0x17b5")) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
await sbv2Utils.sleep(2500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await awaitCallback(provider.connection, vrfClientPubkey, 45_000)
|
||||||
|
.then(() => {
|
||||||
|
success = true;
|
||||||
|
successes++;
|
||||||
|
})
|
||||||
|
.catch(async (error) => {
|
||||||
|
failures++;
|
||||||
|
const vrf = await vrfAccount.loadData();
|
||||||
|
|
||||||
|
clearStatus();
|
||||||
|
console.error(error.message);
|
||||||
|
writeVrfState(vrf);
|
||||||
|
writeStatus(i, numLoops, successes, failures);
|
||||||
|
});
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
|
const vrf = await vrfAccount.loadData();
|
||||||
|
const vrfStatus = sbv2Utils.toVrfStatusString(vrf.status);
|
||||||
|
const result: RequestRandomnessResult = {
|
||||||
|
success: vrfStatus === "StatusCallbackSuccess",
|
||||||
|
counter: vrf.counter.toString(10),
|
||||||
|
producer: vrf.builders[0].producer.toString(),
|
||||||
|
status: vrfStatus,
|
||||||
|
txRemaining: vrf.builders[0].txRemaining,
|
||||||
|
alpha: `[${vrf.currentRound.alpha.slice(0, 32).toString()}]`,
|
||||||
|
alphaHex: Buffer.from(vrf.currentRound.alpha.slice(0, 32)).toString(
|
||||||
|
"hex"
|
||||||
|
),
|
||||||
|
proof: `[${vrf.builders[0].reprProof.slice(0, 80).toString()}]`,
|
||||||
|
proofHex: Buffer.from(
|
||||||
|
vrf.builders[0].reprProof.slice(0, 80)
|
||||||
|
).toString("hex"),
|
||||||
|
proofBase64: Buffer.from(
|
||||||
|
vrf.builders[0].reprProof.slice(0, 80)
|
||||||
|
).toString("base64"),
|
||||||
|
result: `[${vrf.currentRound.result.toString()}]`,
|
||||||
|
stage: vrf.builders[0].stage,
|
||||||
|
txs:
|
||||||
|
vrfStatus === "StatusCallbackSuccess"
|
||||||
|
? undefined
|
||||||
|
: await fetchTransactions(
|
||||||
|
vrfAccount.program.provider.connection,
|
||||||
|
vrfAccount.publicKey,
|
||||||
|
15
|
||||||
|
),
|
||||||
|
};
|
||||||
|
results.push(result);
|
||||||
|
|
||||||
|
saveResults(startTime, vrfClientKey, results);
|
||||||
|
|
||||||
|
clearStatus();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`GLOBAL: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeStatus(numLoops, numLoops, successes, failures);
|
||||||
|
|
||||||
|
saveResults(startTime, vrfClientKey, results);
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.options({
|
||||||
|
cluster: {
|
||||||
|
type: "string",
|
||||||
|
alias: "c",
|
||||||
|
describe: "Solana cluster to interact with",
|
||||||
|
options: ["devnet", "mainnet-beta", "localnet"],
|
||||||
|
default: "devnet",
|
||||||
|
demand: false,
|
||||||
|
},
|
||||||
|
rpcUrl: {
|
||||||
|
type: "string",
|
||||||
|
alias: "u",
|
||||||
|
describe: "Alternative RPC URL",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.help().argv;
|
||||||
|
|
||||||
|
function getRpcUrl(cluster: string): string {
|
||||||
|
switch (cluster) {
|
||||||
|
case "mainnet-beta":
|
||||||
|
return DEFAULT_MAINNET_RPC;
|
||||||
|
case "devnet":
|
||||||
|
return DEFAULT_DEVNET_RPC;
|
||||||
|
case "localnet":
|
||||||
|
return DEFAULT_LOCALNET_RPC;
|
||||||
|
default:
|
||||||
|
throw new Error(`Failed to find RPC_URL for cluster ${cluster}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeVrfState(vrf: any) {
|
||||||
|
console.log(`Status: ${toVrfStatusString(vrf.builders[0]?.status) ?? ""}`);
|
||||||
|
console.log(`TxRemaining: ${vrf.builders[0]?.txRemaining ?? ""}`);
|
||||||
|
console.log(
|
||||||
|
`Alpha: [${vrf.currentRound.alpha
|
||||||
|
.slice(0, 32)
|
||||||
|
.map((value) => value.toString())}]`
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`AlphaHex: ${Buffer.from(vrf.currentRound.alpha.slice(0, 32)).toString(
|
||||||
|
"hex"
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`Proof: [${vrf.builders[0].reprProof
|
||||||
|
.slice(0, 80)
|
||||||
|
.map((value) => value.toString())}]`
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`ProofHex: ${Buffer.from(vrf.builders[0].reprProof.slice(0, 80)).toString(
|
||||||
|
"hex"
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`ProofBase64: ${Buffer.from(
|
||||||
|
vrf.builders[0].reprProof.slice(0, 80)
|
||||||
|
).toString("base64")}`
|
||||||
|
);
|
||||||
|
console.log(`Stage: ${vrf.builders[0].stage}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveResults(
|
||||||
|
timestamp: number,
|
||||||
|
vrfClientKey: anchor.web3.PublicKey,
|
||||||
|
results: RequestRandomnessResult[]
|
||||||
|
) {
|
||||||
|
if (results.length) {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(process.cwd(), `${vrfClientKey}_${timestamp}.json`),
|
||||||
|
JSON.stringify(results, undefined, 2),
|
||||||
|
"utf-8"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorResults = results.filter((result) => !result.success);
|
||||||
|
|
||||||
|
if (errorResults.length) {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(process.cwd(), `${vrfClientKey}_ERRORS_${timestamp}.json`),
|
||||||
|
JSON.stringify(errorResults, undefined, 2),
|
||||||
|
"utf-8"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCli(
|
||||||
|
rpcUrl: string,
|
||||||
|
cluster: string
|
||||||
|
): Promise<{
|
||||||
|
vrfClientProgram: anchor.Program<AnchorVrfParser>;
|
||||||
|
switchboardProgram: anchor.Program;
|
||||||
|
payer: anchor.web3.Keypair;
|
||||||
|
provider: anchor.AnchorProvider;
|
||||||
|
}> {
|
||||||
|
if (cluster !== "mainnet-beta" && cluster !== "devnet") {
|
||||||
|
throw new Error(
|
||||||
|
`cluster must be mainnet-beta or devnet, cluster = ${cluster}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.env.ANCHOR_WALLET = sbv2Utils.getAnchorWalletPath();
|
||||||
|
const url = rpcUrl || getRpcUrl(cluster);
|
||||||
|
const envProvider = anchor.AnchorProvider.local(url);
|
||||||
|
const provider = new anchor.AnchorProvider(
|
||||||
|
new anchor.web3.Connection(url, {
|
||||||
|
commitment: DEFAULT_COMMITMENT,
|
||||||
|
}),
|
||||||
|
envProvider.wallet,
|
||||||
|
{
|
||||||
|
commitment: DEFAULT_COMMITMENT,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const switchboardProgram = await sbv2.loadSwitchboardProgram(
|
||||||
|
cluster,
|
||||||
|
provider.connection,
|
||||||
|
(provider.wallet as sbv2.AnchorWallet).payer,
|
||||||
|
{
|
||||||
|
commitment: DEFAULT_COMMITMENT,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const payer = sbv2.programWallet(switchboardProgram);
|
||||||
|
|
||||||
|
// load VRF Client program
|
||||||
|
const vrfClientProgram = new anchor.Program(
|
||||||
|
IDL,
|
||||||
|
PROGRAM_ID,
|
||||||
|
provider,
|
||||||
|
new anchor.BorshCoder(IDL)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
vrfClientProgram,
|
||||||
|
switchboardProgram,
|
||||||
|
payer,
|
||||||
|
provider,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function awaitCallback(
|
||||||
|
connection: anchor.web3.Connection,
|
||||||
|
vrfClientKey: anchor.web3.PublicKey,
|
||||||
|
timeoutInterval: number,
|
||||||
|
errorMsg = "Timed out waiting for VRF Client callback"
|
||||||
|
) {
|
||||||
|
let ws: number | undefined = undefined;
|
||||||
|
const result: anchor.BN = await sbv2Utils
|
||||||
|
.promiseWithTimeout(
|
||||||
|
timeoutInterval,
|
||||||
|
new Promise(
|
||||||
|
(
|
||||||
|
resolve: (result: anchor.BN) => void,
|
||||||
|
reject: (reason: string) => void
|
||||||
|
) => {
|
||||||
|
ws = connection.onAccountChange(
|
||||||
|
vrfClientKey,
|
||||||
|
async (
|
||||||
|
accountInfo: anchor.web3.AccountInfo<Buffer>,
|
||||||
|
context: anchor.web3.Context
|
||||||
|
) => {
|
||||||
|
const clientState = VrfClient.decode(accountInfo.data);
|
||||||
|
if (clientState.result.gt(new anchor.BN(0))) {
|
||||||
|
resolve(clientState.result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
new Error(errorMsg)
|
||||||
|
)
|
||||||
|
.finally(async () => {
|
||||||
|
if (ws) {
|
||||||
|
await connection.removeAccountChangeListener(ws);
|
||||||
|
}
|
||||||
|
ws = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearLastLine = () => {
|
||||||
|
process.stdout.moveCursor(0, -1); // up one line
|
||||||
|
process.stdout.clearLine(1); // from cursor to end
|
||||||
|
};
|
||||||
|
|
||||||
|
function writeStatus(
|
||||||
|
i: number,
|
||||||
|
numLoops: number,
|
||||||
|
successes: number,
|
||||||
|
failures: number
|
||||||
|
) {
|
||||||
|
process.stdout.write(`# ${i} / ${numLoops}\n`);
|
||||||
|
process.stdout.write(`${chalk.green("Success:", successes)} / ${i}\n`);
|
||||||
|
process.stdout.write(`${chalk.red("Errors: ", failures)} / ${i}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearStatus() {
|
||||||
|
process.stdout.moveCursor(0, -1); // up one line
|
||||||
|
process.stdout.clearLine(1); // from cursor to end
|
||||||
|
process.stdout.moveCursor(0, -1); // up one line
|
||||||
|
process.stdout.clearLine(1); // from cursor to end
|
||||||
|
process.stdout.moveCursor(0, -1); // up one line
|
||||||
|
process.stdout.clearLine(1); // from cursor to end
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchTransactions(
|
||||||
|
connection: Connection,
|
||||||
|
pubkey: PublicKey,
|
||||||
|
numTransactions = 10
|
||||||
|
): Promise<any[]> {
|
||||||
|
const signatures = (
|
||||||
|
await connection.getSignaturesForAddress(
|
||||||
|
pubkey,
|
||||||
|
{ limit: numTransactions },
|
||||||
|
"confirmed"
|
||||||
|
)
|
||||||
|
).map((t) => t.signature);
|
||||||
|
|
||||||
|
console.log(`FETCHED ${signatures.length} transactions`);
|
||||||
|
|
||||||
|
let parsedTxns: ParsedTransactionWithMeta[] | null = null;
|
||||||
|
while (!parsedTxns) {
|
||||||
|
parsedTxns = await connection.getParsedTransactions(
|
||||||
|
signatures,
|
||||||
|
"confirmed"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!parsedTxns || parsedTxns.length !== signatures.length) {
|
||||||
|
await sbv2Utils.sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedTxns.map((tx, i) => {
|
||||||
|
return {
|
||||||
|
signature: signatures[i],
|
||||||
|
logs: tx.meta.logMessages,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,40 +1,46 @@
|
||||||
import * as borsh from "@project-serum/borsh" // eslint-disable-line @typescript-eslint/no-unused-vars
|
import * as borsh from "@project-serum/borsh"; // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
import { Connection, PublicKey } from "@solana/web3.js"
|
import { Connection, PublicKey } from "@solana/web3.js";
|
||||||
import BN from "bn.js" // eslint-disable-line @typescript-eslint/no-unused-vars
|
import BN from "bn.js"; // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
import { PROGRAM_ID } from "../programId"
|
import { PROGRAM_ID } from "../programId";
|
||||||
|
|
||||||
export interface VrfClientFields {
|
export interface VrfClientFields {
|
||||||
bump: number
|
bump: number;
|
||||||
maxResult: BN
|
maxResult: BN;
|
||||||
resultBuffer: Array<number>
|
resultBuffer: Array<number>;
|
||||||
result: BN
|
result: BN;
|
||||||
lastTimestamp: BN
|
lastTimestamp: BN;
|
||||||
authority: PublicKey
|
authority: PublicKey;
|
||||||
vrf: PublicKey
|
vrf: PublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VrfClientJSON {
|
export interface VrfClientJSON {
|
||||||
bump: number
|
bump: number;
|
||||||
maxResult: string
|
maxResult: string;
|
||||||
resultBuffer: Array<number>
|
resultBuffer: Array<number>;
|
||||||
result: string
|
result: string;
|
||||||
lastTimestamp: string
|
lastTimestamp: string;
|
||||||
authority: string
|
authority: string;
|
||||||
vrf: string
|
vrf: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VrfClient {
|
export class VrfClient {
|
||||||
readonly bump: number
|
readonly bump: number;
|
||||||
readonly maxResult: BN
|
|
||||||
readonly resultBuffer: Array<number>
|
readonly maxResult: BN;
|
||||||
readonly result: BN
|
|
||||||
readonly lastTimestamp: BN
|
readonly resultBuffer: Array<number>;
|
||||||
readonly authority: PublicKey
|
|
||||||
readonly vrf: PublicKey
|
readonly result: BN;
|
||||||
|
|
||||||
|
readonly lastTimestamp: BN;
|
||||||
|
|
||||||
|
readonly authority: PublicKey;
|
||||||
|
|
||||||
|
readonly vrf: PublicKey;
|
||||||
|
|
||||||
static readonly discriminator = Buffer.from([
|
static readonly discriminator = Buffer.from([
|
||||||
230, 174, 157, 153, 51, 18, 230, 163,
|
230, 174, 157, 153, 51, 18, 230, 163,
|
||||||
])
|
]);
|
||||||
|
|
||||||
static readonly layout = borsh.struct([
|
static readonly layout = borsh.struct([
|
||||||
borsh.u8("bump"),
|
borsh.u8("bump"),
|
||||||
|
@ -44,58 +50,58 @@ export class VrfClient {
|
||||||
borsh.i64("lastTimestamp"),
|
borsh.i64("lastTimestamp"),
|
||||||
borsh.publicKey("authority"),
|
borsh.publicKey("authority"),
|
||||||
borsh.publicKey("vrf"),
|
borsh.publicKey("vrf"),
|
||||||
])
|
]);
|
||||||
|
|
||||||
constructor(fields: VrfClientFields) {
|
constructor(fields: VrfClientFields) {
|
||||||
this.bump = fields.bump
|
this.bump = fields.bump;
|
||||||
this.maxResult = fields.maxResult
|
this.maxResult = fields.maxResult;
|
||||||
this.resultBuffer = fields.resultBuffer
|
this.resultBuffer = fields.resultBuffer;
|
||||||
this.result = fields.result
|
this.result = fields.result;
|
||||||
this.lastTimestamp = fields.lastTimestamp
|
this.lastTimestamp = fields.lastTimestamp;
|
||||||
this.authority = fields.authority
|
this.authority = fields.authority;
|
||||||
this.vrf = fields.vrf
|
this.vrf = fields.vrf;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fetch(
|
static async fetch(
|
||||||
c: Connection,
|
c: Connection,
|
||||||
address: PublicKey
|
address: PublicKey
|
||||||
): Promise<VrfClient | null> {
|
): Promise<VrfClient | null> {
|
||||||
const info = await c.getAccountInfo(address)
|
const info = await c.getAccountInfo(address);
|
||||||
|
|
||||||
if (info === null) {
|
if (info === null) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
if (!info.owner.equals(PROGRAM_ID)) {
|
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(
|
static async fetchMultiple(
|
||||||
c: Connection,
|
c: Connection,
|
||||||
addresses: PublicKey[]
|
addresses: PublicKey[]
|
||||||
): Promise<Array<VrfClient | null>> {
|
): Promise<Array<VrfClient | null>> {
|
||||||
const infos = await c.getMultipleAccountsInfo(addresses)
|
const infos = await c.getMultipleAccountsInfo(addresses);
|
||||||
|
|
||||||
return infos.map((info) => {
|
return infos.map((info) => {
|
||||||
if (info === null) {
|
if (info === null) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
if (!info.owner.equals(PROGRAM_ID)) {
|
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 {
|
static decode(data: Buffer): VrfClient {
|
||||||
if (!data.slice(0, 8).equals(VrfClient.discriminator)) {
|
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({
|
return new VrfClient({
|
||||||
bump: dec.bump,
|
bump: dec.bump,
|
||||||
|
@ -105,7 +111,7 @@ export class VrfClient {
|
||||||
lastTimestamp: dec.lastTimestamp,
|
lastTimestamp: dec.lastTimestamp,
|
||||||
authority: dec.authority,
|
authority: dec.authority,
|
||||||
vrf: dec.vrf,
|
vrf: dec.vrf,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON(): VrfClientJSON {
|
toJSON(): VrfClientJSON {
|
||||||
|
@ -117,7 +123,7 @@ export class VrfClient {
|
||||||
lastTimestamp: this.lastTimestamp.toString(),
|
lastTimestamp: this.lastTimestamp.toString(),
|
||||||
authority: this.authority.toString(),
|
authority: this.authority.toString(),
|
||||||
vrf: this.vrf.toString(),
|
vrf: this.vrf.toString(),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJSON(obj: VrfClientJSON): VrfClient {
|
static fromJSON(obj: VrfClientJSON): VrfClient {
|
||||||
|
@ -129,6 +135,6 @@ export class VrfClient {
|
||||||
lastTimestamp: new BN(obj.lastTimestamp),
|
lastTimestamp: new BN(obj.lastTimestamp),
|
||||||
authority: new PublicKey(obj.authority),
|
authority: new PublicKey(obj.authority),
|
||||||
vrf: new PublicKey(obj.vrf),
|
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.
|
// 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(
|
export const PROGRAM_ID_CLI = new PublicKey(
|
||||||
"HjjRFjCyQH3ne6Gg8Yn3TQafrrYecRrphwLwnh2A26vM"
|
"HjjRFjCyQH3ne6Gg8Yn3TQafrrYecRrphwLwnh2A26vM"
|
||||||
)
|
);
|
||||||
|
|
||||||
// This constant will not get overwritten on subsequent code generations and it's safe to modify it's value.
|
// 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;
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
"url": "https://github.com/switchboard-xyz/switchboard-v2",
|
"url": "https://github.com/switchboard-xyz/switchboard-v2",
|
||||||
"directory": "programs/anchor-vrf-parser"
|
"directory": "programs/anchor-vrf-parser"
|
||||||
},
|
},
|
||||||
|
"bin": {
|
||||||
|
"sbv2-vrf-example": "./sbv2-vrf-example.sh"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "echo \"For workspace anchor-vrf-parser, run 'anchor build' from the project root\" && exit 0",
|
"build": "echo \"For workspace anchor-vrf-parser, run 'anchor build' from the project root\" && exit 0",
|
||||||
"lint": "eslint --ext .js,.json,.ts 'src/**' --fix"
|
"lint": "eslint --ext .js,.json,.ts 'src/**' --fix"
|
||||||
|
@ -20,7 +23,8 @@
|
||||||
"@switchboard-xyz/switchboard-v2": "^0.0.120",
|
"@switchboard-xyz/switchboard-v2": "^0.0.120",
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
"child_process": "^1.0.2",
|
"child_process": "^1.0.2",
|
||||||
"dotenv": "^16.0.1"
|
"dotenv": "^16.0.1",
|
||||||
|
"yargs": "^17.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@switchboard-xyz/switchboardv2-cli": "^0.2.14",
|
"@switchboard-xyz/switchboardv2-cli": "^0.2.14",
|
||||||
|
@ -43,7 +47,7 @@
|
||||||
"prettier-plugin-organize-imports": "^2.3.4",
|
"prettier-plugin-organize-imports": "^2.3.4",
|
||||||
"shx": "^0.3.4",
|
"shx": "^0.3.4",
|
||||||
"ts-mocha": "^9.0.2",
|
"ts-mocha": "^9.0.2",
|
||||||
"ts-node": "^10.4.0",
|
"ts-node": "^10.8.0",
|
||||||
"typescript": "^4.3.5"
|
"typescript": "^4.7.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
script_dir=$(dirname "$(readlink -f "$BASH_SOURCE")")
|
||||||
|
# echo "$script_dir"
|
||||||
|
|
||||||
|
# /usr/bin/env node --enable-source-maps "$script_dir"/cli.js "$@"
|
||||||
|
# "$script_dir"/node_modules/.bin/ts-node-esm "$script_dir"/cli.ts "$@"
|
||||||
|
/usr/bin/env ts-node-esm "$script_dir"/cli.ts "$@"
|
|
@ -111,6 +111,9 @@ impl RequestResult<'_> {
|
||||||
state_seeds,
|
state_seeds,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let mut client_state = ctx.accounts.state.load_mut()?;
|
||||||
|
client_state.result = 0;
|
||||||
|
|
||||||
emit!(RequestingRandomness{
|
emit!(RequestingRandomness{
|
||||||
vrf_client: ctx.accounts.state.key(),
|
vrf_client: ctx.accounts.state.key(),
|
||||||
max_result: max_result,
|
max_result: max_result,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as anchor from "@project-serum/anchor";
|
import * as anchor from "@project-serum/anchor";
|
||||||
import { AnchorProvider, Program } from "@project-serum/anchor";
|
import { AnchorProvider } from "@project-serum/anchor";
|
||||||
import * as spl from "@solana/spl-token";
|
import * as spl from "@solana/spl-token";
|
||||||
import {
|
import {
|
||||||
SystemProgram,
|
SystemProgram,
|
||||||
|
@ -29,7 +29,7 @@ describe("anchor-vrf-parser test", () => {
|
||||||
// const vrfClientProgram = anchor.workspace
|
// const vrfClientProgram = anchor.workspace
|
||||||
// .AnchorVrfParser as Program<AnchorVrfParser>;
|
// .AnchorVrfParser as Program<AnchorVrfParser>;
|
||||||
|
|
||||||
const vrfClientProgram = new Program(
|
const vrfClientProgram = new anchor.Program(
|
||||||
IDL,
|
IDL,
|
||||||
PROGRAM_ID,
|
PROGRAM_ID,
|
||||||
provider,
|
provider,
|
||||||
|
@ -195,10 +195,6 @@ describe("anchor-vrf-parser test", () => {
|
||||||
55_000
|
55_000
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
throw new Error(`failed to get a VRF result`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`VrfClient Result: ${result}`);
|
console.log(`VrfClient Result: ${result}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
{
|
{
|
||||||
"extends": "../../tsconfig.json",
|
"extends": "../../tsconfig.json",
|
||||||
|
"ts-node": {
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"types": ["mocha", "chai"],
|
"module": "commonjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["mocha", "chai", "node"],
|
||||||
"typeRoots": ["./node_modules/@types"],
|
"typeRoots": ["./node_modules/@types"],
|
||||||
"module": "CommonJS",
|
"module": "commonjs",
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"strict": false,
|
"strict": false,
|
||||||
|
"strictNullChecks": false,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@switchboard-xyz/switchboard-v2": ["../../libraries/ts"],
|
"@switchboard-xyz/switchboard-v2": ["../../libraries/ts"],
|
||||||
"@switchboard-xyz/sbv2-utils": ["../../libraries/sbv2-utils"],
|
"@switchboard-xyz/sbv2-utils": ["../../libraries/sbv2-utils"],
|
||||||
|
@ -16,12 +22,8 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["tests/**/*", "./cli.ts", "./client/**/*"],
|
||||||
"tests/**/*",
|
"exclude": ["target", "lib"],
|
||||||
"client/**/*",
|
|
||||||
"../../../target/types/anchor_vrf_parser",
|
|
||||||
"../../../target/idl/anchor_vrf_parser"
|
|
||||||
],
|
|
||||||
"references": [
|
"references": [
|
||||||
{ "path": "../../libraries/ts" },
|
{ "path": "../../libraries/ts" },
|
||||||
{ "path": "../../libraries/sbv2-utils" }
|
{ "path": "../../libraries/sbv2-utils" }
|
||||||
|
|
55
yarn.lock
55
yarn.lock
|
@ -4149,6 +4149,48 @@
|
||||||
"@svgr/plugin-jsx" "^6.2.1"
|
"@svgr/plugin-jsx" "^6.2.1"
|
||||||
"@svgr/plugin-svgo" "^6.2.0"
|
"@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"
|
||||||
|
integrity sha512-BCdKacL4osK32SYo/OlNcSMO8rAwdo7ArtUQ0BTSKqce6wgL2naXFg82KmGIG4+SuoxNLFvkbG0/zT8UYliwgw==
|
||||||
|
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.6"
|
||||||
|
protobufjs "^6.11.3"
|
||||||
|
|
||||||
"@szmarczak/http-timer@^1.1.2":
|
"@szmarczak/http-timer@^1.1.2":
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
|
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
|
||||||
|
@ -12033,6 +12075,15 @@ node-fetch@2, node-fetch@2.6.7, node-fetch@^2.2.0, node-fetch@^2.6.1, node-fetch
|
||||||
dependencies:
|
dependencies:
|
||||||
whatwg-url "^5.0.0"
|
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:
|
node-fetch@^3.2.6:
|
||||||
version "3.2.6"
|
version "3.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.6.tgz#6d4627181697a9d9674aae0d61548e0d629b31b9"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.6.tgz#6d4627181697a9d9674aae0d61548e0d629b31b9"
|
||||||
|
@ -17104,9 +17155,9 @@ yargs@16.2.0, yargs@^16.2.0:
|
||||||
y18n "^5.0.5"
|
y18n "^5.0.5"
|
||||||
yargs-parser "^20.2.2"
|
yargs-parser "^20.2.2"
|
||||||
|
|
||||||
yargs@^17.0.1, yargs@^17.3.1:
|
yargs@^17.0.1, yargs@^17.3.1, yargs@^17.5.1:
|
||||||
version "17.5.1"
|
version "17.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e"
|
resolved "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e"
|
||||||
integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==
|
integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==
|
||||||
dependencies:
|
dependencies:
|
||||||
cliui "^7.0.2"
|
cliui "^7.0.2"
|
||||||
|
|
Loading…
Reference in New Issue