updated oclif to v2 for esm support

- uprev @oclif to v2
- cleaned up commands to use async this.parse
- misc command cleanup
- configured lerna publish to skip examples
This commit is contained in:
Conner Gallagher 2022-06-07 21:26:07 -05:00
parent 94b484c39b
commit fa39a4a8c3
173 changed files with 4862 additions and 8959 deletions

16
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,16 @@
version: 2
updates:
- package-ecosystem: "npm"
versioning-strategy: increase
directory: "/"
schedule:
interval: "monthly"
labels:
- "dependencies"
open-pull-requests-limit: 100
pull-request-branch-name:
separator: "-"
ignore:
- dependency-name: "fs-extra"
- dependency-name: "*"
update-types: ["version-update:semver-major"]

11
cli/.editorconfig Normal file
View File

@ -0,0 +1,11 @@
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

View File

@ -1,6 +1 @@
lib
dist
dts
node_modules
scripts
test
/dist

View File

@ -9,6 +9,8 @@
"prettier"
],
"plugins": ["@typescript-eslint", "prettier", "unicorn", "import"],
"parserOptions": { "project": ["./tsconfig.json"] },
"ignorePatterns": ["test/**/*.ts"],
"rules": {
"valid-jsdoc": "off",
"camelcase": "off",

11
cli/.gitignore vendored
View File

@ -3,16 +3,7 @@
/.nyc_output
/dist
/lib
/package-lock.json
/tmp
/yarn.lock
node_modules
switchboardv2_idl*.json
.archive
.keypairs
*-keypair.json
*.schema.json
.data
oclif.manifest.json

12
cli/.mocharc.json Normal file
View File

@ -0,0 +1,12 @@
{
"require": [
"test/helpers/init.js",
"ts-node/register"
],
"watch-extensions": [
"ts"
],
"recursive": true,
"reporter": "spec",
"timeout": 60000
}

8
cli/Dockerfile Normal file
View File

@ -0,0 +1,8 @@
FROM node:16-alpine
WORKDIR /usr/src/sbv2-cli
COPY package.json ./
RUN npm i --legacy-peer-deps
COPY tsconfig.build.json ./tsconfig.json
COPY src ./src
RUN npm run build
RUN ["node", "dist", "print", "GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR"]

4
cli/Dockerfile.sbv2 Normal file
View File

@ -0,0 +1,4 @@
FROM node:16-alpine
ARG SBV2_VERSION=latest
RUN npm i -g @switchboard-xyz/switchboardv2-cli@$SBV2_VERSION
RUN sbv2 print GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR --verbose

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 Switchboard
Copyright (c) 2019 Salesforce
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

File diff suppressed because it is too large Load Diff

17
cli/bin/dev Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env node
const oclif = require('@oclif/core')
const path = require('path')
const project = path.join(__dirname, '..', 'tsconfig.json')
// In dev mode -> use ts-node and dev plugins
process.env.NODE_ENV = 'development'
require('ts-node').register({project})
// In dev mode, always show stack traces
oclif.settings.debug = true;
// Start the CLI
oclif.run().then(oclif.flush).catch(oclif.Errors.handle)

3
cli/bin/dev.cmd Normal file
View File

@ -0,0 +1,3 @@
@echo off
node "%~dp0\dev" %*

View File

@ -1,5 +1,5 @@
#!/usr/bin/env node
require('@oclif/command').run()
.then(require('@oclif/command/flush'))
.catch(require('@oclif/errors/handle'))
const oclif = require('@oclif/core')
oclif.run().then(require('@oclif/core/flush')).catch(require('@oclif/core/handle'))

View File

@ -1,30 +0,0 @@
{
"name": "WHEAT",
"metadata": "",
"oracleRequestBatchSize": 1,
"minOracleResults": 1,
"minJobResults": 1,
"minUpdateDelaySeconds": 300,
"jobs": [
{
"name": "commodities-api WHEAT",
"tasks": [
{
"httpTask": {
"url": "https://www.commodities-api.com/api/latest?access_key=ke9lwg53l34qis22zr2t568f8k32agaewndr3j8mvzr33ys9wixhrudh73fj&base=USD&symbols=WHEAT"
}
},
{
"jsonParseTask": {
"path": "$.data.rates.WHEAT"
}
},
{
"powTask": {
"scalar": -1
}
}
]
}
]
}

View File

@ -1,15 +0,0 @@
{
"name": "FtxCom MNGO/USD",
"tasks": [
{
"httpTask": {
"url": "https://ftx.com/api/markets/mngo/usd"
}
},
{
"jsonParseTask": {
"path": "$.result.price"
}
}
]
}

View File

@ -1,10 +0,0 @@
{
"name": "Serum SOL/USDC",
"tasks": [
{
"serumSwapTask": {
"serumPoolAddress": "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT"
}
}
]
}

View File

@ -1,60 +0,0 @@
{
"name": "test_1",
"metadata": "queue test_1",
"minStake": 0,
"minUpdateDelaySeconds": 10,
"cranks": [
{
"name": "crank-1",
"maxRows": 125
},
{
"name": "crank-2",
"maxRows": 150
}
],
"oracles": 1,
"aggregators": [
{
"name": "MNGO_USD",
"metadata": "",
"crank": "crank-1",
"oracleRequestBatchSize": 1,
"minOracleResults": 1,
"minJobResults": 1,
"minUpdateDelaySeconds": 6,
"jobs": [
{
"name": "FtxCom MNGO/USD",
"tasks": [
{
"httpTask": {
"url": "https://ftx.com/api/markets/mngo/usd"
}
},
{
"jsonParseTask": {
"path": "$.result.price"
}
}
]
},
{
"name": "Raydium MNGO/USDC",
"tasks": [
{
"httpTask": {
"url": "https://api.raydium.io/pairs"
}
},
{
"jsonParseTask": {
"path": "$[?(@.name == 'MNGO-USDC')].price"
}
}
]
}
]
}
]
}

View File

@ -1,7 +1,13 @@
{
"name": "@switchboard-xyz/switchboardv2-cli",
"version": "0.2.0",
"description": "command line tool to interact with switchboard v2",
"version": "0.1.28",
"author": "gallynaut @gallynaut",
"bin": {
"sbv2": "./bin/run"
},
"homepage": "https://docs.switchboard.xyz",
"main": "dist/index.js",
"license": "MIT",
"repository": {
"type": "git",
@ -9,17 +15,75 @@
"directory": "cli"
},
"bugs": "https://github.com/switchboard-xyz/switchboard-v2/tree/main/cli/issues",
"homepage": "https://docs.switchboard.xyz",
"bin": {
"sbv2": "./bin/run"
"files": [
"/bin",
"/dist",
"/npm-shrinkwrap.json",
"/oclif.manifest.json"
],
"dependencies": {
"@google-cloud/secret-manager": "^3.10.1",
"@oclif/core": "^1",
"@oclif/parser": "^3.8.7",
"@oclif/plugin-help": "^5.1.12",
"@oclif/plugin-plugins": "^2.0.1",
"@oclif/plugin-update": "^3.0.0",
"@oclif/plugin-version": "^1.0.4",
"@oclif/plugin-warn-if-update-available": "^2.0.4",
"@project-serum/anchor": "^0.24.2",
"@solana/spl-token": "^0.1.8",
"@solana/web3.js": "^1.43.5",
"@switchboard-xyz/sbv2-utils": "^0.1.19",
"@switchboard-xyz/switchboard-v2": "^0.0.108",
"assert": "^2.0.0",
"big.js": "^6.2.0",
"bs58": "^5.0.0",
"chalk": "4",
"node-fetch": "^2.6.6",
"winston": "^3.3.3"
},
"devDependencies": {
"@oclif/test": "^2",
"@types/chai": "^4",
"@types/mocha": "^9.0.0",
"@types/node": "^16.9.4",
"@typescript-eslint/eslint-plugin": "^5.5.0",
"@typescript-eslint/parser": "^5.5.0",
"aws-sdk": "^2.1116.0",
"chai": "^4",
"eslint": "^8.3.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-oclif": "^3.1.0",
"eslint-config-oclif-typescript": "^1.0.2",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-unicorn": "^39.0.0",
"globby": "^11",
"mocha": "^9",
"oclif": "^3",
"shx": "^0.3.3",
"ts-node": "^10.2.1",
"tslib": "^2.3.1",
"typescript": "^4.4.3"
},
"oclif": {
"commands": "./lib/commands",
"additionalHelpFlags": [
"-h"
],
"commands": "./dist/commands",
"bin": "sbv2",
"dirname": "@switchboard-xyz/sbv2-cli",
"topicSeparator": " ",
"macos": {
"identifier": "com.sbv2.cli"
},
"plugins": [
"@oclif/plugin-update",
"@oclif/plugin-help",
"@oclif/plugin-version",
"@oclif/plugin-warn-if-update-available"
],
"update": {
"s3": {
"bucket": "sbv2-cli",
@ -40,12 +104,6 @@
"version": "16.14.2"
}
},
"plugins": [
"@oclif/plugin-update",
"@oclif/plugin-help",
"@oclif/plugin-warn-if-update-available",
"@oclif/config"
],
"topics": {
"aggregator": {
"description": "interact with a switchboard aggregator account"
@ -67,90 +125,25 @@
},
"print": {
"description": "find and print a switchboard account by public key for a given cluster"
},
"json": {
"description": "create and manage an oracle queue from a json file"
}
}
},
"scripts": {
"build": "shx rm -rf dist && tsc -b",
"lint": "eslint . --ext .ts --config .eslintrc",
"lint:fix": "eslint . --ext .ts --config .eslintrc --fix",
"postpack": "shx rm -f oclif.manifest.json",
"posttest1": "eslint . --ext .ts --config .eslintrc.json",
"prepack": "yarn build",
"build": "shx rm -rf lib && tsc -b && oclif-dev manifest && oclif-dev readme",
"doc": "oclif-dev readme",
"doc:out": "oclif-dev readme --multi --dir ../website/api/switchboardv2-cli/ && cd ../website/api/switchboardv2-cli/ && for f in *.md; do mv \"${f%.md}.md\" \"_${f%.md}.md\"; sed -i \"\" '1,2d' \"_${f%.md}.md\"; done",
"test:old": "nyc --extension .ts mocha --forbid-only \"test/**/*.test.ts\"",
"test": "echo \"No test script for @switchboard-xyz/switchboardv2-cli\" && exit 0",
"version": "oclif-dev readme && git add README.md",
"fmt": "prettier --write 'src/**/*.ts'",
"lint": "eslint . --ext .ts --config .eslintrc.json --fix"
},
"dependencies": {
"@google-cloud/secret-manager": "^3.10.1",
"@oclif/command": "^1.8.16",
"@oclif/config": "^1.18.2",
"@oclif/parser": "^3.8.6",
"@oclif/plugin-autocomplete": "^1.2.0",
"@oclif/plugin-help": "^5.1.12",
"@oclif/plugin-update": "^1.5.0",
"@oclif/plugin-warn-if-update-available": "^1.7.3",
"@project-serum/anchor": "^0.24.2",
"@solana/spl-token": "^0.1.8",
"@solana/web3.js": "^1.42.0",
"@switchboard-xyz/sbv2-utils": "^0.1.19",
"@switchboard-xyz/switchboard-v2": "^0.0.108",
"assert": "^2.0.0",
"big.js": "^6.1.1",
"bs58": "^5.0.0",
"chalk": "^4.1.2",
"decimal.js": "^10.3.1",
"node-fetch": "^2.6.6",
"readline-sync": "^1.4.10",
"winston": "^3.3.3"
},
"devDependencies": {
"@oclif/dev-cli": "^1.26.5",
"@oclif/test": "^2.0.3",
"@types/mocha": "^5.2.7",
"@types/node": "^17.0.31",
"@typescript-eslint/eslint-plugin": "^5.5.0",
"@typescript-eslint/parser": "^5.5.0",
"aws-sdk": "^2.1116.0",
"eslint": "^8.3.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-oclif": "^3.1.0",
"eslint-config-oclif-typescript": "^1.0.2",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-unicorn": "^39.0.0",
"mocha": "^9.1.3",
"nyc": "^15.1.0",
"prettier": "^2.5.0",
"prettier-plugin-organize-imports": "^2.3.4",
"shx": "^0.3.4",
"ts-node": "^10.4.0",
"tslib": "^2.3.1",
"typescript": "^4.4.4"
"posttest": "yarn lint",
"prepack": "yarn build && oclif manifest && oclif readme",
"test": "mocha --forbid-only \"test/**/*.test.ts\"",
"version": "oclif readme && git add README.md",
"fmt": "prettier --write 'src/**/*.ts'"
},
"engines": {
"node": ">=8.5.0"
"node": ">=12.0.0"
},
"files": [
"bin",
"lib",
"src",
"examples",
"npm-shrinkwrap.json",
"oclif.manifest.json"
],
"keywords": [
"oclif",
"switchboard",
"solana",
"oracle"
"oclif"
],
"main": "lib/index.js",
"types": "lib/index.d.ts"
"types": "dist/index.d.ts"
}

View File

@ -1,4 +1,4 @@
import Command, { flags } from "@oclif/command";
import { Command, Flags } from "@oclif/core";
import { Input } from "@oclif/parser";
import * as anchor from "@project-serum/anchor";
import {
@ -17,7 +17,6 @@ import Big from "big.js";
import chalk from "chalk";
import * as fs from "fs";
import * as path from "path";
import { DEFAULT_KEYPAIR } from "./accounts";
import { CliConfig, ConfigParameter, DEFAULT_CONFIG } from "./config";
import { AuthorityMismatch } from "./types";
import { CommandContext } from "./types/context/context";
@ -27,28 +26,25 @@ import { FAILED_ICON, loadKeypair, toCluster } from "./utils";
abstract class BaseCommand extends Command {
static flags = {
help: flags.help({ char: "h" }),
verbose: flags.boolean({
verbose: Flags.boolean({
char: "v",
description: "log everything",
default: false,
}),
silent: flags.boolean({
silent: Flags.boolean({
char: "s",
description: "suppress cli prompts",
default: false,
}),
mainnetBeta: flags.boolean({
mainnetBeta: Flags.boolean({
description: "WARNING: use mainnet-beta solana cluster",
}),
rpcUrl: flags.string({
rpcUrl: Flags.string({
char: "u",
description: "alternate RPC url",
}),
programId: flags.string({
programId: Flags.string({
description: "alternative Switchboard program ID to interact with",
}),
keypair: flags.string({
keypair: Flags.string({
char: "k",
description:
"keypair that will pay for onchain transactions. defaults to new account authority if no alternate authority provided",
@ -71,16 +67,20 @@ abstract class BaseCommand extends Command {
public program: anchor.Program;
public payerKeypair?: Keypair | undefined = undefined;
public payerKeypair: Keypair;
async init() {
const { flags } = this.parse(<Input<any>>this.constructor);
BaseCommand.flags = flags;
const { flags } = await this.parse((<Input<any>>this.constructor) as any);
BaseCommand.flags = flags as any;
// setup logging
this.silent = flags.silent;
this.verbose = flags.verbose;
const level = flags.silent ? "error" : flags.verbose ? "debug" : "info";
this.silent = (flags as any).silent;
this.verbose = (flags as any).verbose;
const level = (flags as any).silent
? "error"
: (flags as any).verbose
? "debug"
: "info";
const logFilename = path.join(this.config.cacheDir, "log.txt");
const logParameters: LoggerParameters = {
console: {
@ -90,8 +90,8 @@ abstract class BaseCommand extends Command {
level: "debug",
filename: logFilename,
},
silent: flags.silent,
verbose: flags.verbose,
silent: this.silent ?? false,
verbose: this.verbose ?? false,
};
this.logger = new LogProvider(logParameters);
@ -99,10 +99,14 @@ abstract class BaseCommand extends Command {
this.loadConfig();
this.cluster = flags.mainnetBeta
this.cluster = (flags as any).mainnetBeta
? toCluster("mainnet-beta")
: toCluster("devnet");
const url = flags.rpcUrl ?? clusterApiUrl(this.cluster);
const url =
(flags as any).rpcUrl ??
this.getRpcUrl(this.cluster) ??
clusterApiUrl(this.cluster);
try {
this.connection = new Connection(url, {
commitment: "finalized",
@ -120,12 +124,12 @@ abstract class BaseCommand extends Command {
);
}
this.payerKeypair = flags.keypair
? await loadKeypair(flags.keypair)
: DEFAULT_KEYPAIR;
this.payerKeypair = (flags as any).keypair
? await loadKeypair((flags as any).keypair)
: Keypair.fromSeed(new Uint8Array(32).fill(1));
const programId = flags.programId
? new anchor.web3.PublicKey(flags.programId)
const programId = (flags as any).programId
? new anchor.web3.PublicKey((flags as any).programId)
: getSwitchboardPid(this.cluster as "mainnet-beta" | "devnet");
const wallet = new anchor.Wallet(this.payerKeypair);
@ -158,7 +162,7 @@ abstract class BaseCommand extends Command {
};
}
async catch(error, message?: string) {
async catch(error: any, message?: string) {
// fall back to console if logger is not initialized yet
const logger = this.logger ?? console;
@ -185,12 +189,13 @@ abstract class BaseCommand extends Command {
/** Load an authority from a CLI flag and optionally check if it matches the expected account authority */
async loadAuthority(
authorityPath?: string,
authorityPath: string | unknown,
expectedAuthority?: PublicKey
): Promise<Keypair> {
const authority = authorityPath
? await loadKeypair(authorityPath)
: programWallet(this.program);
const authority: Keypair =
typeof authorityPath === "string"
? await loadKeypair(authorityPath)
: programWallet(this.program);
if (expectedAuthority && !expectedAuthority.equals(authority.publicKey)) {
throw new AuthorityMismatch();
@ -202,7 +207,7 @@ abstract class BaseCommand extends Command {
mainnetCheck(): void {
if (this.cluster === "mainnet-beta") {
throw new Error(
`switchboardv2-cli is still in beta, mainnet is disabled for this command.`
"switchboardv2-cli is still in beta, mainnet is disabled for this command."
);
}
}
@ -270,6 +275,8 @@ abstract class BaseCommand extends Command {
this.cliConfig.devnet.rpcUrl ||
clusterApiUrl(toCluster("mainnet-beta"))
);
default:
return clusterApiUrl(toCluster("devnet"));
}
}

View File

@ -1,68 +0,0 @@
import { flags } from "@oclif/command";
import { Input } from "@oclif/parser";
import { Keypair } from "@solana/web3.js";
import * as fs from "fs";
import * as path from "path";
import {
OracleQueueClass,
pubKeyConverter,
pubKeyReviver,
QueueDefinition,
} from "./accounts";
import BaseCommand from "./BaseCommand";
import { loadKeypair } from "./utils";
abstract class JsonBaseCommand extends BaseCommand {
public queueAuthority?: Keypair;
public queueSchemaPath?: string;
public queueSchema?: QueueDefinition;
static flags = {
...BaseCommand.flags,
authority: flags.string({
char: "a",
description:
"alternate keypair that is the authority for the oracle queue",
}),
schema: flags.string({
char: "a",
description: "filesystem path for an oracle queue schema",
}),
};
async init() {
await super.init();
const { flags } = this.parse(<Input<any>>this.constructor);
JsonBaseCommand.flags = flags;
this.queueSchemaPath =
flags.schema && flags.schema.startsWith("/")
? flags.schema
: path.join(process.cwd(), flags.schema);
if (fs.existsSync(this.queueSchemaPath)) {
this.queueSchema = JSON.parse(
fs.readFileSync(this.queueSchemaPath, "utf-8"),
pubKeyReviver
);
}
if (flags.authority) {
this.queueAuthority = flags.authority
? await loadKeypair(flags.authority)
: undefined;
}
}
save(queue: OracleQueueClass) {
const outputString = JSON.stringify(queue, pubKeyConverter, 2);
if (!outputString || outputString.length === 0) {
throw new Error(`failed to save oracle queue (len === 0)`);
}
fs.writeFileSync(this.queueSchemaPath, outputString);
}
}
export default JsonBaseCommand;

View File

@ -1,10 +1,7 @@
/* eslint-disable unicorn/no-process-exit */
/* eslint-disable no-process-exit */
import Command, { flags } from "@oclif/command";
import { Input } from "@oclif/parser";
import { Command, Flags } from "@oclif/core";
import * as anchor from "@project-serum/anchor";
import { ACCOUNT_DISCRIMINATOR_SIZE } from "@project-serum/anchor/dist/cjs/coder";
import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js";
import { clusterApiUrl, Connection, Keypair, PublicKey } from "@solana/web3.js";
import {
prettyPrintAggregator,
prettyPrintCrank,
@ -15,6 +12,8 @@ import {
prettyPrintProgramState,
prettyPrintQueue,
prettyPrintVrf,
SwitchboardAccountType,
SWITCHBOARD_DISCRIMINATOR_MAP,
} from "@switchboard-xyz/sbv2-utils";
import {
AggregatorAccount,
@ -29,11 +28,6 @@ import {
} from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
import * as path from "path";
import {
DEFAULT_KEYPAIR,
SwitchboardAccountType,
SWITCHBOARD_DISCRIMINATOR_MAP,
} from "./accounts";
import { CliConfig } from "./config";
import { FsProvider } from "./types";
import { CommandContext } from "./types/context/context";
@ -47,8 +41,8 @@ export interface ClusterConfigs {
abstract class PrintBaseCommand extends Command {
static flags = {
help: flags.help({ char: "h" }),
verbose: flags.boolean({
help: Flags.help({ char: "h" }),
verbose: Flags.boolean({
char: "v",
description: "log everything",
default: false,
@ -64,8 +58,8 @@ abstract class PrintBaseCommand extends Command {
public clusters: ClusterConfigs;
async init() {
const { flags } = this.parse(<Input<any>>this.constructor);
PrintBaseCommand.flags = flags;
const { flags } = (await this.parse(this.constructor as any)) as any;
// this.flags = flags;
// setup logging
const level = flags.silent ? "error" : flags.verbose ? "debug" : "info";
@ -93,12 +87,12 @@ abstract class PrintBaseCommand extends Command {
devnet: await loadAnchor(
"devnet",
new Connection(clusterApiUrl("devnet")),
DEFAULT_KEYPAIR
Keypair.fromSeed(new Uint8Array(32).fill(1))
),
mainnet: await loadAnchor(
"mainnet-beta",
new Connection(clusterApiUrl("mainnet-beta")),
DEFAULT_KEYPAIR
Keypair.fromSeed(new Uint8Array(32).fill(1))
),
};
}
@ -114,6 +108,7 @@ abstract class PrintBaseCommand extends Command {
this.logger.log(await prettyPrintJob(job));
break;
}
case "AggregatorAccountData": {
const aggregator = new AggregatorAccount({ program, publicKey });
this.logger.log(
@ -121,42 +116,50 @@ abstract class PrintBaseCommand extends Command {
);
break;
}
case "OracleAccountData": {
const oracle = new OracleAccount({ program, publicKey });
this.logger.log(await prettyPrintOracle(oracle, undefined, true));
break;
}
case "PermissionAccountData": {
const permission = new PermissionAccount({ program, publicKey });
this.logger.log(await prettyPrintPermissions(permission, undefined));
this.logger.log(await prettyPrintPermissions(permission));
break;
}
case "LeaseAccountData": {
const lease = new LeaseAccount({ program, publicKey });
this.logger.log(await prettyPrintLease(lease, undefined));
this.logger.log(await prettyPrintLease(lease));
break;
}
case "OracleQueueAccountData": {
const queue = new OracleQueueAccount({ program, publicKey });
this.logger.log(await prettyPrintQueue(queue));
break;
}
case "CrankAccountData": {
const crank = new CrankAccount({ program, publicKey });
this.logger.log(await prettyPrintCrank(crank));
break;
}
case "SbState":
case "ProgramStateAccountData": {
const [programState] = ProgramStateAccount.fromSeed(program);
this.logger.log(await prettyPrintProgramState(programState));
break;
}
case "VrfAccountData": {
const vrfAccount = new VrfAccount({ program, publicKey });
this.logger.log(await prettyPrintVrf(vrfAccount));
break;
}
case "BUFFERxx": {
console.log(`Found buffer account but dont know which one`);
break;
@ -173,6 +176,7 @@ abstract class PrintBaseCommand extends Command {
if (!account) {
throw new Error(`devnet account not found`);
}
const accountDiscriminator = account.data.slice(
0,
ACCOUNT_DISCRIMINATOR_SIZE
@ -228,6 +232,7 @@ abstract class PrintBaseCommand extends Command {
if (message) {
logger.info(chalk.red(`${FAILED_ICON}${message}`));
}
if (error.message) {
const messageLines = error.message.split("\n");
logger.error(messageLines[0]);

View File

@ -1,600 +0,0 @@
import * as anchor from "@project-serum/anchor";
import { Keypair, PublicKey } from "@solana/web3.js";
import {
AggregatorAccount,
OracleQueueAccount,
programWallet,
SwitchboardDecimal,
} from "@switchboard-xyz/switchboard-v2";
import Big from "big.js";
import chalk from "chalk";
import { ProgramStateClass } from "..";
import { AggregatorIllegalRoundOpenCall } from "../../types";
import {
CommandContext,
DEFAULT_CONTEXT,
LogProvider,
} from "../../types/context";
import { getProgramPayer } from "../../utils";
import { JobClass, JobDefinition } from "../job";
import { LeaseClass } from "../lease";
import { PermissionClass } from "../permission";
import { copyAccount, DEFAULT_PUBKEY } from "../types";
import {
anchorBNtoDateTimeString,
buffer2string,
chalkString,
pubKeyConverter,
} from "../utils";
import {
AggregatorAccountData,
AggregatorDefinition,
fromAggregatorJSON,
IAggregatorClass,
} from "./types";
export class AggregatorClass implements IAggregatorClass {
account: AggregatorAccount;
logger: LogProvider;
publicKey: PublicKey;
authorWalletPublicKey: PublicKey;
authorityPublicKey: PublicKey;
oracleRequestBatchSize: number; // REQ, will default to some value
crankPublicKey?: PublicKey;
historyBufferPublicKey?: PublicKey;
expiration: anchor.BN;
forceReportPeriod: anchor.BN;
isLocked?: boolean;
metadata: string;
minRequiredJobResults: number; // REQ, will default to 75% of jobs
minRequiredOracleResults: number; // REQ, will default to 1
minUpdateDelaySeconds: number; // REQ, will default to 30s
name: string;
queuePublicKey: PublicKey;
startAfter: number;
varianceThreshold: SwitchboardDecimal;
jobs: JobClass[];
leaseAccount: LeaseClass;
permissionAccount: PermissionClass;
result: string;
resultTimestamp: string;
private constructor() {}
static async init(
context: CommandContext,
aggregatorAccount: AggregatorAccount,
definition?: AggregatorDefinition
) {
const aggregator = new AggregatorClass();
const wallet = programWallet(aggregatorAccount.program);
aggregator.account = aggregatorAccount;
aggregator.publicKey = aggregator.account.publicKey;
aggregator.logger = context.logger;
await aggregator.loadData();
const queueAccount = new OracleQueueAccount({
program: aggregatorAccount.program,
publicKey: aggregator.queuePublicKey,
});
try {
aggregator.permissionAccount = await PermissionClass.build(
context,
aggregator.account,
queueAccount,
definition && "permissionAccount" in definition
? definition.permissionAccount
: undefined
);
} catch {}
try {
aggregator.leaseAccount = await LeaseClass.build(
context,
aggregator.account,
queueAccount,
definition && "leaseAccount" in definition
? definition.leaseAccount
: undefined
);
} catch {}
try {
const { authority } = await queueAccount.loadData();
if (
aggregator.permissionAccount.permission === "NONE" &&
authority.equals(wallet.publicKey)
) {
aggregator.permissionAccount = await PermissionClass.grantPermission(
context,
aggregator.account,
wallet
);
}
} catch {}
await aggregator.loadData();
return aggregator;
}
public static async build(
context: CommandContext,
program: anchor.Program,
definition: AggregatorDefinition,
queueAccount?: OracleQueueAccount
) {
if (
"account" in definition &&
definition.account instanceof AggregatorAccount
) {
return AggregatorClass.init(context, definition.account, definition);
}
if ("publicKey" in definition) {
return AggregatorClass.fromPublicKey(
context,
program,
definition.publicKey
);
}
if (queueAccount) {
// need queue account defined to create any new aggregators
if ("jobs" in definition) {
if (definition.jobs.length > 0) {
return AggregatorClass.fromJSON(context, queueAccount, definition);
}
throw new Error(
"need to provide at least one job definition to build an aggregator"
);
}
if ("sourcePublicKey" in definition) {
return AggregatorClass.fromCopyAccount(
context,
queueAccount,
definition
);
}
}
throw new Error(`failed to build aggregator from definition ${definition}`);
}
public static async fromAccount(
context: CommandContext,
account: AggregatorAccount
) {
return AggregatorClass.init(context, account);
}
public static fromPublicKey(
context: CommandContext,
program: anchor.Program,
publicKey: PublicKey
) {
const account = new AggregatorAccount({
program,
publicKey,
});
return AggregatorClass.init(context, account);
}
public static async fromJSON(
context: CommandContext,
queueAccount: OracleQueueAccount,
definition: fromAggregatorJSON
) {
if (!definition.jobs || definition.jobs.length === 0)
throw new Error("cannot create an aggregator with no jobs provided");
const aggregatorAccount = await AggregatorAccount.create(
queueAccount.program,
{
authority:
definition.authorityPublicKey ??
getProgramPayer(queueAccount.program).publicKey,
authorWallet:
definition.authorWalletPublicKey ??
(await ProgramStateClass.getProgramTokenAddress(
queueAccount.program
)),
batchSize: definition.oracleRequestBatchSize ?? 1,
expiration: definition.expiration
? new anchor.BN(definition.expiration)
: undefined,
keypair: definition.existingKeypair ?? undefined,
minRequiredOracleResults: definition.minRequiredOracleResults ?? 1,
minRequiredJobResults: definition.minRequiredJobResults ?? 1,
minUpdateDelaySeconds: definition.minUpdateDelaySeconds ?? 30,
name: definition.name ? Buffer.from(definition.name) : undefined,
metadata: definition.metadata
? Buffer.from(definition.metadata)
: undefined,
queueAccount,
}
);
if (aggregatorAccount.keypair) {
context.fs.saveKeypair(aggregatorAccount.keypair);
}
try {
if (definition.historyBuffer) {
const size = Math.floor(definition.historyBuffer);
await aggregatorAccount.setHistoryBuffer({
size,
});
context.logger.debug(`created history buffer of size ${size}`);
}
} catch {}
context.logger.info(
`created aggregator ${definition.name} ${aggregatorAccount.publicKey}`
);
await AggregatorClass.buildJobs(
context,
aggregatorAccount,
definition.jobs
);
return AggregatorClass.init(context, aggregatorAccount, definition);
}
public static async fromCopyAccount(
context: CommandContext,
queueAccount: OracleQueueAccount,
definition: copyAccount
) {
const wallet = programWallet(queueAccount.program);
const sourceAggregator = new AggregatorAccount({
program: queueAccount.program,
publicKey: definition.sourcePublicKey,
});
const source = await AggregatorClass.fromAccount(context, sourceAggregator);
const variance = new Big(source.varianceThreshold.mantissa.toString()).div(
new Big(10).pow(source.varianceThreshold.scale)
);
const targetDefinition: fromAggregatorJSON = {
...source.toJSON(),
authorityPublicKey:
definition.authorityKeypair?.publicKey || wallet.publicKey,
crank: undefined,
expiration: source.expiration.toString(),
forceReportPeriod: source.forceReportPeriod.toString(),
queuePublicKey: queueAccount.publicKey,
varianceThreshold: variance.toNumber(),
};
return AggregatorClass.fromJSON(context, queueAccount, targetDefinition);
}
async addJob(
jobDefinition: JobDefinition,
context = DEFAULT_CONTEXT
): Promise<number> {
const job = await JobClass.build(
context,
this.account.program,
jobDefinition
);
await this.account.addJob(
job.account,
getProgramPayer(this.account.program)
);
this.jobs.push(job);
const newJobIndex = this.jobs.findIndex((existingJob) =>
existingJob.publicKey.equals(job.publicKey)
);
if (newJobIndex === -1) {
throw new Error(`failed to find new job in aggregator`);
}
return newJobIndex;
}
async removeJob(jobKey: PublicKey, authority?: Keypair): Promise<JobClass> {
const removeIndex = this.jobs.findIndex((job) =>
job.publicKey.equals(jobKey)
);
if (removeIndex === -1) {
throw new Error(`failed to remove job with publicKey ${jobKey}`);
}
const removedJob = this.jobs[removeIndex];
await this.account.removeJob(removedJob.account, authority);
this.jobs = this.jobs.filter((job, index) => index !== removeIndex);
return removedJob;
}
async extendLease(
funderTokenAccount: PublicKey,
amount: anchor.BN
): Promise<string> {
return this.leaseAccount.account.extend({
funder: funderTokenAccount,
funderAuthority: getProgramPayer(this.account.program),
loadAmount: amount,
});
}
async addHistoryBuffer(size: number, authority?: Keypair): Promise<string> {
return this.account.setHistoryBuffer({
size,
authority: authority || undefined,
});
}
static async buildJobs(
context: CommandContext,
aggregatorAccount: AggregatorAccount,
jobs: JobDefinition[]
) {
const newJobs: JobClass[] = [];
for await (const jobDefinition of jobs) {
try {
const newJob = await JobClass.build(
context,
aggregatorAccount.program,
jobDefinition
);
newJobs.push(newJob);
await aggregatorAccount.addJob(
newJob.account,
getProgramPayer(aggregatorAccount.program)
);
} catch (error) {
context.logger.log(
`failed to add job to aggregator ${error.message}\r\n${jobDefinition} `
);
}
}
}
async grantPermission(
context: CommandContext,
queueAuthority = programWallet(this.account.program)
): Promise<string> {
if (
this.permissionAccount.permission === "NONE" &&
this.authorityPublicKey.equals(
programWallet(this.account.program).publicKey
)
) {
this.permissionAccount = await PermissionClass.grantPermission(
context,
this.account,
queueAuthority
);
}
return this.permissionAccount.permission;
}
static updateReady(aggregator: AggregatorAccountData) {
const timestamp: anchor.BN = new anchor.BN(Math.round(Date.now() / 1000));
const minUpdateDelay: number = aggregator.minUpdateDelaySeconds;
const currentTimestamp = aggregator.currentRound.roundOpenTimestamp;
const diff = timestamp.sub(currentTimestamp).abs().toNumber();
if (diff < minUpdateDelay) {
throw new AggregatorIllegalRoundOpenCall(
`${diff} / ${minUpdateDelay} sec`
);
}
}
async update(
payoutAddress?: PublicKey,
context = DEFAULT_CONTEXT
): Promise<string> {
AggregatorClass.updateReady(await this.account.loadData());
const oracleQueueAccount = new OracleQueueAccount({
program: this.account.program,
publicKey: this.queuePublicKey,
});
const payoutWallet =
payoutAddress ??
(await ProgramStateClass.getProgramTokenAddress(
this.account.program,
context
));
return this.account.openRound({
oracleQueueAccount,
payoutWallet,
});
}
static async getJobs(
aggregatorAccount: AggregatorAccount,
aggregatorData?: Promise<AggregatorAccountData>,
context = DEFAULT_CONTEXT
): Promise<JobClass[]> {
const data: AggregatorAccountData = aggregatorData
? await aggregatorData
: await aggregatorAccount.loadData();
const jobs: JobClass[] = [];
for await (const jobKey of data.jobPubkeysData) {
if (!jobKey.equals(DEFAULT_PUBKEY)) {
const job = await JobClass.build(context, aggregatorAccount.program, {
publicKey: jobKey,
});
jobs.push(job);
}
}
return jobs;
}
// loads onchain jobs, lease, permission, and account data
async loadData() {
const dataPromise: Promise<AggregatorAccountData> = this.account.loadData();
this.jobs = await AggregatorClass.getJobs(this.account, dataPromise);
const data = await dataPromise;
this.result = "";
try {
this.result = new SwitchboardDecimal(
data.latestConfirmedRound.result.mantissa ?? new anchor.BN(0),
data.latestConfirmedRound.result.scale ?? 0
)
.toBig()
.toString();
} catch {}
this.resultTimestamp = anchorBNtoDateTimeString(
data.latestConfirmedRound.roundOpenTimestamp
);
this.publicKey = this.account.publicKey;
this.authorWalletPublicKey = data.authorWallet;
this.authorityPublicKey = data.authority;
this.crankPublicKey = data.crankPubkey;
this.historyBufferPublicKey = data.historyBuffer;
this.oracleRequestBatchSize = data.oracleRequestBatchSize;
this.expiration = data.expiration;
this.forceReportPeriod = data.forceReportPeriod;
this.isLocked = data.isLocked;
this.metadata = buffer2string(data.metadata as any);
this.minRequiredJobResults = data.minJobResults;
this.minRequiredOracleResults = data.minOracleResults;
this.minUpdateDelaySeconds = data.minUpdateDelaySeconds;
this.name = buffer2string(data.name as any);
this.queuePublicKey = data.queuePubkey;
this.startAfter = data.startAfter.toNumber();
this.varianceThreshold = data.varianceThreshold as SwitchboardDecimal;
}
toJSON(): IAggregatorClass {
return {
name: this.name,
metadata: this.metadata,
publicKey: this.publicKey,
authorityPublicKey: this.authorityPublicKey,
crankPublicKey: this.crankPublicKey,
authorWalletPublicKey: this.authorWalletPublicKey,
oracleRequestBatchSize: this.oracleRequestBatchSize,
expiration: this.expiration,
forceReportPeriod: this.forceReportPeriod,
isLocked: this.isLocked,
minRequiredJobResults: this.minRequiredJobResults,
minRequiredOracleResults: this.minRequiredOracleResults,
minUpdateDelaySeconds: this.minUpdateDelaySeconds,
queuePublicKey: this.queuePublicKey,
startAfter: this.startAfter,
varianceThreshold: this.varianceThreshold,
leaseAccount: this.leaseAccount.toJSON(),
permissionAccount: this.permissionAccount.toJSON(),
jobs: this.jobs ? this.jobs.map((job) => job.toJSON()) : [],
};
}
toString(): string {
return JSON.stringify(this.toJSON(), pubKeyConverter, 2);
}
prettyPrint(all = false, SPACING = 24): string {
let outputString = "";
outputString += chalk.underline(
chalkString("## Aggregator", this.account.publicKey.toString(), SPACING) +
"\r\n"
);
outputString +=
chalkString(
"latestResult",
`${this.result} (${this.resultTimestamp})`,
SPACING
) + "\r\n";
outputString += chalkString("name", this.name, SPACING) + "\r\n";
outputString += chalkString("metadata", this.metadata, SPACING) + "\r\n";
outputString +=
chalkString("authority", this.authorityPublicKey, SPACING) + "\r\n";
outputString +=
chalkString("queuePubkey", this.queuePublicKey, SPACING) + "\r\n";
outputString +=
chalkString("crankPubkey", this.crankPublicKey, SPACING) + "\r\n";
outputString +=
chalkString(
"historyBufferPublicKey",
this.historyBufferPublicKey,
SPACING
) + "\r\n";
outputString +=
chalkString("authorWallet", this.authorWalletPublicKey, SPACING) + "\r\n";
outputString +=
chalkString("jobPubkeysSize", this.jobs.length, SPACING) + "\r\n";
outputString +=
chalkString("minJobResults", this.minRequiredJobResults, SPACING) +
"\r\n";
outputString +=
chalkString(
"oracleRequestBatchSize",
this.oracleRequestBatchSize,
SPACING
) + "\r\n";
outputString +=
chalkString("minOracleResults", this.minRequiredOracleResults, SPACING) +
"\r\n";
outputString +=
chalkString(
"varianceThreshold",
SwitchboardDecimal.from(this.varianceThreshold).toBig().toString(),
SPACING
) + "\r\n";
outputString +=
chalkString(
"minUpdateDelaySeconds",
this.minUpdateDelaySeconds,
SPACING
) + "\r\n";
outputString +=
chalkString(
"forceReportPeriod",
this.forceReportPeriod.toNumber(),
SPACING
) + "\r\n";
outputString += chalkString("isLocked", this.isLocked, SPACING) + "\r\n";
if (all) {
if (this.permissionAccount) {
outputString += `\r\n${this.permissionAccount.prettyPrint(
true,
SPACING
)}`;
}
if (this.leaseAccount) {
outputString += `\r\n${this.leaseAccount.prettyPrint(true, SPACING)}`;
}
for (const job of this.jobs) {
outputString += `\r\n${job.prettyPrint(true)}`;
}
}
return outputString;
}
}

View File

@ -1,2 +0,0 @@
export * from "./aggregator";
export * from "./types";

View File

@ -1,121 +0,0 @@
import * as anchor from "@project-serum/anchor";
import { Keypair, PublicKey } from "@solana/web3.js";
import { SwitchboardDecimal } from "@switchboard-xyz/switchboard-v2";
import {
copyAccount,
fromPublicKey,
fromSwitchboardAccount,
IJobClass,
ILeaseClass,
IPermissionClass,
JobDefinition,
jsonPath,
PermissionDefinition,
} from "..";
export interface AggregatorHistoryRow {
timestamp: anchor.BN;
value: SwitchboardDecimal;
}
export interface AggregatorRound {
numSuccess: number;
numError: number;
isClosed: boolean;
roundOpenSlot: anchor.BN;
roundOpenTimestamp: anchor.BN;
result: SwitchboardDecimal;
stdDeviation: SwitchboardDecimal;
minResponse: SwitchboardDecimal;
maxResponse: SwitchboardDecimal;
oraclePubkeysData: PublicKey[];
mediansData: SwitchboardDecimal[];
currentPayout: anchor.BN[];
mediansFulfilled: boolean[];
errorsFulfilled: boolean[];
}
export interface AggregatorAccountData {
name: Buffer;
metadata: Buffer;
authorWallet: PublicKey;
queuePubkey: PublicKey;
crankPubkey: PublicKey;
oracleRequestBatchSize: number;
minOracleResults: number;
minJobResults: number;
minUpdateDelaySeconds: number;
startAfter: anchor.BN;
varianceThreshold: SwitchboardDecimal;
forceReportPeriod: anchor.BN;
expiration: anchor.BN;
consecutiveFailureCount: anchor.BN;
nextAllowedUpdateTime: anchor.BN;
isLocked: boolean;
schedule: Buffer;
latestConfirmedRound: AggregatorRound;
currentRound: AggregatorRound;
jobPubkeysData: PublicKey[]; // is there a way to define sizeof 16?
jobHashes: Buffer; // Hash[16]
jobPubkeysSize: number;
jobsChecksum: Buffer;
authority: PublicKey;
historyBuffer: PublicKey;
}
/** JSON interface to construct a new Aggregator Account */
export interface fromAggregatorJSON {
authorWalletPublicKey?: PublicKey;
authorityPublicKey?: PublicKey;
oracleRequestBatchSize?: number; // REQ, will default to some value
crank?: string | number | boolean | undefined;
expiration?: string | number; // BN
forceReportPeriod?: string | number; // BN
existingKeypair?: Keypair; // TODO: fs path to keypair
metadata?: string;
minRequiredJobResults?: number; // REQ, will default to 75% of jobs
minRequiredOracleResults?: number; // REQ, will default to 1
minUpdateDelaySeconds?: number; // REQ, will default to 30s
name?: string;
queuePublicKey: PublicKey;
startAfter?: number;
varianceThreshold?: number;
historyBuffer?: number;
// accounts
jobs: JobDefinition[];
leaseAccount?: ILeaseClass;
permissionAccount?: PermissionDefinition;
}
/** Object representing a loaded onchain Aggregator Account */
export interface IAggregatorClass {
publicKey: PublicKey;
authorWalletPublicKey: PublicKey;
historyBufferPublicKey?: PublicKey;
authorityPublicKey: PublicKey;
oracleRequestBatchSize: number; // REQ, will default to some value
crankPublicKey?: PublicKey | string | number | boolean;
expiration: anchor.BN;
forceReportPeriod: anchor.BN;
isLocked?: boolean;
metadata: string;
minRequiredJobResults: number; // REQ, will default to 75% of jobs
minRequiredOracleResults: number; // REQ, will default to 1
minUpdateDelaySeconds: number; // REQ, will default to 30s
name: string;
queuePublicKey: PublicKey;
startAfter: number;
varianceThreshold: SwitchboardDecimal;
jobs: IJobClass[];
leaseAccount?: ILeaseClass;
permissionAccount?: IPermissionClass;
}
/** Type representing the different ways to build an Aggregator Account */
export type AggregatorDefinition =
| fromSwitchboardAccount
| fromPublicKey
| fromAggregatorJSON
| copyAccount
| jsonPath;

View File

@ -1,215 +0,0 @@
import * as anchor from "@project-serum/anchor";
import { PublicKey } from "@solana/web3.js";
import {
CrankAccount,
CrankRow,
OracleQueueAccount,
} from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
import { buffer2string, chalkString, pubKeyConverter } from "../";
import { CommandContext } from "../../types/context";
import { DEFAULT_CONTEXT } from "../../types/context/context";
import { LogProvider } from "../../types/context/logging";
import { AggregatorClass } from "../aggregator";
import { ProgramStateClass } from "../state";
import {
CrankAccountData,
CrankDefinition,
fromCrankJSON,
ICrankClass,
} from "./types";
export class CrankClass implements ICrankClass {
account: CrankAccount;
logger: LogProvider;
publicKey: PublicKey;
queuePublicKey: PublicKey;
maxRows: number;
metadata: string;
name: string;
aggregatorKeys: PublicKey[];
dataBuffer: PublicKey;
size: number;
private constructor() {}
private static async init(
context: CommandContext,
account: CrankAccount
): Promise<CrankClass> {
const crank = new CrankClass();
crank.logger = context.logger;
crank.account = account;
crank.publicKey = crank.account.publicKey;
await crank.loadData();
return crank;
}
static async build(
context,
program: anchor.Program,
definition: CrankDefinition,
queueAccount?: OracleQueueAccount
) {
if ("account" in definition) {
if (definition.account instanceof CrankAccount) {
return CrankClass.fromAccount(context, definition.account);
}
throw new TypeError(`account type should be CrankAccount`);
} else if ("publicKey" in definition) {
return CrankClass.fromPublicKey(context, program, definition.publicKey);
} else if (queueAccount) {
return CrankClass.fromJSON(context, definition, queueAccount);
}
throw new Error("failed to build crank class");
}
public static async fromAccount(
context: CommandContext,
account: CrankAccount
) {
return CrankClass.init(context, account);
}
public static fromPublicKey(
context: CommandContext,
program: anchor.Program,
publicKey: PublicKey
) {
return CrankClass.init(
context,
new CrankAccount({
program,
publicKey,
})
);
}
public static async fromJSON(
context: CommandContext,
definition: fromCrankJSON,
queueAccount: OracleQueueAccount
) {
const { name, metadata, maxRows } = definition;
const account = await CrankAccount.create(queueAccount.program, {
queueAccount,
name: name ? Buffer.from(name) : undefined,
metadata: metadata ? Buffer.from(metadata) : undefined,
maxRows,
});
context.logger.info(`created crank account ${name} ${account.publicKey}`);
return CrankClass.init(context, account);
}
public static async fromDefault(
context: CommandContext,
queueAccount: OracleQueueAccount,
name = ""
) {
return CrankClass.fromJSON(context, { name }, queueAccount);
}
static async turn(
crankAccount: CrankAccount,
context = DEFAULT_CONTEXT
): Promise<string> {
const { queuePubkey } = await crankAccount.loadData();
const queueAccount = new OracleQueueAccount({
program: crankAccount.program,
publicKey: queuePubkey,
});
const { authority } = await queueAccount.loadData();
const authorityTokenWallet = await ProgramStateClass.getProgramTokenAddress(
crankAccount.program
);
const state = await ProgramStateClass.build(crankAccount.program, context);
const popTxn = await crankAccount.pop({
payoutWallet: authorityTokenWallet,
queuePubkey,
queueAuthority: authority,
crank: 0,
queue: 0,
tokenMint: state.tokenMintPublicKey,
});
return popTxn;
}
async push(aggregator: AggregatorClass): Promise<string> {
return this.account.push({ aggregatorAccount: aggregator.account });
}
// loads anchor idl and parses response
async loadData() {
const data: CrankAccountData | any = await this.account.loadData();
this.aggregatorKeys = data.pqData
.slice(0, data.pqSize)
.map((item: CrankRow) => item.pubkey);
this.name = buffer2string(data.name as any);
this.metadata = buffer2string(data.metadata as any);
this.publicKey = this.account.publicKey;
this.queuePublicKey = data.queuePubkey;
this.maxRows = data.maxRows;
this.dataBuffer = data.dataBuffer;
this.size = data.pqSize;
}
toJSON(): ICrankClass {
return {
name: this.name,
metadata: this.metadata,
publicKey: this.publicKey,
queuePublicKey: this.queuePublicKey,
maxRows: this.maxRows,
};
}
toString(): string {
return JSON.stringify(this.toJSON(), pubKeyConverter, 2);
}
prettyPrint(all = false, SPACING = 30): string {
let outputString = "";
outputString += chalk.underline(
chalkString("## Crank", this.publicKey.toString(), SPACING) + "\r\n"
);
outputString += chalkString("name", this.name, SPACING) + "\r\n";
outputString += chalkString("metadata", this.metadata, SPACING) + "\r\n";
outputString +=
chalkString("dataBuffer", this.dataBuffer, SPACING) + "\r\n";
outputString +=
chalkString("queuePubkey", this.queuePublicKey, SPACING) + "\r\n";
outputString +=
chalkString("rows", `${this.size} / ${this.maxRows}`, SPACING) + "\r\n";
if (all) {
outputString +=
chalkString(
"maxRows",
JSON.stringify(this.aggregatorKeys, pubKeyConverter, 2),
SPACING
) + "\r\n";
}
return outputString;
}
}

View File

@ -1,2 +0,0 @@
export * from "./crank";
export * from "./types";

View File

@ -1,40 +0,0 @@
import { PublicKey } from "@solana/web3.js";
import { CrankRow } from "@switchboard-xyz/switchboard-v2";
import { fromPublicKey, fromSwitchboardAccount } from "..";
export interface CrankAccountData {
name: Buffer;
metadata: Buffer;
queuePubkey: PublicKey;
pqSize: number;
maxRows: number;
jitterModifier: number; // u8
pqData: CrankRow[];
dataBuffer: PublicKey;
}
/** JSON interface to construct a new Crank Account */
export interface fromCrankJSON {
name?: string;
metadata?: string;
maxRows?: number;
queuePublicKey?: PublicKey;
}
/** Object representing a loaded onchain Crank Account */
export interface ICrankClass {
publicKey: PublicKey;
maxRows: number;
metadata: string;
name: string;
queuePublicKey?: PublicKey;
}
/** Type representing the different ways to build a Crank Account */
export type CrankDefinition =
| fromSwitchboardAccount
| fromPublicKey
| fromCrankJSON;
/** Type representing the different ways to build a set of Crank Accounts */
export type CrankDefinitions = CrankDefinition[] | number;

View File

@ -1,10 +0,0 @@
export * from "./aggregator";
export * from "./crank";
export * from "./job";
export * from "./lease";
export * from "./oracle";
export * from "./permission";
export * from "./queue";
export * from "./state";
export * from "./types";
export * from "./utils";

View File

@ -1,71 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import { TemplateSource, TEMPLATE_SOURCES } from ".";
import { Ascendex } from "./jobTemplates/ascendex";
import { BinanceCom } from "./jobTemplates/binanceCom";
import { BinanceUs } from "./jobTemplates/binanceUs";
import { Bitfinex } from "./jobTemplates/bitfinex";
import { Bitstamp } from "./jobTemplates/bitstamp";
import { Bittrex } from "./jobTemplates/bittrex";
import { Bonfida } from "./jobTemplates/bonfida";
import { Coinbase } from "./jobTemplates/coinbase";
import { FtxCom } from "./jobTemplates/ftxCom";
import { FtxUs } from "./jobTemplates/ftxUs";
import { Gate } from "./jobTemplates/gate";
import { Huobi } from "./jobTemplates/huobi";
import { Kraken } from "./jobTemplates/kraken";
import { Kucoin } from "./jobTemplates/kucoin";
import { Mexc } from "./jobTemplates/mexc";
import { Okex } from "./jobTemplates/okex";
import { Orca } from "./jobTemplates/orca";
import { Raydium } from "./jobTemplates/raydium";
import { SMB } from "./jobTemplates/smb";
export const buildJobTasks = async (
source: TemplateSource,
id?: string
): Promise<OracleJob.Task[]> => {
switch (source.toLowerCase()) {
case "ascendex":
return new Ascendex(id).tasks();
case "binancecom":
return new BinanceCom(id).tasks();
case "binanceus":
return new BinanceUs(id).tasks();
case "bitfinex":
return new Bitfinex(id).tasks();
case "bitstamp":
return new Bitstamp(id).tasks();
case "bittrex":
return new Bittrex(id).tasks();
case "bonfida":
return new Bonfida(id).tasks();
case "coinbase":
return new Coinbase(id).tasks();
case "ftxcom":
return new FtxCom(id).tasks();
case "ftxus":
return new FtxUs(id).tasks();
case "gate":
return new Gate(id).tasks();
case "huobi":
return new Huobi(id).tasks();
case "kraken":
return new Kraken(id).tasks();
case "kucoin":
return new Kucoin(id).tasks();
case "mexc":
return new Mexc(id).tasks();
case "okex":
return new Okex(id).tasks();
case "orca":
return new Orca(id).tasks();
case "raydium":
return new Raydium(id).tasks();
case "smb":
return new SMB(id).tasks();
default:
throw new Error(
`No job template found for ${source}. Available options:\r\n${TEMPLATE_SOURCES}`
);
}
};

View File

@ -1,4 +0,0 @@
export * from "./buildTemplate";
export * from "./job";
export * from "./types";
export * from "./utils";

View File

@ -1,216 +0,0 @@
import * as anchor from "@project-serum/anchor";
import { PublicKey } from "@solana/web3.js";
import {
JobAccount,
OracleJob,
programWallet,
} from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
import { getUrlFromTask } from ".";
import { buffer2string, chalkString, copyAccount, pubKeyConverter } from "../";
import { CommandContext } from "../../types/context";
import { LogProvider } from "../../types/context/logging";
import { ProgramStateClass } from "../state";
import { buildJobTasks } from "./buildTemplate";
import {
fromJobJSON,
fromJobTemplate,
IJobClass,
JobAccountData,
JobDefinition,
} from "./types";
export class JobClass implements IJobClass {
account: JobAccount;
logger: LogProvider;
publicKey: PublicKey;
authorityWalletPublicKey: PublicKey;
expiration: anchor.BN;
metadata: string;
name: string;
tasks: OracleJob.ITask[];
private constructor() {}
private static async init(
context: CommandContext,
account: JobAccount
): Promise<JobClass> {
const job = new JobClass();
job.account = account;
job.publicKey = job.account.publicKey;
job.logger = context.logger;
await job.loadData();
return job;
}
static async build(
context: CommandContext,
program: anchor.Program,
definition: JobDefinition
): Promise<JobClass> {
if ("account" in definition) {
if (definition.account instanceof JobAccount) {
return JobClass.fromAccount(context, definition.account);
}
throw new TypeError(`account type should be CrankAccount`);
} else if ("publicKey" in definition) {
return JobClass.fromPublicKey(context, program, definition.publicKey);
} else if ("template" in definition) {
return JobClass.fromTemplate(context, program, definition);
} else if ("tasks" in definition) {
return JobClass.fromJSON(context, program, definition);
} else if ("sourcePublicKey" in definition) {
return JobClass.fromCopyAccount(context, program, definition);
} else {
throw new Error(`failed to build job from definition ${definition}`);
}
}
public static fromAccount(context: CommandContext, account: JobAccount) {
return JobClass.init(context, account);
}
public static fromPublicKey(
context: CommandContext,
program: anchor.Program,
publicKey: PublicKey
) {
return JobClass.init(
context,
new JobAccount({
program,
publicKey,
})
);
}
public static async fromTemplate(
context: CommandContext,
program: anchor.Program,
definition: fromJobTemplate
) {
const tasks = await buildJobTasks(definition.template, definition.id);
const job = OracleJob.create({ tasks });
const data = Buffer.from(OracleJob.encodeDelimited(job).finish());
const jobUrl = getUrlFromTask(job);
const wallet = programWallet(program);
const account = await JobAccount.create(program, {
data,
name: Buffer.from(`${jobUrl} ${definition.id}`),
authority: wallet.publicKey,
});
return JobClass.init(context, account);
}
public static async fromJSON(
context: CommandContext,
program: anchor.Program,
definition: fromJobJSON
) {
const {
name,
tasks,
expiration,
authorityWalletPublicKey,
existingKeypair,
} = definition;
const data = Buffer.from(
OracleJob.encodeDelimited(
OracleJob.create({
tasks,
})
).finish()
);
const keypair = existingKeypair ?? anchor.web3.Keypair.generate();
const account = await JobAccount.create(program, {
data: data,
name: name ? Buffer.from(name) : Buffer.from(""),
expiration: expiration ? new anchor.BN(expiration) : undefined,
authority:
authorityWalletPublicKey ??
(await ProgramStateClass.getProgramTokenAddress(program)),
keypair,
});
context.fs.saveKeypair(keypair);
context.logger.info(`created job account ${name} ${account.publicKey}`);
return JobClass.init(context, account);
}
public static async fromCopyAccount(
context: CommandContext,
program: anchor.Program,
definition: copyAccount
) {
const sourceJob = new JobAccount({
program,
publicKey: definition.sourcePublicKey,
});
const jobData: JobAccountData = await sourceJob.loadData();
const account = await JobAccount.create(program, {
data: jobData.data,
name: Buffer.from(jobData.name),
expiration: jobData.expiration
? new anchor.BN(jobData.expiration)
: undefined,
authority: jobData.authority ?? undefined,
});
return JobClass.init(context, account);
}
// loads anchor idl and parses response
async loadData() {
const data: JobAccountData = await this.account.loadData();
this.authorityWalletPublicKey = data.authority;
this.expiration = data.expiration;
this.metadata = buffer2string(data.metadata as any);
this.name = buffer2string(data.name as any);
this.tasks = OracleJob.decodeDelimited(data.data).tasks;
}
toJSON(): IJobClass {
return {
name: this.name,
metadata: this.metadata,
publicKey: this.publicKey,
authorityWalletPublicKey: this.authorityWalletPublicKey,
expiration: this.expiration,
tasks: this.tasks,
};
}
toString(): string {
return JSON.stringify(this.toJSON(), pubKeyConverter, 2);
}
prettyPrint(all = false): string {
let outputString = "";
outputString += chalk.underline(
chalkString("## Job", this.publicKey) + "\r\n"
);
outputString += chalkString("name", this.name) + "\r\n";
outputString +=
chalkString("authority", this.authorityWalletPublicKey) + "\r\n";
outputString +=
chalkString("expiration", this.expiration.toString()) + "\r\n";
outputString +=
chalkString("tasks", JSON.stringify(this.tasks, undefined, 2)) + "\r\n";
return outputString;
}
}

View File

@ -1,42 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class Ascendex extends AbstractJobTemplate {
public id: string;
url(): string {
return `https://ascendex.com/api/pro/v1/ticker?symbol=${this.id}`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
httpTask: OracleJob.HttpTask.create({
url: ``,
}),
}),
OracleJob.Task.create({
medianTask: OracleJob.MedianTask.create({
tasks: [
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: `$.data.ask[0]`,
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: `$.data.bid[0]`,
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: `$.data.close`,
}),
}),
],
}),
}),
];
return tasks;
}
}

View File

@ -1,24 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class BinanceCom extends AbstractJobTemplate {
public id: string;
url(): string {
return `https://www.binance.com/api/v3/ticker/price?symbol=${this.id}`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
httpTask: OracleJob.HttpTask.create({
url: this.url(),
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({ path: "$.price" }),
}),
];
return tasks;
}
}

View File

@ -1,22 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class BinanceUs extends AbstractJobTemplate {
url(): string {
return `https://www.binance.us/api/v3/ticker/price?symbol=${this.id}`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
httpTask: OracleJob.HttpTask.create({
url: this.url(),
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({ path: "$.price" }),
}),
];
return tasks;
}
}

View File

@ -1,44 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class Bitfinex extends AbstractJobTemplate {
public id: string;
url(): string {
const cleanedupId =
this.id.charAt(0).toLowerCase() + this.id.toUpperCase().slice(1);
return `https://api-pub.bitfinex.com/v2/tickers?symbols=${cleanedupId}`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
httpTask: OracleJob.HttpTask.create({
url: this.url(),
}),
}),
OracleJob.Task.create({
medianTask: OracleJob.MedianTask.create({
tasks: [
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$[0][1]",
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$[0][3]",
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$[0][7]",
}),
}),
],
}),
}),
];
return tasks;
}
}

View File

@ -1,36 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class Bitstamp extends AbstractJobTemplate {
public id: string;
url(): string {
return `https://www.bitstamp.net/api/v2/ticker/${this.id.toLowerCase()}`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
httpTask: OracleJob.HttpTask.create({
url: this.url(),
}),
}),
OracleJob.Task.create({
medianTask: OracleJob.MedianTask.create({
tasks: [
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({ path: "$.ask" }),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({ path: "$.bid" }),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({ path: "$.last" }),
}),
],
}),
}),
];
return tasks;
}
}

View File

@ -1,42 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class Bittrex extends AbstractJobTemplate {
public id: string;
url(): string {
return `https://api.bittrex.com/v3/markets/${this.id}/ticker`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
httpTask: OracleJob.HttpTask.create({
url: this.url(),
}),
}),
OracleJob.Task.create({
medianTask: OracleJob.MedianTask.create({
tasks: [
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.askRate",
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.bidRate",
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.lastTradeRate",
}),
}),
],
}),
}),
];
return tasks;
}
}

View File

@ -1,37 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class Bonfida extends AbstractJobTemplate {
public id: string;
url(): string {
return `https://serum-api.bonfida.com/orderbooks/${this.id}`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
httpTask: OracleJob.HttpTask.create({
url: ``,
}),
}),
OracleJob.Task.create({
medianTask: OracleJob.MedianTask.create({
tasks: [
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.data.bids[0].price",
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.data.asks[0].price",
}),
}),
],
}),
}),
];
return tasks;
}
}

View File

@ -1,35 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class Coinbase extends AbstractJobTemplate {
url(): string {
return `wss://ws-feed.pro.coinbase.com`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
websocketTask: OracleJob.WebsocketTask.create({
url: this.url(),
subscription: JSON.stringify({
type: "subscribe",
product_ids: [this.id],
channels: [
"ticker",
{
name: "ticker",
product_ids: [this.id],
},
],
}),
maxDataAgeSeconds: 15,
filter: `$[?(@.type == 'ticker' && @.product_id == '${this.id}')]`,
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({ path: "$.price" }),
}),
];
return tasks;
}
}

View File

@ -1,47 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class FtxCom extends AbstractJobTemplate {
url(): string {
return `wss://ftx.com/ws/`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
websocketTask: OracleJob.WebsocketTask.create({
url: this.url(),
subscription: JSON.stringify({
op: "subscribe",
channel: "ticker",
market: this.id,
}),
maxDataAgeSeconds: 15,
filter: `$[?(@.type == 'update' && @.channel == 'ticker' && @.market == '${this.id}')]`,
}),
}),
OracleJob.Task.create({
medianTask: OracleJob.MedianTask.create({
tasks: [
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.data.bid",
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.data.ask",
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.data.last",
}),
}),
],
}),
}),
];
return tasks;
}
}

View File

@ -1,24 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class FtxUs extends AbstractJobTemplate {
url(): string {
return `https://ftx.us/api/markets/${this.id}`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
httpTask: OracleJob.HttpTask.create({
url: this.url(),
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.result.price",
}),
}),
];
return tasks;
}
}

