Merge branch 'fix-create-agg-cli' into main

This commit is contained in:
Conner Gallagher 2022-05-30 22:42:40 -06:00
commit 7d56377292
24 changed files with 1421 additions and 668 deletions

View File

@ -13,7 +13,6 @@ npm install -g @switchboard-xyz/switchboardv2-cli
<!-- commands -->
* [`sbv2 aggregator:add:job AGGREGATORKEY`](#sbv2-aggregatoraddjob-aggregatorkey)
* [`sbv2 aggregator:create QUEUEKEY`](#sbv2-aggregatorcreate-queuekey)
* [`sbv2 aggregator:create:copy AGGREGATORSOURCE`](#sbv2-aggregatorcreatecopy-aggregatorsource)
* [`sbv2 aggregator:create:json DEFINITIONFILE`](#sbv2-aggregatorcreatejson-definitionfile)
* [`sbv2 aggregator:lock AGGREGATORKEY`](#sbv2-aggregatorlock-aggregatorkey)
@ -36,6 +35,7 @@ npm install -g @switchboard-xyz/switchboardv2-cli
* [`sbv2 crank:push CRANKKEY AGGREGATORKEY`](#sbv2-crankpush-crankkey-aggregatorkey)
* [`sbv2 crank:turn CRANKKEY`](#sbv2-crankturn-crankkey)
* [`sbv2 help [COMMAND]`](#sbv2-help-command)
* [`sbv2 job:create JOBDEFINITION`](#sbv2-jobcreate-jobdefinition)
* [`sbv2 job:create:copy JOBSOURCE`](#sbv2-jobcreatecopy-jobsource)
* [`sbv2 job:create:json DEFINITIONFILE`](#sbv2-jobcreatejson-definitionfile)
* [`sbv2 job:create:template TEMPLATE ID`](#sbv2-jobcreatetemplate-template-id)
@ -125,57 +125,6 @@ EXAMPLE
_See code: [src/commands/aggregator/add/job.ts](https://github.com/switchboard-xyz/switchboard-v2/blob/v0.1.26/src/commands/aggregator/add/job.ts)_
## `sbv2 aggregator:create QUEUEKEY`
create an aggregator account
```
USAGE
$ sbv2 aggregator:create QUEUEKEY
ARGUMENTS
QUEUEKEY public key of the oracle queue account to create aggregator for
OPTIONS
-a, --authority=authority alternate keypair that is the authority for the aggregator
-h, --help show CLI help
-j, --job=job filesystem path to job definition file
-k, --keypair=keypair keypair that will pay for onchain transactions. defaults to new account
authority if no alternate authority provided
-s, --silent suppress cli prompts
-u, --rpcUrl=rpcUrl alternate RPC url
-v, --verbose log everything
--batchSize=batchSize number of oracles requested for each open round call
--force skip job confirmation
--forceReportPeriod=forceReportPeriod Number of seconds for which, even if the variance threshold is not passed,
accept new responses from oracles.
--mainnetBeta WARNING: use mainnet-beta solana cluster
--minJobs=minJobs number of jobs that must respond before an oracle responds
--minOracles=minOracles number of oracles that must respond before a value is accepted on-chain
--newQueue=newQueue public key of the new oracle queue
--programId=programId alternative Switchboard program ID to interact with
--updateInterval=updateInterval set an aggregator's minimum update delay
--varianceThreshold=varianceThreshold percentage change between a previous accepted result and the next round before
an oracle reports a value on-chain. Used to conserve lease cost during low
volatility
```
_See code: [src/commands/aggregator/create/index.ts](https://github.com/switchboard-xyz/switchboard-v2/blob/v0.1.26/src/commands/aggregator/create/index.ts)_
## `sbv2 aggregator:create:copy AGGREGATORSOURCE`
copy an aggregator account to a new oracle queue
@ -203,8 +152,12 @@ OPTIONS
--batchSize=batchSize override source aggregator's oracleRequestBatchSize
--copyJobs create copy of job accounts instead of referincing existing job account
--crankKey=crankKey public key of the crank to push aggregator to
--enable set permissions to PERMIT_ORACLE_QUEUE_USAGE
--force skip job confirmation
--forceReportPeriod=forceReportPeriod override source aggregator's forceReportPeriod
@ -219,9 +172,9 @@ OPTIONS
--programId=programId alternative Switchboard program ID to interact with
--queueKey=queueKey (required) public key of the queue to create aggregator for
--queueAuthority=queueAuthority alternative keypair to use for queue authority
--sourceCluster=devnet|mainnet-beta alternative solana cluster to copy source aggregator from
--queueKey=queueKey (required) public key of the queue to create aggregator for
--varianceThreshold=varianceThreshold override source aggregator's varianceThreshold
@ -959,6 +912,39 @@ OPTIONS
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v5.1.12/src/commands/help.ts)_
## `sbv2 job:create JOBDEFINITION`
create a buffer relayer account
```
USAGE
$ sbv2 job:create JOBDEFINITION
ARGUMENTS
JOBDEFINITION filesystem path to job definition
OPTIONS
-a, --authority=authority alternate keypair that will be the aggregator authority
-h, --help show CLI help
-k, --keypair=keypair keypair that will pay for onchain transactions. defaults to new account authority if no
alternate authority provided
-n, --name=name name of the buffer account
-s, --silent suppress cli prompts
-u, --rpcUrl=rpcUrl alternate RPC url
-v, --verbose log everything
--mainnetBeta WARNING: use mainnet-beta solana cluster
--programId=programId alternative Switchboard program ID to interact with
```
_See code: [src/commands/job/create/index.ts](https://github.com/switchboard-xyz/switchboard-v2/blob/v0.1.26/src/commands/job/create/index.ts)_
## `sbv2 job:create:copy JOBSOURCE`
copy a job account
@ -2153,8 +2139,8 @@ ARGUMENTS
OPTIONS
-h, --help show CLI help
-k, --keypair=keypair keypair that will pay for onchain transactions. defaults to new account authority if no
alternate authority provided
-k, --keypair=keypair keypair that will pay for onchain transactions. defaults to new account authority if
no alternate authority provided
-n, --name=name name of the crank for easier identification
@ -2170,6 +2156,8 @@ OPTIONS
--programId=programId alternative Switchboard program ID to interact with
--queueAuthority=queueAuthority alternative keypair to use for queue authority
EXAMPLE
$ sbv2 queue:add:crank 5aYuxRdcB9GpWrEXVMBQp2R5uf94uoBiFdMEBwcmHuU4 -k ../authority-keypair.json -n crank-1
```

View File

@ -1,5 +1,3 @@
/* eslint-disable unicorn/no-process-exit */
/* eslint-disable no-process-exit */
import Command, { flags } from "@oclif/command";
import { Input } from "@oclif/parser";
import * as anchor from "@project-serum/anchor";
@ -146,6 +144,7 @@ abstract class BaseCommand extends Command {
if (this.verbose) {
this.logger.log("verbose logging enabled");
}
this.logger.debug(chalk.underline(chalk.blue("## Config".padEnd(16))));
this.logger.debug(
`${chalk.yellow("cluster:")} ${chalk.blue(this.cluster)}`
@ -166,10 +165,12 @@ abstract class BaseCommand extends Command {
if (message) {
logger.info(chalk.red(`${FAILED_ICON}${message}`));
}
if (error.message) {
const messageLines = error.message.split("\n");
logger.error(messageLines[0]);
}
if (this.verbose) {
console.error(error);
}
@ -209,7 +210,6 @@ abstract class BaseCommand extends Command {
loadConfig(): void {
const configPath = path.join(this.config.configDir, "config.json");
if (fs.existsSync(configPath)) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const userConfig: CliConfig = JSON.parse(
fs.readFileSync(configPath, "utf-8")
);
@ -240,6 +240,7 @@ abstract class BaseCommand extends Command {
this.saveConfig(newConfig);
break;
}
case "mainnet-rpc": {
const newConfig = {
...this.cliConfig,
@ -251,6 +252,7 @@ abstract class BaseCommand extends Command {
this.saveConfig(newConfig);
break;
}
default: {
this.logger.warn("not implemented yet");
}
@ -274,15 +276,17 @@ abstract class BaseCommand extends Command {
// Converts a string to a tokenAmount
// If a decimal is found, it will be normalized using 9 decimal places
getTokenAmount(value: string, decimals = 9): anchor.BN {
if (isNaN(Number(value))) {
throw new Error("tokenAmount must be an integer or decimal");
if (Number.isNaN(Number(value))) {
throw new TypeError("tokenAmount must be an integer or decimal");
}
if (value.split(".").length > 1) {
const float = new Big(value);
const scale = BigUtils.safePow(new Big(10), decimals);
const tokenAmount = BigUtils.safeMul(float, scale);
return new anchor.BN(tokenAmount.toFixed(0));
}
return new anchor.BN(value);
}
}

View File

@ -81,12 +81,14 @@ export default class AggregatorAddJob extends BaseCommand {
if (flags.jobDefinition) {
this.jobDefinition = JSON.parse(flags.jobDefinition, pubKeyReviver);
}
if (flags.jobKey) {
this.jobAccount = new JobAccount({
program: this.program,
publicKey: new PublicKey(flags.jobKey),
});
}
if (!this.jobDefinition && !this.jobAccount) {
throw new Error("need to provide --jobDefinition or --jobKey");
}
@ -95,6 +97,7 @@ export default class AggregatorAddJob extends BaseCommand {
if (fs.existsSync(flags.outputFile) && !flags.force) {
throw new OutputFileExistsNoForce(flags.outputFile);
}
this.outputFile = flags.outputFile;
}
}

View File

@ -1,3 +1,4 @@
/* eslint-disable unicorn/no-array-push-push */
import { flags } from "@oclif/command";
import * as anchor from "@project-serum/anchor";
import * as spl from "@solana/spl-token";
@ -9,6 +10,7 @@ import {
TransactionInstruction,
} from "@solana/web3.js";
import {
packAndSend,
prettyPrintAggregator,
promiseWithTimeout,
} from "@switchboard-xyz/sbv2-utils";
@ -17,7 +19,6 @@ import {
CrankAccount,
JobAccount,
LeaseAccount,
loadSwitchboardProgram,
OracleJob,
OracleQueueAccount,
PermissionAccount,
@ -28,8 +29,9 @@ import {
import Big from "big.js";
import BaseCommand from "../../../BaseCommand";
import { verifyProgramHasPayer } from "../../../utils";
import { packAndSend } from "../../../utils/transaction";
// TODO: Fix command so it accepts a feed authority flag
// TODO: Add flag that skips job creation
export default class AggregatorCreateCopy extends BaseCommand {
static description = "copy an aggregator account to a new oracle queue";
@ -70,11 +72,21 @@ export default class AggregatorCreateCopy extends BaseCommand {
description: "public key of the crank to push aggregator to",
required: false,
}),
sourceCluster: flags.string({
description: "alternative solana cluster to copy source aggregator from",
required: false,
options: ["devnet", "mainnet-beta"],
enable: flags.boolean({
description: "set permissions to PERMIT_ORACLE_QUEUE_USAGE",
}),
queueAuthority: flags.string({
description: "alternative keypair to use for queue authority",
}),
copyJobs: flags.boolean({
description:
"create copy of job accounts instead of referincing existing job account",
}),
// sourceCluster: flags.string({
// description: "alternative solana cluster to copy source aggregator from",
// required: false,
// options: ["devnet", "mainnet-beta"],
// }),
};
static args = [
@ -97,20 +109,24 @@ export default class AggregatorCreateCopy extends BaseCommand {
const { args, flags } = this.parse(AggregatorCreateCopy);
const payerKeypair = programWallet(this.program);
const feedAuthority = await this.loadAuthority(flags.authority);
const queueAuthority = await this.loadAuthority(flags.queueAuthority);
const sourceProgram = !flags.sourceCluster
? this.program
: flags.sourceCluster === "devnet" ||
flags.sourceCluster === "mainnet-beta"
? await loadSwitchboardProgram(
flags.sourceCluster,
undefined,
payerKeypair
)
: undefined;
// const sourceProgram = !flags.sourceCluster
// ? this.program
// : flags.sourceCluster === "devnet" ||
// flags.sourceCluster === "mainnet-beta"
// ? await loadSwitchboardProgram(
// flags.sourceCluster,
// undefined,
// payerKeypair
// )
// : undefined;
const sourceProgram = this.program;
if (sourceProgram === undefined) {
throw new Error(`Invalid sourceAggregatorCluster ${flags.sourceCluster}`);
throw new Error(`Invalid sourceAggregatorCluster`);
}
const sourceAggregatorAccount = new AggregatorAccount({
program: sourceProgram,
publicKey: args.aggregatorSource,
@ -126,14 +142,6 @@ export default class AggregatorCreateCopy extends BaseCommand {
return new JobAccount({ program: sourceProgram, publicKey: publicKey });
});
const sourceJobs = await Promise.all(
sourceJobAccounts.map(async (jobAccount) => {
const data = await jobAccount.loadData();
const job = OracleJob.decodeDelimited(data.data);
return { job, data };
})
);
const [programStateAccount, stateBump] = ProgramStateAccount.fromSeed(
this.program
);
@ -153,67 +161,17 @@ export default class AggregatorCreateCopy extends BaseCommand {
| TransactionInstruction
| TransactionInstruction[]
)[] = [];
const createAccountSigners: Keypair[] = [payerKeypair];
const jobAccounts = await Promise.all(
sourceJobs.map(async ({ job, data }) => {
const jobKeypair = Keypair.generate();
createAccountSigners.push(jobKeypair);
const jobData = Buffer.from(
OracleJob.encodeDelimited(
OracleJob.create({
tasks: job.tasks,
})
).finish()
);
const size =
280 + jobData.length + (data.variables?.join("")?.length ?? 0);
createAccountInstructions.push([
SystemProgram.createAccount({
fromPubkey: payerKeypair.publicKey,
newAccountPubkey: jobKeypair.publicKey,
space: size,
lamports:
await this.program.provider.connection.getMinimumBalanceForRentExemption(
size
),
programId: this.program.programId,
}),
await this.program.methods
.jobInit({
name: Buffer.from(data.name),
data: jobData,
variables:
data.variables?.map((item) => Buffer.from("")) ??
new Array<Buffer>(),
authorWallet: payerKeypair.publicKey,
stateBump,
})
.accounts({
job: jobKeypair.publicKey,
authorWallet: tokenWallet,
authority: payerKeypair.publicKey,
programState: programStateAccount.publicKey,
})
// .signers([jobKeypair])
.instruction(),
]);
return new JobAccount({
program: this.program,
publicKey: jobKeypair.publicKey,
});
})
);
const createAccountSigners: Keypair[] = [
payerKeypair,
feedAuthority,
queueAuthority,
];
// Create Aggregator & Permissions
const aggregatorKeypair = Keypair.generate();
this.logger.debug(`Aggregator: ${aggregatorKeypair.publicKey}`);
createAccountSigners.push(aggregatorKeypair);
const aggregatorSize = this.program.account.aggregatorAccountData.size;
const permissionAccountSize =
this.program.account.permissionAccountData.size;
const [permissionAccount, permissionBump] = PermissionAccount.fromSeed(
this.program,
queue.authority,
@ -240,23 +198,6 @@ export default class AggregatorCreateCopy extends BaseCommand {
true
);
const jobPubkeys: Array<PublicKey> = [];
const jobWallets: Array<PublicKey> = [];
const walletBumps: Array<number> = [];
for (let idx in jobAccounts) {
const [jobWallet, bump] = anchor.utils.publicKey.findProgramAddressSync(
[
payerKeypair.publicKey.toBuffer(),
spl.TOKEN_PROGRAM_ID.toBuffer(),
tokenMint.publicKey.toBuffer(),
],
spl.ASSOCIATED_TOKEN_PROGRAM_ID
);
jobPubkeys.push(jobAccounts[idx].publicKey);
jobWallets.push(jobWallet);
walletBumps.push(bump);
}
createAccountInstructions.push(
[
// allocate aggregator space
@ -291,7 +232,7 @@ export default class AggregatorCreateCopy extends BaseCommand {
})
.accounts({
aggregator: aggregatorKeypair.publicKey,
authority: payerKeypair.publicKey,
authority: feedAuthority.publicKey,
queue: queueAccount.publicKey,
authorWallet: tokenWallet,
programState: programStateAccount.publicKey,
@ -309,18 +250,23 @@ export default class AggregatorCreateCopy extends BaseCommand {
systemProgram: SystemProgram.programId,
})
.instruction(),
payerKeypair.publicKey.equals(queue.authority)
flags.enable && queueAuthority.publicKey.equals(queue.authority)
? await this.program.methods
.permissionSet({
permission: { permitOracleQueueUsage: null },
permission: { permitOracleQueueUsage: undefined },
enable: true,
})
.accounts({
permission: permissionAccount.publicKey,
authority: queue.authority,
authority: queueAuthority.publicKey,
})
.instruction()
: undefined,
].filter((item) => item)
);
createAccountInstructions.push(
[
spl.Token.createAssociatedTokenAccountInstruction(
spl.ASSOCIATED_TOKEN_PROGRAM_ID,
spl.TOKEN_PROGRAM_ID,
@ -334,7 +280,7 @@ export default class AggregatorCreateCopy extends BaseCommand {
loadAmount: new anchor.BN(0),
stateBump,
leaseBump,
withdrawAuthority: payerKeypair.publicKey,
withdrawAuthority: feedAuthority.publicKey,
walletBumps: Buffer.from([]),
})
.accounts({
@ -364,7 +310,7 @@ export default class AggregatorCreateCopy extends BaseCommand {
})
.accounts({
crank: new PublicKey(flags.crankKey),
aggregator: aggregatorAccount.publicKey,
aggregator: aggregatorKeypair.publicKey,
oracleQueue: queueAccount.publicKey,
queueAuthority: queue.authority,
permission: permissionAccount.publicKey,
@ -383,54 +329,116 @@ export default class AggregatorCreateCopy extends BaseCommand {
].filter((item) => item)
);
const finalInstructions: (
| TransactionInstruction
| TransactionInstruction[]
)[] = [];
const createJobIxns = flags.copyJobs
? // create job account copies
await Promise.all(
sourceJobAccounts.map(async (jobAccount) => {
const jobKeypair = Keypair.generate();
createAccountSigners.push(jobKeypair); // add signers
finalInstructions.push(
...(await Promise.all(
jobAccounts.map(async (jobAccount) => {
return this.program.methods
const job = await jobAccount.loadData();
const data = await jobAccount.loadData();
const jobData = Buffer.from(
OracleJob.encodeDelimited(
OracleJob.create({
tasks: job.tasks,
})
).finish()
);
const size =
280 + jobData.length + (data.variables?.join("")?.length ?? 0);
return [
SystemProgram.createAccount({
fromPubkey: payerKeypair.publicKey,
newAccountPubkey: jobKeypair.publicKey,
space: size,
lamports:
await this.program.provider.connection.getMinimumBalanceForRentExemption(
size
),
programId: this.program.programId,
}),
await this.program.methods
.jobInit({
name: Buffer.from(data.name),
data: jobData,
variables:
data.variables?.map((item) => Buffer.from("")) ??
new Array<Buffer>(),
authorWallet: payerKeypair.publicKey,
stateBump,
})
.accounts({
job: jobKeypair.publicKey,
authorWallet: tokenWallet,
authority: feedAuthority.publicKey,
programState: programStateAccount.publicKey,
})
.signers([feedAuthority])
.instruction(),
await this.program.methods
.aggregatorAddJob({
weight: 1,
})
.accounts({
aggregator: aggregatorKeypair.publicKey,
authority: payerKeypair.publicKey,
authority: feedAuthority.publicKey,
job: jobAccount.publicKey,
})
.instruction(),
];
})
)
: // add job by pubkey
await Promise.all(
sourceJobAccounts.map(async (jobAccount) => {
const addJobIxn = await this.program.methods
.aggregatorAddJob({
weight: 1,
})
.accounts({
aggregator: aggregatorKeypair.publicKey,
authority: feedAuthority.publicKey,
job: jobAccount.publicKey,
})
.instruction();
return addJobIxn;
})
))
);
const createAccountSignatures = packAndSend(
this.program,
createAccountInstructions,
finalInstructions,
[createAccountInstructions, createJobIxns],
createAccountSigners,
payerKeypair.publicKey
);
).catch((error) => {
throw error;
});
let aggInitWs: number;
const aggInitPromise = new Promise((resolve: (result: boolean) => void) => {
const aggInitPromise = new Promise((resolve: (result: any) => void) => {
aggInitWs = this.program.provider.connection.onAccountChange(
aggregatorAccount.publicKey,
aggregatorKeypair.publicKey,
(accountInfo: AccountInfo<Buffer>, slot) => {
resolve(true);
const aggData = new anchor.BorshAccountsCoder(
this.program.idl
).decode("AggregatorAccountData", accountInfo.data);
resolve(aggData);
}
);
});
const awaitResult = await promiseWithTimeout(
22_000,
aggInitPromise
).finally(() => {
const result = await promiseWithTimeout(45_000, aggInitPromise).finally(
() => {
try {
this.program.provider.connection.removeAccountChangeListener(aggInitWs);
this.program.provider.connection.removeAccountChangeListener(
aggInitWs
);
} catch {}
});
}
);
if (this.silent) {
console.log(aggregatorAccount.publicKey.toString());
@ -438,13 +446,7 @@ export default class AggregatorCreateCopy extends BaseCommand {
}
this.logger.info(
await prettyPrintAggregator(
aggregatorAccount,
undefined,
true,
true,
true
)
await prettyPrintAggregator(aggregatorAccount, result, true, true, true)
);
}

View File

@ -1,16 +1,41 @@
import { flags } from "@oclif/command";
import { PublicKey } from "@solana/web3.js";
import * as anchor from "@project-serum/anchor";
import * as spl from "@solana/spl-token";
import {
AccountInfo,
Keypair,
PublicKey,
SystemProgram,
TransactionInstruction,
} from "@solana/web3.js";
import {
packAndSend,
prettyPrintAggregator,
promiseWithTimeout,
sleep,
} from "@switchboard-xyz/sbv2-utils";
import {
AggregatorAccount,
CrankAccount,
JobAccount,
LeaseAccount,
OracleJob,
OracleQueueAccount,
PermissionAccount,
ProgramStateAccount,
programWallet,
SwitchboardDecimal,
} from "@switchboard-xyz/switchboard-v2";
import Big from "big.js";
import fs from "fs";
import path from "path";
import BaseCommand from "../../../BaseCommand";
import { verifyProgramHasPayer } from "../../../utils";
// TODO: Finish
export default class AggregatorCreate extends BaseCommand {
static hidden = true;
static description = "create an aggregator account";
static flags = {
@ -20,7 +45,20 @@ export default class AggregatorCreate extends BaseCommand {
char: "a",
description: "alternate keypair that is the authority for the aggregator",
}),
forceReportPeriod: flags.string({
crankKey: flags.string({
description: "public key of the crank to join",
}),
enable: flags.boolean({
description: "set permissions to PERMIT_ORACLE_QUEUE_USAGE",
}),
queueAuthority: flags.string({
description: "alternative keypair to use for queue authority",
}),
name: flags.string({
char: "n",
description: "name of the aggregator",
}),
forceReportPeriod: flags.integer({
description:
"Number of seconds for which, even if the variance threshold is not passed, accept new responses from oracles.",
}),
@ -34,9 +72,6 @@ export default class AggregatorCreate extends BaseCommand {
description:
"number of oracles that must respond before a value is accepted on-chain",
}),
newQueue: flags.string({
description: "public key of the new oracle queue",
}),
updateInterval: flags.string({
description: "set an aggregator's minimum update delay",
}),
@ -66,19 +101,41 @@ export default class AggregatorCreate extends BaseCommand {
const { args, flags } = this.parse(AggregatorCreate);
const payerKeypair = programWallet(this.program);
const feedAuthority = await this.loadAuthority(flags.authority);
const queueAuthority = await this.loadAuthority(flags.queueAuthority);
const [programStateAccount, stateBump] = ProgramStateAccount.fromSeed(
this.program
);
const queueAccount = new OracleQueueAccount({
program: this.program,
publicKey: args.queueKey,
});
const queue = await queueAccount.loadData();
const switchTokenMint = await queueAccount.loadMint();
const payerTokenWallet = (
const tokenWallet = (
await switchTokenMint.getOrCreateAssociatedAccountInfo(
payerKeypair.publicKey
)
).address;
const jobs = flags.job.map((jobDefinition) => {
const createAccountInstructions: (
| TransactionInstruction
| TransactionInstruction[]
)[] = [];
const createAccountSigners: Keypair[] = [
payerKeypair,
feedAuthority,
queueAuthority,
];
// Create Job Accounts
const createJobs = flags.job
? await Promise.all(
flags.job.map(
async (
jobDefinition
): Promise<[TransactionInstruction, Keypair]> => {
const jobJson = JSON.parse(
fs.readFileSync(
jobDefinition.startsWith("/")
@ -90,6 +147,9 @@ export default class AggregatorCreate extends BaseCommand {
if (!jobJson || !("tasks" in jobJson)) {
throw new Error("job definition missing tasks");
}
const jobKeypair = anchor.web3.Keypair.generate();
const data = Buffer.from(
OracleJob.encodeDelimited(
OracleJob.create({
@ -97,7 +157,251 @@ export default class AggregatorCreate extends BaseCommand {
})
).finish()
);
const createJobIxn = await this.program.methods
.jobInit({
name: Buffer.from("").slice(0, 32),
data: data,
variables: new Array<Buffer>(),
authorWallet: payerKeypair.publicKey,
stateBump,
})
.accounts({
job: jobKeypair.publicKey,
authorWallet: tokenWallet,
authority: feedAuthority.publicKey,
programState: programStateAccount.publicKey,
})
.signers([feedAuthority])
.instruction();
return [createJobIxn, jobKeypair];
}
)
)
: [];
createAccountInstructions.push(createJobs.map((index) => index[0]));
createAccountSigners.push(...createJobs.map((index) => index[1]));
const jobAccounts = createJobs
.map((index) => index[1])
.map((jobKeypair) => {
return new JobAccount({
program: this.program,
publicKey: jobKeypair.publicKey,
keypair: jobKeypair,
});
});
// Create Aggregator Account
const aggregatorKeypair = anchor.web3.Keypair.generate();
const aggregatorSize = this.program.account.aggregatorAccountData.size;
const aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: aggregatorKeypair.publicKey,
});
const [permissionAccount, permissionBump] = PermissionAccount.fromSeed(
this.program,
queue.authority,
queueAccount.publicKey,
aggregatorKeypair.publicKey
);
createAccountInstructions.push(
[
SystemProgram.createAccount({
fromPubkey: payerKeypair.publicKey,
newAccountPubkey: aggregatorKeypair.publicKey,
space: aggregatorSize,
lamports:
await this.program.provider.connection.getMinimumBalanceForRentExemption(
aggregatorSize
),
programId: this.program.programId,
}),
// create aggregator
await this.program.methods
.aggregatorInit({
name: Buffer.from(flags.name ?? "").slice(0, 32),
metadata: Buffer.from("").slice(0, 64),
batchSize: flags.batchSize ?? 1,
minOracleResults: flags.minOracles ?? 1,
minJobResults: flags.minJobs ?? 1,
minUpdateDelaySeconds: flags.updateInterval ?? 30,
varianceThreshold: flags.varianceThreshold
? SwitchboardDecimal.fromBig(new Big(flags.varianceThreshold))
: SwitchboardDecimal.fromBig(new Big(0)),
forceReportPeriod: flags.forceReportPeriod ?? 0,
stateBump,
})
.accounts({
aggregator: aggregatorKeypair.publicKey,
authority: feedAuthority.publicKey,
queue: queueAccount.publicKey,
authorWallet: tokenWallet,
programState: programStateAccount.publicKey,
})
.instruction(),
await this.program.methods
.permissionInit({})
.accounts({
permission: permissionAccount.publicKey,
authority: queue.authority,
granter: queueAccount.publicKey,
grantee: aggregatorKeypair.publicKey,
payer: payerKeypair.publicKey,
systemProgram: SystemProgram.programId,
})
.instruction(),
flags.enable && queueAuthority.publicKey.equals(queue.authority)
? await this.program.methods
.permissionSet({
permission: { permitOracleQueueUsage: undefined },
enable: true,
})
.accounts({
permission: permissionAccount.publicKey,
authority: queueAuthority.publicKey,
})
.instruction()
: undefined,
].filter((item) => item)
);
createAccountSigners.push(aggregatorKeypair);
// Create Lease Account
// Add to crank if applicable
const [leaseAccount, leaseBump] = LeaseAccount.fromSeed(
this.program,
queueAccount,
aggregatorAccount
);
const leaseEscrow = await spl.Token.getAssociatedTokenAddress(
spl.ASSOCIATED_TOKEN_PROGRAM_ID,
spl.TOKEN_PROGRAM_ID,
switchTokenMint.publicKey,
leaseAccount.publicKey,
true
);
createAccountInstructions.push(
[
spl.Token.createAssociatedTokenAccountInstruction(
spl.ASSOCIATED_TOKEN_PROGRAM_ID,
spl.TOKEN_PROGRAM_ID,
switchTokenMint.publicKey,
leaseEscrow,
leaseAccount.publicKey,
payerKeypair.publicKey
),
await this.program.methods
.leaseInit({
loadAmount: new anchor.BN(0),
stateBump,
leaseBump,
withdrawAuthority: feedAuthority.publicKey,
walletBumps: Buffer.from([]),
})
.accounts({
programState: programStateAccount.publicKey,
lease: leaseAccount.publicKey,
queue: queueAccount.publicKey,
aggregator: aggregatorAccount.publicKey,
systemProgram: SystemProgram.programId,
funder: tokenWallet,
payer: payerKeypair.publicKey,
tokenProgram: spl.TOKEN_PROGRAM_ID,
escrow: leaseEscrow,
owner: payerKeypair.publicKey,
mint: switchTokenMint.publicKey,
})
// .remainingAccounts(
// jobPubkeys.concat(jobWallets).map((pubkey: PublicKey) => {
// return { isSigner: false, isWritable: true, pubkey };
// })
// )
.instruction(),
flags.crankKey
? await this.program.methods
.crankPush({
stateBump,
permissionBump,
})
.accounts({
crank: new PublicKey(flags.crankKey),
aggregator: aggregatorKeypair.publicKey,
oracleQueue: queueAccount.publicKey,
queueAuthority: queue.authority,
permission: permissionAccount.publicKey,
lease: leaseAccount.publicKey,
escrow: leaseEscrow,
programState: programStateAccount.publicKey,
dataBuffer: (
await new CrankAccount({
program: this.program,
publicKey: new PublicKey(flags.crankKey),
}).loadData()
).dataBuffer,
})
.instruction()
: undefined,
].filter((item) => item)
);
// Add Job Accounts
const addJobIxns = await Promise.all(
jobAccounts.map(async (job) => {
return this.program.methods
.aggregatorAddJob({
weight: 1,
})
.accounts({
aggregator: aggregatorKeypair.publicKey,
authority: feedAuthority.publicKey,
job: job.publicKey,
})
.instruction();
})
);
const createAccountSignatures = packAndSend(
this.program,
[createAccountInstructions, addJobIxns],
createAccountSigners,
payerKeypair.publicKey
).catch((error) => {
throw error;
});
let aggInitWs: number;
const aggInitPromise = new Promise((resolve: (result: any) => void) => {
aggInitWs = this.program.provider.connection.onAccountChange(
aggregatorKeypair.publicKey,
(accountInfo: AccountInfo<Buffer>, slot) => {
const aggData = new anchor.BorshAccountsCoder(
this.program.idl
).decode("AggregatorAccountData", accountInfo.data);
resolve(aggData);
}
);
});
const result = await promiseWithTimeout(45_000, aggInitPromise).finally(
() => {
try {
this.program.provider.connection.removeAccountChangeListener(
aggInitWs
);
} catch {}
}
);
if (this.silent) {
console.log(aggregatorAccount.publicKey.toString());
}
await sleep(2500);
this.logger.info(
await prettyPrintAggregator(aggregatorAccount, result, true, true, true)
);
}
async catch(error) {

View File

@ -70,18 +70,17 @@ export default class JsonCreateAggregator extends BaseCommand {
if (!fs.existsSync(definitionFile)) {
throw new Error("input file does not exist");
}
let aggregatorDefinition: fromAggregatorJSON = JSON.parse(
const aggregatorDefinition: fromAggregatorJSON = JSON.parse(
fs.readFileSync(definitionFile, "utf-8"),
pubKeyReviver
);
if (flags.outputFile) {
if (fs.existsSync(flags.outputFile) && !flags.force) {
if (flags.outputFile && fs.existsSync(flags.outputFile) && !flags.force) {
throw new Error(
"output file exists. Run the command with '--force' to overwrite it"
);
}
}
let authority = programWallet(this.program);
if (flags.authority) {
@ -91,6 +90,7 @@ export default class JsonCreateAggregator extends BaseCommand {
if (!aggregatorDefinition.queuePublicKey && !flags.queueKey) {
throw new Error("you must provide a --queueKey to create aggregator for");
}
const queueAccount = new OracleQueueAccount({
program: this.program,
publicKey: aggregatorDefinition.queuePublicKey
@ -173,6 +173,7 @@ export default class JsonCreateAggregator extends BaseCommand {
jobs.push(account);
}
}
for await (const job of jobs) {
await aggregatorAccount.addJob(job, authority);
}

View File

@ -0,0 +1,84 @@
import { flags } from "@oclif/command";
import { prettyPrintJob } from "@switchboard-xyz/sbv2-utils";
import {
JobAccount,
OracleJob,
programWallet,
} from "@switchboard-xyz/switchboard-v2";
import fs from "fs";
import path from "path";
import BaseCommand from "../../../BaseCommand";
import { verifyProgramHasPayer } from "../../../utils";
export default class JobCreate extends BaseCommand {
static description = "create a buffer relayer account";
static flags = {
...BaseCommand.flags,
authority: flags.string({
char: "a",
description: "alternate keypair that will be the aggregator authority",
}),
name: flags.string({
char: "n",
description: "name of the buffer account",
}),
};
static args = [
{
name: "jobDefinition",
required: true,
description: "filesystem path to job definition",
},
];
async run() {
verifyProgramHasPayer(this.program);
const { args, flags } = this.parse(JobCreate);
const payerKeypair = programWallet(this.program);
const authority = await this.loadAuthority(flags.authority);
const jobDefinitionPath = args.jobDefinition.startsWith("/")
? args.jobDefinition
: path.join(process.cwd(), args.jobDefinition);
if (!fs.existsSync(jobDefinitionPath)) {
throw new Error(`jobDefinitionPath does not exist, ${jobDefinitionPath}`);
}
const oracleJob = OracleJob.create(
JSON.parse(fs.readFileSync(jobDefinitionPath, "utf-8"))
);
if (!("tasks" in oracleJob)) {
throw new Error("Must provide tasks in job definition");
}
const data = Buffer.from(
OracleJob.encodeDelimited(
OracleJob.create({
tasks: oracleJob.tasks,
})
).finish()
);
console.log(`DATA: [${data.join(",")}]`);
const jobAccount = await JobAccount.create(this.program, {
authority: authority.publicKey,
name: flags.name ? Buffer.from(flags.name) : Buffer.from(""),
data,
});
if (this.silent) {
this.logger.info(jobAccount.publicKey.toString());
return;
}
const job = await jobAccount.loadData();
this.logger.info(await prettyPrintJob(jobAccount, job));
console.log(`DATA: [${job.data.join(",")}]`);
}
async catch(error) {
super.catch(error, "failed to create buffer relayer account");
}
}

View File

@ -64,11 +64,19 @@ export default class OracleCreate extends BaseCommand {
const { args, flags } = this.parse(OracleCreate);
verifyProgramHasPayer(this.program);
const payerKeypair = programWallet(this.program);
const signers: Keypair[] = [payerKeypair];
const authorityKeypair = await this.loadAuthority(flags.authority);
// if (!payerKeypair.publicKey.equals(authorityKeypair.publicKey)) {
// signers.push(authorityKeypair);
// }
const queueAuthority: Keypair = flags.queueAuthority
? await loadKeypair(flags.queueAuthority)
: payerKeypair;
if (!payerKeypair.publicKey.equals(queueAuthority.publicKey)) {
signers.push(queueAuthority);
}
const queueAccount = new OracleQueueAccount({
program: this.program,
@ -82,6 +90,7 @@ export default class OracleCreate extends BaseCommand {
);
const tokenWalletKeypair = anchor.web3.Keypair.generate();
signers.push(tokenWalletKeypair);
const [oracleAccount, oracleBump] = OracleAccount.fromSeed(
this.program,
queueAccount,
@ -92,7 +101,7 @@ export default class OracleCreate extends BaseCommand {
const [permissionAccount, permissionBump] = PermissionAccount.fromSeed(
this.program,
authorityKeypair.publicKey,
queue.authority,
queueAccount.publicKey,
oracleAccount.publicKey
);
@ -137,7 +146,7 @@ export default class OracleCreate extends BaseCommand {
.permissionInit({})
.accounts({
permission: permissionAccount.publicKey,
authority: authorityKeypair.publicKey,
authority: queue.authority,
granter: queueAccount.publicKey,
grantee: oracleAccount.publicKey,
payer: payerKeypair.publicKey,
@ -152,9 +161,11 @@ export default class OracleCreate extends BaseCommand {
`Invalid queue authority, received ${queueAuthority.publicKey}, expected ${queue.authority}`
);
}
createOracleTxn.add(
await this.program.methods
.permissionSet({
// eslint-disable-next-line unicorn/no-null
permission: { permitOracleHeartbeat: null },
enable: true,
})
@ -168,7 +179,7 @@ export default class OracleCreate extends BaseCommand {
const signature = await this.program.provider.sendAndConfirm(
createOracleTxn,
[payerKeypair, authorityKeypair, tokenWalletKeypair]
signers
);
const oracleData = await oracleAccount.loadData();
@ -176,6 +187,7 @@ export default class OracleCreate extends BaseCommand {
console.log(oracleAccount.publicKey.toString());
return;
}
this.logger.log(
`${chalk.green(`${CHECK_ICON}Oracle account created successfully`)}`
);

View File

@ -57,12 +57,14 @@ export default class PermissionSet extends BaseCommand {
authorityKey = data.authority;
break;
}
default: {
throw new Error(
`Granter should be a OracleQueueAccount, received ${granterAccountType}`
);
}
}
const authority = await this.loadAuthority(flags.authority, authorityKey);
// check and load grantees account type, and assign permissions based on type

View File

@ -1,4 +1,3 @@
/* eslint-disable unicorn/import-style */
import { flags } from "@oclif/command";
import { PublicKey } from "@solana/web3.js";
import {

View File

@ -1,19 +1,20 @@
import { flags } from "@oclif/command";
import { Keypair, PublicKey } from "@solana/web3.js";
import { OracleQueueAccount } from "@switchboard-xyz/switchboard-v2";
import * as anchor from "@project-serum/anchor";
import { PublicKey, SystemProgram } from "@solana/web3.js";
import {
prettyPrintCrank,
verifyProgramHasPayer,
} from "@switchboard-xyz/sbv2-utils";
import {
CrankAccount,
OracleQueueAccount,
programWallet,
} from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
import { fromCrankJSON } from "../../../accounts";
import { CrankClass } from "../../../accounts/crank/crank";
import BaseCommand from "../../../BaseCommand";
import { CHECK_ICON } from "../../../utils";
export default class QueueAddCrank extends BaseCommand {
queueAccount: OracleQueueAccount;
crankDefinition: fromCrankJSON;
queueAuthority: Keypair | undefined = undefined;
static description = "add a crank to an existing oracle queue";
static flags = {
@ -26,10 +27,9 @@ export default class QueueAddCrank extends BaseCommand {
char: "r",
description: "maximum number of rows a crank can support",
}),
// authority: flags.string({
// char: "a",
// description: "alternate keypair that is the authority for oracle queue",
// }),
queueAuthority: flags.string({
description: "alternative keypair to use for queue authority",
}),
};
static args = [
@ -46,41 +46,72 @@ export default class QueueAddCrank extends BaseCommand {
// "$ sbv2 queue:add:crank 5aYuxRdcB9GpWrEXVMBQp2R5uf94uoBiFdMEBwcmHuU4 -k ../payer-keypair.json -a ../authority-keypair.json",
];
async init() {
await super.init();
async run() {
const { args, flags } = this.parse(QueueAddCrank);
this.queueAccount = new OracleQueueAccount({
program: this.program,
publicKey: args.queueKey,
});
// TODO: Not implemented yet
// if (flags.authority) {
// this.queueAuthority = await loadKeypair(flags.authority);
// }
verifyProgramHasPayer(this.program);
const payerKeypair = programWallet(this.program);
if (flags.maxRows < 0) {
throw new Error("max rows must be a positive number");
}
this.crankDefinition = {
name: flags.name || "",
maxRows: flags.maxRows || undefined,
};
}
const maxRows = flags.maxRows;
async run() {
const crank = await CrankClass.fromJSON(
this.context,
this.crankDefinition,
this.queueAccount
const queueAccount = new OracleQueueAccount({
program: this.program,
publicKey: args.queueKey,
});
const queue = await queueAccount.loadData();
const queueAuthority = await this.loadAuthority(
flags.queueAuthority,
queue.authority
);
const crankKeypair = anchor.web3.Keypair.generate();
const bufferKeypair = anchor.web3.Keypair.generate();
const crankSize = this.program.account.crankAccountData.size;
const bufferSize = maxRows * 40 + 8;
const signature = await this.program.methods
.crankInit({
name: (flags.name ? Buffer.from(flags.name) : Buffer.from("")).slice(
0,
32
),
metadata: Buffer.from("").slice(0, 64),
crankSize: maxRows,
})
.accounts({
crank: crankKeypair.publicKey,
queue: queueAccount.publicKey,
buffer: bufferKeypair.publicKey,
systemProgram: SystemProgram.programId,
payer: payerKeypair.publicKey,
})
.signers([crankKeypair, bufferKeypair])
.preInstructions([
anchor.web3.SystemProgram.createAccount({
fromPubkey: payerKeypair.publicKey,
newAccountPubkey: bufferKeypair.publicKey,
space: bufferSize,
lamports:
await this.program.provider.connection.getMinimumBalanceForRentExemption(
bufferSize
),
programId: this.program.programId,
}),
])
.rpc();
const crankAccount = new CrankAccount({
program: this.program,
publicKey: crankKeypair.publicKey,
});
if (this.silent) {
console.log(crank.account.publicKey.toString());
console.log(crankKeypair.publicKey.toString());
} else {
this.logger.log(crank.prettyPrint());
this.logger.log(await prettyPrintCrank(crankAccount));
this.logger.log(
`${chalk.green(`${CHECK_ICON}Crank created successfully\r\n`)}`
);

View File

@ -1,4 +1,4 @@
/* eslint-disable unicorn/prevent-abbreviations */
/* eslint-disable complexity */
/* eslint-disable unicorn/new-for-builtins */
import { flags } from "@oclif/command";
import * as anchor from "@project-serum/anchor";
@ -102,7 +102,7 @@ export default class QueueCreate extends BaseCommand {
verifyProgramHasPayer(this.program);
const { flags, args } = this.parse(QueueCreate);
const payerKeypair = programWallet(this.program);
const signers: Keypair[] = [];
const signers: Keypair[] = [payerKeypair];
const outputPath =
flags.outputFile === undefined
@ -119,6 +119,7 @@ export default class QueueCreate extends BaseCommand {
if (!authorityKeypair.publicKey.equals(payerKeypair.publicKey)) {
signers.push(authorityKeypair);
}
const [programStateAccount, stateBump] = ProgramStateAccount.fromSeed(
this.program
);
@ -186,18 +187,6 @@ export default class QueueCreate extends BaseCommand {
this.logger.debug(chalkString("OracleQueue", queueKeypair.publicKey));
this.logger.debug(chalkString("OracleBuffer", queueBuffer.publicKey));
const crankKeypair = anchor.web3.Keypair.generate();
const crankBuffer = anchor.web3.Keypair.generate();
const crankSize = flags.crankSize ? flags.crankSize * 40 + 8 : 0;
this.logger.debug(chalkString("CrankAccount", crankKeypair.publicKey));
this.logger.debug(chalkString("CrankBuffer", crankBuffer.publicKey));
const crankAccount = new CrankAccount({
program: this.program,
publicKey: crankKeypair.publicKey,
});
setupQueueTxns.push(
anchor.web3.SystemProgram.createAccount({
fromPubkey: payerKeypair.publicKey,
@ -238,7 +227,25 @@ export default class QueueCreate extends BaseCommand {
payer: payerKeypair.publicKey,
mint: tokenMint.publicKey,
})
.instruction(),
.instruction()
);
signers.push(queueKeypair, queueBuffer);
let crankAccount: CrankAccount | undefined;
if (flags.crankSize) {
const crankKeypair = anchor.web3.Keypair.generate();
const crankBuffer = anchor.web3.Keypair.generate();
const crankSize = flags.crankSize ? flags.crankSize * 40 + 8 : 0;
this.logger.debug(chalkString("CrankAccount", crankKeypair.publicKey));
this.logger.debug(chalkString("CrankBuffer", crankBuffer.publicKey));
crankAccount = new CrankAccount({
program: this.program,
publicKey: crankKeypair.publicKey,
});
signers.push(crankKeypair, crankBuffer);
setupQueueTxns.push(
anchor.web3.SystemProgram.createAccount({
fromPubkey: payerKeypair.publicKey,
newAccountPubkey: crankBuffer.publicKey,
@ -264,7 +271,7 @@ export default class QueueCreate extends BaseCommand {
})
.instruction()
);
signers.push(queueKeypair, queueBuffer, crankKeypair, crankBuffer);
}
const finalTransactions: (
| TransactionInstruction
@ -272,7 +279,7 @@ export default class QueueCreate extends BaseCommand {
)[] = [];
const oracleAccounts = await Promise.all(
Array.from(Array(flags.numOracles).keys()).map(async (n) => {
[...Array(flags.numOracles).keys()].map(async (n) => {
const name = `Oracle-${n + 1}`;
const tokenWalletKeypair = anchor.web3.Keypair.generate();
const [oracleAccount, oracleBump] = OracleAccount.fromSeed(
@ -340,6 +347,7 @@ export default class QueueCreate extends BaseCommand {
.instruction(),
await this.program.methods
.permissionSet({
// eslint-disable-next-line unicorn/no-null
permission: { permitOracleHeartbeat: null },
enable: true,
})
@ -386,13 +394,17 @@ export default class QueueCreate extends BaseCommand {
name: flags.name,
queueAccount: queueKeypair.publicKey.toString(),
queueSize: flags.queueSize,
queueReward: Number.parseInt(flags.reward),
minStake: Number.parseInt(flags.minStake),
queueReward: Number.parseInt(flags.reward, 10),
minStake: Number.parseInt(flags.minStake, 10),
oracleTimeout: flags.oracleTimeout,
crankAccounts: [
// crankAccount === undefined ? undefined :
crankAccounts:
crankAccount === undefined
? undefined
: [
{
name: "Crank",
crankAccount: crankKeypair.publicKey.toString(),
crankAccount: crankAccount.publicKey.toString(),
maxRows: flags.crankSize,
},
],

View File

@ -60,7 +60,9 @@ export const loadGoogleSecretKeypair = async (
const client = new SecretManagerServiceClient();
const [accessResponse] = await client.accessSecretVersion({
name: secretPath,
name: secretPath.includes("/versions/")
? secretPath
: `${secretPath}/versions/latest`,
});
const secrets = accessResponse?.payload.data;
@ -91,6 +93,7 @@ export const loadKeypair = async (
const keypair = loadKeypairFs(keypairPath);
return keypair;
} catch {}
try {
const keypair = await loadGoogleSecretKeypair(keypairPath);
return keypair;
@ -100,6 +103,7 @@ export const loadKeypair = async (
} else if ("secretPath" in keypairPath) {
return loadGoogleSecretKeypair(keypairPath.secretPath);
}
throw new InvalidKeypairProvided(keypairPath);
};
@ -140,5 +144,6 @@ export const loadKeypairWithDescriptor = async (
};
return [keypair, descriptor];
}
throw new InvalidKeypairProvided(keypairPath);
};

View File

@ -31,11 +31,12 @@ export async function packAndSend(
).wallet.signAllTransactions(signedTransactions);
for (let k = 0; k < packedTransactions.length; k += 1) {
const tx = signedTxs[k];
const rawTx = tx!.serialize();
const tx = signedTxs[k]!;
const rawTx = tx.serialize();
signatures.push(
sendAndConfirmRawTransaction(program.provider.connection, rawTx, {
maxRetries: 10,
commitment: "processed",
})
.then((sig) => {
return sig;

View File

@ -25,7 +25,7 @@
"scripts": {
"docgen": "yarn build && npx typedoc",
"build:protos": "pbjs -t static-module -o src/protos/index.js ../protos/*.proto && pbts -o src/protos/index.d.ts src/protos/index.js",
"build:cjs:protos": "shx mkdir -p lib/cjs/protos; pbjs -t static-module -w commonjs -o lib/cjs/protos/index.js ../protos/*.proto && pbts -o lib/cjs/protos/index.d.ts lib/cjs/protos/index.js",
"build:cjs:protos": "shx mkdir -p lib/cjs/protos; pbjs -t static-module -o lib/cjs/protos/index.js ../protos/*.proto && pbts -o lib/cjs/protos/index.d.ts lib/cjs/protos/index.js",
"build:esm:protos": "shx mkdir -p lib/esm/protos; pbjs -t static-module -o lib/esm/protos/index.js ../protos/*.proto && pbts -o lib/esm/protos/index.d.ts lib/esm/protos/index.js",
"build:cjs": "shx rm -rf lib/cjs && tsc -p tsconfig.cjs.json && shx echo '{\"type\": \"commonjs\"}' > lib/cjs/package.json && yarn build:cjs:protos",
"build:esm": "shx rm -rf lib/esm && tsc -p tsconfig.esm.json && shx echo '{\"type\": \"module\"}' > lib/esm/package.json && yarn build:esm:protos",
@ -43,6 +43,7 @@
"@project-serum/anchor": "^0.24.2",
"@solana/spl-governance": "^0.0.34",
"@switchboard-xyz/eslint-config": "^0.1.1",
"@switchboard-xyz/switchboard-api": "^0.2.201",
"assert": "^2.0.0",
"big.js": "^6.1.1",
"bs58": "^4.0.1",
@ -65,12 +66,5 @@
"shx": "^0.3.4",
"typedoc": "^0.22.13",
"typescript": "^4.2.4"
},
"eslintConfig": {
"extends": "@switchboard-xyz",
"rules": {
"no-return-await": "off",
"quotes": "off"
}
}
}

View File

@ -18,11 +18,12 @@ import {
TransactionInstruction,
TransactionSignature,
} from "@solana/web3.js";
// eslint-disable-next-line import/extensions
// import { OracleJob } from "./protos/index.js";
import { OracleJob } from "@switchboard-xyz/switchboard-api";
import assert from "assert";
import Big from "big.js";
import * as crypto from "crypto";
// eslint-disable-next-line import/extensions
import { OracleJob } from "./protos/index.js";
const assert = require("assert");
/**
* Switchboard Devnet Program ID

View File

@ -27,7 +27,7 @@
"docs:build:sbv2-utils": "yarn workspace @switchboard-xyz/sbv2-utils docgen",
"docs:build:cli": "node ./tools/scripts/generate-cli-docs.js",
"docs:build:site": "yarn workspace website build:site",
"docs:build:tasks": "protoc --proto_path=../switchboard-core/protos --doc_out=website/api --doc_opt=markdown,_tasks.md ../switchboard-core/protos/job_schemas.proto",
"docs:build:tasks": "protoc --proto_path=./libraries/protos --doc_out=website/api --doc_opt=markdown,_tasks.md ./libraries/protos/job_schemas.proto",
"docs:build": "run-s docs:build:ts docs:build:sbv2-lite docs:build:sbv2-utils docs:build:cli docs:build:site",
"docs:deploy": "yarn workspace website deploy",
"gen:idl": "rawrtools gen:anchor SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f -o website/idl -p /idl",

View File

@ -1,16 +1,15 @@
# Protocol Documentation
<a name="top"></a>
## Table of Contents
- [job_schemas.proto](#job_schemas.proto)
- [JobPosting](#.JobPosting)
- [JobResult](#.JobResult)
- [OracleJob](#.OracleJob)
- [OracleJob.AddTask](#.OracleJob.AddTask)
- [OracleJob.AnchorFetchTask](#.OracleJob.AnchorFetchTask)
- [OracleJob.CacheTask](#.OracleJob.CacheTask)
- [OracleJob.ConditionalTask](#.OracleJob.ConditionalTask)
- [OracleJob.DefiKingdomsTask](#.OracleJob.DefiKingdomsTask)
- [OracleJob.DefiKingdomsTask.Token](#.OracleJob.DefiKingdomsTask.Token)
@ -28,6 +27,7 @@
- [OracleJob.MedianTask](#.OracleJob.MedianTask)
- [OracleJob.MultiplyTask](#.OracleJob.MultiplyTask)
- [OracleJob.OracleTask](#.OracleJob.OracleTask)
- [OracleJob.PancakeswapExchangeRateTask](#.OracleJob.PancakeswapExchangeRateTask)
- [OracleJob.PerpMarketTask](#.OracleJob.PerpMarketTask)
- [OracleJob.PowTask](#.OracleJob.PowTask)
- [OracleJob.RegexExtractTask](#.OracleJob.RegexExtractTask)
@ -36,6 +36,7 @@
- [OracleJob.SplTokenParseTask](#.OracleJob.SplTokenParseTask)
- [OracleJob.SubtractTask](#.OracleJob.SubtractTask)
- [OracleJob.SushiswapExchangeRateTask](#.OracleJob.SushiswapExchangeRateTask)
- [OracleJob.SysclockOffsetTask](#.OracleJob.SysclockOffsetTask)
- [OracleJob.Task](#.OracleJob.Task)
- [OracleJob.TpsTask](#.OracleJob.TpsTask)
- [OracleJob.TwapTask](#.OracleJob.TwapTask)
@ -44,376 +45,589 @@
- [OracleJob.WebsocketTask](#.OracleJob.WebsocketTask)
- [OracleJob.XStepPriceTask](#.OracleJob.XStepPriceTask)
- [OracleJob.CacheTask.Method](#.OracleJob.CacheTask.Method)
- [OracleJob.HttpTask.Method](#.OracleJob.HttpTask.Method)
- [OracleJob.JsonParseTask.AggregationMethod](#.OracleJob.JsonParseTask.AggregationMethod)
- [OracleJob.LendingRateTask.Field](#.OracleJob.LendingRateTask.Field)
- [Scalar Value Types](#scalar-value-types)
<a name="job_schemas.proto"></a>
<a name="job_schemas.proto"></a>
<p align="right"><a href="#top">Top</a></p>
## job_schemas.proto
<a name=".JobPosting"></a>
### JobPosting
The schema Oracle nodes receive when they are notified to fulfill a job.
| Field | Type | Label | Description |
| ----------------------- | ----------------- | -------- | ------------------------------------------------ |
| ----- | ---- | ----- | ----------- |
| aggregator_state_pubkey | [bytes](#bytes) | optional | Pubkey of the aggregator to fulfill the job for. |
| node_pubkeys | [bytes](#bytes) | repeated | The pubkey of the nodes this job is assigned to. |
| slot | [uint64](#uint64) | optional | Slot number of the job posting. |
<a name=".JobResult"></a>
### JobResult
This schema Oracle nodes respond with when fulfilling a job.
| Field | Type | Label | Description |
| ----------- | --------------- | -------- | ----------------------------------------------------------------- |
| ----- | ---- | ----- | ----------- |
| node_pubkey | [bytes](#bytes) | optional | The public key of the responding node. |
| result | double | optional | The median value of the jobs the node has fulfilled successfully. |
| result | [double](#double) | optional | The median value of the jobs the node has fulfilled successfully. |
| error | [bool](#bool) | optional | True if the node failed to decide on an answer to the job. |
<a name=".OracleJob"></a>
### OracleJob
Represnts a list of tasks to be performed by a switchboard oracle.
Represents a list of tasks to be performed by a switchboard oracle.
| Field | Type | Label | Description |
| ----- | --------------------------------- | -------- | ------------------------------------------------- |
| ----- | ---- | ----- | ----------- |
| tasks | [OracleJob.Task](#OracleJob.Task) | repeated | The chain of tasks to perform for this OracleJob. |
<a name=".OracleJob.AddTask"></a>
### OracleJob.AddTask
This task will add a numerical input by a scalar value or by another
aggregate.
| Field | Type | Label | Description |
| ----------------- | ----------------------- | -------- | -------------------------------------------------------------------------------- |
| scalar | double | optional | Specifies a scalar to add by. |
| aggregator_pubkey | string | optional | Specifies an aggregator to add by. |
| ----- | ---- | ----- | ----------- |
| scalar | [double](#double) | optional | Specifies a scalar to add by. |
| aggregator_pubkey | [string](#string) | optional | Specifies an aggregator to add by. |
| job | [OracleJob](#OracleJob) | optional | A job whose result is computed before adding our numerical input by that result. |
<a name=".OracleJob.AnchorFetchTask"></a>
### OracleJob.AnchorFetchTask
Load a parse an Anchor based solana account.
| Field | Type | Label | Description |
| --------------- | ------ | -------- | --------------------------------------- |
| program_id | string | optional | Owning program of the account to parse. |
| account_address | string | optional | The account to parse. |
| ----- | ---- | ----- | ----------- |
| program_id | [string](#string) | optional | Owning program of the account to parse. |
| account_address | [string](#string) | optional | The account to parse. |
<a name=".OracleJob.CacheTask"></a>
### OracleJob.CacheTask
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| name | [string](#string) | optional | |
| method | [OracleJob.CacheTask.Method](#OracleJob.CacheTask.Method) | optional | |
<a name=".OracleJob.ConditionalTask"></a>
### OracleJob.ConditionalTask
This task will run the `attempt` subtasks in an effort to produce a valid numerical result. If
`attempt` fails to produce an acceptable result, `on_failure` subtasks will be run instead.
| Field | Type | Label | Description |
| ---------- | --------------------------------- | -------- | ----------------------------------------------------------------------------------------------------- |
| ----- | ---- | ----- | ----------- |
| attempt | [OracleJob.Task](#OracleJob.Task) | repeated | A list of subtasks to process in an attempt to produce a valid numerical result. |
| on_failure | [OracleJob.Task](#OracleJob.Task) | repeated | A list of subtasks that will be run if `attempt` subtasks are unable to produce an acceptable result. |
<a name=".OracleJob.DefiKingdomsTask"></a>
### OracleJob.DefiKingdomsTask
| Field | Type | Label | Description |
| --------- | --------------------------------------------------------------------- | -------- | ----------- |
| provider | string | optional | |
| ----- | ---- | ----- | ----------- |
| provider | [string](#string) | optional | |
| in_token | [OracleJob.DefiKingdomsTask.Token](#OracleJob.DefiKingdomsTask.Token) | optional | |
| out_token | [OracleJob.DefiKingdomsTask.Token](#OracleJob.DefiKingdomsTask.Token) | optional | |
<a name=".OracleJob.DefiKingdomsTask.Token"></a>
### OracleJob.DefiKingdomsTask.Token
| Field | Type | Label | Description |
| -------- | ------ | -------- | ----------- |
| address | string | optional | |
| decimals | int32 | optional | |
| ----- | ---- | ----- | ----------- |
| address | [string](#string) | optional | |
| decimals | [int32](#int32) | optional | |
<a name=".OracleJob.DivideTask"></a>
### OracleJob.DivideTask
This task will divide a numerical input by a scalar value or by another
aggregate.
| Field | Type | Label | Description |
| ----------------- | ----------------------- | -------- | ---------------------------------------------------------------------------------- |
| scalar | double | optional | Specifies a basic scalar denominator to divide by. |
| aggregator_pubkey | string | optional | Specifies another aggregator result to divide by. |
| ----- | ---- | ----- | ----------- |
| scalar | [double](#double) | optional | Specifies a basic scalar denominator to divide by. |
| aggregator_pubkey | [string](#string) | optional | Specifies another aggregator resut to divide by. |
| job | [OracleJob](#OracleJob) | optional | A job whose result is computed before dividing our numerical input by that result. |
<a name=".OracleJob.HttpTask"></a>
### OracleJob.HttpTask
The adapter will report the text body of a successful HTTP request to the specified url,
or return an error if the response status code is greater than or equal to 400.
@return string representation of it&#39;s output.
| Field | Type | Label | Description |
| ------- | ------------------------------------------------------- | -------- | ----------------------------------------------------------- |
| url | string | optional | A string containing the URL to direct this HTTP request to. |
| ----- | ---- | ----- | ----------- |
| url | [string](#string) | optional | A string containing the URL to direct this HTTP request to. |
| method | [OracleJob.HttpTask.Method](#OracleJob.HttpTask.Method) | optional | The type of HTTP request to make. |
| headers | [OracleJob.HttpTask.Header](#OracleJob.HttpTask.Header) | repeated | A list of headers to add to this HttpTask. |
| body | string | optional | A stringified body (if any) to add to this HttpTask. |
| body | [string](#string) | optional | A stringified body (if any) to add to this HttpTask. |
<a name=".OracleJob.HttpTask.Header"></a>
### OracleJob.HttpTask.Header
An object that represents a header to add to an HTTP request.
| Field | Type | Label | Description |
| ----- | ------ | -------- | ----------- |
| key | string | optional | |
| value | string | optional | |
| ----- | ---- | ----- | ----------- |
| key | [string](#string) | optional | |
| value | [string](#string) | optional | |
<a name=".OracleJob.JsonParseTask"></a>
### OracleJob.JsonParseTask
The adapter walks the path specified and returns the value found at that result. If returning
JSON data from the HttpGet or HttpPost adapters, you must use this adapter to parse the
response.
| Field | Type | Label | Description |
| ------------------ | --------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| path | string | optional | JSONPath formatted path to the element. https://t.ly/uLtw https://www.npmjs.com/package/jsonpath-plus |
| ----- | ---- | ----- | ----------- |
| path | [string](#string) | optional | JSONPath formatted path to the element. https://t.ly/uLtw https://www.npmjs.com/package/jsonpath-plus |
| aggregation_method | [OracleJob.JsonParseTask.AggregationMethod](#OracleJob.JsonParseTask.AggregationMethod) | optional | The technique that will be used to aggregate the results if walking the specified path returns multiple numerical results. |
<a name=".OracleJob.JupiterSwapTask"></a>
### OracleJob.JupiterSwapTask
| Field | Type | Label | Description |
| ----------------- | ------ | -------- | ----------- |
| in_token_address | string | optional | |
| out_token_address | string | optional | |
| base_amount | double | optional | |
| ----- | ---- | ----- | ----------- |
| in_token_address | [string](#string) | optional | |
| out_token_address | [string](#string) | optional | |
| base_amount | [double](#double) | optional | |
<a name=".OracleJob.LendingRateTask"></a>
### OracleJob.LendingRateTask
Fetch the lending rates for various Solana protocols
| Field | Type | Label | Description |
| ---------- | ------------------------------------------------------------------- | -------- | ------------------------------------------------------------- |
| protocol | string | optional | 01, apricot, francium, jet, larix, mango, port, solend, tulip |
| asset_mint | string | optional | A token mint address supported by the chosen protocol |
| ----- | ---- | ----- | ----------- |
| protocol | [string](#string) | optional | 01, apricot, francium, jet, larix, mango, port, solend, tulip |
| asset_mint | [string](#string) | optional | A token mint address supported by the chosen protocol |
| field | [OracleJob.LendingRateTask.Field](#OracleJob.LendingRateTask.Field) | optional | |
<a name=".OracleJob.LpExchangeRateTask"></a>
### OracleJob.LpExchangeRateTask
Fetch the current swap price for a given liquidity pool
| Field | Type | Label | Description |
| ---------------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------- |
| in_token_address | string | optional | Not Used |
| out_token_address | string | optional | Not Used |
| mercurial_pool_address | string | optional | Mercurial finance pool address. A full list can be found here: https://github.com/mercurial-finance/stable-swap-n-pool-js |
| saber_pool_address | string | optional | Saber pool address. A full list can be found here: https://github.com/saber-hq/saber-registry-dist |
| orca_pool_token_mint_address | string | optional | Orca pool address. A full list can be found here: https://www.orca.so/pools |
| raydium_pool_address | string | optional | The Raydium liquidity pool ammId. A full list can be found here: https://sdk.raydium.io/liquidity/mainnet.json |
| ----- | ---- | ----- | ----------- |
| in_token_address | [string](#string) | optional | Not Used |
| out_token_address | [string](#string) | optional | Not Used |
| mercurial_pool_address | [string](#string) | optional | Mercurial finance pool address. A full list can be found here: https://github.com/mercurial-finance/stable-swap-n-pool-js |
| saber_pool_address | [string](#string) | optional | Saber pool address. A full list can be found here: https://github.com/saber-hq/saber-registry-dist |
| orca_pool_token_mint_address | [string](#string) | optional | Orca pool address. A full list can be found here: https://www.orca.so/pools |
| raydium_pool_address | [string](#string) | optional | The Raydium liquidity pool ammId. A full list can be found here: https://sdk.raydium.io/liquidity/mainnet.json |
<a name=".OracleJob.LpTokenPriceTask"></a>
### OracleJob.LpTokenPriceTask
Fetch LP token price info from a number of supported exchanges.
| Field | Type | Label | Description |
| ---------------------- | ----------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| mercurial_pool_address | string | optional | Mercurial finance pool address. A full list can be found here: https://github.com/mercurial-finance/stable-swap-n-pool-js |
| saber_pool_address | string | optional | Saber pool address. A full list can be found here: https://github.com/saber-hq/saber-registry-dist |
| orca_pool_address | string | optional | Orca pool address. A full list can be found here: https://www.orca.so/pools |
| raydium_pool_address | string | optional | The Raydium liquidity pool ammId. A full list can be found here: https://sdk.raydium.io/liquidity/mainnet.json |
| price_feed_addresses | string | repeated | A list of Switchboard aggregator accounts used to calculate the fair LP price. This ensures the price is based on the previous round to mitigate flash loan price manipulation. |
| ----- | ---- | ----- | ----------- |
| mercurial_pool_address | [string](#string) | optional | Mercurial finance pool address. A full list can be found here: https://github.com/mercurial-finance/stable-swap-n-pool-js |
| saber_pool_address | [string](#string) | optional | Saber pool address. A full list can be found here: https://github.com/saber-hq/saber-registry-dist |
| orca_pool_address | [string](#string) | optional | Orca pool address. A full list can be found here: https://www.orca.so/pools |
| raydium_pool_address | [string](#string) | optional | The Raydium liquidity pool ammId. A full list can be found here: https://sdk.raydium.io/liquidity/mainnet.json |
| price_feed_addresses | [string](#string) | repeated | A list of Switchboard aggregator accounts used to calculate the fair LP price. This ensures the price is based on the previous round to mitigate flash loan price manipulation. |
| price_feed_jobs | [OracleJob](#OracleJob) | repeated | |
| use_fair_price | [bool](#bool) | optional | If enabled and price_feed_addresses provided, the oracle will calculate the fair LP price based on the liquidity pool reserves. See our blog post for more information: https://switchboardxyz.medium.com/fair-lp-token-oracles-94a457c50239 |
<a name=".OracleJob.MangoPerpMarketTask"></a>
### OracleJob.MangoPerpMarketTask
Fetch the current price for a Mango perpetual market
| Field | Type | Label | Description |
| ------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| perp_market_address | string | optional | Mainnet address for a mango perpetual market. A full list can be found here: https://github.com/blockworks-foundation/mango-client-v3/blob/main/src/ids.json |
| ----- | ---- | ----- | ----------- |
| perp_market_address | [string](#string) | optional | Mainnet address for a mango perpetual market. A full list can be found here: https://github.com/blockworks-foundation/mango-client-v3/blob/main/src/ids.json |
<a name=".OracleJob.MaxTask"></a>
### OracleJob.MaxTask
Returns the maximum value of all the results returned by the provided subtasks and subjobs.
| Field | Type | Label | Description |
| ----- | --------------------------------- | -------- | ------------------------------------------------------------------ |
| ----- | ---- | ----- | ----------- |
| tasks | [OracleJob.Task](#OracleJob.Task) | repeated | A list of subtasks to process and produce a list of result values. |
| jobs | [OracleJob](#OracleJob) | repeated | A list of subjobs to process and produce a list of result values. |
<a name=".OracleJob.MeanTask"></a>
### OracleJob.MeanTask
Returns the mean of all the results returned by the provided subtasks and subjobs.
| Field | Type | Label | Description |
| ----- | --------------------------------- | -------- | ------------------------------------------------------------------ |
| ----- | ---- | ----- | ----------- |
| tasks | [OracleJob.Task](#OracleJob.Task) | repeated | A list of subtasks to process and produce a list of result values. |
| jobs | [OracleJob](#OracleJob) | repeated | A list of subjobs to process and produce a list of result values. |
<a name=".OracleJob.MedianTask"></a>
### OracleJob.MedianTask
Returns the median of all the results returned by the provided subtasks and subjobs. Nested
tasks must return a Number.
| Field | Type | Label | Description |
| ----------------------- | --------------------------------- | -------- | ------------------------------------------------------------------ |
| ----- | ---- | ----- | ----------- |
| tasks | [OracleJob.Task](#OracleJob.Task) | repeated | A list of subtasks to process and produce a list of result values. |
| jobs | [OracleJob](#OracleJob) | repeated | A list of subjobs to process and produce a list of result values. |
| min_successful_required | int32 | optional | |
| min_successful_required | [int32](#int32) | optional | |
<a name=".OracleJob.MultiplyTask"></a>
### OracleJob.MultiplyTask
This task will multiply a numerical input by a scalar value or by another
aggregate.
| Field | Type | Label | Description |
| ----------------- | ----------------------- | -------- | ------------------------------------------------------------------------------------- |
| scalar | double | optional | Specifies a scalar to multiply by. |
| aggregator_pubkey | string | optional | Specifies an aggregator to multiply by. |
| ----- | ---- | ----- | ----------- |
| scalar | [double](#double) | optional | Specifies a scalar to multiply by. |
| aggregator_pubkey | [string](#string) | optional | Specifies an aggregator to multiply by. |
| job | [OracleJob](#OracleJob) | optional | A job whose result is computed before multiplying our numerical input by that result. |
<a name=".OracleJob.OracleTask"></a>
### OracleJob.OracleTask
Fetch the current price of a Solana oracle protocol
| Field | Type | Label | Description |
| -------------------------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| switchboard_address | string | optional | Mainnet address of a Switchboard V2 feed. Switchboard is decentralized and allows anyone to build their own feed. A small subset of feeds is available here: https://switchboard.xyz/explorer |
| pyth_address | string | optional | Mainnet address for a Pyth feed. A full list can be found here: https://pyth.network/markets/ |
| chainlink_address | string | optional | Devnet address for a Chainlink feed. A full list can be found here: https://docs.chain.link/docs/solana/data-feeds-solana |
| pyth_allowed_confidence_interval | double | optional | Value (as a percentage) that the lower bound confidence interval is of the actual value. Confidence intervals that are larger that this threshold are rejected. |
| ----- | ---- | ----- | ----------- |
| switchboard_address | [string](#string) | optional | Mainnet address of a Switchboard V2 feed. Switchboard is decentralized and allows anyone to build their own feed. A small subset of feeds is available here: https://switchboard.xyz/explorer |
| pyth_address | [string](#string) | optional | Mainnet address for a Pyth feed. A full list can be found here: https://pyth.network/markets/ |
| chainlink_address | [string](#string) | optional | Devnet address for a Chainlink feed. A full list can be found here: https://docs.chain.link/docs/solana/data-feeds-solana |
| pyth_allowed_confidence_interval | [double](#double) | optional | Value (as a percentage) that the lower bound confidence interval is of the actual value. Confidence intervals that are larger that this treshold are rejected. |
<a name=".OracleJob.PancakeswapExchangeRateTask"></a>
### OracleJob.PancakeswapExchangeRateTask
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| in_token_address | [string](#string) | optional | |
| out_token_address | [string](#string) | optional | |
| in_token_amount | [double](#double) | optional | |
| slippage | [double](#double) | optional | |
| provider | [string](#string) | optional | |
<a name=".OracleJob.PerpMarketTask"></a>
### OracleJob.PerpMarketTask
Fetch the current price of a perpetual market
| Field | Type | Label | Description |
| -------------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| mango_market_address | string | optional | Market address for a mango perpetual market. A full list can be found here: https://github.com/blockworks-foundation/mango-client-v3/blob/main/src/ids.json |
| drift_market_address | string | optional | Market address for a drift perpetual market. A full list can be found here: https://github.com/drift-labs/protocol-v1/blob/master/sdk/src/constants/markets.ts |
| zeta_market_address | string | optional | Market address for a zeta perpetual market. |
| zo_market_address | string | optional | Market address for a 01 protocol perpetual market. |
| ----- | ---- | ----- | ----------- |
| mango_market_address | [string](#string) | optional | Market address for a mango perpetual market. A full list can be found here: https://github.com/blockworks-foundation/mango-client-v3/blob/main/src/ids.json |
| drift_market_address | [string](#string) | optional | Market address for a drift perpetual market. A full list can be found here: https://github.com/drift-labs/protocol-v1/blob/master/sdk/src/constants/markets.ts |
| zeta_market_address | [string](#string) | optional | Market address for a zeta perpetual market. |
| zo_market_address | [string](#string) | optional | Market address for a 01 protocol perpetual market. |
<a name=".OracleJob.PowTask"></a>
### OracleJob.PowTask
Take the power of the working value.
| Field | Type | Label | Description |
| ----------------- | ------ | -------- | ---------------------------------------------------------------- |
| scalar | double | optional | Take the working value to the exponent of value. |
| aggregator_pubkey | string | optional | Take the working value to the exponent of the aggregators value. |
| ----- | ---- | ----- | ----------- |
| scalar | [double](#double) | optional | Take the working value to the exponent of value. |
| aggregator_pubkey | [string](#string) | optional | Take the working value to the exponent of the aggregators value. |
<a name=".OracleJob.RegexExtractTask"></a>
### OracleJob.RegexExtractTask
Find a pattern within a string of a previous task and extract a group number.
| Field | Type | Label | Description |
| ------------ | ------ | -------- | ------------------------ |
| pattern | string | optional | Regex pattern to find. |
| group_number | int32 | optional | Group number to extract. |
| ----- | ---- | ----- | ----------- |
| pattern | [string](#string) | optional | Regex pattern to find. |
| group_number | [int32](#int32) | optional | Group number to extract. |
<a name=".OracleJob.SerumSwapTask"></a>
### OracleJob.SerumSwapTask
Fetch the latest swap price on Serum&#39;s orderbook
| Field | Type | Label | Description |
| ------------------ | ------ | -------- | -------------------------------------- |
| serum_pool_address | string | optional | The serum pool to fetch swap price for |
| ----- | ---- | ----- | ----------- |
| serum_pool_address | [string](#string) | optional | The serum pool to fetch swap price for |
<a name=".OracleJob.SplStakePoolTask"></a>
### OracleJob.SplStakePoolTask
| Field | Type | Label | Description |
| ------ | ------ | -------- | ----------------------------------- |
| pubkey | string | optional | The pubkey of the SPL Stake Pool.`` |
| ----- | ---- | ----- | ----------- |
| pubkey | [string](#string) | optional | The pubkey of the SPL Stake Pool.`` |
<a name=".OracleJob.SplTokenParseTask"></a>
### OracleJob.SplTokenParseTask
| Field | Type | Label | Description |
| --------------------- | ------ | -------- | ----------- |
| token_account_address | string | optional | |
| mint_address | string | optional | |
| ----- | ---- | ----- | ----------- |
| token_account_address | [string](#string) | optional | |
| mint_address | [string](#string) | optional | |
<a name=".OracleJob.SubtractTask"></a>
### OracleJob.SubtractTask
This task will subtract a numerical input by a scalar value or by another
aggregate.
| Field | Type | Label | Description |
| ----------------- | ----------------------- | -------- | ------------------------------------------------------------------------------------- |
| scalar | double | optional | Specifies a scalar to subtract by. |
| aggregator_pubkey | string | optional | Specifies an aggregator to subtract by. |
| ----- | ---- | ----- | ----------- |
| scalar | [double](#double) | optional | Specifies a scalar to subtract by. |
| aggregator_pubkey | [string](#string) | optional | Specifies an aggregator to subtract by. |
| job | [OracleJob](#OracleJob) | optional | A job whose result is computed before subtracting our numerical input by that result. |
<a name=".OracleJob.SushiswapExchangeRateTask"></a>
### OracleJob.SushiswapExchangeRateTask
| Field | Type | Label | Description |
| ----------------- | ------ | -------- | ----------- |
| in_token_address | string | optional | |
| out_token_address | string | optional | |
| in_token_amount | uint32 | optional | |
| slippage | double | optional | |
| provider | string | optional | |
| ----- | ---- | ----- | ----------- |
| in_token_address | [string](#string) | optional | |
| out_token_address | [string](#string) | optional | |
| in_token_amount | [double](#double) | optional | |
| slippage | [double](#double) | optional | |
| provider | [string](#string) | optional | |
<a name=".OracleJob.SysclockOffsetTask"></a>
### OracleJob.SysclockOffsetTask
<a name=".OracleJob.Task"></a>
### OracleJob.Task
| Field | Type | Label | Description |
| ---------------------------- | --------------------------------------------------------------------------- | -------- | ----------- |
| ----- | ---- | ----- | ----------- |
| http_task | [OracleJob.HttpTask](#OracleJob.HttpTask) | optional | |
| json_parse_task | [OracleJob.JsonParseTask](#OracleJob.JsonParseTask) | optional | |
| median_task | [OracleJob.MedianTask](#OracleJob.MedianTask) | optional | |
@ -445,108 +659,177 @@ aggregate.
| spl_token_parse_task | [OracleJob.SplTokenParseTask](#OracleJob.SplTokenParseTask) | optional | |
| uniswap_exchange_rate_task | [OracleJob.UniswapExchangeRateTask](#OracleJob.UniswapExchangeRateTask) | optional | |
| sushiswap_exchange_rate_task | [OracleJob.SushiswapExchangeRateTask](#OracleJob.SushiswapExchangeRateTask) | optional | |
| pancakeswap_exchange_rate_task | [OracleJob.PancakeswapExchangeRateTask](#OracleJob.PancakeswapExchangeRateTask) | optional | |
| cache_task | [OracleJob.CacheTask](#OracleJob.CacheTask) | optional | |
| sysclock_offset_task | [OracleJob.SysclockOffsetTask](#OracleJob.SysclockOffsetTask) | optional | |
<a name=".OracleJob.TpsTask"></a>
### OracleJob.TpsTask
<a name=".OracleJob.TwapTask"></a>
### OracleJob.TwapTask
Takes a twap over a set period for a certain aggregator.
| Field | Type | Label | Description |
| -------------------------- | ------------- | -------- | -------------------------------------------------------------------- |
| aggregator_pubkey | string | optional | The target aggregator for the TWAP. |
| period | int32 | optional | Period, in seconds, the twap should account for |
| ----- | ---- | ----- | ----------- |
| aggregator_pubkey | [string](#string) | optional | The target aggregator for the TWAP. |
| period | [int32](#int32) | optional | Period, in seconds, the twap should account for |
| weight_by_propagation_time | [bool](#bool) | optional | Weight samples by their propagation time |
| min_samples | uint32 | optional | Minimum number of samples in the history to calculate a valid result |
| ending_unix_timestamp | int32 | optional | Ending unix timestamp to collect values up to |
| min_samples | [uint32](#uint32) | optional | Minimum number of samples in the history to calculate a valid result |
| ending_unix_timestamp | [int32](#int32) | optional | Ending unix timestamp to collect values up to |
<a name=".OracleJob.UniswapExchangeRateTask"></a>
### OracleJob.UniswapExchangeRateTask
| Field | Type | Label | Description |
| ----------------- | ------ | -------- | ----------- |
| in_token_address | string | optional | |
| out_token_address | string | optional | |
| in_token_amount | uint32 | optional | |
| slippage | double | optional | |
| provider | string | optional | |
| ----- | ---- | ----- | ----------- |
| in_token_address | [string](#string) | optional | |
| out_token_address | [string](#string) | optional | |
| in_token_amount | [double](#double) | optional | |
| slippage | [double](#double) | optional | |
| provider | [string](#string) | optional | |
<a name=".OracleJob.ValueTask"></a>
### OracleJob.ValueTask
Returns a specified value.
| Field | Type | Label | Description |
| ----------------- | ------ | -------- | ----------------------------------------------- |
| value | double | optional | The value that will be returned from this task. |
| aggregator_pubkey | string | optional | Specifies an aggregator to pull the value of. |
| ----- | ---- | ----- | ----------- |
| value | [double](#double) | optional | The value that will be returned from this task. |
| aggregator_pubkey | [string](#string) | optional | Specifies an aggregatorr to pull the value of. |
<a name=".OracleJob.WebsocketTask"></a>
### OracleJob.WebsocketTask
Opens and maintains a websocket for light speed data retrieval.
| Field | Type | Label | Description |
| -------------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
| url | string | optional | The websocket url. |
| subscription | string | optional | The websocket message to notify of a new subscription. |
| max_data_age_seconds | int32 | optional | Minimum amount of time required between when the horses are taking out. |
| filter | string | optional | Incoming message JSONPath filter. Example: &#34;$[?(@.channel == &#39;ticker&#39; &amp;&amp; @.market == &#39;BTC/USD&#39;)]&#34; |
| ----- | ---- | ----- | ----------- |
| url | [string](#string) | optional | The websocket url. |
| subscription | [string](#string) | optional | The websocket message to notify of a new subscription. |
| max_data_age_seconds | [int32](#int32) | optional | Minimum amount of time required between when the horses are taking out. |
| filter | [string](#string) | optional | Incoming message JSONPath filter. Example: &#34;$[?(@.channel == &#39;ticker&#39; &amp;&amp; @.market == &#39;BTC/USD&#39;)]&#34; |
<a name=".OracleJob.XStepPriceTask"></a>
### OracleJob.XStepPriceTask
| Field | Type | Label | Description |
| ---------------------- | --------------------------------------------- | -------- | ---------------------------------------------------------------------- |
| ----- | ---- | ----- | ----------- |
| step_job | [OracleJob.MedianTask](#OracleJob.MedianTask) | optional | median task containing the job definitions to fetch the STEP/USD price |
| step_aggregator_pubkey | string | optional | existing aggregator pubkey for STEP/USD |
| step_aggregator_pubkey | [string](#string) | optional | existing aggregator pubkey for STEP/USD |
<a name=".OracleJob.CacheTask.Method"></a>
### OracleJob.CacheTask.Method
| Name | Number | Description |
| ---- | ------ | ----------- |
| METHOD_GET | 0 | |
| METHOD_SET | 1 | |
<a name=".OracleJob.HttpTask.Method"></a>
### OracleJob.HttpTask.Method
An enumeration representing the types of HTTP requests available to make.
| Name | Number | Description |
| -------------- | ------ | -------------------------------------------- |
| METHOD_UNKNOWN | 0 | Unset HTTP method will default to METHOD_GET |
| ---- | ------ | ----------- |
| METHOD_UNKOWN | 0 | Unset HTTP method will default to METHOD_GET |
| METHOD_GET | 1 | Perform an HTTP &#39;GET&#39; request. |
| METHOD_POST | 2 | Perform an HTTP &#39;POST&#39; request. |
<a name=".OracleJob.JsonParseTask.AggregationMethod"></a>
### OracleJob.JsonParseTask.AggregationMethod
The methods of combining a list of numerical results.
| Name | Number | Description |
| ---- | ------ | -------------------------------------- |
| ---- | ------ | ----------- |
| NONE | 0 | |
| MIN | 1 | Grab the minimum value of the results. |
| MAX | 2 | Grab the maximum value of the results. |
| SUM | 3 | Sum up all of the results. |
<a name=".OracleJob.LendingRateTask.Field"></a>
### OracleJob.LendingRateTask.Field
| Name | Number | Description |
| ------------------ | ------ | -------------------- |
| ---- | ------ | ----------- |
| FIELD_DEPOSIT_RATE | 0 | deposit lending rate |
| FIELD_BORROW_RATE | 1 | borrow lending rate |
## Scalar Value Types
| .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------- | ----------- | ------- | ---------- | -------------- | ------------------------------ |
| ----------- | ----- | --- | ---- | ------ | -- | -- | --- | ---- |
| <a name="double" /> double | | double | double | float | float64 | double | float | Float |
| <a name="float" /> float | | float | float | float | float32 | float | float | Float |
| <a name="int32" /> int32 | Uses variable-length encoding. Inefficient for encoding negative numbers if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
@ -562,3 +845,4 @@ The methods of combining a list of numerical results.
| <a name="bool" /> bool | | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass |
| <a name="string" /> string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) |
| <a name="bytes" /> bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) |

View File

@ -41,6 +41,6 @@ You will need to manually add:
- BACKUP_MAINNET_RPC
- ORACLE_KEY
- GRAFANA_HOSTNAME
- GRAFANA_PASSWORD
- GRAFANA_ADMIN_PASSWORD
- GRAFANA_TLS_CRT
- GRAFANA_TLS_KEY

View File

@ -187,7 +187,7 @@ You will need to collect the following environment variables to inject into the
</td>
</tr>
<tr>
<td>GRAFANA_PASSWORD</td>
<td>GRAFANA_ADMIN_PASSWORD</td>
<td>
<b>
<u>Optional</u>

View File

@ -32,6 +32,6 @@ When the necessary keys and CSR are generated, head over to [gethttpsforfree.com
Your `PROJECTNAME.env` file should now contain
- GRAFANA_HOSTNAME
- GRAFANA_PASSWORD (You can set this to whatever value you want)
- GRAFANA_ADMIN_PASSWORD (You can set this to whatever value you want)
- GRAFANA_TLS_CRT
- GRAFANA_TLS_KEY

View File

@ -180,6 +180,6 @@ You will need to manually add:
- BACKUP_MAINNET_RPC
- ORACLE_KEY
- GRAFANA_HOSTNAME
- GRAFANA_PASSWORD
- GRAFANA_ADMIN_PASSWORD
- GRAFANA_TLS_CRT
- GRAFANA_TLS_KEY

View File

@ -207,7 +207,7 @@ You will need to collect the following environment variables to inject into the
</td>
</tr>
<tr>
<td>GRAFANA_PASSWORD</td>
<td>GRAFANA_ADMIN_PASSWORD</td>
<td>
<b>
<u>Optional</u>
@ -287,7 +287,7 @@ You will need to manually add:
- BACKUP_MAINNET_RPC
- ORACLE_KEY
- GRAFANA_HOSTNAME
- GRAFANA_PASSWORD
- GRAFANA_ADMIN_PASSWORD
- GRAFANA_TLS_CRT
- GRAFANA_TLS_KEY
@ -469,7 +469,7 @@ You will need to manually add:
- BACKUP_MAINNET_RPC
- ORACLE_KEY
- GRAFANA_HOSTNAME
- GRAFANA_PASSWORD
- GRAFANA_ADMIN_PASSWORD
- GRAFANA_TLS_CRT
- GRAFANA_TLS_KEY
@ -502,7 +502,7 @@ When the necessary keys and CSR are generated, head over to [gethttpsforfree.com
Your `PROJECTNAME.env` file should now contain
- GRAFANA_HOSTNAME
- GRAFANA_PASSWORD (You can set this to whatever value you want)
- GRAFANA_ADMIN_PASSWORD (You can set this to whatever value you want)
- GRAFANA_TLS_CRT
- GRAFANA_TLS_KEY

View File

@ -4668,6 +4668,18 @@
"@svgr/plugin-jsx" "^6.2.1"
"@svgr/plugin-svgo" "^6.2.0"
"@switchboard-xyz/switchboard-api@^0.2.201":
version "0.2.201"
resolved "https://registry.npmjs.org/@switchboard-xyz/switchboard-api/-/switchboard-api-0.2.201.tgz#d082206d521d24dbcdeb06a77e6637a56ab883eb"
integrity sha512-hlxgeYmO6dbOEcmQzT1SqRxdiCFyVOMpyW4HFPgmPKT0+wSVkjsLc+BKkMGYPDaO0sWMLTJrj0FGhhTsrqd8Mg==
dependencies:
"@solana/web3.js" "^1.17.0"
form-data "^4.0.0"
protobufjs "^6.10.2"
rpc-websockets "^7.4.12"
typedoc "^0.22.15"
ws "^7.4.6"
"@szmarczak/http-timer@^1.1.2":
version "1.1.2"
resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz"
@ -6884,7 +6896,7 @@ combine-promises@^1.1.0:
resolved "https://registry.npmjs.org/combine-promises/-/combine-promises-1.1.0.tgz"
integrity sha512-ZI9jvcLDxqwaXEixOhArm3r7ReIivsXkpbyEWyeOhzz1QS0iSgBPnWvEqvIQtYyamGCYA88gFhmUrs9hrrQ0pg==
combined-stream@^1.0.6, combined-stream@~1.0.6:
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
@ -9169,6 +9181,15 @@ fork-ts-checker-webpack-plugin@^6.5.0:
semver "^7.3.2"
tapable "^1.0.0"
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz"
@ -14600,7 +14621,7 @@ rimraf@^3.0.0, rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
rpc-websockets@^7.4.2:
rpc-websockets@^7.4.12, rpc-websockets@^7.4.2:
version "7.4.18"
resolved "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.4.18.tgz"
integrity sha512-bVu+4qM5CkGVlTqJa6FaAxLbb5uRnyH4te7yjFvoCzbnif7PT4BcvXtNTprHlNvsH+/StB81zUQicxMrUrIomA==
@ -16894,6 +16915,11 @@ ws@^7.0.0, ws@^7.3.1, ws@^7.4.5:
resolved "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz"
integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==
ws@^7.4.6:
version "7.5.8"
resolved "https://registry.npmjs.org/ws/-/ws-7.5.8.tgz#ac2729881ab9e7cbaf8787fe3469a48c5c7f636a"
integrity sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw==
ws@^8.4.2, ws@^8.5.0:
version "8.5.0"
resolved "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz"