diff --git a/examples/clients/feed-walkthrough/README.md b/examples/clients/feed-walkthrough/README.md index e69de29..f09213d 100644 --- a/examples/clients/feed-walkthrough/README.md +++ b/examples/clients/feed-walkthrough/README.md @@ -0,0 +1,59 @@ +# Switchboard-V2 Feed Walkthrough + +This example will walk you through + +- creating a personal oracle queue with a crank +- add a SOL/USD data feed onto the crank +- spin up a docker environment to run your own oracle +- fulfill your update request on-chain + +## Usage + +```bash +ts-node src/main [PAYER_KEYPAIR_PATH] +``` + +where **PAYER_KEYPAIR_PATH** is the location of your Solana keypair, defaulting +to `~/.config/solana/id.json` if not provided + +When prompted, run the docker compose script in a new shell to start your local +oracle then confirm the prompt to turn the crank and request an update on-chain. +The oracle is ready to fulfill updates when it sees the following logs: + +```bash +{"timestamp":"2022-09-23T19:24:11.874Z","level":"info","message":"Loaded 1000 nonce accounts"} +{"timestamp":"2022-09-23T19:24:11.885Z","level":"info","message":"started health check handler"} +{"timestamp":"2022-09-23T19:24:11.886Z","level":"info","message":"Heartbeat routine started with an interval of 15 seconds."} +{"timestamp":"2022-09-23T19:24:11.887Z","level":"info","message":"Watching event: AggregatorOpenRoundEvent ..."} +{"timestamp":"2022-09-23T19:24:11.893Z","level":"info","message":"Watching event: VrfRequestRandomnessEvent ..."} +{"timestamp":"2022-09-23T19:24:11.894Z","level":"info","message":"Using default performance monitoring"} +``` + +Example Output: + +```bash +$ ts-node src/main +######## Switchboard Setup ######## +Program State BYM81n8HvTJuqZU1PmTVcwZ9G8uoji7FKM6EaPkwphPt +Oracle Queue AVbBmSeKJppRcphaPY1fPbFQW48Eg851G4XfqyTPMZNF +Crank 6fNsrJhaB2MPpwpcxW7AL5zyoiq7Gyz2mM6q3aVz7xxh +Oracle CmTr9FSeuhMPBLEPa3o2M71RwRnBz6LMcsfzHaW721Ak + Permission 2pC5ESkVKGx4yowGrVB21f6eXaaMRQY5cBazfqn1bAQs +Aggregator (SOL/USD) FLixyyJVzfCF4PmDG2VcFm1LUBu1aBTXox3oCWNVU88m + Permission EVerqanwRrHRvtPXDRdFHPc7VnXuyEPRr9XA5udpFA4E + Lease FC6SfAEuoB1SoZAnCqkMyyYnSfLSy8KfPUFH9SASBUzU + Job (FTX) BbNzfRQjTYiCZVfvK1qpQkkon3kP2tbvaCHfzsyjeBU3 +✔ Switchboard setup complete +######## Start the Oracle ######## +Run the following command in a new shell + + ORACLE_KEY=CmTr9FSeuhMPBLEPa3o2M71RwRnBz6LMcsfzHaW721Ak PAYER_KEYPAIR=/Users/switchboard/.config/solana/id.json RPC_URL=https://api.devnet.solana.com docker-compose up + +Select 'Y' when the docker container displays Starting listener... [y/n]: y + +✔ Crank turned +######## Aggregator Result ######## +Result: 30.91 + +✔ Aggregator succesfully updated! +``` diff --git a/examples/clients/feed-walkthrough/docker-compose.yml b/examples/clients/feed-walkthrough/docker-compose.yml new file mode 100644 index 0000000..d7da480 --- /dev/null +++ b/examples/clients/feed-walkthrough/docker-compose.yml @@ -0,0 +1,17 @@ +version: "3.3" +services: + oracle: + image: "switchboardlabs/node:dev-v2-09-13-22" + network_mode: host + restart: always + secrets: + - PAYER_SECRETS + environment: + - LIVE=1 + - CLUSTER=devnet + - HEARTBEAT_INTERVAL=15 # Seconds + - ORACLE_KEY=${ORACLE_KEY} + - RPC_URL=${RPC_URL} +secrets: + PAYER_SECRETS: + file: ${PAYER_KEYPAIR} diff --git a/examples/clients/feed-walkthrough/src/main.ts b/examples/clients/feed-walkthrough/src/main.ts index cf28a6f..ac49d66 100644 --- a/examples/clients/feed-walkthrough/src/main.ts +++ b/examples/clients/feed-walkthrough/src/main.ts @@ -1,5 +1,6 @@ import * as anchor from "@project-serum/anchor"; import * as spl from "@solana/spl-token-v2"; +import type { PublicKey } from "@solana/web3.js"; import { clusterApiUrl, Connection, Keypair } from "@solana/web3.js"; import { IOracleJob, OracleJob } from "@switchboard-xyz/common"; import { @@ -17,23 +18,54 @@ import { } from "@switchboard-xyz/switchboard-v2"; import chalk from "chalk"; import dotenv from "dotenv"; +import fs from "fs"; import os from "os"; import path from "path"; import readlineSync from "readline-sync"; -import { getKeypair, sleep, toAccountString } from "./utils"; dotenv.config(); +export const toAccountString = ( + label: string, + publicKey: PublicKey | string | undefined +): string => { + if (typeof publicKey === "string") { + return `${chalk.blue(label.padEnd(24, " "))} ${chalk.yellow(publicKey)}`; + } + if (!publicKey) { + return ""; + } + return `${chalk.blue(label.padEnd(24, " "))} ${chalk.yellow( + publicKey.toString() + )}`; +}; + +export const sleep = (ms: number): Promise => + new Promise((s) => setTimeout(s, ms)); + +export const getKeypair = (keypairPath: string): Keypair => { + if (!fs.existsSync(keypairPath)) { + throw new Error( + `failed to load authority keypair from ${keypairPath}, try providing a path to your keypair with the script 'ts-node src/main KEYPAIR_PATH'` + ); + } + const keypairString = fs.readFileSync(keypairPath, "utf8"); + const keypairBuffer = new Uint8Array(JSON.parse(keypairString)); + const walletKeypair = Keypair.fromSecretKey(keypairBuffer); + return walletKeypair; +}; + async function main() { // get payer keypair - let authority: Keypair; - if (process.env.PAYER_KEYPAIR) { - authority = getKeypair(process.env.PAYER_KEYPAIR); + let payerKeypairPath: string; + if (process.argv.length > 2 && process.argv[2]) { + payerKeypairPath = process.argv[2]; + } else if (process.env.PAYER_KEYPAIR) { + payerKeypairPath = process.env.PAYER_KEYPAIR; } else { - // attempt to load default keypair - const homeDir = os.homedir(); - authority = getKeypair(path.join(homeDir, ".config/solana/id.json")); + payerKeypairPath = path.join(os.homedir(), ".config/solana/id.json"); } + const authority = getKeypair(payerKeypairPath); // get cluster let cluster: "mainnet-beta" | "devnet" | "localnet"; @@ -195,7 +227,7 @@ async function main() { console.log(chalk.yellow("######## Start the Oracle ########")); console.log(chalk.blue("Run the following command in a new shell\r\n")); console.log( - ` ORACLE_KEY=${oracleAccount.publicKey} PAYER_KEYPAIR=${authority} RPC_URL=${rpcUrl} docker-compose up\r\n` + ` ORACLE_KEY=${oracleAccount.publicKey} PAYER_KEYPAIR=${payerKeypairPath} RPC_URL=${rpcUrl} docker-compose up\r\n` ); if ( !readlineSync.keyInYN( @@ -217,7 +249,7 @@ async function main() { const crank = await crankAccount.loadData(); const queue = await queueAccount.loadData(); - const crankTurnSignature = await crankAccount.popTxn({ + const crankTurnSignature = await crankAccount.pop({ payoutWallet: tokenAccount, queuePubkey: queueAccount.publicKey, queueAuthority: queue.authority, @@ -248,7 +280,15 @@ async function main() { console.log(chalk.yellow("######## Aggregator Result ########")); await sleep(5000); try { - const result = await aggregatorAccount.getLatestValue(); + let result = await aggregatorAccount.getLatestValue(); + if (result === null) { + // wait a bit longer + await sleep(2500); + result = await aggregatorAccount.getLatestValue(); + if (result === null) { + throw new Error(`Aggregator currently holds no value.`); + } + } console.log(`${chalk.blue("Result:")} ${chalk.green(result)}\r\n`); console.log(chalk.green("\u2714 Aggregator succesfully updated!")); } catch (error: any) { diff --git a/examples/clients/feed-walkthrough/src/utils.ts b/examples/clients/feed-walkthrough/src/utils.ts deleted file mode 100644 index e0ad1ee..0000000 --- a/examples/clients/feed-walkthrough/src/utils.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { PublicKey } from "@solana/web3.js"; -import { Keypair } from "@solana/web3.js"; -import chalk from "chalk"; -import fs from "fs"; - -export const toAccountString = ( - label: string, - publicKey: PublicKey | string | undefined -): string => { - if (typeof publicKey === "string") { - return `${chalk.blue(label.padEnd(24, " "))} ${chalk.yellow(publicKey)}`; - } - if (!publicKey) return ""; - return `${chalk.blue(label.padEnd(24, " "))} ${chalk.yellow( - publicKey.toString() - )}`; -}; - -export const sleep = (ms: number): Promise => - new Promise((s) => setTimeout(s, ms)); - -export const getKeypair = (path: string): Keypair => { - if (!fs.existsSync(path)) { - throw new Error(`failed to load authority keypair from ${path}`); - } - const keypairString = fs.readFileSync(path, "utf8"); - const keypairBuffer = new Uint8Array(JSON.parse(keypairString)); - const walletKeypair = Keypair.fromSecretKey(keypairBuffer); - return walletKeypair; -};