View File

@ -1,42 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class Gate extends AbstractJobTemplate {
public id: string;
url(): string {
return `https://api.gateio.ws/api/v4/spot/tickers?currency_pair=${this.id}`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
httpTask: OracleJob.HttpTask.create({
url: this.url(),
}),
}),
OracleJob.Task.create({
medianTask: OracleJob.MedianTask.create({
tasks: [
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: `$[0].lowest_ask`,
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: `$[0].highest_bid`,
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: `$[0].last`,
}),
}),
],
}),
}),
];
return tasks;
}
}

View File

@ -1,35 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class Huobi extends AbstractJobTemplate {
url(): string {
return `https://api.huobi.pro/market/detail/merged?symbol=${this.id}`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
httpTask: OracleJob.HttpTask.create({
url: this.url(),
}),
}),
OracleJob.Task.create({
medianTask: OracleJob.MedianTask.create({
tasks: [
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.tick.bid[0]",
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.tick.ask[0]",
}),
}),
],
}),
}),
];
return tasks;
}
}

View File

@ -1,40 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class Kraken extends AbstractJobTemplate {
url(): string {
return `https://api.kraken.com/0/public/Ticker?pair=${this.id}`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
httpTask: OracleJob.HttpTask.create({
url: this.url(),
}),
}),
OracleJob.Task.create({
medianTask: OracleJob.MedianTask.create({
tasks: [
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: `$.result.${this.id}.a[0]`,
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: `$.result.${this.id}.b[0]`,
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: `$.result.${this.id}.c[0]`,
}),
}),
],
}),
}),
];
return tasks;
}
}

