sbv2-solana/examples/functions/04_randomness_callback/request.ts

274 lines
8.3 KiB
TypeScript

import {
type CustomRandomnessRequest,
IDL,
// eslint-disable-next-line
} from "./target/types/custom_randomness_request";
import * as anchor from "@coral-xyz/anchor";
import type { TransactionSignature } from "@solana/web3.js";
import { clusterApiUrl } from "@solana/web3.js";
import { sleep, toUtf8 } from "@switchboard-xyz/common";
import {
FunctionAccount,
FunctionRequestAccount,
loadKeypair,
NativeMint,
SwitchboardProgram,
TransactionObject,
} from "@switchboard-xyz/solana.js";
import dotenv from "dotenv";
import * as fs from "fs";
import path from "path";
dotenv.config();
// The amount of funds to transfer to the new keypair to run this command
const NEW_PAYER_TRANSFER_AMOUNT = anchor.web3.LAMPORTS_PER_SOL / 50;
// The commitment level to use for reading and writing to the chain
const COMMITMENT_LEVEL: anchor.web3.Commitment = "processed";
async function main() {
const { program, provider, switchboard, newPayer } = await loadCli();
// 1. Load our function that we will be creating requests for
console.log(
green(`1. Load our function that we will be creating requests for`)
);
const functionAccount = new FunctionAccount(
switchboard,
process.env.FUNCTION_KEY ?? "7nzAjbw6k8CJYq4ggKXkZ75V2A4jiuvN5uuA1mf3MDtV"
);
const functionState = await functionAccount.loadData();
console.log(`## Function - ${functionAccount.publicKey}`);
console.log(` => queue: ${functionState.attestationQueue}`);
console.log(` => schedule: ${toUtf8(functionState.schedule)}`);
console.log(` => requestsDisabled: ${functionState.requestsDisabled}`);
console.log(
` => container: ${toUtf8(functionState.container)}:${toUtf8(
functionState.version
)}`
);
// 2. Derive the anchor programs PDAs to build the transactions
console.log(
green(`2. Derive the anchor programs PDAs to build the transactions`)
);
const housePubkey = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from("CUSTOMRANDOMNESS")],
program.programId
)[0];
const houseTokenWallet = anchor.utils.token.associatedAddress({
mint: NativeMint.address,
owner: housePubkey,
});
console.log(`House Pubkey: ${housePubkey}`);
console.log(`House Escrow: ${houseTokenWallet}`);
// 3. Create a new user for our program
console.log(green(`3. Create a new user for our program`));
const userPubkey = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from("CUSTOMRANDOMNESS"), newPayer.publicKey.toBytes()],
program.programId
)[0];
const userTokenWallet = anchor.utils.token.associatedAddress({
mint: NativeMint.address,
owner: userPubkey,
});
console.log(`User Pubkey: ${userPubkey}`);
console.log(`User Escrow: ${userTokenWallet}`);
const userInitTxn = await program.methods
.userInit()
.accounts({
house: housePubkey,
houseTokenWallet: houseTokenWallet,
user: userPubkey,
userTokenWallet,
mint: NativeMint.address,
})
.rpc();
await awaitTxnConfirmation(provider.connection, userInitTxn, "user_init");
// 4. Create a new request account and submit a guess for our user
console.log(
green(`4. Create a new request account and submit a guess for our user`)
);
const requestKeypair = anchor.web3.Keypair.generate();
const requestAccount = new FunctionRequestAccount(
switchboard,
requestKeypair.publicKey
);
console.log(`Request Account: ${requestAccount.publicKey}`);
const tx = await program.methods
.userGuess(4, new anchor.BN(10000))
.accounts({
house: housePubkey,
user: userPubkey,
request: requestKeypair.publicKey,
function: functionAccount.publicKey,
requestEscrow: anchor.utils.token.associatedAddress({
mint: NativeMint.address,
owner: requestKeypair.publicKey,
}),
mint: NativeMint.address,
state: switchboard.attestationProgramState.publicKey,
attestationQueue: functionState.attestationQueue,
switchboard: switchboard.attestationProgramId,
userTokenWallet,
houseTokenWallet,
})
.signers([requestKeypair])
.rpc();
awaitTxnConfirmation(provider.connection, tx, "user_guess").catch();
// 5. Await off-chain verifiers to fulfill our request
console.log(green(`5. Await off-chain verifiers to fulfill our request`));
const requestRound = await requestAccount.poll();
const requestState = await requestAccount.loadData();
console.log(`\n## REQUEST - ${requestAccount.publicKey}`);
console.log(` => status: ${requestState.status.kind}`);
console.log(` => requestSlot: ${requestRound.requestSlot.toNumber()}`);
console.log(` => fulfilledSlot: ${requestRound.fulfilledSlot.toNumber()}`);
console.log(` => params: ${toUtf8(requestState.containerParams)}`);
const userState = await program.account.userState.fetch(userPubkey);
console.log(`\n## USER - ${userPubkey}`);
console.log(` => status: ${Object.keys(userState.currentRound.status)[0]}`);
console.log(` => guess: ${userState.currentRound.guess}`);
console.log(` => result: ${userState.currentRound.result}`);
console.log(` => wager: ${userState.currentRound.wager.toNumber()}`);
}
main()
.then()
.catch((err) => {
console.error(err);
throw err;
});
function loadProgramId() {
if (process.env.RANDOMNESS_CALLBACK_PID) {
return new anchor.web3.PublicKey(process.env.RANDOMNESS_CALLBACK_PID);
}
if (fs.existsSync("target/deploy/custom_randomness_request-keypair.json")) {
return loadKeypair("target/deploy/custom_randomness_request-keypair.json")
.publicKey;
}
return new anchor.web3.PublicKey(
"Csx5AU83fPiaSChJUBZg2cW9GcCVVwZ4rwFqDA2pomX2"
);
}
interface ICli {
program: anchor.Program<CustomRandomnessRequest>;
switchboard: SwitchboardProgram;
origPayer: anchor.web3.PublicKey;
provider: anchor.AnchorProvider;
newPayer: {
publicKey: anchor.web3.PublicKey;
signer: anchor.web3.Keypair;
};
}
async function loadCli(): Promise<ICli> {
const provider = new anchor.AnchorProvider(
new anchor.web3.Connection(
process.env.ANCHOR_PROVIDER_URL ?? clusterApiUrl("devnet"),
{ commitment: COMMITMENT_LEVEL }
),
new anchor.Wallet(
loadKeypair(process.env.ANCHOR_WALLET ?? "~/.config/solana/id.json")
),
{ commitment: COMMITMENT_LEVEL }
);
const program: anchor.Program<CustomRandomnessRequest> = new anchor.Program(
IDL,
loadProgramId(),
provider
);
const switchboard = await SwitchboardProgram.fromProvider(provider);
const newPayer = anchor.web3.Keypair.generate();
const transferTx = await switchboard.signAndSend(
new TransactionObject(
provider.publicKey,
[
anchor.web3.SystemProgram.transfer({
fromPubkey: provider.publicKey,
toPubkey: newPayer.publicKey,
lamports: NEW_PAYER_TRANSFER_AMOUNT,
}),
],
[]
)
);
// 0. Setup new payer and transfer some SOL
console.log(green(`0. Setup new payer and transfer some SOL`));
console.log(`New Payer: ${newPayer.publicKey}`);
awaitTxnConfirmation(
provider.connection,
transferTx,
"transfer SOL to new payer"
).catch();
// set provider to new payer
const newSwitchboard = switchboard.newWithPayer(newPayer);
const newProgram = new anchor.Program(
program.idl,
program.programId,
newSwitchboard.provider
);
return {
newPayer: {
publicKey: newPayer.publicKey,
signer: newPayer,
},
origPayer: provider.publicKey,
provider: provider,
program: newProgram,
switchboard: newSwitchboard,
};
}
async function awaitTxnConfirmation(
connection: anchor.web3.Connection,
signature: TransactionSignature,
label?: string,
maxAttempts = 10
) {
console.log(
`\n[TXN]${
label ? ": " + label : ""
}\n => https://explorer.solana.com/tx/${signature}?cluster=custom&customUrl=${
connection.rpcEndpoint.includes("rpcpool")
? clusterApiUrl("devnet")
: connection.rpcEndpoint
}\n`
);
let attempts = maxAttempts;
while (attempts > 0) {
try {
const txnStatus = await connection.getParsedTransaction(signature, {
commitment:
COMMITMENT_LEVEL === "finalized" ? "finalized" : "confirmed",
});
if (txnStatus) {
return;
}
} catch {}
await sleep(1000);
attempts = attempts - 1;
}
throw new Error(`Failed to await txn confirmation`);
}
const green = (message: string) => `\x1b[32m${message}\x1b[0m`;