programs: added sbv2-vrf-example CLI script

deps


deps


wip


wip
This commit is contained in:
Conner Gallagher 2022-07-14 14:58:56 -06:00
parent cce9392789
commit 62a3ad9d5c
15 changed files with 954 additions and 77 deletions

View File

@ -4,5 +4,6 @@
},
"files.eol": "\n",
"editor.formatOnSave": true,
"editor.wordWrap": "on"
"editor.wordWrap": "on",
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@ -196,9 +196,7 @@ export default class AggregatorCreateCopy extends BaseCommand {
const leaseEscrow = await spl.getAssociatedTokenAddress(
mint.address,
leaseAccount.publicKey,
true,
spl.TOKEN_PROGRAM_ID,
spl.ASSOCIATED_TOKEN_PROGRAM_ID
true
);
createAccountInstructions.push(
@ -310,6 +308,8 @@ export default class AggregatorCreateCopy extends BaseCommand {
.crankPush({
stateBump,
permissionBump,
nofitiRef: null,
notifiRef: null,
})
.accounts({
crank: new PublicKey(flags.crankKey),

View File

@ -341,6 +341,8 @@ export default class AggregatorCreate extends BaseCommand {
.crankPush({
stateBump,
permissionBump,
nofitiRef: null,
notifiRef: null,
})
.accounts({
crank: new PublicKey(flags.crankKey),

View File

@ -6,6 +6,8 @@ import BaseCommand from "../../BaseCommand";
export default class VrfPrint extends BaseCommand {
outputFile?: string;
static enableJsonFlag = true;
static description = "Print the deserialized Switchboard VRF account";
static aliases = ["vrf:print"];

View File

@ -38,7 +38,7 @@ export default class VrfVerify extends BaseCommand {
const vrf = await vrfAccount.loadData();
const status = toVrfStatusString(vrf.status);
if (status !== "statusVerifying") {
if (status !== "StatusVerifying") {
throw new Error(`Vrf not ready to be verified, current status ${status}`);
}

View File

@ -73,10 +73,18 @@ export const toPermissionString = (
}
};
export const toVrfStatusString = (status: Record<string, unknown>): string => {
if (status === undefined) {
return "";
}
export type VrfStatusString =
| ""
| "StatusNone"
| "StatusRequesting"
| "StatusVerifying"
| "StatusVerified"
| "StatusCallbackSuccess"
| "StatusVerifyFailure";
export const toVrfStatusString = (
status: Record<string, unknown>
): VrfStatusString => {
if ("statusNone" in status) {
return "StatusNone";
}
@ -95,7 +103,7 @@ export const toVrfStatusString = (status: Record<string, unknown>): string => {
if ("statusVerifyFailure" in status) {
return "StatusVerifyFailure";
}
return "Unknown";
return "";
};
export async function prettyPrintProgramState(
@ -582,10 +590,14 @@ export async function prettyPrintVrf(
"latestResult",
JSON.stringify(
{
producer: data.builders[0]?.producer.toString() ?? "",
status: toVrfStatusString(data.builders[0]?.status) ?? "",
verified: data.builders[0]?.verified ?? "",
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: {
result: `[${data.currentRound.result.map((value) =>
value.toString()
@ -593,6 +605,7 @@ export async function prettyPrintVrf(
alpha: `[${data.currentRound.alpha.map((value) =>
value.toString()
)}]`,
alphaHex: Buffer.from(data.currentRound.alpha).toString("hex"),
requestSlot: data.currentRound?.requestSlot?.toString() ?? "",
requestTimestamp: anchorBNtoDateTimeString(
data.currentRound.requestTimestamp

763
programs/anchor-vrf-parser/cli.ts Executable file
View File

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

View File

@ -1,40 +1,46 @@
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"),
@ -44,58 +50,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,
@ -105,7 +111,7 @@ export class VrfClient {
lastTimestamp: dec.lastTimestamp,
authority: dec.authority,
vrf: dec.vrf,
})
});
}
toJSON(): VrfClientJSON {
@ -117,7 +123,7 @@ export class VrfClient {
lastTimestamp: this.lastTimestamp.toString(),
authority: this.authority.toString(),
vrf: this.vrf.toString(),
}
};
}
static fromJSON(obj: VrfClientJSON): VrfClient {
@ -129,6 +135,6 @@ export class VrfClient {
lastTimestamp: new BN(obj.lastTimestamp),
authority: new PublicKey(obj.authority),
vrf: new PublicKey(obj.vrf),
})
});
}
}

View File

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

View File

@ -7,6 +7,9 @@
"url": "https://github.com/switchboard-xyz/switchboard-v2",
"directory": "programs/anchor-vrf-parser"
},
"bin": {
"sbv2-vrf-example": "./sbv2-vrf-example.sh"
},
"scripts": {
"build": "echo \"For workspace anchor-vrf-parser, run 'anchor build' from the project root\" && exit 0",
"lint": "eslint --ext .js,.json,.ts 'src/**' --fix"
@ -20,7 +23,8 @@
"@switchboard-xyz/switchboard-v2": "^0.0.120",
"chalk": "^4.1.2",
"child_process": "^1.0.2",
"dotenv": "^16.0.1"
"dotenv": "^16.0.1",
"yargs": "^17.5.1"
},
"devDependencies": {
"@switchboard-xyz/switchboardv2-cli": "^0.2.14",
@ -43,7 +47,7 @@
"prettier-plugin-organize-imports": "^2.3.4",
"shx": "^0.3.4",
"ts-mocha": "^9.0.2",
"ts-node": "^10.4.0",
"typescript": "^4.3.5"
"ts-node": "^10.8.0",
"typescript": "^4.7.3"
}
}

View File

@ -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 "$@"

View File

@ -111,6 +111,9 @@ impl RequestResult<'_> {
state_seeds,
)?;
let mut client_state = ctx.accounts.state.load_mut()?;
client_state.result = 0;
emit!(RequestingRandomness{
vrf_client: ctx.accounts.state.key(),
max_result: max_result,

View File

@ -1,5 +1,5 @@
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 {
SystemProgram,
@ -29,7 +29,7 @@ describe("anchor-vrf-parser test", () => {
// const vrfClientProgram = anchor.workspace
// .AnchorVrfParser as Program<AnchorVrfParser>;
const vrfClientProgram = new Program(
const vrfClientProgram = new anchor.Program(
IDL,
PROGRAM_ID,
provider,
@ -164,6 +164,28 @@ describe("anchor-vrf-parser test", () => {
spl.ASSOCIATED_TOKEN_PROGRAM_ID
);
// watch VrfClientState for a populated result
let ws: number | undefined = undefined;
const waitForResultPromise = new Promise(
(
resolve: (result: anchor.BN) => void,
reject: (reason: string) => void
) => {
ws = vrfClientProgram.provider.connection.onAccountChange(
vrfClientKey,
async (accountInfo: AccountInfo<Buffer>, context: Context) => {
const clientState: VrfClientState = vrfClientAccountCoder.decode(
"VrfClient",
accountInfo.data
);
if (clientState.result.gt(new anchor.BN(0))) {
resolve(clientState.result);
}
}
);
}
);
// Request randomness
console.log(`Sending RequestRandomness instruction`);
const requestTxn = vrfClientProgram.methods.requestResult!({

View File

@ -1,12 +1,18 @@
{
"extends": "../../tsconfig.json",
"ts-node": {
"compilerOptions": {
"module": "commonjs"
}
},
"compilerOptions": {
"types": ["mocha", "chai"],
"types": ["mocha", "chai", "node"],
"typeRoots": ["./node_modules/@types"],
"module": "CommonJS",
"module": "commonjs",
"noEmit": true,
"esModuleInterop": true,
"strict": false,
"strictNullChecks": false,
"paths": {
"@switchboard-xyz/switchboard-v2": ["../../libraries/ts"],
"@switchboard-xyz/sbv2-utils": ["../../libraries/sbv2-utils"],
@ -16,12 +22,8 @@
]
}
},
"include": [
"tests/**/*",
"client/**/*",
"../../../target/types/anchor_vrf_parser",
"../../../target/idl/anchor_vrf_parser"
],
"include": ["tests/**/*", "./cli.ts", "./client/**/*"],
"exclude": ["target", "lib"],
"references": [
{ "path": "../../libraries/ts" },
{ "path": "../../libraries/sbv2-utils" }

View File

@ -4149,6 +4149,48 @@
"@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"
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":
version "1.1.2"
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:
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"
@ -17104,9 +17155,9 @@ yargs@16.2.0, yargs@^16.2.0:
y18n "^5.0.5"
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"
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==
dependencies:
cliui "^7.0.2"