View File

@ -1,22 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class Kucoin extends AbstractJobTemplate {
url(): string {
return `https://api.kucoin.com/api/v1/market/orderbook/level1?symbol=${this.id}`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
httpTask: OracleJob.HttpTask.create({
url: this.url(),
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({ path: `$.data.price` }),
}),
];
return tasks;
}
}

View File

@ -1,40 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class Mexc extends AbstractJobTemplate {
url(): string {
return `https://www.mexc.com/open/api/v2/market/ticker?symbol=${this.id}`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
httpTask: OracleJob.HttpTask.create({
url: this.url(),
}),
}),
OracleJob.Task.create({
medianTask: OracleJob.MedianTask.create({
tasks: [
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.data[0].ask",
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.data[0].bid",
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.data[0].last",
}),
}),
],
}),
}),
];
return tasks;
}
}

View File

@ -1,52 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class Okex extends AbstractJobTemplate {
url(): string {
return `wss://ws.okex.com:8443/ws/v5/public`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
websocketTask: OracleJob.WebsocketTask.create({
url: "wss://ws.okex.com:8443/ws/v5/public",
subscription: JSON.stringify({
op: "subscribe",
args: [{ channel: "tickers", instId: this.id }],
}),
maxDataAgeSeconds: 15,
filter:
"$[?(" +
`@.event != 'subscribe' && ` +
`@.arg.channel == 'tickers' && ` +
`@.arg.instId == '${this.id}' && ` +
`@.data[0].instType == 'SPOT' && ` +
`@.data[0].instId == '${this.id}')]`,
}),
}),
OracleJob.Task.create({
medianTask: OracleJob.MedianTask.create({
tasks: [
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.data[0].bidPx",
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.data[0].askPx",
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: "$.data[0].last",
}),
}),
],
}),
}),
];
return tasks;
}
}

