import type { CreateQueueBufferRelayerParams, CreateQueueCrankParams, CreateQueueFeedParams, CreateQueueOracleParams, CreateQueueVrfParams, QueueInitParams, } from "./accounts/index.js"; import { AggregatorAccount, BufferRelayerAccount, CrankAccount, JobAccount, LeaseAccount, OracleAccount, PermissionAccount, ProgramStateAccount, QueueAccount, VrfAccount, } from "./accounts/index.js"; import { AggregatorAccountData, BufferRelayerAccountData, CrankAccountData, JobAccountData, LeaseAccountData, OracleAccountData, OracleQueueAccountData, PermissionAccountData, SbState, VrfAccountData, } from "./generated/index.js"; import type { SendTransactionOptions, SwitchboardProgram, } from "./SwitchboardProgram.js"; import { TransactionObject } from "./TransactionObject.js"; import type { AggregatorDefinition, BufferRelayerDefinition, CrankDefinition, LoadedAccountDefinition, LoadedAggregatorDefinition, LoadedBufferRelayerDefinition, LoadedCrankDefinition, LoadedJobDefinition, LoadedOracleDefinition, LoadedProgramStateDefinition, LoadedQueueDefinition, LoadedVrfDefinition, OracleDefinition, ProgramStateDefinition, QueueDefinition, VrfDefinition, } from "./types.js"; import * as anchor from "@coral-xyz/anchor"; import type { AccountInfo, TransactionSignature } from "@solana/web3.js"; import { Keypair, PublicKey } from "@solana/web3.js"; import { OracleJob } from "@switchboard-xyz/common"; import fs from "fs"; import path from "path"; export const isKeypairString = (value: string): boolean => /^\[(\s)?[0-9]+((\s)?,(\s)?[0-9]+){31,}\]/.test(value); /** Parameters to create a new queue with a set of associated accounts */ export type NetworkInitParams = Omit & { authority?: Keypair; } & { /** The {@linkcode CrankAccount}s to add to the queue */ cranks?: Array; /** The {@linkcode OracleAccount}s to add to the queue */ oracles?: Array; /** The {@linkcode AggregatorAccount}s to add to the queue */ aggregators?: Array< CreateQueueFeedParams & { /** The index of the crank to add the feed to */ crankIndex?: number; } >; /** The {@linkcode VrfAccount}s to add to the queue */ vrfs?: Array; /** The {@linkcode BufferRelayerAccount}s to add to the queue */ bufferRelayers?: Array; }; /** The queue and associated accounts for the newly created Switchboard network. */ export interface ISwitchboardNetwork { /** The {@linkcode ProgramStateAccount} and PDA bump. */ programState: ProgramStateDefinition; /** The {@linkcode QueueAccount} for the newly created Switchboard network. */ queue: QueueDefinition; /** The {@linkcode CrankAccount}s for the newly created Switchboard network. */ cranks: Array; oracles: Array; aggregators: Array; vrfs: Array; bufferRelayers: Array; } /** Wrapper to quickly create a Switchboard network including: * - an oracle queue * - a set of cranks * - a set of oracles * - a set of aggregators with job configs * - a set of vrf accounts * - a set of buffer relayers * */ export class SwitchboardNetwork implements ISwitchboardNetwork { programState: ProgramStateDefinition; queue: QueueDefinition; cranks: Array; oracles: Array; aggregators: Array; vrfs: Array; bufferRelayers: Array; crankMap: Map; oracleMap: Map; aggregatorMap: Map; vrfMap: Map; bufferRelayerMap: Map; constructor( // readonly program: SwitchboardProgram, fields: ISwitchboardNetwork ) { this.programState = fields.programState; this.queue = fields.queue; this.cranks = fields.cranks; this.crankMap = this.cranks.reduce((map, crank) => { map.set(crank.account.publicKey.toBase58(), crank); return map; }, new Map()); this.oracles = fields.oracles; this.oracleMap = this.oracles.reduce((map, oracle) => { map.set(oracle.account.publicKey.toBase58(), oracle); return map; }, new Map()); this.aggregators = fields.aggregators; this.aggregatorMap = this.aggregators.reduce((map, aggregator) => { map.set(aggregator.account.publicKey.toBase58(), aggregator); return map; }, new Map()); this.vrfs = fields.vrfs; this.vrfMap = this.vrfs.reduce((map, vrf) => { map.set(vrf.account.publicKey.toBase58(), vrf); return map; }, new Map()); this.bufferRelayers = fields.bufferRelayers; this.bufferRelayerMap = this.bufferRelayers.reduce((map, bufferRelayer) => { map.set(bufferRelayer.account.publicKey.toBase58(), bufferRelayer); return map; }, new Map()); } get program(): SwitchboardProgram { return this.queue.account.program; } getCrank(crankPubkey: PublicKey | string): CrankDefinition | undefined { return this.crankMap.get( typeof crankPubkey === "string" ? crankPubkey : crankPubkey.toBase58() ); } getOracle(oraclePubkey: PublicKey | string): OracleDefinition | undefined { return this.oracleMap.get( typeof oraclePubkey === "string" ? oraclePubkey : oraclePubkey.toBase58() ); } getAggregator( aggregatorPubkey: PublicKey | string ): AggregatorDefinition | undefined { return this.aggregatorMap.get( typeof aggregatorPubkey === "string" ? aggregatorPubkey : aggregatorPubkey.toBase58() ); } getVrf(vrfPubkey: PublicKey | string): VrfDefinition | undefined { return this.vrfMap.get( typeof vrfPubkey === "string" ? vrfPubkey : vrfPubkey.toBase58() ); } getBufferRelayer( bufferRelayerPubkey: PublicKey | string ): BufferRelayerDefinition | undefined { return this.bufferRelayerMap.get( typeof bufferRelayerPubkey === "string" ? bufferRelayerPubkey : bufferRelayerPubkey.toBase58() ); } public static find( program: SwitchboardProgram, networkName = "default", switchboardDir = path.join(process.cwd(), ".switchboard") ): SwitchboardNetwork { if ( !fs.existsSync(switchboardDir) || !fs.statSync(switchboardDir).isDirectory ) { throw new Error( `Failed to find switchboard directory: ${switchboardDir}` ); } const networkDir = path.join(switchboardDir, "networks"); if (!fs.existsSync(networkDir) || !fs.statSync(networkDir).isDirectory) { throw new Error( `Failed to find switchboard network directory: ${networkDir}` ); } const networkFile = path.join(networkDir, `${networkName}.json`); if (!fs.existsSync(networkFile) || !fs.statSync(networkFile).isFile) { throw new Error( `Failed to find switchboard network ${networkName}: ${networkFile}` ); } const obj: Record = JSON.parse( fs.readFileSync(networkFile, "utf-8") ); return SwitchboardNetwork.from(program, obj); } /** * * Creates a transaction object to initialize a SwitchboardNetwork. * * Basic usage example: * * ```ts * const [accounts, signatures] = await SwitchboardNetwork.createInstructions( program, progam.walletPubkey, { name: 'Queue-1', reward: 0, minStake: 1, unpermissionedFeeds: false, unpermissionedVrf: false, authority: origQueueAuthority, oracles: [ { name: 'Oracle-1', enable: true, stakeAmount: 1, }, ], cranks: [{ name: 'Crank-1', maxRows: 100 }], aggregators: [{ name: "Aggregator-1", crankIndex: 0, enable: true, fundAmount: 2.5, "minUpdateDelaySeconds": 15, "batchSize": 3, "minRequiredOracleResults": 2, "jobs": [ { "weight": 1, "name": "Job #1", "tasks": [ { "valueTask": { "value": "1" } } ] } ] }] } ); * ``` */ public static async createInstructions( program: SwitchboardProgram, payer: PublicKey, params: NetworkInitParams ): Promise<[Array, SwitchboardNetwork]> { const txns: Array = []; // const userTokenAddress = this.mint.getAssociatedAddress(payer); // calculate the total amount of funds we'll need to wrap let neededWrappedFunds = 0; (params.oracles ?? []).forEach((o) => { if (o.stakeAmount) { neededWrappedFunds = neededWrappedFunds + o.stakeAmount; } }); (params.aggregators ?? []).forEach((a) => { if (a.fundAmount) { neededWrappedFunds = neededWrappedFunds + a.fundAmount; } }); const [userTokenAddress, wrapFundsTxn] = await program.mint.getOrCreateWrappedUserInstructions(payer, { fundUpTo: neededWrappedFunds, }); const queueAuthorityPubkey = params.authority ? params.authority.publicKey : payer; // get or create the program state const [programState, stateBump, programInit] = await ProgramStateAccount.getOrCreateInstructions(program, payer); if (programInit) { txns.push(programInit); } // create a new queue const [queueAccount, queueInit] = await QueueAccount.createInstructions( program, payer, { ...params, authority: queueAuthorityPubkey, } ); txns.push(queueInit); // create the cranks const cranks: Array<[TransactionObject, CrankDefinition]> = await Promise.all( (params.cranks ?? []).map(async (crankInitParams) => { const [crankAccount, crankInit] = await queueAccount.createCrankInstructions(payer, crankInitParams); return [ crankInit, { account: crankAccount, dataBuffer: crankAccount.dataBuffer!.publicKey, }, ]; }) ); txns.push(...cranks.map((crank) => crank[0])); // create the oracles const oracles: Array<[Array, OracleDefinition]> = await Promise.all( (params.oracles ?? []).map(async (oracleInitParams) => { const [oracleAccount, oracleInit] = await queueAccount.createOracleInstructions(payer, { ...oracleInitParams, queueAuthority: params.authority, queueAuthorityPubkey: queueAuthorityPubkey, funderTokenWallet: userTokenAddress, disableWrap: true, enable: true, }); const [oraclePermissionAccount, oraclePermissionBump] = PermissionAccount.fromSeed( program, queueAuthorityPubkey, queueAccount.publicKey, oracleAccount.publicKey ); return [ oracleInit, { account: oracleAccount, permission: { account: oraclePermissionAccount, bump: oraclePermissionBump, }, }, ]; }) ); txns.push(...oracles.map((oracle) => oracle[0]).flat()); // create the feeds const aggregators: Array<[Array, AggregatorDefinition]> = await Promise.all( (params.aggregators ?? []).map(async (feedInitParams) => { const crank = feedInitParams.crankPubkey ? new CrankAccount(program, feedInitParams.crankPubkey) : feedInitParams.crankIndex !== undefined && feedInitParams.crankIndex >= 0 && cranks.length > feedInitParams.crankIndex ? cranks[feedInitParams.crankIndex][1].account : undefined; const crankPubkey = crank?.publicKey ?? undefined; const crankDataBuffer = feedInitParams.crankDataBuffer ?? crank?.dataBuffer?.publicKey ?? (await crank?.loadData())?.dataBuffer; const [aggregatorAccount, aggregatorInit] = await queueAccount.createFeedInstructions(payer, { ...feedInitParams, queueAuthority: params.authority, queueAuthorityPubkey, funderTokenWallet: userTokenAddress, disableWrap: true, crankPubkey: crankPubkey, crankDataBuffer: crankDataBuffer, }); const [aggregatorPermissionAccount, aggregatorPermissionBump] = PermissionAccount.fromSeed( program, queueAuthorityPubkey, queueAccount.publicKey, aggregatorAccount.publicKey ); const [aggregatorLeaseAccount, aggregatorLeaseBump] = LeaseAccount.fromSeed( program, queueAccount.publicKey, aggregatorAccount.publicKey ); return [ aggregatorInit, { account: aggregatorAccount, permission: { account: aggregatorPermissionAccount, bump: aggregatorPermissionBump, }, lease: { account: aggregatorLeaseAccount, bump: aggregatorLeaseBump, }, }, ]; }) ); txns.push(...aggregators.map((aggregator) => aggregator[0]).flat()); // create the vrfs const vrfs: Array<[TransactionObject, VrfDefinition]> = await Promise.all( (params.vrfs ?? []).map(async (vrfInitParams) => { const [vrfAccount, vrfInit] = await queueAccount.createVrfInstructions( payer, { ...vrfInitParams, queueAuthority: params.authority, queueAuthorityPubkey, } ); const [vrfPermissionAccount, vrfPermissionBump] = PermissionAccount.fromSeed( program, queueAuthorityPubkey, queueAccount.publicKey, vrfAccount.publicKey ); return [ vrfInit, { account: vrfAccount, permission: { account: vrfPermissionAccount, bump: vrfPermissionBump, }, }, ]; }) ); txns.push(...vrfs.map((vrf) => vrf[0])); // create the buffer relayers const bufferRelayers: Array<[TransactionObject, BufferRelayerDefinition]> = await Promise.all( (params.bufferRelayers ?? []).map(async (bufferRelayerInitParams) => { const [bufferRelayerAccount, bufferRelayerInit] = await queueAccount.createBufferRelayerInstructions(payer, { ...bufferRelayerInitParams, queueAuthority: params.authority, queueAuthorityPubkey, }); const [bufferRelayerPermissionAccount, bufferRelayerPermissionBump] = PermissionAccount.fromSeed( program, queueAuthorityPubkey, queueAccount.publicKey, bufferRelayerAccount.publicKey ); return [ bufferRelayerInit, { account: bufferRelayerAccount, permission: { account: bufferRelayerPermissionAccount, bump: bufferRelayerPermissionBump, }, }, ]; }) ); txns.push(...bufferRelayers.map((bufferRelayer) => bufferRelayer[0])); const accounts: ISwitchboardNetwork = { programState: { account: programState, bump: stateBump, }, queue: { account: queueAccount, }, cranks: cranks.map((c) => c[1]), oracles: oracles.map((o) => o[1]), aggregators: aggregators.map((a) => a[1]), vrfs: vrfs.map((v) => v[1]), bufferRelayers: bufferRelayers.map((b) => b[1]), }; return [ TransactionObject.pack(wrapFundsTxn ? [wrapFundsTxn, ...txns] : txns), new SwitchboardNetwork(accounts), ]; } /** * * Create a new SwitchboardNetwork. * * Basic usage example: * * ```ts * const [accounts, signatures] = await SwitchboardNetwork.create( program, { name: 'Queue-1', reward: 0, minStake: 1, unpermissionedFeeds: false, unpermissionedVrf: false, authority: origQueueAuthority, oracles: [ { name: 'Oracle-1', enable: true, stakeAmount: 1, }, ], cranks: [{ name: 'Crank-1', maxRows: 100 }], aggregators: [{ name: "Aggregator-1", crankIndex: 0, enable: true, fundAmount: 2.5, "minUpdateDelaySeconds": 15, "batchSize": 3, "minRequiredOracleResults": 2, "jobs": [ { "weight": 1, "name": "Job #1", "tasks": [ { "valueTask": { "value": "1" } } ] } ] }] } ); * ``` */ public static async create( program: SwitchboardProgram, params: NetworkInitParams, opts?: SendTransactionOptions ): Promise<[SwitchboardNetwork, Array]> { const [networkInit, accounts] = await SwitchboardNetwork.createInstructions( program, program.walletPubkey, params ); const txnSignatures = await program.signAndSendAll(networkInit, { skipPreflight: true, ...opts, }); return [accounts, txnSignatures]; } /** * Load the account states for a {@linkcode SwitchboardNetwork}. * * @returns LoadedSwitchboardNetwork */ async load(): Promise { const program = this.queue.account.program; // load the accounts const publicKeys: Array = [ this.programState.account.publicKey, this.queue.account.publicKey, // cranks ...this.cranks.map((c) => c.account.publicKey), // oracles ...this.oracles .map((o) => [o.account.publicKey, o.permission.account.publicKey]) .flat(), // aggregators ...this.aggregators .map((a) => [ a.account.publicKey, a.permission.account.publicKey, a.lease.account.publicKey, ]) .flat(), // vrfs ...this.vrfs .map((v) => [v.account.publicKey, v.permission.account.publicKey]) .flat(), // buffer relayers ...this.bufferRelayers .map((b) => [b.account.publicKey, b.permission.account.publicKey]) .flat(), ]; const accountInfos: Map< string, AccountInfo > = await anchor.utils.rpc .getMultipleAccounts(program.connection, publicKeys) .then( ( values: ({ publicKey: anchor.web3.PublicKey; account: anchor.web3.AccountInfo; } | null)[] ) => { return values.reduce((map, account) => { if (account && account.account.data) { map.set(account.publicKey.toBase58(), account.account); } return map; }, new Map>()); } ); // build tree const programState: LoadedProgramStateDefinition = { ...this.programState, state: SbState.decode( accountInfos.get(this.programState.account.publicKey.toBase58()) ?.data ?? Buffer.from("") ), }; const queue: LoadedQueueDefinition = { ...this.queue, state: OracleQueueAccountData.decode( accountInfos.get(this.queue.account.publicKey.toBase58())?.data ?? Buffer.from("") ), }; const cranks: Array = this.cranks.map((crank) => { return { ...crank, state: CrankAccountData.decode( accountInfos.get(crank.account.publicKey.toBase58())?.data ?? Buffer.from("") ), }; }); const oracles: Array = this.oracles.map( (oracle) => { return { ...oracle, state: OracleAccountData.decode( accountInfos.get(oracle.account.publicKey.toBase58())?.data ?? Buffer.from("") ), permission: { ...oracle.permission, state: PermissionAccountData.decode( accountInfos.get(oracle.permission.account.publicKey.toBase58()) ?.data ?? Buffer.from("") ), }, }; } ); const aggregators: Array = this.aggregators.map( (aggregator) => { return { ...aggregator, state: AggregatorAccountData.decode( accountInfos.get(aggregator.account.publicKey.toBase58())?.data ?? Buffer.from("") ), permission: { ...aggregator.permission, state: PermissionAccountData.decode( accountInfos.get( aggregator.permission.account.publicKey.toBase58() )?.data ?? Buffer.from("") ), }, lease: { ...aggregator.lease, state: LeaseAccountData.decode( accountInfos.get(aggregator.lease.account.publicKey.toBase58()) ?.data ?? Buffer.from("") ), }, }; } ); const vrfs: Array = this.vrfs.map((vrf) => { return { ...vrf, state: VrfAccountData.decode( accountInfos.get(vrf.account.publicKey.toBase58())?.data ?? Buffer.from("") ), permission: { ...vrf.permission, state: PermissionAccountData.decode( accountInfos.get(vrf.permission.account.publicKey.toBase58()) ?.data ?? Buffer.from("") ), }, }; }); const bufferRelayers: Array = this.bufferRelayers.map((bufferRelayer) => { return { ...bufferRelayer, state: BufferRelayerAccountData.decode( accountInfos.get(bufferRelayer.account.publicKey.toBase58()) ?.data ?? Buffer.from("") ), permission: { ...bufferRelayer.permission, state: PermissionAccountData.decode( accountInfos.get( bufferRelayer.permission.account.publicKey.toBase58() )?.data ?? Buffer.from("") ), }, }; }); const jobPublicKeys: Array = []; aggregators.forEach((aggregator) => { const jobs = aggregator.state.jobPubkeysData.slice( 0, aggregator.state.jobPubkeysSize ); jobPublicKeys.push(...jobs); }); bufferRelayers.forEach((bufferRelayer) => { jobPublicKeys.push(bufferRelayer.state.jobPubkey); }); const jobs: Array = ( await anchor.utils.rpc .getMultipleAccounts(program.connection, jobPublicKeys) .then( ( values: ({ publicKey: anchor.web3.PublicKey; account: anchor.web3.AccountInfo; } | null)[] ) => { return values.map((account) => { if (account === null) { return undefined; } const jobAccount = new JobAccount(program, account.publicKey); const state = JobAccountData.decode(account.account.data); let job: OracleJob; try { job = OracleJob.decodeDelimited(state.data); } catch { job = OracleJob.fromObject({ tasks: [] }); } const loadedJob: LoadedJobDefinition = { account: jobAccount, state: state, job, }; return loadedJob; }); } ) ).filter(Boolean) as Array< LoadedAccountDefinition & { job: OracleJob } >; return new LoadedSwitchboardNetwork({ programState, queue, cranks, oracles, aggregators, vrfs, bufferRelayers, jobs, }); } /** * Load a SwitchboardNetwork from an outputted JSON file * * @param program * @param obj * @returns */ static from( program: SwitchboardProgram, obj: Record ): SwitchboardNetwork { const programState: ProgramStateDefinition = { account: new ProgramStateAccount(program, program.programState.publicKey), bump: program.programState.bump, }; if (!("queue" in obj) || typeof obj.queue !== "object") { throw new Error(`SwitchboardNetwork requires a queue object`); } let queueAccount: QueueAccount; if ("publicKey" in obj.queue) { queueAccount = new QueueAccount(program, obj.queue.publicKey); } else if ( "keypair" in obj.queue && typeof obj.queue.keypair === "string" && isKeypairString(obj.queue.keypair) ) { const queueKeypair = Keypair.fromSecretKey( new Uint8Array(JSON.parse(obj.queue.keypair)) ); queueAccount = new QueueAccount(program, queueKeypair.publicKey); } else { throw new Error(`Failed to load queue`); } const queue: QueueDefinition = { account: queueAccount, }; let queueAuthority: PublicKey; if ("authority" in obj.queue && typeof obj.queue.authority === "string") { queueAuthority = new PublicKey(obj.queue.authority); } else if ( "queueAuthorityKeypair" in obj.queue && typeof obj.queue.queueAuthorityKeypair === "string" && isKeypairString(obj.queue.queueAuthorityKeypair) ) { const queueAuthorityKeypair = Keypair.fromSecretKey( new Uint8Array(JSON.parse(obj.queue.queueAuthorityKeypair)) ); queueAuthority = queueAuthorityKeypair.publicKey; } else { throw new Error(`Failed to load queue authority`); } const cranks: Array = []; if ("cranks" in obj && Array.isArray(obj.cranks)) { for (const crank of obj.cranks ?? []) { if ("publicKey" in crank) { const account = new CrankAccount(program, crank.publicKey); cranks.push({ account, }); } else if ( "keypair" in crank && typeof crank.keypair === "string" && isKeypairString(crank.keypair) ) { const keypair = Keypair.fromSecretKey( new Uint8Array(JSON.parse(crank.keypair)) ); const account = new CrankAccount(program, keypair.publicKey); cranks.push({ account, }); } } } const oracles: Array = []; if ("oracles" in obj && Array.isArray(obj.oracles)) { for (const oracle of obj.oracles ?? []) { let account: OracleAccount | undefined = undefined; if ("publicKey" in oracle) { account = new OracleAccount(program, oracle.publicKey); } else if ( "stakingWalletKeypair" in oracle && typeof oracle.stakingWalletKeypair === "string" && isKeypairString(oracle.stakingWalletKeypair) ) { const keypair = Keypair.fromSecretKey( new Uint8Array(JSON.parse(oracle.stakingWalletKeypair)) ); [account] = OracleAccount.fromSeed( program, queueAccount.publicKey, keypair.publicKey ); } if (account) { const [permissionAccount, permissionBump] = PermissionAccount.fromSeed( program, queueAuthority, queueAccount.publicKey, account.publicKey ); oracles.push({ account, permission: { account: permissionAccount, bump: permissionBump, }, }); } } } const aggregators: Array = []; if ("aggregators" in obj && Array.isArray(obj.aggregators)) { for (const aggregator of obj.aggregators ?? []) { let account: AggregatorAccount | undefined = undefined; if ("publicKey" in aggregator) { account = new AggregatorAccount(program, aggregator.publicKey); } else if ( "keypair" in aggregator && typeof aggregator.keypair === "string" && isKeypairString(aggregator.keypair) ) { const keypair = Keypair.fromSecretKey( new Uint8Array(JSON.parse(aggregator.keypair)) ); account = new AggregatorAccount(program, keypair.publicKey); } if (account) { const [permissionAccount, permissionBump] = PermissionAccount.fromSeed( program, queueAuthority, queueAccount.publicKey, account.publicKey ); const [leaseAccount, leaseBump] = LeaseAccount.fromSeed( program, queueAccount.publicKey, account.publicKey ); aggregators.push({ account, permission: { account: permissionAccount, bump: permissionBump, }, lease: { account: leaseAccount, bump: leaseBump, }, }); } } } const vrfs: Array = []; if ("vrfs" in obj && Array.isArray(obj.vrfs)) { for (const vrf of obj.vrfs ?? []) { let account: VrfAccount | undefined = undefined; if ("publicKey" in vrf) { account = new VrfAccount(program, vrf.publicKey); } else if ( "keypair" in vrf && typeof vrf.keypair === "string" && isKeypairString(vrf.keypair) ) { const keypair = Keypair.fromSecretKey( new Uint8Array(JSON.parse(vrf.keypair)) ); account = new VrfAccount(program, keypair.publicKey); } if (account) { const [permissionAccount, permissionBump] = PermissionAccount.fromSeed( program, queueAuthority, queueAccount.publicKey, account.publicKey ); vrfs.push({ account, permission: { account: permissionAccount, bump: permissionBump, }, }); } } } const bufferRelayers: Array = []; if ("bufferRelayers" in obj && Array.isArray(obj.bufferRelayers)) { for (const bufferRelayer of obj.bufferRelayers ?? []) { let account: BufferRelayerAccount | undefined = undefined; if ("publicKey" in bufferRelayer) { account = new BufferRelayerAccount(program, bufferRelayer.publicKey); } else if ( "keypair" in bufferRelayer && typeof bufferRelayer.keypair === "string" && isKeypairString(bufferRelayer.keypair) ) { const keypair = Keypair.fromSecretKey( new Uint8Array(JSON.parse(bufferRelayer.keypair)) ); account = new BufferRelayerAccount(program, keypair.publicKey); } if (account) { const [permissionAccount, permissionBump] = PermissionAccount.fromSeed( program, queueAuthority, queueAccount.publicKey, account.publicKey ); bufferRelayers.push({ account, permission: { account: permissionAccount, bump: permissionBump, }, }); } } } return new SwitchboardNetwork({ programState, queue, oracles, cranks, aggregators, vrfs, bufferRelayers, }); } /** * Load the associated accounts and states for a given {@linkcode QueueAccount}. * * Basic usage example: * * ```ts * const network = await SwitchboardNetwork.fromQueue(queueAccount); * fs.writeFileSync("MyQueue.json", JSON.stringify(network.toJSON(), undefined, 2)); * ``` */ static async fromQueue( queueAccount: QueueAccount ): Promise { const program = queueAccount.program; const accounts = await program.getProgramAccounts(); const programStateAccounts = Array.from(accounts.programState); if (programStateAccounts.length < 1) { throw new Error(`Failed to find programState account`); } const [programStateAccount, programStateBump] = ProgramStateAccount.fromSeed(program); const programStateAccountDefinition: LoadedProgramStateDefinition = { account: programStateAccount, bump: programStateBump, state: programStateAccounts[0][1], }; const queue = accounts.queues.get(queueAccount.publicKey.toBase58()); if (!queue) { throw new Error(`Failed to find queue in program accounts`); } const queueDefinition: LoadedQueueDefinition = { account: queueAccount, state: queue, }; const jobPublicKeys: Array = []; // filter cranks const cranks: Array = Array.from(accounts.cranks) .filter(([crankKey, crank]) => crank.queuePubkey.equals(queueAccount.publicKey) ) .map(([crankKey, crank]) => { return { account: new CrankAccount(program, crankKey), state: crank, dataBuffer: crank.dataBuffer, }; }); // filter oracles const oracles: Array = Array.from(accounts.oracles) .filter(([oracleKey, oracle]) => oracle.queuePubkey.equals(queueAccount.publicKey) ) .map(([oracleKey, oracle]): LoadedOracleDefinition | undefined => { const oracleAccount = new OracleAccount(program, oracleKey); const [permissionAccount, permissionBump] = PermissionAccount.fromSeed( program, queue.authority, queueAccount.publicKey, oracleAccount.publicKey ); const permission = accounts.permissions.get( permissionAccount.publicKey.toBase58() ); if (!permission) { return undefined; } return { account: oracleAccount, state: oracle, permission: { account: permissionAccount, bump: permissionBump, state: permission, }, }; }) .filter(Boolean) as Array; const aggregators: Array = Array.from( accounts.aggregators ) .filter(([aggregatorKey, aggregator]) => aggregator.queuePubkey.equals(queueAccount.publicKey) ) .map( ([aggregatorKey, aggregator]): | LoadedAggregatorDefinition | undefined => { const aggregatorAccount = new AggregatorAccount( program, aggregatorKey ); const [permissionAccount, permissionBump] = PermissionAccount.fromSeed( program, queue.authority, queueAccount.publicKey, aggregatorAccount.publicKey ); const permission = accounts.permissions.get( permissionAccount.publicKey.toBase58() ); if (!permission) { return undefined; } const [leaseAccount, leaseBump] = LeaseAccount.fromSeed( program, queueAccount.publicKey, aggregatorAccount.publicKey ); const lease = accounts.leases.get(leaseAccount.publicKey.toBase58()); if (!lease) { return undefined; } // push jobs to filter job definitions jobPublicKeys.push( ...aggregator.jobPubkeysData.slice(0, aggregator.jobPubkeysSize) ); return { account: aggregatorAccount, state: aggregator, permission: { account: permissionAccount, bump: permissionBump, state: permission, }, lease: { account: leaseAccount, bump: leaseBump, state: lease, }, }; } ) .filter(Boolean) as Array; // filter vrfs const vrfs: Array = Array.from(accounts.vrfs) .filter(([vrfKey, vrf]) => vrf.oracleQueue.equals(queueAccount.publicKey)) .map(([vrfKey, vrf]): LoadedVrfDefinition | undefined => { const vrfAccount = new VrfAccount(program, vrfKey); const [permissionAccount, permissionBump] = PermissionAccount.fromSeed( program, queue.authority, queueAccount.publicKey, vrfAccount.publicKey ); const permission = accounts.permissions.get( permissionAccount.publicKey.toBase58() ); if (!permission) { return undefined; } return { account: vrfAccount, state: vrf, permission: { account: permissionAccount, bump: permissionBump, state: permission, }, }; }) .filter(Boolean) as Array; // filter buffer relayers const bufferRelayers: Array = Array.from( accounts.bufferRelayers ) .filter(([bufferRelayerKey, bufferRelayer]) => bufferRelayer.queuePubkey.equals(queueAccount.publicKey) ) .map( ([bufferRelayerKey, bufferRelayer]): | LoadedBufferRelayerDefinition | undefined => { const bufferRelayerAccount = new BufferRelayerAccount( program, bufferRelayerKey ); const [permissionAccount, permissionBump] = PermissionAccount.fromSeed( program, queue.authority, queueAccount.publicKey, bufferRelayerAccount.publicKey ); const permission = accounts.permissions.get( permissionAccount.publicKey.toBase58() ); if (!permission) { return undefined; } jobPublicKeys.push(bufferRelayer.jobPubkey); return { account: bufferRelayerAccount, state: bufferRelayer, permission: { account: permissionAccount, bump: permissionBump, state: permission, }, }; } ) .filter(Boolean) as Array; const jobs: Array = Array.from(accounts.jobs) .filter( ([jobKey]) => jobPublicKeys.findIndex((j) => j.equals(new PublicKey(jobKey))) !== -1 ) .map(([jobKey, job]): LoadedJobDefinition | undefined => { const jobAccount = new JobAccount(program, jobKey); let oracleJob: OracleJob; try { oracleJob = OracleJob.decodeDelimited(job.data); } catch { oracleJob = OracleJob.fromObject({ tasks: [] }); } return { account: jobAccount, state: job, job: oracleJob, }; }) .filter(Boolean) as Array; return new LoadedSwitchboardNetwork({ programState: programStateAccountDefinition, queue: queueDefinition, cranks, oracles, aggregators, vrfs, bufferRelayers, jobs, }); } } /** LOADED ACCOUNTS */ /** The queue and associated accounts for the newly created Switchboard network. */ export interface ILoadedSwitchboardNetwork extends ISwitchboardNetwork { /** The {@linkcode ProgramStateAccount} and PDA bump. */ programState: LoadedProgramStateDefinition; /** The {@linkcode QueueAccount} for the newly created Switchboard network. */ queue: LoadedQueueDefinition; /** The {@linkcode CrankAccount}s for the newly created Switchboard network. */ cranks: Array; oracles: Array; aggregators: Array; vrfs: Array; bufferRelayers: Array; jobs: Array; } export class LoadedSwitchboardNetwork implements ILoadedSwitchboardNetwork { programState: LoadedProgramStateDefinition; queue: LoadedQueueDefinition; /** The {@linkcode CrankAccount}s for the newly created Switchboard network. */ cranks: Array; oracles: Array; aggregators: Array; vrfs: Array; bufferRelayers: Array; jobs: Array; get program(): SwitchboardProgram { return this.queue.account.program; } constructor(fields: ILoadedSwitchboardNetwork) { this.programState = fields.programState; this.queue = fields.queue; this.cranks = fields.cranks; this.oracles = fields.oracles; this.aggregators = fields.aggregators; this.vrfs = fields.vrfs; this.bufferRelayers = fields.bufferRelayers; this.jobs = fields.jobs; } getJob(jobPubkey: PublicKey): LoadedJobDefinition | undefined { return this.jobs.find((job) => job.account.publicKey.equals(jobPubkey)); } getJobs(jobPubkeys: Array): Array { return jobPubkeys .map((jobPubkey) => this.getJob(jobPubkey)) .filter(Boolean) as Array; } toJSON() { return { programState: { publicKey: this.programState.account.publicKey.toBase58(), authority: this.programState.state.authority.toBase58(), state: this.programState.state.toJSON(), }, queue: { publicKey: this.queue.account.publicKey.toBase58(), authority: this.queue.state.authority.toBase58(), state: this.queue.state.toJSON(), }, cranks: this.cranks.map((crank) => { return { publicKey: crank.account.publicKey.toBase58(), state: crank.state.toJSON(), }; }), oracles: this.oracles.map((oracle) => { return { publicKey: oracle.account.publicKey.toBase58(), authority: oracle.state.oracleAuthority.toBase58(), state: oracle.state.toJSON(), permission: { publicKey: oracle.permission.account.publicKey.toBase58(), bump: oracle.permission.bump, state: oracle.permission.state.toJSON(), }, }; }), aggregators: this.aggregators.map((aggregator) => { return { publicKey: aggregator.account.publicKey.toBase58(), authority: aggregator.state.authority.toBase58(), state: { ...aggregator.state.toJSON(), reserved1: undefined, previousConfirmedRoundResult: aggregator.state.previousConfirmedRoundResult.toBig(), jobPubkeysData: aggregator.state.jobPubkeysData.slice( 0, aggregator.state.jobPubkeysSize ), jobHashes: aggregator.state.jobHashes.slice( 0, aggregator.state.jobPubkeysSize ), currentRound: undefined, latestConfirmedRound: undefined, ebuf: undefined, }, permission: { publicKey: aggregator.permission.account.publicKey.toBase58(), bump: aggregator.permission.bump, state: aggregator.permission.state.toJSON(), }, lease: { publicKey: aggregator.lease.account.publicKey.toBase58(), bump: aggregator.lease.bump, state: aggregator.lease.state.toJSON(), }, jobs: this.getJobs( aggregator.state.jobPubkeysData.slice( 0, aggregator.state.jobPubkeysSize ) ).map((job) => { return { publicKey: job.account.publicKey.toBase58(), authority: job.state.authority.toBase58(), state: job.state, data: job.job.toJSON(), }; }), }; }), vrfs: this.vrfs.map((vrf) => { return { publicKey: vrf.account.publicKey.toBase58(), authority: vrf.state.authority.toBase58(), state: { ...vrf.state.toJSON(), callback: { ...vrf.state.callback.toJSON(), accounts: vrf.state.callback.accounts .slice(0, vrf.state.callback.accountsLen) .map((a) => a.toJSON()), ixData: `[${new Uint8Array( vrf.state.callback.ixData.slice(0, vrf.state.callback.ixDataLen) )}]`, }, ebuf: undefined, builders: undefined, }, permission: { publicKey: vrf.permission.account.publicKey.toBase58(), bump: vrf.permission.bump, state: vrf.permission.state.toJSON(), }, }; }), bufferRelayers: this.bufferRelayers.map((bufferRelayer) => { const job = this.getJob(bufferRelayer.state.jobPubkey); return { publicKey: bufferRelayer.account.publicKey.toBase58(), authority: bufferRelayer.state.authority.toBase58(), state: { ...bufferRelayer.state.toJSON(), currentRound: undefined, latestConfirmedRound: undefined, }, permission: { publicKey: bufferRelayer.permission.account.publicKey.toBase58(), bump: bufferRelayer.permission.bump, state: bufferRelayer.permission.state.toJSON(), }, job: { publicKey: job?.account.publicKey.toBase58(), authority: job?.state.authority.toBase58(), state: job?.state, data: job?.job.toJSON(), }, }; }), }; } }