View File

@ -1,24 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class Orca extends AbstractJobTemplate {
url(): string {
return `https://api.orca.so/pools`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
httpTask: OracleJob.HttpTask.create({
url: this.url(),
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({
path: `$[?(@.name == '${this.id}[aquafarm]')].price`,
}),
}),
];
return tasks;
}
}

View File

@ -1,21 +0,0 @@
import { PublicKey } from "@solana/web3.js";
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
export async function buildOrcaLpTask(
key: string,
solKey: PublicKey
): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
lpExchangeRateTask: OracleJob.LpExchangeRateTask.create({
saberPoolAddress: key,
}),
}),
OracleJob.Task.create({
multiplyTask: OracleJob.MultiplyTask.create({
aggregatorPubkey: solKey.toBase58(),
}),
}),
];
return tasks;
}

View File

@ -1,22 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class Raydium extends AbstractJobTemplate {
url(): string {
return `https://api.raydium.io/coin/price?coins`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
httpTask: OracleJob.HttpTask.create({
url: this.url(),
}),
}),
OracleJob.Task.create({
jsonParseTask: OracleJob.JsonParseTask.create({ path: `$.${this.id}` }),
}),
];
return tasks;
}
}

View File

@ -1,19 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class SaberLp extends AbstractJobTemplate {
url(): string {
return `${this.id}`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
lpTokenPriceTask: OracleJob.LpTokenPriceTask.create({
saberPoolAddress: this.url(),
}),
}),
];
return tasks;
}
}

View File

@ -1,25 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import AbstractJobTemplate from "./template";
export class SMB extends AbstractJobTemplate {
url(): string {
return `https://market.solanamonkey.business/.netlify/functions/fetchOffers`;
}
async tasks(): Promise<OracleJob.Task[]> {
const tasks = [
OracleJob.Task.create({
httpTask: {
url: `https://market.solanamonkey.business/.netlify/functions/fetchOffers`,
},
}),
OracleJob.Task.create({
jsonParseTask: {
path: `$.offers[?(@.price)].price`,
aggregationMethod: OracleJob.JsonParseTask.AggregationMethod.MIN,
},
}),
];
return tasks;
}
}

View File

@ -1,17 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
export function solanartFloorPrice(projectId: string): Array<OracleJob.Task> {
return [
OracleJob.Task.create({
httpTask: {
url: `https://jmccmlyu33.medianetwork.cloud/nft_for_sale?collection=${projectId}`,
},
}),
OracleJob.Task.create({
jsonParseTask: {
path: `$[?(@.price)].price`,
aggregationMethod: OracleJob.JsonParseTask.AggregationMethod.MIN,
},
}),
];
}

View File

@ -1,15 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
abstract class AbstractJobTemplate {
public id: string;
constructor(id: string) {
this.id = id ?? "";
}
abstract url(): string;
abstract tasks(verify?: boolean): Promise<OracleJob.Task[]>;
}
export default AbstractJobTemplate;

View File

@ -1,82 +0,0 @@
import * as anchor from "@project-serum/anchor";
import { Keypair, PublicKey } from "@solana/web3.js";
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import {
copyAccount,
fromPublicKey,
fromSwitchboardAccount,
jsonPath,
} from "../types/types";
export interface JobAccountData {
name: Buffer; // Uint8Array
metadata: Buffer;
authority: PublicKey;
expiration: anchor.BN;
hash: Buffer;
data: Buffer; // ??
referenceCount: number;
}
export const TEMPLATE_SOURCES = [
"ascendex",
"binanceCom",
"binanceUs",
"bitfinex",
"bitstamp",
"bittrex",
"bonfida",
"coinbase",
"ftxCom",
"ftxUs",
"gate",
"huobi",
"kraken",
"kucoin",
"mexc",
"okex",
"orca",
"raydium",
"smb",
] as const;
/** Type representing the different predefined job templates */
export type TemplateSource = typeof TEMPLATE_SOURCES[number];
/** Create a job account from a predefined template */
export interface fromJobTemplate {
template: TemplateSource;
id?: string;
existingKeypair?: Keypair;
name?: string;
}
/** JSON interface to construct a new Job Account */
export interface fromJobJSON {
aggregator?: string | PublicKey; // add by agg name (BTC_USD)
authorityWalletPublicKey?: PublicKey; // Defaults to authority who created
existingKeypair?: Keypair;
expiration?: number;
metadata?: string;
name?: string;
tasks: OracleJob.ITask[];
}
/** Type representing the different ways to build a Job Account */
export type JobDefinition =
| fromSwitchboardAccount
| fromPublicKey
| fromJobJSON
| fromJobTemplate
| copyAccount
| jsonPath;
/** Object representing a loaded onchain Job Account */
export interface IJobClass {
publicKey: PublicKey;
authorityWalletPublicKey: PublicKey;
expiration: anchor.BN;
metadata: string;
name: string;
tasks: OracleJob.ITask[];
}

View File

@ -1,15 +0,0 @@
import { OracleJob } from "@switchboard-xyz/switchboard-v2";
import { URL } from "url";
export const getUrlFromTask = (job: OracleJob): string => {
const { tasks } = job;
const firstTask = tasks[0];
const jobUrl: string = firstTask.httpTask
? firstTask.httpTask.url
: firstTask.websocketTask
? firstTask.websocketTask.url
: "";
if (jobUrl === "") return jobUrl;
const parsedUrl = new URL(jobUrl);
return parsedUrl.hostname;
};

View File

@ -1,2 +0,0 @@
export * from "./lease";
export * from "./types";

View File

@ -1,244 +0,0 @@
import * as anchor from "@project-serum/anchor";
import { PublicKey } from "@solana/web3.js";
import {
AggregatorAccount,
LeaseAccount,
OracleQueueAccount,
programWallet,
} from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
import {
AggregatorAccountData,
chalkString,
LeaseAccountData,
pubKeyConverter,
} from "../";
import { CommandContext } from "../../types/context";
import { LogProvider } from "../../types/context/logging";
import { programHasPayer } from "../../utils";
import { ProgramStateClass } from "../state";
import { ILeaseClass, LeaseDefinition } from "./types";
export class LeaseClass implements ILeaseClass {
account: LeaseAccount;
logger: LogProvider;
publicKey: PublicKey;
aggregatorPublicKey: PublicKey;
escrowPublicKey: PublicKey;
isActive: boolean;
tokenProgramPublicKey: PublicKey;
queuePublicKey: PublicKey;
withdrawAuthorityPublicKey: PublicKey;
escrowBalance: number;
private constructor() {}
private static async init(context: CommandContext, account: LeaseAccount) {
const lease = new LeaseClass();
lease.account = account;
lease.publicKey = lease.account.publicKey;
lease.logger = context.logger;
await lease.loadData();
return lease;
}
public static async build(
context: CommandContext,
aggregatorAccount: AggregatorAccount,
queueAccount: OracleQueueAccount,
definition?: LeaseDefinition
): Promise<LeaseClass> {
// eslint-disable-next-line unicorn/prefer-ternary
if (definition && "account" in definition) {
if (definition.account instanceof LeaseAccount) {
return LeaseClass.fromAccount(context, definition.account);
}
throw new TypeError("account must be an instance of PermissionAccount");
} else if (definition && "publicKey" in definition) {
return LeaseClass.fromPublicKey(
context,
aggregatorAccount.program,
definition.publicKey
);
}
if (programHasPayer(aggregatorAccount.program)) {
return LeaseClass.getOrCreateLeaseAccount(
context,
aggregatorAccount,
queueAccount
);
}
return LeaseClass.getLeaseAccount(context, aggregatorAccount, queueAccount);
}
public static fromAccount(context: CommandContext, account: LeaseAccount) {
return LeaseClass.init(context, account);
}
public static fromPublicKey(
context: CommandContext,
program: anchor.Program,
publicKey: PublicKey
) {
return LeaseClass.init(
context,
new LeaseAccount({
program,
publicKey,
})
);
}
public static async getLeaseAccount(
context: CommandContext,
aggregatorAccount: AggregatorAccount,
queueAccount?: OracleQueueAccount
): Promise<LeaseClass | undefined> {
let leaseAccount: LeaseAccount;
let queue = queueAccount;
if (!queue) {
const agg: AggregatorAccountData = await aggregatorAccount.loadData();
queue = new OracleQueueAccount({
program: aggregatorAccount.program,
publicKey: agg.queuePubkey,
});
}
try {
[leaseAccount] = LeaseAccount.fromSeed(
aggregatorAccount.program,
queueAccount,
aggregatorAccount
);
await leaseAccount.loadData();
return await LeaseClass.init(context, leaseAccount);
} catch {
context.logger.debug(
`no lease account found for ${aggregatorAccount.publicKey}`
);
}
}
public static async getOrCreateLeaseAccount(
context: CommandContext,
aggregatorAccount: AggregatorAccount,
queueAccount?: OracleQueueAccount
): Promise<LeaseClass | undefined> {
let queue = queueAccount;
if (!queue) {
const agg: AggregatorAccountData = await aggregatorAccount.loadData();
queue = new OracleQueueAccount({
program: aggregatorAccount.program,
publicKey: agg.queuePubkey,
});
}
// find existing account
const lease: LeaseClass | undefined = await LeaseClass.getLeaseAccount(
context,
aggregatorAccount,
queue
);
if (lease) return lease;
// if payer, create new
if (programHasPayer(aggregatorAccount.program)) {
const programTokenWallet = await ProgramStateClass.getProgramTokenAddress(
aggregatorAccount.program
);
try {
const leaseAccount = await LeaseAccount.create(
aggregatorAccount.program,
{
aggregatorAccount,
oracleQueueAccount: queueAccount,
loadAmount: new anchor.BN(0),
funder: programTokenWallet,
funderAuthority: programWallet(aggregatorAccount.program),
withdrawAuthority: programWallet(aggregatorAccount.program)
.publicKey,
}
);
return await LeaseClass.init(context, leaseAccount);
} catch (error) {
throw new Error(`failed to create lease account ${error.message}`);
}
}
}
async getBalance(): Promise<number> {
const resp =
await this.account.program.provider.connection.getTokenAccountBalance(
this.escrowPublicKey
);
return Number.parseInt(resp.value.amount, 10);
}
// loads anchor idl and parses response
async loadData() {
const data: LeaseAccountData = await this.account.loadData();
this.publicKey = this.account.publicKey;
this.aggregatorPublicKey = data.aggregator;
this.escrowPublicKey = data.escrow;
this.isActive = data.isActive;
this.tokenProgramPublicKey = data.tokenProgram;
this.queuePublicKey = data.queue;
this.withdrawAuthorityPublicKey = data.withdrawAuthority;
this.escrowBalance = await this.getBalance();
}
toJSON(): ILeaseClass {
return {
publicKey: this.account.publicKey,
aggregatorPublicKey: this.aggregatorPublicKey,
queuePublicKey: this.queuePublicKey,
escrowPublicKey: this.escrowPublicKey,
isActive: this.isActive,
tokenProgramPublicKey: this.tokenProgramPublicKey,
withdrawAuthorityPublicKey: this.withdrawAuthorityPublicKey,
};
}
toString(): string {
return JSON.stringify(this.toJSON(), pubKeyConverter, 2);
}
prettyPrint(all = false, SPACING = 24): string {
let outputString = "";
outputString += chalk.underline(
chalkString("## Lease", this.publicKey, SPACING) + "\r\n"
);
outputString +=
chalkString("escrow", this.escrowPublicKey, SPACING) + "\r\n";
outputString +=
chalkString("escrowBalance", this.escrowBalance, SPACING) + "\r\n";
outputString +=
chalkString(
"withdrawAuthority",
this.withdrawAuthorityPublicKey,
SPACING
) + "\r\n";
outputString += chalkString("queue", this.queuePublicKey, SPACING) + "\r\n";
outputString +=
chalkString("aggregator", this.aggregatorPublicKey, SPACING) + "\r\n";
outputString += chalkString("isActive", this.isActive, SPACING) + "\r\n";
return outputString;
}
}

View File

@ -1,31 +0,0 @@
import * as anchor from "@project-serum/anchor";
import { PublicKey } from "@solana/web3.js";
import { fromPublicKey, fromSwitchboardAccount } from "..";
export interface LeaseAccountData {
escrow: PublicKey;
queue: PublicKey;
aggregator: PublicKey;
tokenProgram: PublicKey;
isActive: boolean;
crankRowCount: number;
createdAt: anchor.BN;
updateCount: anchor.BN;
withdrawAuthority: PublicKey;
}
/** Object representing a loaded onchain Lease Account */
export interface ILeaseClass {
publicKey: PublicKey;
aggregatorPublicKey: PublicKey;
escrowPublicKey: PublicKey;
isActive: boolean;
tokenProgramPublicKey: PublicKey;
queuePublicKey: PublicKey;
withdrawAuthorityPublicKey: PublicKey;
}
export type LeaseDefinition =
| fromSwitchboardAccount
| fromPublicKey
| ILeaseClass;

View File

@ -1,2 +0,0 @@
export * from "./oracle";
export * from "./types";

View File

@ -1,374 +0,0 @@
import * as anchor from "@project-serum/anchor";
import { Keypair, PublicKey } from "@solana/web3.js";
import {
OracleAccount,
OracleQueueAccount,
programWallet,
} from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
import {
anchorBNtoDateTimeString,
buffer2string,
chalkString,
pubKeyConverter,
} from "../";
import { CommandContext, DEFAULT_CONTEXT } from "../../types/context";
import { LogProvider } from "../../types/context/logging";
import { getProgramPayer } from "../../utils";
import { PermissionClass } from "../permission";
import { ProgramStateClass } from "../state";
import {
fromOracleJSON,
IOracleClass,
OracleAccountData,
OracleDefinition,
OracleMetricsData,
} from "./types";
export class OracleClass implements IOracleClass {
account: OracleAccount;
logger: LogProvider;
publicKey: PublicKey;
name: string;
metadata: string;
authorityPublicKey: PublicKey;
tokenAccountPublicKey: PublicKey;
queuePublicKey: PublicKey;
balance: number;
lastHeartbeat: string;
numInUse: number;
metrics: OracleMetricsData;
permissionAccount?: PermissionClass;
private constructor() {}
private static async init(
context: CommandContext,
account: OracleAccount,
definition: OracleDefinition
): Promise<OracleClass> {
const oracle = new OracleClass();
oracle.logger = context.logger;
oracle.account = account;
oracle.publicKey = oracle.account.publicKey;
await oracle.loadData();
const queueAccount = new OracleQueueAccount({
program: account.program,
publicKey: oracle.queuePublicKey,
});
try {
oracle.permissionAccount = await PermissionClass.build(
context,
oracle.account,
queueAccount,
definition && "permissionAccount" in definition
? definition.permissionAccount
: undefined
);
} catch {}
await oracle.loadData();
return oracle;
}
async grantPermission(
context: CommandContext,
queueAuthority = programWallet(this.account.program)
): Promise<string> {
const queueAccount = new OracleQueueAccount({
program: this.account.program,
publicKey: this.queuePublicKey,
});
const { authority } = await queueAccount.loadData();
if (
this.permissionAccount.permission === "NONE" &&
queueAuthority.publicKey.equals(authority)
) {
const anchorWallet = programWallet(this.account.program);
this.permissionAccount = await PermissionClass.grantPermission(
context,
this.account,
anchorWallet
);
}
return this.permissionAccount.permission;
}
static async build(
context: CommandContext,
program: anchor.Program,
definition: OracleDefinition,
queueAccount?: OracleQueueAccount
): Promise<OracleClass> {
if ("account" in definition) {
if (definition.account instanceof OracleAccount) {
return OracleClass.fromAccount(context, definition.account);
}
throw new TypeError("account must be an instance of OracleAccount");
} else if ("publicKey" in definition) {
return OracleClass.fromPublicKey(context, program, definition.publicKey);
} else if (queueAccount) {
return OracleClass.fromJSON(context, queueAccount, definition);
}
throw new Error(
`need to provide oracle queue account to build new oracle account`
);
}
public static async fromAccount(
context: CommandContext,
account: OracleAccount
): Promise<OracleClass> {
return OracleClass.init(context, account, {});
}
public static fromPublicKey(
context: CommandContext,
program: anchor.Program,
publicKey: PublicKey
) {
return OracleClass.init(
context,
new OracleAccount({
program,
publicKey,
}),
{}
);
}
private static async fromJSON(
context: CommandContext,
queueAccount: OracleQueueAccount,
definition: fromOracleJSON
) {
const account = await OracleAccount.create(queueAccount.program, {
queueAccount,
name: definition.name ? Buffer.from(definition.name) : Buffer.from(""),
oracleAuthority: definition.authorityKeypair,
metadata: definition.metadata
? Buffer.from(definition.metadata)
: Buffer.from(""),
});
context.logger.info(
`created oracle account ${definition.name} ${account.publicKey}`
);
return OracleClass.init(context, account, definition);
}
static async fromDefault(
context: CommandContext,
queueAccount: OracleQueueAccount,
name = ""
): Promise<OracleClass> {
return OracleClass.build(
context,
queueAccount.program,
{ name },
queueAccount
);
}
static async getBalance(
oracleAccount: OracleAccount,
tokenAccount?: PublicKey,
context = DEFAULT_CONTEXT
): Promise<number> {
const oracleTokenAccount =
// eslint-disable-next-line unicorn/no-await-expression-member
tokenAccount ?? (await oracleAccount.loadData()).tokenAccount;
const tokenAmount =
await oracleAccount.program.provider.connection.getTokenAccountBalance(
oracleTokenAccount
);
return Number.parseInt(tokenAmount.value.amount, 10);
}
static async withdrawTokens(
context: CommandContext,
oracleAccount: OracleAccount,
amount: number,
withdrawAccount: PublicKey,
authority?: Keypair,
force = false
): Promise<string> {
const { queuePubkey, tokenAccount, oracleAuthority } =
await oracleAccount.loadData();
const authorityKeypair =
authority || getProgramPayer(oracleAccount.program);
if (!oracleAuthority.equals(authorityKeypair.publicKey)) {
throw new Error(
`invalid oracle authority provided (expected) ${oracleAuthority}, (received) ${authority.publicKey}`
);
}
const oracleQueueAccount = new OracleQueueAccount({
program: oracleAccount.program,
publicKey: queuePubkey,
});
const oracleQueueData = await oracleQueueAccount.loadData();
const minStake: number = oracleQueueData.minStake.toNumber();
// check final balance is greater than min stake
const initialOracleBalance = await OracleClass.getBalance(
oracleAccount,
tokenAccount
);
const finalOracleBalance = initialOracleBalance - amount;
if (amount > initialOracleBalance) {
throw new Error(
`requested withdraw amount ${amount} exceeds current balance ${initialOracleBalance}`
);
}
if (!force && minStake > finalOracleBalance)
throw new Error(
`withdrawing will result in your account falling below the minimum stake`
);
// withdraw
const withdrawTxn = await oracleAccount.withdraw({
amount: new anchor.BN(amount),
oracleAuthority: authorityKeypair,
withdrawAccount,
});
return withdrawTxn;
}
static async depositTokens(
context: CommandContext,
oracleAccount: OracleAccount,
amount: number,
funderTokenAccount?: PublicKey
): Promise<string> {
const oracleTokenAccount =
// eslint-disable-next-line unicorn/no-await-expression-member
(await oracleAccount.loadData()).tokenAccount;
const state = await ProgramStateClass.build(oracleAccount.program, context);
const payerTokenAccount =
funderTokenAccount ||
(await ProgramStateClass.getProgramTokenAddress(
oracleAccount.program,
context
));
// check payer has enough funds
const payerTokenBalance =
await oracleAccount.program.provider.connection.getBalance(
payerTokenAccount
);
if (amount > payerTokenBalance)
throw new Error(
`trying to deposit ${amount} tokens but current balance is ${payerTokenBalance}`
);
return state.token.transfer(
payerTokenAccount,
oracleTokenAccount,
programWallet(oracleAccount.program),
[],
amount
);
}
// loads anchor idl and parses response
async loadData() {
const data: OracleAccountData = await this.account.loadData();
this.publicKey = this.account.publicKey;
this.name = buffer2string(data.name as any);
this.metadata = buffer2string(data.metadata as any);
this.authorityPublicKey = data.oracleAuthority;
this.lastHeartbeat = anchorBNtoDateTimeString(data.lastHeartbeat);
this.numInUse = data.numInUse;
this.tokenAccountPublicKey = data.tokenAccount;
this.queuePublicKey = data.queuePubkey;
this.metrics = data.metrics;
this.balance = await OracleClass.getBalance(
this.account,
this.tokenAccountPublicKey
);
}
toJSON(): IOracleClass {
return {
name: this.name,
metadata: this.metadata,
publicKey: this.publicKey,
authorityPublicKey: this.authorityPublicKey,
queuePublicKey: this.queuePublicKey,
tokenAccountPublicKey: this.tokenAccountPublicKey,
permissionAccount: this.permissionAccount.toJSON(),
};
}
toString(): string {
return JSON.stringify(this.toJSON(), pubKeyConverter, 2);
}
prettyPrint(all = false, SPACING = 24): string {
let outputString = "";
outputString += chalk.underline(
chalkString("## Oracle", this.publicKey.toString(), SPACING) + "\r\n"
);
outputString += chalkString("name", this.name, SPACING) + "\r\n";
outputString += chalkString("metadata", this.metadata, SPACING) + "\r\n";
outputString += chalkString("balance", this.balance, SPACING) + "\r\n";
outputString +=
chalkString("oracleAuthority", this.authorityPublicKey, SPACING) + "\r\n";
outputString +=
chalkString("tokenAccount", this.tokenAccountPublicKey, SPACING) + "\r\n";
outputString +=
chalkString("queuePubkey", this.queuePublicKey, SPACING) + "\r\n";
if (this.permissionAccount) {
outputString +=
chalkString(
"permissionAccount",
this.permissionAccount.publicKey || "N/A",
SPACING
) + "\r\n";
outputString +=
chalkString(
"permissions",
this.permissionAccount.permission || "",
SPACING
) + "\r\n";
}
outputString +=
chalkString("lastHeartbeat", this.lastHeartbeat, SPACING) + "\r\n";
outputString += chalkString("numInUse", this.numInUse, SPACING) + "\r\n";
outputString +=
chalkString(
"metrics",
JSON.stringify(this.metrics, undefined, 2),
SPACING
) + "\r\n";
if (all && this.permissionAccount) {
outputString += this.permissionAccount.prettyPrint(all, SPACING);
}
return outputString;
}
}

View File

@ -1,61 +0,0 @@
import * as anchor from "@project-serum/anchor";
import { Keypair, PublicKey } from "@solana/web3.js";
import {
fromPublicKey,
fromSwitchboardAccount,
IPermissionClass,
PermissionDefinition,
} from "..";
export interface OracleMetricsData {
consecutiveSuccess: anchor.BN;
consecutiveError: anchor.BN;
consecutiveDisagreement: anchor.BN;
consecutiveLateResponse: anchor.BN;
consecutiveFailure: anchor.BN;
totalSuccess: anchor.BN;
totalError: anchor.BN;
totalDisagreement: anchor.BN;
totalLateResponse: anchor.BN;
}
export interface OracleAccountData {
name: Buffer;
metadata: Buffer;
oracleAuthority: PublicKey;
lastHeartbeat: anchor.BN;
numInUse: number;
tokenAccount: PublicKey;
queuePubkey: PublicKey;
metrics: OracleMetricsData;
}
/** JSON interface to construct a new Oracle Account */
export interface fromOracleJSON {
name?: string;
metadata?: string;
queuePublicKey?: PublicKey;
tokenAccountPublicKey?: PublicKey;
authorityKeypair?: Keypair;
permissionAccount?: PermissionDefinition;
}
/** Object representing a loaded onchain Oracle Account */
export interface IOracleClass {
publicKey: PublicKey;
name: string;
metadata: string;
queuePublicKey?: PublicKey;
tokenAccountPublicKey?: PublicKey;
authorityPublicKey?: PublicKey;
permissionAccount?: IPermissionClass;
}
/** Type representing the different ways to build an Oracle Account */
export type OracleDefinition =
| fromSwitchboardAccount
| fromPublicKey
| fromOracleJSON;
/** Type representing the different ways to build a set of Oracle Accounts */
export type OracleDefinitions = OracleDefinition[] | number;

View File

@ -1,2 +0,0 @@
export * from "./permission";
export * from "./types";

View File

@ -1,304 +0,0 @@
import * as anchor from "@project-serum/anchor";
import { Keypair, PublicKey } from "@solana/web3.js";
import {
AggregatorAccount,
OracleAccount,
OracleQueueAccount,
PermissionAccount,
SwitchboardPermission,
} from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
import { CommandContext } from "../../types/context";
import { LogProvider } from "../../types/context/logging";
import { programHasPayer } from "../../utils";
import { AggregatorAccountData } from "../aggregator";
import { OracleAccountData } from "../oracle";
import {
anchorBNtoDateTimeString,
chalkString,
pubKeyConverter,
toPermissionString,
} from "../utils";
import {
IPermissionClass,
PermissionAccountData,
PermissionDefinition,
} from "./types";
export class PermissionClass implements IPermissionClass {
account: PermissionAccount;
logger: LogProvider;
publicKey: PublicKey;
authorityPublicKey: PublicKey;
granterPublicKey: PublicKey;
granteePublicKey: PublicKey;
permission: string;
expiration: anchor.BN;
private constructor() {}
private static async init(
context: CommandContext,
account: PermissionAccount
) {
const permission = new PermissionClass();
permission.logger = context.logger;
permission.account = account;
permission.publicKey = permission.account.publicKey;
await permission.loadData();
return permission;
}
static async build(
context: CommandContext,
granteeAccount: AggregatorAccount | OracleAccount,
queueAccount?: OracleQueueAccount,
definition?: PermissionDefinition
): Promise<PermissionClass | undefined> {
// eslint-disable-next-line unicorn/prefer-ternary
if (definition && "account" in definition) {
if (definition.account instanceof PermissionAccount) {
return PermissionClass.fromAccount(context, definition.account);
}
throw new TypeError("account must be an instance of PermissionAccount");
} else if (definition && "publicKey" in definition) {
return PermissionClass.fromPublicKey(
context,
granteeAccount.program,
definition.publicKey
);
}
if (programHasPayer(granteeAccount.program)) {
return PermissionClass.getOrCreatePermissionAccount(
context,
granteeAccount,
queueAccount
);
}
return PermissionClass.getPermissionAccount(
context,
granteeAccount,
queueAccount
);
}
public static fromAccount(
context: CommandContext,
account: PermissionAccount
) {
return PermissionClass.init(context, account);
}
public static fromPublicKey(
context: CommandContext,
program: anchor.Program,
publicKey: PublicKey
) {
return PermissionClass.init(
context,
new PermissionAccount({
program,
publicKey,
})
);
}
static async grantPermission(
context: CommandContext,
granteeAccount: OracleAccount | AggregatorAccount,
granterAuthority: Keypair
): Promise<PermissionClass> {
const permission: PermissionClass | undefined = programHasPayer(
granteeAccount.program
)
? await PermissionClass.getOrCreatePermissionAccount(
context,
granteeAccount
)
: await PermissionClass.getPermissionAccount(context, granteeAccount);
if (permission === undefined) {
throw new Error(
`no payer provided and no existing permission account found for ${granteeAccount.publicKey}`
);
}
if (!permission.authorityPublicKey.equals(granterAuthority.publicKey)) {
throw new Error(
`wrong authority provided to grant permission, expected ${permission.authorityPublicKey}, received ${granterAuthority.publicKey}`
);
}
if (granteeAccount instanceof AggregatorAccount) {
await permission.account.set({
authority: granterAuthority,
enable: true,
permission: SwitchboardPermission.PERMIT_ORACLE_QUEUE_USAGE,
});
await permission.loadData();
return permission;
}
if (granteeAccount instanceof OracleAccount) {
await permission.account.set({
authority: granterAuthority,
enable: true,
permission: SwitchboardPermission.PERMIT_ORACLE_HEARTBEAT,
});
await permission.loadData();
return permission;
}
throw new Error(
`permission grantee account isnt an aggregator or oracle account`
);
}
static async getPermissionAccount(
context: CommandContext,
granteeAccount: OracleAccount | AggregatorAccount,
oracleQueueAccount?: OracleQueueAccount
): Promise<PermissionClass | undefined> {
let queueAccount = oracleQueueAccount;
if (!queueAccount) {
const data: AggregatorAccountData | OracleAccountData =
await granteeAccount.loadData();
queueAccount = new OracleQueueAccount({
program: granteeAccount.program,
publicKey: data.queuePubkey,
});
}
const queueAuthority: anchor.web3.PublicKey =
// eslint-disable-next-line unicorn/no-await-expression-member
new PublicKey((await queueAccount.loadData()).authority);
try {
const [permissionAccount] = PermissionAccount.fromSeed(
granteeAccount.program,
queueAuthority,
queueAccount.publicKey,
granteeAccount.publicKey
);
await permissionAccount.loadData();
context.logger.debug(
`loaded permission account ${permissionAccount.publicKey} from seed for ${granteeAccount.publicKey}`
);
return await PermissionClass.init(context, permissionAccount);
} catch {}
context.logger.debug(
`no permission account found for ${granteeAccount.publicKey}`
);
}
static async getOrCreatePermissionAccount(
context: CommandContext,
granteeAccount: OracleAccount | AggregatorAccount,
oracleQueueAccount?: OracleQueueAccount
): Promise<PermissionClass> {
let queueAccount = oracleQueueAccount;
if (!queueAccount) {
const data: AggregatorAccountData | OracleAccountData =
await granteeAccount.loadData();
queueAccount = new OracleQueueAccount({
program: granteeAccount.program,
publicKey: data.queuePubkey,
});
}
const queueAuthority: anchor.web3.PublicKey =
// eslint-disable-next-line unicorn/no-await-expression-member
new PublicKey((await queueAccount.loadData()).authority);
try {
const [permissionAccount] = PermissionAccount.fromSeed(
granteeAccount.program,
queueAuthority,
queueAccount.publicKey,
granteeAccount.publicKey
);
await permissionAccount.loadData();
context.logger.debug(
`loaded permission account ${permissionAccount.publicKey} from seed for ${granteeAccount.publicKey}`
);
return await PermissionClass.init(context, permissionAccount);
} catch {
try {
const permissionAccount = await PermissionAccount.create(
queueAccount.program,
{
authority: queueAuthority,
grantee: granteeAccount.publicKey,
granter: queueAccount.publicKey,
}
);
await permissionAccount.loadData();
context.logger.debug(
`created new permission account ${permissionAccount.publicKey} for ${granteeAccount.publicKey}`
);
return PermissionClass.init(context, permissionAccount);
} catch (error) {
throw new Error(`failed to create permission account ${error.message}`);
}
}
}
// loads anchor idl and parses response
async loadData() {
const data: PermissionAccountData = await this.account.loadData();
this.publicKey = this.account.publicKey;
this.permission = toPermissionString(data.permissions);
this.authorityPublicKey = data.authority;
this.expiration = data.expiration;
this.granterPublicKey = data.granter;
this.granteePublicKey = data.grantee;
}
toJSON(): IPermissionClass {
return {
publicKey: this.account.publicKey,
permission: this.permission,
authorityPublicKey: this.authorityPublicKey,
expiration: this.expiration,
granterPublicKey: this.granterPublicKey,
granteePublicKey: this.granteePublicKey,
};
}
toString(): string {
return JSON.stringify(this.toJSON(), pubKeyConverter, 2);
}
prettyPrint(all = false, SPACING = 24): string {
let outputString = "";
outputString += chalk.underline(
chalkString("## Permission", this.publicKey, SPACING) + "\r\n"
);
outputString +=
chalkString("authority", this.authorityPublicKey, SPACING) + "\r\n";
outputString +=
chalkString("permissions", this.permission, SPACING) + "\r\n";
outputString +=
chalkString("granter", this.granterPublicKey, SPACING) + "\r\n";
outputString +=
chalkString("grantee", this.granteePublicKey, SPACING) + "\r\n";
outputString +=
chalkString(
"expiration",
anchorBNtoDateTimeString(this.expiration),
SPACING
) + "\r\n";
return outputString;
}
}

View File

@ -1,27 +0,0 @@
import * as anchor from "@project-serum/anchor";
import { PublicKey } from "@solana/web3.js";
import { SwitchboardPermissionValue } from "@switchboard-xyz/switchboard-v2";
import { fromPublicKey, fromSwitchboardAccount } from "..";
export interface PermissionAccountData {
authority: PublicKey;
permissions: SwitchboardPermissionValue;
granter: PublicKey;
grantee: PublicKey;
expiration: anchor.BN;
}
/** Object representing a loaded onchain Permission Account */
export interface IPermissionClass {
publicKey: PublicKey;
authorityPublicKey: PublicKey;
granterPublicKey: PublicKey;
granteePublicKey: PublicKey;
permission: string;
expiration: anchor.BN;
}
export type PermissionDefinition =
| fromSwitchboardAccount
| fromPublicKey
| IPermissionClass;

View File

@ -1,2 +0,0 @@
export * from "./queue";
export * from "./types";

View File

@ -1,540 +0,0 @@
/* eslint-disable max-depth */
import * as anchor from "@project-serum/anchor";
import * as spl from "@solana/spl-token";
import { PublicKey } from "@solana/web3.js";
import {
OracleQueueAccount,
programWallet,
SwitchboardDecimal,
} from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
import { fromPublicKey, fromQueueJSON } from "..";
import { CommandContext } from "../../types/context";
import { LogProvider } from "../../types/context/logging";
import { AggregatorClass, AggregatorDefinition } from "../aggregator";
import { CrankClass, CrankDefinition, CrankDefinitions } from "../crank";
import { OracleClass, OracleDefinition, OracleDefinitions } from "../oracle";
import {
buffer2string,
chalkString,
getArrayOfSizeN,
pubKeyConverter,
} from "../utils";
import {
IOracleQueueClass,
OracleQueueAccountData,
QueueDefinition,
} from "./types";
export class OracleQueueClass implements IOracleQueueClass {
account: OracleQueueAccount;
logger: LogProvider;
publicKey: PublicKey;
authorityPublicKey: PublicKey;
consecutiveFeedFailureLimit: anchor.BN;
consecutiveOracleFailureLimit: anchor.BN;
feedProbationPeriod: number;
metadata: string;
minStake: anchor.BN;
minUpdateDelaySeconds: number;
name: string;
oracleTimeout: anchor.BN;
queueSize: number;
reward: anchor.BN;
slashingEnabled: boolean;
unpermissionedFeedsEnabled: boolean;
unpermissionedVrfEnabled: boolean;
varianceToleranceMultiplier: SwitchboardDecimal;
oracleBuffer: PublicKey;
cranks: CrankClass[] = [];
oracles: OracleClass[] = [];
aggregators: AggregatorClass[] = [];
private constructor() {}
private static async init(
context: CommandContext,
account: OracleQueueAccount,
definition: QueueDefinition
) {
const queue = new OracleQueueClass();
queue.logger = context.logger;
queue.account = account;
queue.publicKey = queue.account.publicKey;
await queue.loadData();
if (definition && "oracles" in definition) {
queue.oracles = await OracleQueueClass.buildOracles(
context,
queue.account,
definition.oracles
);
}
if (definition && "cranks" in definition) {
queue.cranks = await OracleQueueClass.buildCranks(
context,
queue.account,
definition.cranks
);
}
if (definition && "aggregators" in definition) {
queue.logger.debug(
`creating ${definition.aggregators.length} aggregators from json definition`
);
for await (const aggregatorDefinition of definition.aggregators) {
const aggregator = await AggregatorClass.build(
context,
queue.account.program,
aggregatorDefinition,
queue.account
);
await aggregator.grantPermission(context);
queue.aggregators.push(aggregator);
if (
"crank" in aggregatorDefinition &&
queue.cranks &&
queue.cranks.length > 0
) {
const key = aggregatorDefinition.crank;
const crank = await OracleQueueClass.findCrankByKey(
context,
queue.cranks,
key
);
if (typeof key === "object" && !("publicKey" in key)) {
crank.account.push({
aggregatorAccount: aggregator.account,
});
context.logger.debug(
`added aggregator ${aggregator.name} to crank ${key} ${crank.publicKey}`
);
}
aggregator.crankPublicKey = crank.publicKey;
}
}
}
return queue;
}
static async build(
context: CommandContext,
program: anchor.Program,
definition: QueueDefinition
): Promise<OracleQueueClass> {
if ("account" in definition) {
if (definition.account instanceof OracleQueueAccount) {
return OracleQueueClass.fromAccount(context, definition.account);
}
const errorMessage = `found account key in definition file but account is not an instanceof OracleQueueAccount`;
context.logger.error(errorMessage);
throw new Error(errorMessage);
} else if ("publicKey" in definition) {
return OracleQueueClass.fromPublicKey(context, program, definition);
} else if ("name" in definition) {
return OracleQueueClass.fromJSON(context, program, definition);
}
throw new Error("failed to build QueueClass");
}
public static fromAccount(
context: CommandContext,
account: OracleQueueAccount
) {
return OracleQueueClass.init(context, account, { name: "queue" });
}
public static fromPublicKey(
context: CommandContext,
program: anchor.Program,
definition: fromPublicKey
) {
return OracleQueueClass.init(
context,
new OracleQueueAccount({
program,
publicKey: definition.publicKey,
}),
definition
);
}
public static async fromJSON(
context: CommandContext,
program: anchor.Program,
definition: fromQueueJSON
) {
const payer = programWallet(program);
const queueAccount = await OracleQueueAccount.create(program, {
authority: definition.authorityPublicKey ?? payer.publicKey,
name: definition.name ? Buffer.from(definition.name) : undefined,
metadata: definition.metadata
? Buffer.from(definition.metadata)
: undefined,
reward: definition.reward
? new anchor.BN(definition.reward)
: new anchor.BN(0),
minStake: definition.minStake
? new anchor.BN(definition.minStake)
: new anchor.BN(0),
minimumDelaySeconds: definition.minUpdateDelaySeconds,
oracleTimeout: definition.oracleTimeout
? new anchor.BN(definition.oracleTimeout)
: undefined,
slashingEnabled: definition.slashingEnabled,
unpermissionedFeeds: definition.unpermissionedFeedsEnabled,
feedProbationPeriod: definition.feedProbationPeriod,
consecutiveFeedFailureLimit: definition.consecutiveFeedFailureLimit
? new anchor.BN(definition.consecutiveFeedFailureLimit)
: undefined,
consecutiveOracleFailureLimit: definition.consecutiveOracleFailureLimit
? new anchor.BN(definition.consecutiveOracleFailureLimit)
: undefined,
varianceToleranceMultiplier: definition.varianceToleranceMultiplier,
mint: spl.NATIVE_MINT,
});
context.logger.info(`created queue ${queueAccount.publicKey}`);
return OracleQueueClass.init(context, queueAccount, definition);
}
async addCrank(
context: CommandContext,
crankDefinition: CrankDefinition
): Promise<number> {
const crank = await CrankClass.fromJSON(
context,
{
...crankDefinition,
queuePublicKey: this.publicKey,
},
this.account
);
this.cranks.push(crank);
const newCrankIndex = this.cranks.findIndex((c) =>
c.publicKey.equals(crank.publicKey)
);
if (newCrankIndex === -1) {
throw new Error(`failed to find new crank in queue`);
}
return newCrankIndex;
}
static async buildCranks(
context: CommandContext,
queueAccount: OracleQueueAccount,
cranks: CrankDefinitions
): Promise<CrankClass[]> {
const newCranks: CrankClass[] = [];
if (typeof cranks === "number") {
context.logger.debug(`creating ${cranks} cranks`);
for await (const index of getArrayOfSizeN(cranks)) {
const crank = await CrankClass.fromDefault(
context,
queueAccount,
`Crank-${index}` // should we get last crank num and increment?
);
newCranks.push(crank);
}
} else {
context.logger.debug(
`creating ${cranks.length} cranks from json definition`
);
for await (const crankDefinition of cranks) {
const crank = await CrankClass.fromJSON(
context,
{
...crankDefinition,
queuePublicKey: queueAccount.publicKey,
},
queueAccount
);
newCranks.push(crank);
}
}
return newCranks;
}
async addOracle(
context: CommandContext,
oracleDefinition: OracleDefinition
): Promise<number> {
const oracle = await OracleClass.build(
context,
this.account.program,
{
...oracleDefinition,
queuePublicKey: this.publicKey,
},
this.account
);
this.oracles.push(oracle);
const newOracleIndex = this.oracles.findIndex((o) =>
o.publicKey.equals(oracle.publicKey)
);
if (newOracleIndex === -1) {
throw new Error(`failed to find new oracle in queue`);
}
return newOracleIndex;
}
static async buildOracles(
context: CommandContext,
queueAccount: OracleQueueAccount,
oracles: OracleDefinitions
): Promise<OracleClass[]> {
const newOracles: OracleClass[] = [];
if (typeof oracles === "number") {
context.logger.debug(`creating ${oracles} oracles`);
for await (const index of getArrayOfSizeN(oracles)) {
const oracle = await OracleClass.fromDefault(
context,
queueAccount,
`Oracle-${index}`
);
await oracle.grantPermission(context);
newOracles.push(oracle);
}
} else {
context.logger.debug(
`creating ${oracles.length} oracles from json definition`
);
for await (const oracleDefinition of oracles) {
const oracle = await OracleClass.build(
context,
queueAccount.program,
{
...oracleDefinition,
queuePublicKey: queueAccount.publicKey,
},
queueAccount
);
await oracle.grantPermission(context);
newOracles.push(oracle);
}
}
return newOracles;
}
async addAggregator(
context: CommandContext,
aggregatorDefinition: AggregatorDefinition
): Promise<number> {
const aggregator = await AggregatorClass.build(
context,
this.account.program,
aggregatorDefinition,
this.account
);
await aggregator.grantPermission(context);
this.aggregators.push(aggregator);
const newAggregatorIndex = this.aggregators.findIndex((aggregator) =>
aggregator.publicKey.equals(aggregator.publicKey)
);
if (newAggregatorIndex === -1) {
throw new Error(`failed to find new aggregator in queue`);
}
return newAggregatorIndex;
}
private static async findCrankByKey(
context: CommandContext,
cranks: CrankClass[],
key: string | number | boolean | PublicKey
): Promise<CrankClass> {
if (typeof key === "number") {
if (cranks.length >= key) {
const crank = cranks[key];
return crank;
}
throw new Error(
`failed to find crank by key ${key}, length ${cranks.length}`
);
} else if (typeof key === "boolean") {
if (cranks.length > 0) {
const crank = cranks[0];
return crank;
}
throw new Error(`failed to find any cranks`);
} else if (typeof key === "string") {
const foundCrank = cranks.find((c) => c.name === key);
if (foundCrank) {
return foundCrank;
}
try {
const crankKey = new PublicKey(key);
const crank = cranks.find((c) => c.publicKey === crankKey);
if (crank) {
return crank;
}
} catch {}
}
}
static async addAggregatorToCrank(
context: CommandContext,
aggregator: AggregatorClass,
cranks: CrankClass[],
key: string | number | boolean
) {
const crank = await OracleQueueClass.findCrankByKey(context, cranks, key);
crank.account.push({
aggregatorAccount: aggregator.account,
});
context.logger.debug(
`added aggregator ${aggregator.name} to crank ${key} ${crank.publicKey}`
);
aggregator.crankPublicKey = crank.publicKey;
}
// loads anchor idl and parses response
async loadData() {
const data: OracleQueueAccountData = await this.account.loadData();
this.name = buffer2string(data.name as any);
this.metadata = buffer2string(data.metadata as any);
this.authorityPublicKey = data.authority;
this.oracleTimeout = data.oracleTimeout;
this.reward = data.reward;
this.minStake = data.minStake;
this.slashingEnabled = data.slashingEnabled;
this.varianceToleranceMultiplier = data.varianceToleranceMultiplier;
this.feedProbationPeriod = data.feedProbationPeriod;
this.consecutiveFeedFailureLimit = data.consecutiveFeedFailureLimit;
this.consecutiveOracleFailureLimit = data.consecutiveOracleFailureLimit;
this.unpermissionedFeedsEnabled = data.unpermissionedFeedsEnabled;
this.unpermissionedVrfEnabled = data.unpermissionedVrfEnabled;
this.oracleBuffer = data.dataBuffer;
}
// needed to enforce ordering of json output
toJSON(): IOracleQueueClass {
return {
name: this.name,
metadata: this.metadata,
publicKey: this.publicKey,
authorityPublicKey: this.authorityPublicKey,
queueSize: this.queueSize,
minStake: this.minStake,
reward: this.reward,
slashingEnabled: this.slashingEnabled,
unpermissionedFeedsEnabled: this.unpermissionedFeedsEnabled,
unpermissionedVrfEnabled: this.unpermissionedVrfEnabled,
minUpdateDelaySeconds: this.minUpdateDelaySeconds,
consecutiveFeedFailureLimit: this.consecutiveFeedFailureLimit,
feedProbationPeriod: this.feedProbationPeriod,
consecutiveOracleFailureLimit: this.consecutiveOracleFailureLimit,
oracleTimeout: this.oracleTimeout,
varianceToleranceMultiplier: this.varianceToleranceMultiplier,
cranks: this.cranks ? this.cranks.map((crank) => crank.toJSON()) : [],
oracles: this.oracles
? this.oracles.map((oracle) => oracle.toJSON())
: [],
aggregators: this.aggregators
? this.aggregators.map((aggregator) => aggregator.toJSON())
: [],
};
}
toString(): string {
return JSON.stringify(this.toJSON(), pubKeyConverter, 2);
}
prettyPrint(all = false, SPACING = 30): string {
let outputString = "";
outputString += chalk.underline(
chalkString("## Queue", this.account.publicKey, SPACING) + "\r\n"
);
outputString += chalkString("name", this.name, SPACING) + "\r\n";
outputString += chalkString("metadata", this.metadata, SPACING) + "\r\n";
outputString +=
chalkString("oracleBuffer", this.oracleBuffer, SPACING) + "\r\n";
outputString +=
chalkString("authority", this.authorityPublicKey, SPACING) + "\r\n";
outputString +=
chalkString("oracleTimeout", this.oracleTimeout.toString(), SPACING) +
"\r\n";
outputString +=
chalkString("reward", this.reward.toString(), SPACING) + "\r\n";
outputString +=
chalkString("minStake", this.minStake.toString(), SPACING) + "\r\n";
outputString +=
chalkString("slashingEnabled", this.slashingEnabled, SPACING) + "\r\n";
outputString +=
chalkString(
"consecutiveFeedFailureLimit",
this.consecutiveFeedFailureLimit.toString(),
SPACING
) + "\r\n";
outputString +=
chalkString(
"consecutiveOracleFailureLimit",
this.consecutiveOracleFailureLimit.toString(),
SPACING
) + "\r\n";
// outputString += chalkString(
// "varianceToleranceMultiplier",
// this.varianceToleranceMultiplier.toBig().toString(),
// SPACING
// ) + "\r\n";
outputString +=
chalkString(
"feedProbationPeriod",
this.feedProbationPeriod.toString(),
SPACING
) + "\r\n";
outputString +=
chalkString(
"unpermissionedFeedsEnabled",
this.unpermissionedFeedsEnabled.toString(),
SPACING
) + "\r\n";
outputString +=
chalkString(
"unpermissionedVrfEnabled",
this.unpermissionedVrfEnabled.toString(),
SPACING
) + "\r\n";
if (all) {
for (const crank of this.cranks) {
outputString += crank.prettyPrint(true, SPACING);
}
for (const oracle of this.oracles) {
outputString += oracle.prettyPrint(true, SPACING);
}
}
return outputString;
}
}

View File

@ -1,89 +0,0 @@
import * as anchor from "@project-serum/anchor";
import { PublicKey } from "@solana/web3.js";
import { SwitchboardDecimal } from "@switchboard-xyz/switchboard-v2";
import {
AggregatorDefinition,
CrankDefinitions,
fromPublicKey,
fromSwitchboardAccount,
IAggregatorClass,
ICrankClass,
IOracleClass,
OracleDefinitions,
} from "..";
export interface OracleQueueAccountData {
name: Buffer;
metadata: Buffer;
authority: PublicKey;
oracleTimeout: anchor.BN;
reward: anchor.BN;
minStake: anchor.BN;
slashingEnabled: boolean;
varianceToleranceMultiplier: SwitchboardDecimal;
feedProbationPeriod: number;
currIdx: number;
size: number;
gcIdx: number;
consecutiveFeedFailureLimit: anchor.BN;
consecutiveOracleFailureLimit: anchor.BN;
unpermissionedFeedsEnabled: boolean;
unpermissionedVrfEnabled: boolean;
maxSize: number;
dataBuffer: PublicKey;
}
/** JSON interface to construct a new Oracle Queue Account */
export interface fromQueueJSON {
authorityPublicKey?: PublicKey;
consecutiveFeedFailureLimit?: string | number; // BN
consecutiveOracleFailureLimit?: string | number; // BN
feedProbationPeriod?: number;
metadata?: string; // Buffer
minStake?: string | number; // BN
minUpdateDelaySeconds?: number;
// queues should be named for easy lookup
name: string; // Buffer
oracleTimeout?: string | number; // BN
queueSize?: number;
reward?: string | number; // BN
slashingEnabled?: boolean;
unpermissionedFeedsEnabled?: boolean;
unpermissionedVrfEnabled?: boolean;
varianceToleranceMultiplier?: number;
// accounts
cranks?: CrankDefinitions; // can have crank-less queues
oracles?: OracleDefinitions;
aggregators?: AggregatorDefinition[];
}
/** Object representing a loaded onchain Oracle Queue Account */
export interface IOracleQueueClass {
publicKey: PublicKey;
authorityPublicKey: PublicKey;
consecutiveFeedFailureLimit: anchor.BN;
consecutiveOracleFailureLimit: anchor.BN;
feedProbationPeriod: number;
metadata: string; // Buffer
minStake: anchor.BN;
minUpdateDelaySeconds: number;
// queues should be named for easy lookup
name: string; // Buffer
oracleTimeout: anchor.BN;
queueSize: number;
reward: anchor.BN;
slashingEnabled: boolean;
unpermissionedFeedsEnabled?: boolean;
unpermissionedVrfEnabled?: boolean;
varianceToleranceMultiplier: SwitchboardDecimal;
// accounts
cranks?: ICrankClass[];
oracles?: IOracleClass[];
aggregators?: IAggregatorClass[];
}
export type QueueDefinition =
| fromSwitchboardAccount
| fromPublicKey
| fromQueueJSON
| IOracleQueueClass;

View File

@ -1,2 +0,0 @@
export * from "./state";
export * from "./types";

View File

@ -1,131 +0,0 @@
import * as anchor from "@project-serum/anchor";
import { MintInfo, Token } from "@solana/spl-token";
import { PublicKey } from "@solana/web3.js";
import {
ProgramStateAccount,
programWallet,
} from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
import { DEFAULT_CONTEXT } from "../../types/context/context";
import { LogProvider } from "../../types/context/logging";
import { chalkString, pubKeyConverter } from "../utils";
import { IProgramStateClass, ProgramStateData } from "./types";
export class ProgramStateClass implements IProgramStateClass {
account: ProgramStateAccount;
logger: LogProvider;
publicKey: PublicKey;
authorityPublicKey: PublicKey;
tokenMintPublicKey: PublicKey;
tokenVaultPublicKey: PublicKey;
token: Token;
mintInfo: MintInfo;
private constructor() {}
static async build(
program: anchor.Program,
context = DEFAULT_CONTEXT
): Promise<ProgramStateClass> {
const state = new ProgramStateClass();
state.logger = context.logger;
// eslint-disable-next-line unicorn/prefer-ternary
[state.account] = ProgramStateAccount.fromSeed(program);
await state.loadData();
return state;
}
// loads anchor idl and parses response
async loadData() {
const data: ProgramStateData = await this.account.loadData();
this.publicKey = this.account.publicKey;
this.authorityPublicKey = data.authority;
this.tokenMintPublicKey = data.tokenMint;
this.tokenVaultPublicKey = data.tokenVault;
this.token = await this.account.getTokenMint();
this.mintInfo = await this.token.getMintInfo();
}
static async getAssociatedTokenAddress(
program: anchor.Program,
publicKey: PublicKey,
context = DEFAULT_CONTEXT
): Promise<PublicKey> {
const state = await ProgramStateClass.build(program, context);
const account = await state.token.getOrCreateAssociatedAccountInfo(
publicKey
);
return account.address;
}
static async getProgramTokenAddress(
program: anchor.Program,
context = DEFAULT_CONTEXT
): Promise<PublicKey> {
const state = await ProgramStateClass.build(program, context);
const wallet = programWallet(program);
const account = await state.token.getOrCreateAssociatedAccountInfo(
wallet.publicKey
);
return account.address;
}
toJSON(): IProgramStateClass {
return {
publicKey: this.account.publicKey,
authorityPublicKey: this.authorityPublicKey,
tokenMintPublicKey: this.tokenMintPublicKey,
tokenVaultPublicKey: this.tokenVaultPublicKey,
};
}
toString(): string {
return JSON.stringify(this.toJSON(), pubKeyConverter, 2);
}
prettyPrint(): string {
let outputString = "";
outputString += chalk.underline(
chalkString("## Program State", this.publicKey) + "\r\n"
);
outputString +=
chalkString("programId", this.account.program.programId) + "\r\n";
outputString += chalkString("authority", this.authorityPublicKey) + "\r\n";
outputString += chalkString("tokenMint", this.tokenMintPublicKey) + "\r\n";
outputString +=
chalkString("tokenVault", this.tokenVaultPublicKey) + "\r\n";
return outputString;
}
prettyPrintTokenMint(): string {
let outputString = "";
outputString += chalk.underline(
chalkString("## Token Mint", this.token.publicKey) + "\r\n"
);
outputString += chalkString("decimals", this.mintInfo.decimals) + "\r\n";
outputString +=
chalkString("supply", this.mintInfo.supply.toString()) + "\r\n";
return outputString;
}
static async print(program: anchor.Program): Promise<string> {
const accountClass = await ProgramStateClass.build(program);
return accountClass.prettyPrint() + accountClass.prettyPrintTokenMint();
}
}

View File

@ -1,14 +0,0 @@
import { PublicKey } from "@solana/web3.js";
export interface ProgramStateData {
authority: PublicKey;
tokenMint: PublicKey;
tokenVault: PublicKey;
}
export interface IProgramStateClass {
publicKey: PublicKey;
authorityPublicKey: PublicKey;
tokenMintPublicKey: PublicKey;
tokenVaultPublicKey: PublicKey;
}

View File

@ -1 +0,0 @@
export * from "./types";

View File

@ -1,83 +0,0 @@
/* eslint-disable no-use-before-define */
import * as anchor from "@project-serum/anchor";
import { Keypair, PublicKey } from "@solana/web3.js";
import {
AggregatorAccount,
CrankAccount,
JobAccount,
LeaseAccount,
OracleAccount,
OracleQueueAccount,
PermissionAccount,
ProgramStateAccount,
} from "@switchboard-xyz/switchboard-v2";
export const DEFAULT_KEYPAIR = Keypair.fromSeed(new Uint8Array(32).fill(1));
export const DEFAULT_PUBKEY = new PublicKey("11111111111111111111111111111111");
/** An existing on-chain account to load */
export interface fromPublicKey {
publicKey: PublicKey;
}
export const SWITCHBOARD_ACCOUNT_TYPES = [
"JobAccountData",
"AggregatorAccountData",
"OracleAccountData",
"OracleQueueAccountData",
"PermissionAccountData",
"LeaseAccountData",
"ProgramStateAccountData",
"VrfAccountData",
"SbState",
"BUFFERxx",
"CrankAccountData",
] as const;
export type SwitchboardAccountType = typeof SWITCHBOARD_ACCOUNT_TYPES[number];
export const SWITCHBOARD_DISCRIMINATOR_MAP = new Map<
SwitchboardAccountType,
Buffer
>(
SWITCHBOARD_ACCOUNT_TYPES.map((accountType) => [
accountType,
anchor.BorshAccountsCoder.accountDiscriminator(accountType),
])
);
// export type SwitchboardAccountType =
// | "JobAccount"
// | "AggregatorAccount"
// | "OracleAccount"
// | "OracleQueueAccount"
// | "PermissionAccount"
// | "LeaseAccount"
// | "ProgramStateAccount"
// | "CrankAccount";
export type SwitchboardAccount =
| JobAccount
| AggregatorAccount
| OracleAccount
| ProgramStateAccount
| CrankAccount
| PermissionAccount
| LeaseAccount
| OracleQueueAccount;
export interface fromSwitchboardAccount {
account: SwitchboardAccount;
}
/** Copy an existing account type */
export interface copyAccount {
sourcePublicKey: PublicKey;
existingKeypair?: Keypair;
authorityKeypair?: Keypair;
}
/** Load a JSON definition from a file path */
export interface jsonPath {
jsonPath: string;
}

View File

@ -1 +0,0 @@
export * from "./utils";

View File

@ -1,51 +1,21 @@
import { flags } from "@oclif/command";
import { Keypair, PublicKey } from "@solana/web3.js";
import { AggregatorAccount, JobAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
import * as fs from "fs";
import {
AggregatorClass,
fromJobJSON,
JobClass,
pubKeyConverter,
pubKeyReviver,
} from "../../../accounts";
import { Flags } from "@oclif/core";
import BaseCommand from "../../../BaseCommand";
import { OutputFileExistsNoForce } from "../../../types";
import { CHECK_ICON, loadKeypair } from "../../../utils";
export default class AggregatorAddJob extends BaseCommand {
aggregatorAuthority?: Keypair | undefined = undefined;
aggregatorAccount: AggregatorAccount;
jobDefinition?: fromJobJSON = undefined;
jobAccount?: JobAccount;
outputFile = "";
static description = "add a job account to an aggregator";
static description = "add a job to an aggregator";
static flags = {
...BaseCommand.flags,
force: flags.boolean({
description: "overwrite outputFile if existing",
}),
outputFile: flags.string({
char: "f",
description: "output file to save aggregator definition to",
}),
jobDefinition: flags.string({
jobDefinition: Flags.string({
description: "filesystem path of job json definition file",
exclusive: ["jobKey"],
}),
jobKey: flags.string({
jobKey: Flags.string({
description:
"public key of an existing job account to add to an aggregator",
exclusive: ["jobDefinition"],
}),
aggregatorAuthority: flags.string({
authority: Flags.string({
char: "a",
description: "alternate keypair that is the authority for the aggregator",
}),
@ -54,88 +24,61 @@ export default class AggregatorAddJob extends BaseCommand {
static args = [
{
name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account",
},
];
static examples = ["$ sbv2 aggregator:add:job"];
async init() {
await super.init();
const { args, flags } = this.parse(AggregatorAddJob);
if (flags.aggregatorAuthority) {
this.aggregatorAuthority = await loadKeypair(flags.aggregatorAuthority);
this.context.logger.debug(
`using aggregator authority ${this.aggregatorAuthority.publicKey}`
);
}
this.aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: args.aggregatorKey,
});
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");
}
if (flags.outputFile) {
if (fs.existsSync(flags.outputFile) && !flags.force) {
throw new OutputFileExistsNoForce(flags.outputFile);
}
this.outputFile = flags.outputFile;
}
}
async run() {
const job = this.jobAccount
? await JobClass.fromAccount(this.context, this.jobAccount)
: await JobClass.build(this.context, this.program, this.jobDefinition);
const { args, flags } = await this.parse(AggregatorAddJob);
const txn = await this.aggregatorAccount.addJob(
job.account,
this.aggregatorAuthority
);
// const aggregatorAccount = new AggregatorAccount({
// program: this.program,
// publicKey: new PublicKey(args.aggregatorKey),
// });
// const aggregator = await aggregatorAccount.loadData();
// const authority = await this.loadAuthority(
// flags.authority,
// aggregator.authority
// );
const aggregator = await AggregatorClass.fromAccount(
this.context,
this.aggregatorAccount
);
// let jobAccount: JobAccount;
// if (flags.jobDefinition) {
// const jobDefinition = JSON.parse(flags.jobDefinition, pubKeyReviver);
// // create job account
// }
if (this.silent) {
console.log(txn);
} else {
this.logger.log(
`${chalk.green(
`${CHECK_ICON}Job succesfully added to aggregator account\r\n`
)}`
);
this.logger.log(
`https://explorer.solana.com/tx/${txn}?cluster=${this.cluster}`
);
}
// if (flags.jobKey) {
// jobAccount = new JobAccount({
// program: this.program,
// publicKey: new PublicKey(flags.jobKey),
// });
// }
if (this.outputFile) {
fs.writeFileSync(
this.outputFile,
JSON.stringify(aggregator, pubKeyConverter, 2)
);
}
// const job = this.jobAccount
// ? await JobClass.fromAccount(this.context, this.jobAccount)
// : await JobClass.build(this.context, this.program, this.jobDefinition);
// const txn = await this.aggregatorAccount.addJob(
// job.account,
// this.aggregatorAuthority
// );
// const aggregator = await AggregatorClass.fromAccount(
// this.context,
// this.aggregatorAccount
// );
// if (this.silent) {
// console.log(txn);
// ReadableStreamDefaultController;
// }
// this.logger.log(
// `${chalk.green(
// `${CHECK_ICON}Job succesfully added to aggregator account\r\n`
// )}`
// );
// this.logger.log(
// `https://explorer.solana.com/tx/${txn}?cluster=${this.cluster}`
// );
}
async catch(error) {

View File

@ -1,5 +1,5 @@
/* eslint-disable unicorn/no-array-push-push */
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import * as anchor from "@project-serum/anchor";
import * as spl from "@solana/spl-token";
import {
@ -13,6 +13,7 @@ import {
packAndSend,
prettyPrintAggregator,
promiseWithTimeout,
verifyProgramHasPayer,
} from "@switchboard-xyz/sbv2-utils";
import {
AggregatorAccount,
@ -28,7 +29,6 @@ import {
} from "@switchboard-xyz/switchboard-v2";
import Big from "big.js";
import BaseCommand from "../../../BaseCommand";
import { verifyProgramHasPayer } from "../../../utils";
// TODO: Fix command so it accepts a feed authority flag
// TODO: Add flag that skips job creation
@ -37,52 +37,47 @@ export default class AggregatorCreateCopy extends BaseCommand {
static flags = {
...BaseCommand.flags,
force: flags.boolean({ description: "skip job confirmation" }),
outputFile: flags.string({
char: "f",
description: "output file to save aggregator definition to",
}),
authority: flags.string({
authority: Flags.string({
char: "a",
description: "alternate keypair that will be the aggregator authority",
}),
minOracles: flags.integer({
minOracles: Flags.integer({
description: "override source aggregator's minOracleResults",
}),
batchSize: flags.integer({
batchSize: Flags.integer({
description: "override source aggregator's oracleRequestBatchSize",
}),
minJobs: flags.integer({
minJobs: Flags.integer({
description: "override source aggregator's minJobResults",
}),
minUpdateDelay: flags.integer({
minUpdateDelay: Flags.integer({
description: "override source aggregator's minUpdateDelaySeconds",
}),
forceReportPeriod: flags.integer({
forceReportPeriod: Flags.integer({
description: "override source aggregator's forceReportPeriod",
}),
varianceThreshold: flags.string({
varianceThreshold: Flags.string({
description: "override source aggregator's varianceThreshold",
}),
queueKey: flags.string({
queueKey: Flags.string({
description: "public key of the queue to create aggregator for",
required: true,
}),
crankKey: flags.string({
crankKey: Flags.string({
description: "public key of the crank to push aggregator to",
required: false,
}),
enable: flags.boolean({
enable: Flags.boolean({
description: "set permissions to PERMIT_ORACLE_QUEUE_USAGE",
}),
queueAuthority: flags.string({
queueAuthority: Flags.string({
description: "alternative keypair to use for queue authority",
}),
copyJobs: flags.boolean({
copyJobs: Flags.boolean({
description:
"create copy of job accounts instead of referincing existing job account",
}),
// sourceCluster: flags.string({
// sourceCluster: Flags.string({
// description: "alternative solana cluster to copy source aggregator from",
// required: false,
// options: ["devnet", "mainnet-beta"],
@ -92,8 +87,6 @@ export default class AggregatorCreateCopy extends BaseCommand {
static args = [
{
name: "aggregatorSource",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account to copy",
},
];
@ -106,7 +99,7 @@ export default class AggregatorCreateCopy extends BaseCommand {
async run() {
verifyProgramHasPayer(this.program);
const { args, flags } = this.parse(AggregatorCreateCopy);
const { args, flags } = await this.parse(AggregatorCreateCopy);
const payerKeypair = programWallet(this.program);
const feedAuthority = await this.loadAuthority(flags.authority);

View File

@ -1,4 +1,4 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import * as anchor from "@project-serum/anchor";
import * as spl from "@solana/spl-token";
import {
@ -40,46 +40,45 @@ export default class AggregatorCreate extends BaseCommand {
static flags = {
...BaseCommand.flags,
force: flags.boolean({ description: "skip job confirmation" }),
authority: flags.string({
authority: Flags.string({
char: "a",
description: "alternate keypair that is the authority for the aggregator",
}),
crankKey: flags.string({
crankKey: Flags.string({
description: "public key of the crank to join",
}),
enable: flags.boolean({
enable: Flags.boolean({
description: "set permissions to PERMIT_ORACLE_QUEUE_USAGE",
}),
queueAuthority: flags.string({
queueAuthority: Flags.string({
description: "alternative keypair to use for queue authority",
}),
name: flags.string({
name: Flags.string({
char: "n",
description: "name of the aggregator",
}),
forceReportPeriod: flags.integer({
forceReportPeriod: Flags.integer({
description:
"Number of seconds for which, even if the variance threshold is not passed, accept new responses from oracles.",
}),
batchSize: flags.string({
batchSize: Flags.integer({
description: "number of oracles requested for each open round call",
}),
minJobs: flags.string({
minJobs: Flags.integer({
description: "number of jobs that must respond before an oracle responds",
}),
minOracles: flags.string({
minOracles: Flags.integer({
description:
"number of oracles that must respond before a value is accepted on-chain",
}),
updateInterval: flags.string({
updateInterval: Flags.integer({
description: "set an aggregator's minimum update delay",
}),
varianceThreshold: flags.string({
varianceThreshold: Flags.string({
description:
"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",
}),
job: flags.string({
job: Flags.string({
char: "j",
description: "filesystem path to job definition file",
multiple: true,
@ -89,8 +88,6 @@ export default class AggregatorCreate extends BaseCommand {
static args = [
{
name: "queueKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description:
"public key of the oracle queue account to create aggregator for",
},
@ -98,7 +95,7 @@ export default class AggregatorCreate extends BaseCommand {
async run() {
verifyProgramHasPayer(this.program);
const { args, flags } = this.parse(AggregatorCreate);
const { args, flags } = await this.parse(AggregatorCreate);
const payerKeypair = programWallet(this.program);
const feedAuthority = await this.loadAuthority(flags.authority);
@ -109,7 +106,7 @@ export default class AggregatorCreate extends BaseCommand {
);
const queueAccount = new OracleQueueAccount({
program: this.program,
publicKey: args.queueKey,
publicKey: new PublicKey(args.queueKey),
});
const queue = await queueAccount.loadData();
const switchTokenMint = await queueAccount.loadMint();
@ -135,7 +132,7 @@ export default class AggregatorCreate extends BaseCommand {
flags.job.map(
async (
jobDefinition
): Promise<[TransactionInstruction, Keypair]> => {
): Promise<[TransactionInstruction[], Keypair]> => {
const jobJson = JSON.parse(
fs.readFileSync(
jobDefinition.startsWith("/")
@ -158,6 +155,19 @@ export default class AggregatorCreate extends BaseCommand {
).finish()
);
const size = 280 + data.length;
const allocateAccountIxn = SystemProgram.createAccount({
fromPubkey: payerKeypair.publicKey,
newAccountPubkey: jobKeypair.publicKey,
space: size,
lamports:
await this.program.provider.connection.getMinimumBalanceForRentExemption(
size
),
programId: this.program.programId,
});
const createJobIxn = await this.program.methods
.jobInit({
name: Buffer.from("").slice(0, 32),
@ -175,13 +185,15 @@ export default class AggregatorCreate extends BaseCommand {
.signers([feedAuthority])
.instruction();
return [createJobIxn, jobKeypair];
return [[allocateAccountIxn, createJobIxn], jobKeypair];
}
)
)
: [];
createAccountInstructions.push(createJobs.map((index) => index[0]));
createAccountSigners.push(...createJobs.map((index) => index[1]));
const createJobIxn = createJobs.map((index) => index[0]);
const createJobSigners = createJobs.map((index) => index[1]);
const jobAccounts = createJobs
.map((index) => index[1])
.map((jobKeypair) => {
@ -229,7 +241,7 @@ export default class AggregatorCreate extends BaseCommand {
varianceThreshold: flags.varianceThreshold
? SwitchboardDecimal.fromBig(new Big(flags.varianceThreshold))
: SwitchboardDecimal.fromBig(new Big(0)),
forceReportPeriod: flags.forceReportPeriod ?? 0,
forceReportPeriod: new anchor.BN(flags.forceReportPeriod ?? 0),
stateBump,
})
.accounts({
@ -363,8 +375,8 @@ export default class AggregatorCreate extends BaseCommand {
const createAccountSignatures = packAndSend(
this.program,
[createAccountInstructions, addJobIxns],
createAccountSigners,
[createJobIxn, createAccountInstructions, addJobIxns],
[...createJobSigners, ...createAccountSigners],
payerKeypair.publicKey
).catch((error) => {
throw error;

View File

@ -1,7 +1,11 @@
import { flags } from "@oclif/command";
/* eslint-disable complexity */
import { Flags } from "@oclif/core";
import * as anchor from "@project-serum/anchor";
import { PublicKey } from "@solana/web3.js";
import { prettyPrintAggregator } from "@switchboard-xyz/sbv2-utils";
import {
prettyPrintAggregator,
verifyProgramHasPayer,
} from "@switchboard-xyz/sbv2-utils";
import {
AggregatorAccount,
JobAccount,
@ -12,14 +16,13 @@ import {
import chalk from "chalk";
import * as fs from "fs";
import * as path from "path";
import BaseCommand from "../../../BaseCommand";
import {
fromAggregatorJSON,
fromJobJSON,
CHECK_ICON,
loadKeypair,
pubKeyConverter,
pubKeyReviver,
} from "../../../accounts";
import BaseCommand from "../../../BaseCommand";
import { CHECK_ICON, loadKeypair, verifyProgramHasPayer } from "../../../utils";
} from "../../../utils";
export default class JsonCreateAggregator extends BaseCommand {
static description = "create an aggregator from a json file";
@ -28,18 +31,11 @@ export default class JsonCreateAggregator extends BaseCommand {
static flags = {
...BaseCommand.flags,
force: flags.boolean({
description: "overwrite output file",
}),
outputFile: flags.string({
description: "output aggregator definition to a json file",
char: "f",
}),
queueKey: flags.string({
queueKey: Flags.string({
description: "public key of the oracle queue to create aggregator for",
char: "q",
}),
authority: flags.string({
authority: Flags.string({
description:
"alternate keypair that will be the authority for the aggregator",
char: "a",
@ -49,7 +45,6 @@ export default class JsonCreateAggregator extends BaseCommand {
static args = [
{
name: "definitionFile",
required: true,
description: "filesystem path of queue definition json file",
},
];
@ -59,7 +54,7 @@ export default class JsonCreateAggregator extends BaseCommand {
];
async run() {
const { args, flags } = this.parse(JsonCreateAggregator);
const { args, flags } = await this.parse(JsonCreateAggregator);
verifyProgramHasPayer(this.program);
const payerKeypair = programWallet(this.program);
@ -71,17 +66,11 @@ export default class JsonCreateAggregator extends BaseCommand {
throw new Error("input file does not exist");
}
const aggregatorDefinition: fromAggregatorJSON = JSON.parse(
const aggregatorDefinition: any = JSON.parse(
fs.readFileSync(definitionFile, "utf-8"),
pubKeyReviver
);
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) {
authority = await loadKeypair(flags.authority);
@ -146,7 +135,7 @@ export default class JsonCreateAggregator extends BaseCommand {
const jobs: JobAccount[] = [];
if (aggregatorDefinition.jobs) {
for await (const job of aggregatorDefinition.jobs) {
const jobDefinition: fromJobJSON = JSON.parse(
const jobDefinition: any = JSON.parse(
JSON.stringify(job),
pubKeyConverter
);
@ -190,13 +179,6 @@ export default class JsonCreateAggregator extends BaseCommand {
);
}
if (flags.outputFile) {
fs.writeFileSync(
flags.outputFile,
JSON.stringify(aggregator, pubKeyConverter, 2)
);
}
if (this.silent) {
console.log(aggregator.publicKey.toString());
} else {

View File

@ -1,4 +1,4 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import { PublicKey } from "@solana/web3.js";
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
@ -11,7 +11,7 @@ export default class AggregatorLock extends BaseCommand {
static flags = {
...BaseCommand.flags,
authority: flags.string({
authority: Flags.string({
char: "a",
description: "alternate keypair that is the authority for the aggregator",
}),
@ -20,8 +20,6 @@ export default class AggregatorLock extends BaseCommand {
static args = [
{
name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account",
},
];
@ -29,12 +27,12 @@ export default class AggregatorLock extends BaseCommand {
// static examples = ["$ sbv2 aggregator:set:authority"];
async run() {
const { args, flags } = this.parse(AggregatorLock);
const { args, flags } = await this.parse(AggregatorLock);
verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: args.aggregatorKey,
publicKey: new PublicKey(args.aggregatorKey),
});
const aggregator = await aggregatorAccount.loadData();
const authority = await this.loadAuthority(

View File

@ -19,19 +19,17 @@ export default class AggregatorPermissionCreate extends BaseCommand {
static args = [
{
name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account",
},
];
async run() {
const { args } = this.parse(AggregatorPermissionCreate);
const { args } = await this.parse(AggregatorPermissionCreate);
verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: args.aggregatorKey,
publicKey: new PublicKey(args.aggregatorKey),
});
const aggregator = await aggregatorAccount.loadData();
@ -69,9 +67,7 @@ export default class AggregatorPermissionCreate extends BaseCommand {
console.log(permissionAccount.publicKey.toString());
} else {
this.logger.log(
`${chalk.green(
`${CHECK_ICON}Permission account created successfully`
)}`
`${chalk.green(`${CHECK_ICON}Permission account created successfully`)}`
);
this.logger.log(await prettyPrintPermissions(permissionAccount));
}

View File

@ -1,4 +1,4 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import { PublicKey } from "@solana/web3.js";
import { AggregatorAccount, JobAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
@ -10,10 +10,7 @@ export default class AggregatorRemoveJob extends BaseCommand {
static flags = {
...BaseCommand.flags,
force: flags.boolean({
description: "overwrite outputFile if existing",
}),
authority: flags.string({
authority: Flags.string({
char: "a",
description: "alternate keypair that is the authority for the aggregator",
}),
@ -22,14 +19,10 @@ export default class AggregatorRemoveJob extends BaseCommand {
static args = [
{
name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account",
},
{
name: "jobKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description:
"public key of an existing job account to remove from an aggregator",
},
@ -38,12 +31,12 @@ export default class AggregatorRemoveJob extends BaseCommand {
static examples = ["$ sbv2 aggregator:remove:job"];
async run() {
const { args, flags } = this.parse(AggregatorRemoveJob);
const { args, flags } = await this.parse(AggregatorRemoveJob);
verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: args.aggregatorKey,
publicKey: new PublicKey(args.aggregatorKey),
});
const aggregator = await aggregatorAccount.loadData();
const authority = await this.loadAuthority(

View File

@ -1,4 +1,4 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import { PublicKey } from "@solana/web3.js";
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
@ -10,7 +10,7 @@ export default class AggregatorSetAuthority extends BaseCommand {
static flags = {
...BaseCommand.flags,
currentAuthority: flags.string({
currentAuthority: Flags.string({
char: "a",
description: "alternate keypair that is the authority for the aggregator",
}),
@ -19,13 +19,10 @@ export default class AggregatorSetAuthority extends BaseCommand {
static args = [
{
name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account",
},
{
name: "newAuthority",
required: true,
description: "keypair path of new authority",
},
];
@ -33,13 +30,13 @@ export default class AggregatorSetAuthority extends BaseCommand {
// static examples = ["$ sbv2 aggregator:set:authority"];
async run() {
const { args, flags } = this.parse(AggregatorSetAuthority);
const { args, flags } = await this.parse(AggregatorSetAuthority);
const newAuthority = await loadKeypair(args.newAuthority);
const aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: args.aggregatorKey,
publicKey: new PublicKey(args.aggregatorKey),
});
const aggregator = await aggregatorAccount.loadData();
const currentAuthority = await this.loadAuthority(

View File

@ -1,4 +1,4 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import { PublicKey } from "@solana/web3.js";
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
@ -10,7 +10,7 @@ export default class AggregatorSetBatchSize extends BaseCommand {
static flags = {
...BaseCommand.flags,
authority: flags.string({
authority: Flags.string({
char: "a",
description: "alternate keypair that is the authority for the aggregator",
}),
@ -19,8 +19,6 @@ export default class AggregatorSetBatchSize extends BaseCommand {
static args = [
{
name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account",
},
{
@ -33,7 +31,7 @@ export default class AggregatorSetBatchSize extends BaseCommand {
// static examples = ["$ sbv2 aggregator:set:authority"];
async run() {
const { args, flags } = this.parse(AggregatorSetBatchSize);
const { args, flags } = await this.parse(AggregatorSetBatchSize);
verifyProgramHasPayer(this.program);
const batchSize = Number.parseInt(args.batchSize, 10);
@ -43,7 +41,7 @@ export default class AggregatorSetBatchSize extends BaseCommand {
const aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: args.aggregatorKey,
publicKey: new PublicKey(args.aggregatorKey),
});
const aggregator = await aggregatorAccount.loadData();

View File

@ -1,4 +1,4 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import { PublicKey } from "@solana/web3.js";
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
@ -12,7 +12,7 @@ export default class AggregatorSetForceReportPeriod extends BaseCommand {
static flags = {
...BaseCommand.flags,
authority: flags.string({
authority: Flags.string({
char: "a",
description: "alternate keypair that is the authority for the aggregator",
}),
@ -21,14 +21,10 @@ export default class AggregatorSetForceReportPeriod extends BaseCommand {
static args = [
{
name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator",
},
{
name: "forceReportPeriod",
required: true,
parse: (value: string) => Number.parseInt(value),
description:
"Number of seconds for which, even if the variance threshold is not passed, accept new responses from oracles.",
},
@ -39,12 +35,12 @@ export default class AggregatorSetForceReportPeriod extends BaseCommand {
];
async run() {
const { args, flags } = this.parse(AggregatorSetForceReportPeriod);
const { args, flags } = await this.parse(AggregatorSetForceReportPeriod);
verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: args.aggregatorKey,
publicKey: new PublicKey(args.aggregatorKey),
});
const aggregator = await aggregatorAccount.loadData();
const authority = await this.loadAuthority(

View File

@ -1,4 +1,4 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import { PublicKey } from "@solana/web3.js";
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
@ -13,7 +13,7 @@ export default class AggregatorSetHistoryBuffer extends BaseCommand {
static flags = {
...BaseCommand.flags,
authority: flags.string({
authority: Flags.string({
char: "a",
description: "alternate keypair that is the authority for the aggregator",
}),
@ -22,14 +22,10 @@ export default class AggregatorSetHistoryBuffer extends BaseCommand {
static args = [
{
name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator to add to a crank",
},
{
name: "size",
required: true,
parse: (value: string) => Number.parseInt(value, 10),
description: "size of history buffer",
},
];
@ -39,12 +35,12 @@ export default class AggregatorSetHistoryBuffer extends BaseCommand {
];
async run() {
const { args, flags } = this.parse(AggregatorSetHistoryBuffer);
const { args, flags } = await this.parse(AggregatorSetHistoryBuffer);
verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: args.aggregatorKey,
publicKey: new PublicKey(args.aggregatorKey),
});
const aggregator = await aggregatorAccount.loadData();
const authority = await this.loadAuthority(

View File

@ -1,5 +1,6 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import { PublicKey, Transaction } from "@solana/web3.js";
import { verifyProgramHasPayer } from "@switchboard-xyz/sbv2-utils";
import {
AggregatorAccount,
OracleQueueAccount,
@ -9,7 +10,7 @@ import {
import Big from "big.js";
import chalk from "chalk";
import BaseCommand from "../../../BaseCommand";
import { CHECK_ICON, verifyProgramHasPayer } from "../../../utils";
import { CHECK_ICON } from "../../../utils";
export default class AggregatorSet extends BaseCommand {
static description = "set an aggregator's config";
@ -18,31 +19,31 @@ export default class AggregatorSet extends BaseCommand {
static flags = {
...BaseCommand.flags,
authority: flags.string({
authority: Flags.string({
char: "a",
description: "alternate keypair that is the authority for the aggregator",
}),
forceReportPeriod: flags.string({
forceReportPeriod: Flags.string({
description:
"Number of seconds for which, even if the variance threshold is not passed, accept new responses from oracles.",
}),
// batchSize: flags.string({
// batchSize: Flags.string({
// description: "number of oracles requested for each open round call",
// }),
minJobs: flags.string({
minJobs: Flags.string({
description: "number of jobs that must respond before an oracle responds",
}),
minOracles: flags.string({
minOracles: Flags.string({
description:
"number of oracles that must respond before a value is accepted on-chain",
}),
newQueue: flags.string({
newQueue: Flags.string({
description: "public key of the new oracle queue",
}),
updateInterval: flags.string({
updateInterval: Flags.string({
description: "set an aggregator's minimum update delay",
}),
varianceThreshold: flags.string({
varianceThreshold: Flags.string({
description:
"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",
}),
@ -51,8 +52,6 @@ export default class AggregatorSet extends BaseCommand {
static args = [
{
name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator",
},
];
@ -62,14 +61,14 @@ export default class AggregatorSet extends BaseCommand {
];
async run() {
const { args, flags } = this.parse(AggregatorSet);
const { args, flags } = await this.parse(AggregatorSet);
verifyProgramHasPayer(this.program);
const payerKeypair = programWallet(this.program);
const aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: args.aggregatorKey,
publicKey: new PublicKey(args.aggregatorKey),
});
const aggregator = await aggregatorAccount.loadData();
const authority = await this.loadAuthority(
@ -114,7 +113,7 @@ export default class AggregatorSet extends BaseCommand {
// min oracles responses
if (flags.minOracles) {
const minOracles = Number.parseInt(flags.minOracles);
const minOracles = Number.parseInt(flags.minOracles, 10);
txn.add(
await this.program.methods
.aggregatorSetMinOracles({
@ -130,7 +129,7 @@ export default class AggregatorSet extends BaseCommand {
// min job responses
if (flags.minJobs) {
const minJobs = Number.parseInt(flags.minJobs);
const minJobs = Number.parseInt(flags.minJobs, 10);
txn.add(
await this.program.methods
.aggregatorSetMinJobs({
@ -167,7 +166,7 @@ export default class AggregatorSet extends BaseCommand {
txn.add(
await this.program.methods
.aggregatorSetForceReportPeriod({
forceReportPeriod: Number.parseInt(flags.forceReportPeriod),
forceReportPeriod: Number.parseInt(flags.forceReportPeriod, 10),
})
.accounts({
aggregator: aggregatorAccount.publicKey,

View File

@ -1,9 +1,10 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import { PublicKey } from "@solana/web3.js";
import { verifyProgramHasPayer } from "@switchboard-xyz/sbv2-utils";
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
import BaseCommand from "../../../BaseCommand";
import { CHECK_ICON, verifyProgramHasPayer } from "../../../utils";
import { CHECK_ICON } from "../../../utils";
export default class AggregatorSetMinJobResults extends BaseCommand {
static description =
@ -11,7 +12,7 @@ export default class AggregatorSetMinJobResults extends BaseCommand {
static flags = {
...BaseCommand.flags,
authority: flags.string({
authority: Flags.string({
char: "a",
description: "alternate keypair that is the authority for the aggregator",
}),
@ -20,13 +21,10 @@ export default class AggregatorSetMinJobResults extends BaseCommand {
static args = [
{
name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account",
},
{
name: "minJobResults",
required: true,
description: "number of jobs that must respond before an oracle responds",
},
];
@ -34,12 +32,12 @@ export default class AggregatorSetMinJobResults extends BaseCommand {
// static examples = ["$ sbv2 aggregator:set:authority"];
async run() {
const { args, flags } = this.parse(AggregatorSetMinJobResults);
const { args, flags } = await this.parse(AggregatorSetMinJobResults);
verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: args.aggregatorKey,
publicKey: new PublicKey(args.aggregatorKey),
});
const aggregator = await aggregatorAccount.loadData();
const authority = await this.loadAuthority(
@ -51,6 +49,7 @@ export default class AggregatorSetMinJobResults extends BaseCommand {
if (minJobResults <= 0 || minJobResults > 16) {
throw new Error(`Invalid min job size (1 - 16), ${minJobResults}`);
}
if (minJobResults > aggregator.jobPubkeysSize) {
throw new Error(
`Min jobs ${minJobResults} is greater than current number of jobs ${aggregator.jobPubkeysSize} `

View File

@ -1,9 +1,10 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import { PublicKey } from "@solana/web3.js";
import { verifyProgramHasPayer } from "@switchboard-xyz/sbv2-utils";
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
import BaseCommand from "../../../BaseCommand";
import { CHECK_ICON, verifyProgramHasPayer } from "../../../utils";
import { CHECK_ICON } from "../../../utils";
export default class AggregatorSetMinOracleResults extends BaseCommand {
static description =
@ -11,7 +12,7 @@ export default class AggregatorSetMinOracleResults extends BaseCommand {
static flags = {
...BaseCommand.flags,
authority: flags.string({
authority: Flags.string({
char: "a",
description: "alternate keypair that is the authority for the aggregator",
}),
@ -20,13 +21,10 @@ export default class AggregatorSetMinOracleResults extends BaseCommand {
static args = [
{
name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account",
},
{
name: "minOracleResults",
required: true,
description:
"number of oracles that must respond before a value is accepted on-chain",
},
@ -35,12 +33,12 @@ export default class AggregatorSetMinOracleResults extends BaseCommand {
// static examples = ["$ sbv2 aggregator:set:authority"];
async run() {
const { args, flags } = this.parse(AggregatorSetMinOracleResults);
const { args, flags } = await this.parse(AggregatorSetMinOracleResults);
verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: args.aggregatorKey,
publicKey: new PublicKey(args.aggregatorKey),
});
const aggregator = await aggregatorAccount.loadData();

View File

@ -1,4 +1,4 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import { PublicKey } from "@solana/web3.js";
import {
AggregatorAccount,
@ -13,7 +13,7 @@ export default class AggregatorSetQueue extends BaseCommand {
static flags = {
...BaseCommand.flags,
authority: flags.string({
authority: Flags.string({
char: "a",
description: "alternate keypair that is the authority for the aggregator",
}),
@ -22,31 +22,27 @@ export default class AggregatorSetQueue extends BaseCommand {
static args = [
{
name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator",
},
{
name: "queueKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the oracle queue",
},
];
async run() {
const { args, flags } = this.parse(AggregatorSetQueue);
const { args, flags } = await this.parse(AggregatorSetQueue);
verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: args.aggregatorKey,
publicKey: new PublicKey(args.aggregatorKey),
});
const aggregator = await aggregatorAccount.loadData();
const oracleQueue = new OracleQueueAccount({
program: this.program,
publicKey: args.queueKey,
publicKey: new PublicKey(args.queueKey),
});
const authority = await this.loadAuthority(

View File

@ -1,16 +1,17 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import { PublicKey } from "@solana/web3.js";
import { verifyProgramHasPayer } from "@switchboard-xyz/sbv2-utils";
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
import BaseCommand from "../../../BaseCommand";
import { CHECK_ICON, verifyProgramHasPayer } from "../../../utils";
import { CHECK_ICON } from "../../../utils";
export default class AggregatorSetUpdateInterval extends BaseCommand {
static description = "set an aggregator's minimum update delay";
static flags = {
...BaseCommand.flags,
authority: flags.string({
authority: Flags.string({
char: "a",
description: "alternate keypair that is the authority for the aggregator",
}),
@ -19,14 +20,10 @@ export default class AggregatorSetUpdateInterval extends BaseCommand {
static args = [
{
name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account",
},
{
name: "updateInterval",
required: true,
parse: (newInterval: string) => Number.parseInt(newInterval, 10),
description: "set an aggregator's minimum update delay",
},
];
@ -36,12 +33,12 @@ export default class AggregatorSetUpdateInterval extends BaseCommand {
];
async run() {
const { args, flags } = this.parse(AggregatorSetUpdateInterval);
const { args, flags } = await this.parse(AggregatorSetUpdateInterval);
verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: args.aggregatorKey,
publicKey: new PublicKey(args.aggregatorKey),
});
const aggregator = await aggregatorAccount.loadData();
@ -63,7 +60,7 @@ export default class AggregatorSetUpdateInterval extends BaseCommand {
);
const txn = await aggregatorAccount.setUpdateInterval({
newInterval: args.updateInterval,
newInterval: Number.parseInt(args.updateInterval, 10),
authority,
});

View File

@ -1,4 +1,4 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import { PublicKey } from "@solana/web3.js";
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
import Big from "big.js";
@ -13,7 +13,7 @@ export default class AggregatorSetVarianceThreshold extends BaseCommand {
static flags = {
...BaseCommand.flags,
authority: flags.string({
authority: Flags.string({
char: "a",
description: "alternate keypair that is the authority for the aggregator",
}),
@ -22,14 +22,10 @@ export default class AggregatorSetVarianceThreshold extends BaseCommand {
static args = [
{
name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator",
},
{
name: "varianceThreshold",
required: true,
parse: (variance: string) => new Big(variance),
description:
"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",
},
@ -40,12 +36,12 @@ export default class AggregatorSetVarianceThreshold extends BaseCommand {
];
async run() {
const { args, flags } = this.parse(AggregatorSetVarianceThreshold);
const { args, flags } = await this.parse(AggregatorSetVarianceThreshold);
verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: args.aggregatorKey,
publicKey: new PublicKey(args.aggregatorKey),
});
const aggregator = await aggregatorAccount.loadData();
const authority = await this.loadAuthority(
@ -55,7 +51,7 @@ export default class AggregatorSetVarianceThreshold extends BaseCommand {
const txn = await aggregatorAccount.setVarianceThreshold({
authority,
threshold: args.varianceThreshold,
threshold: new Big(args.varianceThreshold),
});
if (this.silent) {

View File

@ -19,8 +19,6 @@ export default class AggregatorUpdate extends BaseCommand {
static args = [
{
name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account to deserialize",
},
];
@ -30,11 +28,11 @@ export default class AggregatorUpdate extends BaseCommand {
];
async run() {
const { args } = this.parse(AggregatorUpdate);
const { args } = await this.parse(AggregatorUpdate);
const aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: args.aggregatorKey,
publicKey: new PublicKey(args.aggregatorKey),
});
const aggregator = await aggregatorAccount.loadData();
@ -79,6 +77,7 @@ export default class AggregatorUpdate extends BaseCommand {
this.context.logger.info(error.toString());
this.exit(0);
}
super.catch(error, "failed to open a new aggregator update round");
}
}

View File

@ -1,4 +1,4 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import BaseCommand from "../BaseCommand";
import OracleDeposit from "./oracle/deposit";
import OracleWithdraw from "./oracle/withdraw";
@ -16,7 +16,7 @@ export default class Config extends BaseCommand {
static flags = {
...BaseCommand.flags,
pubkey: flags.string({
pubkey: Flags.string({
description:
"command specific. override default account with provided public key",
}),
@ -39,7 +39,7 @@ export default class Config extends BaseCommand {
async init() {
await super.init();
const { args, flags, argv } = this.parse(Config);
const { args, flags, argv } = await this.parse(Config);
this.passThroughArguments = argv.slice(1);
this.baseCommand = args.baseCommand;
this.flags = flags;
@ -51,6 +51,7 @@ export default class Config extends BaseCommand {
await OracleWithdraw.run(this.passThroughArguments);
break;
}
case "oracle:deposit": {
await OracleDeposit.run(this.passThroughArguments);
break;

View File

@ -1,5 +1,5 @@
import { chalkString } from "@switchboard-xyz/sbv2-utils";
import chalk from "chalk";
import { chalkString } from "../../accounts/utils";
import BaseCommand from "../../BaseCommand";
export default class ConfigPrint extends BaseCommand {
@ -13,9 +13,9 @@ export default class ConfigPrint extends BaseCommand {
async run() {
const { devnet, mainnet } = this.cliConfig;
this.log(chalk.underline(chalk.blue("## Mainnet-Beta".padEnd(16))), "info");
this.log(chalkString("mainnet-rpc", mainnet.rpcUrl || "N/A"), "info");
this.log(chalk.underline(chalk.blue("## Devnet".padEnd(16))), "info");
this.log(chalkString("devnet-rpc", devnet.rpcUrl || "N/A"), "info");
this.log(chalk.underline(chalk.blue("## Mainnet-Beta".padEnd(16))));
this.log(chalkString("mainnet-rpc", mainnet.rpcUrl || "N/A"));
this.log(chalk.underline(chalk.blue("## Devnet".padEnd(16))));
this.log(chalkString("devnet-rpc", devnet.rpcUrl || "N/A"));
}
}

View File

@ -1,6 +1,5 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import BaseCommand from "../../BaseCommand";
import { ConfigParameter } from "../../config";
export default class ConfigSet extends BaseCommand {
hidden = true;
@ -9,7 +8,7 @@ export default class ConfigSet extends BaseCommand {
static flags = {
...BaseCommand.flags,
reset: flags.boolean({
reset: Flags.boolean({
char: "r",
description: "remove value or set to default rpc",
}),
@ -18,9 +17,7 @@ export default class ConfigSet extends BaseCommand {
static args = [
{
name: "param",
required: true,
options: ["devnet-rpc", "mainnet-rpc"],
parse: (string_: string) => string_ as ConfigParameter,
description: "configuration parameter to set",
},
{
@ -31,7 +28,7 @@ export default class ConfigSet extends BaseCommand {
];
async run() {
const { args, flags } = this.parse(ConfigSet);
const { args, flags } = await this.parse(ConfigSet);
this.setConfig(args.param, flags.reset ? undefined : args.value);
}

View File

@ -1,4 +1,4 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import * as anchor from "@project-serum/anchor";
import { PublicKey, SystemProgram } from "@solana/web3.js";
import {
@ -11,23 +11,25 @@ import {
programWallet,
} from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
import BaseCommand from "../../../BaseCommand";
import { CHECK_ICON } from "../../../utils";
import BaseCommand from "../../BaseCommand";
import { CHECK_ICON } from "../../utils";
export default class QueueAddCrank extends BaseCommand {
static description = "add a crank to an existing oracle queue";
static alias = ["queue:add:crank"];
static flags = {
...BaseCommand.flags,
name: flags.string({
name: Flags.string({
char: "n",
description: "name of the crank for easier identification",
}),
maxRows: flags.integer({
maxRows: Flags.integer({
char: "r",
description: "maximum number of rows a crank can support",
}),
queueAuthority: flags.string({
queueAuthority: Flags.string({
description: "alternative keypair to use for queue authority",
}),
};
@ -35,8 +37,6 @@ export default class QueueAddCrank extends BaseCommand {
static args = [
{
name: "queueKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the oracle queue to create a crank on",
},
];
@ -47,7 +47,7 @@ export default class QueueAddCrank extends BaseCommand {
];
async run() {
const { args, flags } = this.parse(QueueAddCrank);
const { args, flags } = await this.parse(QueueAddCrank);
verifyProgramHasPayer(this.program);
const payerKeypair = programWallet(this.program);
@ -59,7 +59,7 @@ export default class QueueAddCrank extends BaseCommand {
const queueAccount = new OracleQueueAccount({
program: this.program,
publicKey: args.queueKey,
publicKey: new PublicKey(args.queueKey),
});
const queue = await queueAccount.loadData();

View File

@ -1,4 +1,4 @@
import { flags } from "@oclif/command";
import { Flags } from "@oclif/core";
import { PublicKey } from "@solana/web3.js";
import { CrankAccount, CrankRow } from "@switchboard-xyz/switchboard-v2";
import * as fs from "fs";
@ -10,8 +10,8 @@ export default class CrankList extends BaseCommand {
static flags = {
...BaseCommand.flags,
force: flags.boolean({ description: "overwrite output file if exists" }),
outputFile: flags.string({
force: Flags.boolean({ description: "overwrite output file if exists" }),
outputFile: Flags.string({
char: "f",
description: "output file to save aggregator pubkeys to",
}),
@ -20,14 +20,12 @@ export default class CrankList extends BaseCommand {
static args = [
{
name: "crankKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the crank",
},
];
async run() {
const { args, flags } = this.parse(CrankList);
const { args, flags } = await this.parse(CrankList);
const outputFile = flags.outputFile
? path.join(process.cwd(), flags.outputFile)
@ -40,7 +38,7 @@ export default class CrankList extends BaseCommand {
const crankAccount = new CrankAccount({
program: this.program,
publicKey: args.crankKey,
publicKey: new PublicKey(args.crankKey),
});
const crank = await crankAccount.loadData();

View File

@ -19,30 +19,26 @@ export default class CrankPush extends BaseCommand {
static args = [
{
name: "crankKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the crank",
},
{
name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator",
},
];
async run() {
const { args } = this.parse(CrankPush);
const { args } = await this.parse(CrankPush);
verifyProgramHasPayer(this.program);
const crankAccount = new CrankAccount({
program: this.program,
publicKey: args.crankKey,
publicKey: new PublicKey(args.crankKey),
});
const aggregatorAccount = new AggregatorAccount({
program: this.program,
publicKey: args.aggregatorKey,
publicKey: new PublicKey(args.aggregatorKey),
});
const txn = await crankAccount.push({ aggregatorAccount });
@ -51,9 +47,7 @@ export default class CrankPush extends BaseCommand {
console.log(txn);
} else {
this.logger.log(
`${chalk.green(
`${CHECK_ICON}Aggregator pushed to crank successfully`
)}`
`${chalk.green(`${CHECK_ICON}Aggregator pushed to crank successfully`)}`
);
this.logger.log(
`https://explorer.solana.com/tx/${txn}?cluster=${this.cluster}`

View File

@ -22,8 +22,6 @@ export default class CrankTurn extends BaseCommand {
static args = [
{
name: "crankKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the crank to turn",
},
];
@ -33,14 +31,14 @@ export default class CrankTurn extends BaseCommand {
];
async run() {
const { args } = this.parse(CrankTurn);
const { args } = await this.parse(CrankTurn);
verifyProgramHasPayer(this.program);
const payer = programWallet(this.program);
// load crank
const crankAccount = new CrankAccount({
program: this.program,
publicKey: args.crankKey,
publicKey: new PublicKey(args.crankKey),
});
const crank = await crankAccount.loadData();

View File

@ -1,88 +0,0 @@
import { flags } from "@oclif/command";
import { PublicKey } from "@solana/web3.js";
import { JobAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
import * as fs from "fs";
import { JobClass, pubKeyConverter } from "../../../accounts";
import BaseCommand from "../../../BaseCommand";
import { OutputFileExistsNoForce } from "../../../types";
import { CHECK_ICON } from "../../../utils";
export default class JobCreateCopy extends BaseCommand {
sourceJob: JobAccount;
outputFile = "";
static description = "copy a job account";
static flags = {
...BaseCommand.flags,
force: flags.boolean({ description: "skip job confirmation" }),
outputFile: flags.string({
char: "f",
description: "output file to save job definition to",
}),
};
static args = [
{
name: "jobSource",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account to copy",
},
];
static examples = [
"$ sbv2 job:create:copy 7pdb5RVM6cVBU8XDfpGqakb1S4wX2i5QsZxT117tK4HS --keypair ../payer-keypair.json",
];
async init() {
await super.init();
const { args, flags } = this.parse(JobCreateCopy);
this.sourceJob = new JobAccount({
program: this.program,
publicKey: args.jobSource,
});
if (flags.outputFile) {
if (fs.existsSync(flags.outputFile) && !flags.force) {
throw new OutputFileExistsNoForce(flags.outputFile);
}
this.outputFile = flags.outputFile;
}
}
async run() {
// create aggregator
const job = await JobClass.fromCopyAccount(this.context, this.program, {
sourcePublicKey: this.sourceJob.publicKey,
});
if (this.silent) {
console.log(job.publicKey.toString());
} else {
this.logger.log(job.prettyPrint());
this.logger.log(
`${chalk.green(`${CHECK_ICON}Job account successfully copied`)}`
);
if (job.account.keypair) {
this.context.fs.saveKeypair(job.account.keypair);
}
}
if (this.outputFile) {
fs.writeFileSync(
this.outputFile,
JSON.stringify(job, pubKeyConverter, 2)
);
}
}
async catch(error) {
super.catch(error, "failed to copy job account");
}
}

Some files were not shown because too many files have changed in this diff Show More