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
dist
dts
node_modules
scripts
test

View File

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

11
cli/.gitignore vendored
View File

@ -3,16 +3,7 @@
/.nyc_output /.nyc_output
/dist /dist
/lib /lib
/package-lock.json
/tmp /tmp
/yarn.lock
node_modules node_modules
switchboardv2_idl*.json
.archive
.keypairs
*-keypair.json
*.schema.json
.data
oclif.manifest.json 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 MIT License
Copyright (c) 2022 Switchboard Copyright (c) 2019 Salesforce
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 #!/usr/bin/env node
require('@oclif/command').run() const oclif = require('@oclif/core')
.then(require('@oclif/command/flush'))
.catch(require('@oclif/errors/handle')) 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", "name": "@switchboard-xyz/switchboardv2-cli",
"version": "0.2.0",
"description": "command line tool to interact with switchboard v2", "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", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@ -9,17 +15,75 @@
"directory": "cli" "directory": "cli"
}, },
"bugs": "https://github.com/switchboard-xyz/switchboard-v2/tree/main/cli/issues", "bugs": "https://github.com/switchboard-xyz/switchboard-v2/tree/main/cli/issues",
"homepage": "https://docs.switchboard.xyz", "files": [
"bin": { "/bin",
"sbv2": "./bin/run" "/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": { "oclif": {
"commands": "./lib/commands", "additionalHelpFlags": [
"-h"
],
"commands": "./dist/commands",
"bin": "sbv2", "bin": "sbv2",
"dirname": "@switchboard-xyz/sbv2-cli", "dirname": "@switchboard-xyz/sbv2-cli",
"topicSeparator": " ",
"macos": { "macos": {
"identifier": "com.sbv2.cli" "identifier": "com.sbv2.cli"
}, },
"plugins": [
"@oclif/plugin-update",
"@oclif/plugin-help",
"@oclif/plugin-version",
"@oclif/plugin-warn-if-update-available"
],
"update": { "update": {
"s3": { "s3": {
"bucket": "sbv2-cli", "bucket": "sbv2-cli",
@ -40,12 +104,6 @@
"version": "16.14.2" "version": "16.14.2"
} }
}, },
"plugins": [
"@oclif/plugin-update",
"@oclif/plugin-help",
"@oclif/plugin-warn-if-update-available",
"@oclif/config"
],
"topics": { "topics": {
"aggregator": { "aggregator": {
"description": "interact with a switchboard aggregator account" "description": "interact with a switchboard aggregator account"
@ -67,90 +125,25 @@
}, },
"print": { "print": {
"description": "find and print a switchboard account by public key for a given cluster" "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": { "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", "postpack": "shx rm -f oclif.manifest.json",
"posttest1": "eslint . --ext .ts --config .eslintrc.json", "posttest": "yarn lint",
"prepack": "yarn build", "prepack": "yarn build && oclif manifest && oclif readme",
"build": "shx rm -rf lib && tsc -b && oclif-dev manifest && oclif-dev readme", "test": "mocha --forbid-only \"test/**/*.test.ts\"",
"doc": "oclif-dev readme", "version": "oclif readme && git add README.md",
"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", "fmt": "prettier --write 'src/**/*.ts'"
"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"
}, },
"engines": { "engines": {
"node": ">=8.5.0" "node": ">=12.0.0"
}, },
"files": [
"bin",
"lib",
"src",
"examples",
"npm-shrinkwrap.json",
"oclif.manifest.json"
],
"keywords": [ "keywords": [
"oclif", "oclif"
"switchboard",
"solana",
"oracle"
], ],
"main": "lib/index.js", "types": "dist/index.d.ts"
"types": "lib/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 { Input } from "@oclif/parser";
import * as anchor from "@project-serum/anchor"; import * as anchor from "@project-serum/anchor";
import { import {
@ -17,7 +17,6 @@ import Big from "big.js";
import chalk from "chalk"; import chalk from "chalk";
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import { DEFAULT_KEYPAIR } from "./accounts";
import { CliConfig, ConfigParameter, DEFAULT_CONFIG } from "./config"; import { CliConfig, ConfigParameter, DEFAULT_CONFIG } from "./config";
import { AuthorityMismatch } from "./types"; import { AuthorityMismatch } from "./types";
import { CommandContext } from "./types/context/context"; import { CommandContext } from "./types/context/context";
@ -27,28 +26,25 @@ import { FAILED_ICON, loadKeypair, toCluster } from "./utils";
abstract class BaseCommand extends Command { abstract class BaseCommand extends Command {
static flags = { static flags = {
help: flags.help({ char: "h" }), verbose: Flags.boolean({
verbose: flags.boolean({
char: "v", char: "v",
description: "log everything", description: "log everything",
default: false,
}), }),
silent: flags.boolean({ silent: Flags.boolean({
char: "s", char: "s",
description: "suppress cli prompts", description: "suppress cli prompts",
default: false,
}), }),
mainnetBeta: flags.boolean({ mainnetBeta: Flags.boolean({
description: "WARNING: use mainnet-beta solana cluster", description: "WARNING: use mainnet-beta solana cluster",
}), }),
rpcUrl: flags.string({ rpcUrl: Flags.string({
char: "u", char: "u",
description: "alternate RPC url", description: "alternate RPC url",
}), }),
programId: flags.string({ programId: Flags.string({
description: "alternative Switchboard program ID to interact with", description: "alternative Switchboard program ID to interact with",
}), }),
keypair: flags.string({ keypair: Flags.string({
char: "k", char: "k",
description: description:
"keypair that will pay for onchain transactions. defaults to new account authority if no alternate authority provided", "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 program: anchor.Program;
public payerKeypair?: Keypair | undefined = undefined; public payerKeypair: Keypair;
async init() { async init() {
const { flags } = this.parse(<Input<any>>this.constructor); const { flags } = await this.parse((<Input<any>>this.constructor) as any);
BaseCommand.flags = flags; BaseCommand.flags = flags as any;
// setup logging // setup logging
this.silent = flags.silent; this.silent = (flags as any).silent;
this.verbose = flags.verbose; this.verbose = (flags as any).verbose;
const level = flags.silent ? "error" : flags.verbose ? "debug" : "info"; const level = (flags as any).silent
? "error"
: (flags as any).verbose
? "debug"
: "info";
const logFilename = path.join(this.config.cacheDir, "log.txt"); const logFilename = path.join(this.config.cacheDir, "log.txt");
const logParameters: LoggerParameters = { const logParameters: LoggerParameters = {
console: { console: {
@ -90,8 +90,8 @@ abstract class BaseCommand extends Command {
level: "debug", level: "debug",
filename: logFilename, filename: logFilename,
}, },
silent: flags.silent, silent: this.silent ?? false,
verbose: flags.verbose, verbose: this.verbose ?? false,
}; };
this.logger = new LogProvider(logParameters); this.logger = new LogProvider(logParameters);
@ -99,10 +99,14 @@ abstract class BaseCommand extends Command {
this.loadConfig(); this.loadConfig();
this.cluster = flags.mainnetBeta this.cluster = (flags as any).mainnetBeta
? toCluster("mainnet-beta") ? toCluster("mainnet-beta")
: toCluster("devnet"); : toCluster("devnet");
const url = flags.rpcUrl ?? clusterApiUrl(this.cluster);
const url =
(flags as any).rpcUrl ??
this.getRpcUrl(this.cluster) ??
clusterApiUrl(this.cluster);
try { try {
this.connection = new Connection(url, { this.connection = new Connection(url, {
commitment: "finalized", commitment: "finalized",
@ -120,12 +124,12 @@ abstract class BaseCommand extends Command {
); );
} }
this.payerKeypair = flags.keypair this.payerKeypair = (flags as any).keypair
? await loadKeypair(flags.keypair) ? await loadKeypair((flags as any).keypair)
: DEFAULT_KEYPAIR; : Keypair.fromSeed(new Uint8Array(32).fill(1));
const programId = flags.programId const programId = (flags as any).programId
? new anchor.web3.PublicKey(flags.programId) ? new anchor.web3.PublicKey((flags as any).programId)
: getSwitchboardPid(this.cluster as "mainnet-beta" | "devnet"); : getSwitchboardPid(this.cluster as "mainnet-beta" | "devnet");
const wallet = new anchor.Wallet(this.payerKeypair); 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 // fall back to console if logger is not initialized yet
const logger = this.logger ?? console; 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 */ /** Load an authority from a CLI flag and optionally check if it matches the expected account authority */
async loadAuthority( async loadAuthority(
authorityPath?: string, authorityPath: string | unknown,
expectedAuthority?: PublicKey expectedAuthority?: PublicKey
): Promise<Keypair> { ): Promise<Keypair> {
const authority = authorityPath const authority: Keypair =
? await loadKeypair(authorityPath) typeof authorityPath === "string"
: programWallet(this.program); ? await loadKeypair(authorityPath)
: programWallet(this.program);
if (expectedAuthority && !expectedAuthority.equals(authority.publicKey)) { if (expectedAuthority && !expectedAuthority.equals(authority.publicKey)) {
throw new AuthorityMismatch(); throw new AuthorityMismatch();
@ -202,7 +207,7 @@ abstract class BaseCommand extends Command {
mainnetCheck(): void { mainnetCheck(): void {
if (this.cluster === "mainnet-beta") { if (this.cluster === "mainnet-beta") {
throw new Error( 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 || this.cliConfig.devnet.rpcUrl ||
clusterApiUrl(toCluster("mainnet-beta")) 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 */ import { Command, Flags } from "@oclif/core";
/* eslint-disable no-process-exit */
import Command, { flags } from "@oclif/command";
import { Input } from "@oclif/parser";
import * as anchor from "@project-serum/anchor"; import * as anchor from "@project-serum/anchor";
import { ACCOUNT_DISCRIMINATOR_SIZE } from "@project-serum/anchor/dist/cjs/coder"; 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 { import {
prettyPrintAggregator, prettyPrintAggregator,
prettyPrintCrank, prettyPrintCrank,
@ -15,6 +12,8 @@ import {
prettyPrintProgramState, prettyPrintProgramState,
prettyPrintQueue, prettyPrintQueue,
prettyPrintVrf, prettyPrintVrf,
SwitchboardAccountType,
SWITCHBOARD_DISCRIMINATOR_MAP,
} from "@switchboard-xyz/sbv2-utils"; } from "@switchboard-xyz/sbv2-utils";
import { import {
AggregatorAccount, AggregatorAccount,
@ -29,11 +28,6 @@ import {
} from "@switchboard-xyz/switchboard-v2"; } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk"; import chalk from "chalk";
import * as path from "path"; import * as path from "path";
import {
DEFAULT_KEYPAIR,
SwitchboardAccountType,
SWITCHBOARD_DISCRIMINATOR_MAP,
} from "./accounts";
import { CliConfig } from "./config"; import { CliConfig } from "./config";
import { FsProvider } from "./types"; import { FsProvider } from "./types";
import { CommandContext } from "./types/context/context"; import { CommandContext } from "./types/context/context";
@ -47,8 +41,8 @@ export interface ClusterConfigs {
abstract class PrintBaseCommand extends Command { abstract class PrintBaseCommand extends Command {
static flags = { static flags = {
help: flags.help({ char: "h" }), help: Flags.help({ char: "h" }),
verbose: flags.boolean({ verbose: Flags.boolean({
char: "v", char: "v",
description: "log everything", description: "log everything",
default: false, default: false,
@ -64,8 +58,8 @@ abstract class PrintBaseCommand extends Command {
public clusters: ClusterConfigs; public clusters: ClusterConfigs;
async init() { async init() {
const { flags } = this.parse(<Input<any>>this.constructor); const { flags } = (await this.parse(this.constructor as any)) as any;
PrintBaseCommand.flags = flags; // this.flags = flags;
// setup logging // setup logging
const level = flags.silent ? "error" : flags.verbose ? "debug" : "info"; const level = flags.silent ? "error" : flags.verbose ? "debug" : "info";
@ -93,12 +87,12 @@ abstract class PrintBaseCommand extends Command {
devnet: await loadAnchor( devnet: await loadAnchor(
"devnet", "devnet",
new Connection(clusterApiUrl("devnet")), new Connection(clusterApiUrl("devnet")),
DEFAULT_KEYPAIR Keypair.fromSeed(new Uint8Array(32).fill(1))
), ),
mainnet: await loadAnchor( mainnet: await loadAnchor(
"mainnet-beta", "mainnet-beta",
new Connection(clusterApiUrl("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)); this.logger.log(await prettyPrintJob(job));
break; break;
} }
case "AggregatorAccountData": { case "AggregatorAccountData": {
const aggregator = new AggregatorAccount({ program, publicKey }); const aggregator = new AggregatorAccount({ program, publicKey });
this.logger.log( this.logger.log(
@ -121,42 +116,50 @@ abstract class PrintBaseCommand extends Command {
); );
break; break;
} }
case "OracleAccountData": { case "OracleAccountData": {
const oracle = new OracleAccount({ program, publicKey }); const oracle = new OracleAccount({ program, publicKey });
this.logger.log(await prettyPrintOracle(oracle, undefined, true)); this.logger.log(await prettyPrintOracle(oracle, undefined, true));
break; break;
} }
case "PermissionAccountData": { case "PermissionAccountData": {
const permission = new PermissionAccount({ program, publicKey }); const permission = new PermissionAccount({ program, publicKey });
this.logger.log(await prettyPrintPermissions(permission, undefined)); this.logger.log(await prettyPrintPermissions(permission));
break; break;
} }
case "LeaseAccountData": { case "LeaseAccountData": {
const lease = new LeaseAccount({ program, publicKey }); const lease = new LeaseAccount({ program, publicKey });
this.logger.log(await prettyPrintLease(lease, undefined)); this.logger.log(await prettyPrintLease(lease));
break; break;
} }
case "OracleQueueAccountData": { case "OracleQueueAccountData": {
const queue = new OracleQueueAccount({ program, publicKey }); const queue = new OracleQueueAccount({ program, publicKey });
this.logger.log(await prettyPrintQueue(queue)); this.logger.log(await prettyPrintQueue(queue));
break; break;
} }
case "CrankAccountData": { case "CrankAccountData": {
const crank = new CrankAccount({ program, publicKey }); const crank = new CrankAccount({ program, publicKey });
this.logger.log(await prettyPrintCrank(crank)); this.logger.log(await prettyPrintCrank(crank));
break; break;
} }
case "SbState": case "SbState":
case "ProgramStateAccountData": { case "ProgramStateAccountData": {
const [programState] = ProgramStateAccount.fromSeed(program); const [programState] = ProgramStateAccount.fromSeed(program);
this.logger.log(await prettyPrintProgramState(programState)); this.logger.log(await prettyPrintProgramState(programState));
break; break;
} }
case "VrfAccountData": { case "VrfAccountData": {
const vrfAccount = new VrfAccount({ program, publicKey }); const vrfAccount = new VrfAccount({ program, publicKey });
this.logger.log(await prettyPrintVrf(vrfAccount)); this.logger.log(await prettyPrintVrf(vrfAccount));
break; break;
} }
case "BUFFERxx": { case "BUFFERxx": {
console.log(`Found buffer account but dont know which one`); console.log(`Found buffer account but dont know which one`);
break; break;
@ -173,6 +176,7 @@ abstract class PrintBaseCommand extends Command {
if (!account) { if (!account) {
throw new Error(`devnet account not found`); throw new Error(`devnet account not found`);
} }
const accountDiscriminator = account.data.slice( const accountDiscriminator = account.data.slice(
0, 0,
ACCOUNT_DISCRIMINATOR_SIZE ACCOUNT_DISCRIMINATOR_SIZE
@ -228,6 +232,7 @@ abstract class PrintBaseCommand extends Command {
if (message) { if (message) {
logger.info(chalk.red(`${FAILED_ICON}${message}`)); logger.info(chalk.red(`${FAILED_ICON}${message}`));
} }
if (error.message) { if (error.message) {
const messageLines = error.message.split("\n"); const messageLines = error.message.split("\n");
logger.error(messageLines[0]); 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 { Flags } from "@oclif/core";
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 BaseCommand from "../../../BaseCommand"; import BaseCommand from "../../../BaseCommand";
import { OutputFileExistsNoForce } from "../../../types";
import { CHECK_ICON, loadKeypair } from "../../../utils";
export default class AggregatorAddJob extends BaseCommand { export default class AggregatorAddJob extends BaseCommand {
aggregatorAuthority?: Keypair | undefined = undefined; static description = "add a job to an aggregator";
aggregatorAccount: AggregatorAccount;
jobDefinition?: fromJobJSON = undefined;
jobAccount?: JobAccount;
outputFile = "";
static description = "add a job account to an aggregator";
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
force: flags.boolean({ jobDefinition: Flags.string({
description: "overwrite outputFile if existing",
}),
outputFile: flags.string({
char: "f",
description: "output file to save aggregator definition to",
}),
jobDefinition: flags.string({
description: "filesystem path of job json definition file", description: "filesystem path of job json definition file",
exclusive: ["jobKey"], exclusive: ["jobKey"],
}), }),
jobKey: flags.string({ jobKey: Flags.string({
description: description:
"public key of an existing job account to add to an aggregator", "public key of an existing job account to add to an aggregator",
exclusive: ["jobDefinition"], exclusive: ["jobDefinition"],
}), }),
aggregatorAuthority: flags.string({ authority: Flags.string({
char: "a", char: "a",
description: "alternate keypair that is the authority for the aggregator", description: "alternate keypair that is the authority for the aggregator",
}), }),
@ -54,88 +24,61 @@ export default class AggregatorAddJob extends BaseCommand {
static args = [ static args = [
{ {
name: "aggregatorKey", name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account", description: "public key of the aggregator account",
}, },
]; ];
static examples = ["$ sbv2 aggregator:add:job"]; 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() { async run() {
const job = this.jobAccount const { args, flags } = await this.parse(AggregatorAddJob);
? await JobClass.fromAccount(this.context, this.jobAccount)
: await JobClass.build(this.context, this.program, this.jobDefinition);
const txn = await this.aggregatorAccount.addJob( // const aggregatorAccount = new AggregatorAccount({
job.account, // program: this.program,
this.aggregatorAuthority // publicKey: new PublicKey(args.aggregatorKey),
); // });
// const aggregator = await aggregatorAccount.loadData();
// const authority = await this.loadAuthority(
// flags.authority,
// aggregator.authority
// );
const aggregator = await AggregatorClass.fromAccount( // let jobAccount: JobAccount;
this.context, // if (flags.jobDefinition) {
this.aggregatorAccount // const jobDefinition = JSON.parse(flags.jobDefinition, pubKeyReviver);
); // // create job account
// }
if (this.silent) { // if (flags.jobKey) {
console.log(txn); // jobAccount = new JobAccount({
} else { // program: this.program,
this.logger.log( // publicKey: new PublicKey(flags.jobKey),
`${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 (this.outputFile) { // const job = this.jobAccount
fs.writeFileSync( // ? await JobClass.fromAccount(this.context, this.jobAccount)
this.outputFile, // : await JobClass.build(this.context, this.program, this.jobDefinition);
JSON.stringify(aggregator, pubKeyConverter, 2) // 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) { async catch(error) {

View File

@ -1,5 +1,5 @@
/* eslint-disable unicorn/no-array-push-push */ /* 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 anchor from "@project-serum/anchor";
import * as spl from "@solana/spl-token"; import * as spl from "@solana/spl-token";
import { import {
@ -13,6 +13,7 @@ import {
packAndSend, packAndSend,
prettyPrintAggregator, prettyPrintAggregator,
promiseWithTimeout, promiseWithTimeout,
verifyProgramHasPayer,
} from "@switchboard-xyz/sbv2-utils"; } from "@switchboard-xyz/sbv2-utils";
import { import {
AggregatorAccount, AggregatorAccount,
@ -28,7 +29,6 @@ import {
} from "@switchboard-xyz/switchboard-v2"; } from "@switchboard-xyz/switchboard-v2";
import Big from "big.js"; import Big from "big.js";
import BaseCommand from "../../../BaseCommand"; import BaseCommand from "../../../BaseCommand";
import { verifyProgramHasPayer } from "../../../utils";
// TODO: Fix command so it accepts a feed authority flag // TODO: Fix command so it accepts a feed authority flag
// TODO: Add flag that skips job creation // TODO: Add flag that skips job creation
@ -37,52 +37,47 @@ export default class AggregatorCreateCopy extends BaseCommand {
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
force: flags.boolean({ description: "skip job confirmation" }), authority: Flags.string({
outputFile: flags.string({
char: "f",
description: "output file to save aggregator definition to",
}),
authority: flags.string({
char: "a", char: "a",
description: "alternate keypair that will be the aggregator authority", description: "alternate keypair that will be the aggregator authority",
}), }),
minOracles: flags.integer({ minOracles: Flags.integer({
description: "override source aggregator's minOracleResults", description: "override source aggregator's minOracleResults",
}), }),
batchSize: flags.integer({ batchSize: Flags.integer({
description: "override source aggregator's oracleRequestBatchSize", description: "override source aggregator's oracleRequestBatchSize",
}), }),
minJobs: flags.integer({ minJobs: Flags.integer({
description: "override source aggregator's minJobResults", description: "override source aggregator's minJobResults",
}), }),
minUpdateDelay: flags.integer({ minUpdateDelay: Flags.integer({
description: "override source aggregator's minUpdateDelaySeconds", description: "override source aggregator's minUpdateDelaySeconds",
}), }),
forceReportPeriod: flags.integer({ forceReportPeriod: Flags.integer({
description: "override source aggregator's forceReportPeriod", description: "override source aggregator's forceReportPeriod",
}), }),
varianceThreshold: flags.string({ varianceThreshold: Flags.string({
description: "override source aggregator's varianceThreshold", description: "override source aggregator's varianceThreshold",
}), }),
queueKey: flags.string({ queueKey: Flags.string({
description: "public key of the queue to create aggregator for", description: "public key of the queue to create aggregator for",
required: true, required: true,
}), }),
crankKey: flags.string({ crankKey: Flags.string({
description: "public key of the crank to push aggregator to", description: "public key of the crank to push aggregator to",
required: false, required: false,
}), }),
enable: flags.boolean({ enable: Flags.boolean({
description: "set permissions to PERMIT_ORACLE_QUEUE_USAGE", description: "set permissions to PERMIT_ORACLE_QUEUE_USAGE",
}), }),
queueAuthority: flags.string({ queueAuthority: Flags.string({
description: "alternative keypair to use for queue authority", description: "alternative keypair to use for queue authority",
}), }),
copyJobs: flags.boolean({ copyJobs: Flags.boolean({
description: description:
"create copy of job accounts instead of referincing existing job account", "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", // description: "alternative solana cluster to copy source aggregator from",
// required: false, // required: false,
// options: ["devnet", "mainnet-beta"], // options: ["devnet", "mainnet-beta"],
@ -92,8 +87,6 @@ export default class AggregatorCreateCopy extends BaseCommand {
static args = [ static args = [
{ {
name: "aggregatorSource", name: "aggregatorSource",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account to copy", description: "public key of the aggregator account to copy",
}, },
]; ];
@ -106,7 +99,7 @@ export default class AggregatorCreateCopy extends BaseCommand {
async run() { async run() {
verifyProgramHasPayer(this.program); verifyProgramHasPayer(this.program);
const { args, flags } = this.parse(AggregatorCreateCopy); const { args, flags } = await this.parse(AggregatorCreateCopy);
const payerKeypair = programWallet(this.program); const payerKeypair = programWallet(this.program);
const feedAuthority = await this.loadAuthority(flags.authority); 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 anchor from "@project-serum/anchor";
import * as spl from "@solana/spl-token"; import * as spl from "@solana/spl-token";
import { import {
@ -40,46 +40,45 @@ export default class AggregatorCreate extends BaseCommand {
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
force: flags.boolean({ description: "skip job confirmation" }), authority: Flags.string({
authority: flags.string({
char: "a", char: "a",
description: "alternate keypair that is the authority for the aggregator", description: "alternate keypair that is the authority for the aggregator",
}), }),
crankKey: flags.string({ crankKey: Flags.string({
description: "public key of the crank to join", description: "public key of the crank to join",
}), }),
enable: flags.boolean({ enable: Flags.boolean({
description: "set permissions to PERMIT_ORACLE_QUEUE_USAGE", description: "set permissions to PERMIT_ORACLE_QUEUE_USAGE",
}), }),
queueAuthority: flags.string({ queueAuthority: Flags.string({
description: "alternative keypair to use for queue authority", description: "alternative keypair to use for queue authority",
}), }),
name: flags.string({ name: Flags.string({
char: "n", char: "n",
description: "name of the aggregator", description: "name of the aggregator",
}), }),
forceReportPeriod: flags.integer({ forceReportPeriod: Flags.integer({
description: description:
"Number of seconds for which, even if the variance threshold is not passed, accept new responses from oracles.", "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", 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", description: "number of jobs that must respond before an oracle responds",
}), }),
minOracles: flags.string({ minOracles: Flags.integer({
description: description:
"number of oracles that must respond before a value is accepted on-chain", "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", description: "set an aggregator's minimum update delay",
}), }),
varianceThreshold: flags.string({ varianceThreshold: Flags.string({
description: 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", "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", char: "j",
description: "filesystem path to job definition file", description: "filesystem path to job definition file",
multiple: true, multiple: true,
@ -89,8 +88,6 @@ export default class AggregatorCreate extends BaseCommand {
static args = [ static args = [
{ {
name: "queueKey", name: "queueKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: description:
"public key of the oracle queue account to create aggregator for", "public key of the oracle queue account to create aggregator for",
}, },
@ -98,7 +95,7 @@ export default class AggregatorCreate extends BaseCommand {
async run() { async run() {
verifyProgramHasPayer(this.program); verifyProgramHasPayer(this.program);
const { args, flags } = this.parse(AggregatorCreate); const { args, flags } = await this.parse(AggregatorCreate);
const payerKeypair = programWallet(this.program); const payerKeypair = programWallet(this.program);
const feedAuthority = await this.loadAuthority(flags.authority); const feedAuthority = await this.loadAuthority(flags.authority);
@ -109,7 +106,7 @@ export default class AggregatorCreate extends BaseCommand {
); );
const queueAccount = new OracleQueueAccount({ const queueAccount = new OracleQueueAccount({
program: this.program, program: this.program,
publicKey: args.queueKey, publicKey: new PublicKey(args.queueKey),
}); });
const queue = await queueAccount.loadData(); const queue = await queueAccount.loadData();
const switchTokenMint = await queueAccount.loadMint(); const switchTokenMint = await queueAccount.loadMint();
@ -135,7 +132,7 @@ export default class AggregatorCreate extends BaseCommand {
flags.job.map( flags.job.map(
async ( async (
jobDefinition jobDefinition
): Promise<[TransactionInstruction, Keypair]> => { ): Promise<[TransactionInstruction[], Keypair]> => {
const jobJson = JSON.parse( const jobJson = JSON.parse(
fs.readFileSync( fs.readFileSync(
jobDefinition.startsWith("/") jobDefinition.startsWith("/")
@ -158,6 +155,19 @@ export default class AggregatorCreate extends BaseCommand {
).finish() ).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 const createJobIxn = await this.program.methods
.jobInit({ .jobInit({
name: Buffer.from("").slice(0, 32), name: Buffer.from("").slice(0, 32),
@ -175,13 +185,15 @@ export default class AggregatorCreate extends BaseCommand {
.signers([feedAuthority]) .signers([feedAuthority])
.instruction(); .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 const jobAccounts = createJobs
.map((index) => index[1]) .map((index) => index[1])
.map((jobKeypair) => { .map((jobKeypair) => {
@ -229,7 +241,7 @@ export default class AggregatorCreate extends BaseCommand {
varianceThreshold: flags.varianceThreshold varianceThreshold: flags.varianceThreshold
? SwitchboardDecimal.fromBig(new Big(flags.varianceThreshold)) ? SwitchboardDecimal.fromBig(new Big(flags.varianceThreshold))
: SwitchboardDecimal.fromBig(new Big(0)), : SwitchboardDecimal.fromBig(new Big(0)),
forceReportPeriod: flags.forceReportPeriod ?? 0, forceReportPeriod: new anchor.BN(flags.forceReportPeriod ?? 0),
stateBump, stateBump,
}) })
.accounts({ .accounts({
@ -363,8 +375,8 @@ export default class AggregatorCreate extends BaseCommand {
const createAccountSignatures = packAndSend( const createAccountSignatures = packAndSend(
this.program, this.program,
[createAccountInstructions, addJobIxns], [createJobIxn, createAccountInstructions, addJobIxns],
createAccountSigners, [...createJobSigners, ...createAccountSigners],
payerKeypair.publicKey payerKeypair.publicKey
).catch((error) => { ).catch((error) => {
throw 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 * as anchor from "@project-serum/anchor";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
import { prettyPrintAggregator } from "@switchboard-xyz/sbv2-utils"; import {
prettyPrintAggregator,
verifyProgramHasPayer,
} from "@switchboard-xyz/sbv2-utils";
import { import {
AggregatorAccount, AggregatorAccount,
JobAccount, JobAccount,
@ -12,14 +16,13 @@ import {
import chalk from "chalk"; import chalk from "chalk";
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import BaseCommand from "../../../BaseCommand";
import { import {
fromAggregatorJSON, CHECK_ICON,
fromJobJSON, loadKeypair,
pubKeyConverter, pubKeyConverter,
pubKeyReviver, pubKeyReviver,
} from "../../../accounts"; } from "../../../utils";
import BaseCommand from "../../../BaseCommand";
import { CHECK_ICON, loadKeypair, verifyProgramHasPayer } from "../../../utils";
export default class JsonCreateAggregator extends BaseCommand { export default class JsonCreateAggregator extends BaseCommand {
static description = "create an aggregator from a json file"; static description = "create an aggregator from a json file";
@ -28,18 +31,11 @@ export default class JsonCreateAggregator extends BaseCommand {
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
force: flags.boolean({ queueKey: Flags.string({
description: "overwrite output file",
}),
outputFile: flags.string({
description: "output aggregator definition to a json file",
char: "f",
}),
queueKey: flags.string({
description: "public key of the oracle queue to create aggregator for", description: "public key of the oracle queue to create aggregator for",
char: "q", char: "q",
}), }),
authority: flags.string({ authority: Flags.string({
description: description:
"alternate keypair that will be the authority for the aggregator", "alternate keypair that will be the authority for the aggregator",
char: "a", char: "a",
@ -49,7 +45,6 @@ export default class JsonCreateAggregator extends BaseCommand {
static args = [ static args = [
{ {
name: "definitionFile", name: "definitionFile",
required: true,
description: "filesystem path of queue definition json file", description: "filesystem path of queue definition json file",
}, },
]; ];
@ -59,7 +54,7 @@ export default class JsonCreateAggregator extends BaseCommand {
]; ];
async run() { async run() {
const { args, flags } = this.parse(JsonCreateAggregator); const { args, flags } = await this.parse(JsonCreateAggregator);
verifyProgramHasPayer(this.program); verifyProgramHasPayer(this.program);
const payerKeypair = programWallet(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"); throw new Error("input file does not exist");
} }
const aggregatorDefinition: fromAggregatorJSON = JSON.parse( const aggregatorDefinition: any = JSON.parse(
fs.readFileSync(definitionFile, "utf-8"), fs.readFileSync(definitionFile, "utf-8"),
pubKeyReviver 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); let authority = programWallet(this.program);
if (flags.authority) { if (flags.authority) {
authority = await loadKeypair(flags.authority); authority = await loadKeypair(flags.authority);
@ -146,7 +135,7 @@ export default class JsonCreateAggregator extends BaseCommand {
const jobs: JobAccount[] = []; const jobs: JobAccount[] = [];
if (aggregatorDefinition.jobs) { if (aggregatorDefinition.jobs) {
for await (const job of aggregatorDefinition.jobs) { for await (const job of aggregatorDefinition.jobs) {
const jobDefinition: fromJobJSON = JSON.parse( const jobDefinition: any = JSON.parse(
JSON.stringify(job), JSON.stringify(job),
pubKeyConverter 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) { if (this.silent) {
console.log(aggregator.publicKey.toString()); console.log(aggregator.publicKey.toString());
} else { } else {

View File

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

View File

@ -19,19 +19,17 @@ export default class AggregatorPermissionCreate extends BaseCommand {
static args = [ static args = [
{ {
name: "aggregatorKey", name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account", description: "public key of the aggregator account",
}, },
]; ];
async run() { async run() {
const { args } = this.parse(AggregatorPermissionCreate); const { args } = await this.parse(AggregatorPermissionCreate);
verifyProgramHasPayer(this.program); verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({ const aggregatorAccount = new AggregatorAccount({
program: this.program, program: this.program,
publicKey: args.aggregatorKey, publicKey: new PublicKey(args.aggregatorKey),
}); });
const aggregator = await aggregatorAccount.loadData(); const aggregator = await aggregatorAccount.loadData();
@ -69,9 +67,7 @@ export default class AggregatorPermissionCreate extends BaseCommand {
console.log(permissionAccount.publicKey.toString()); console.log(permissionAccount.publicKey.toString());
} else { } else {
this.logger.log( this.logger.log(
`${chalk.green( `${chalk.green(`${CHECK_ICON}Permission account created successfully`)}`
`${CHECK_ICON}Permission account created successfully`
)}`
); );
this.logger.log(await prettyPrintPermissions(permissionAccount)); 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 { PublicKey } from "@solana/web3.js";
import { AggregatorAccount, JobAccount } from "@switchboard-xyz/switchboard-v2"; import { AggregatorAccount, JobAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk"; import chalk from "chalk";
@ -10,10 +10,7 @@ export default class AggregatorRemoveJob extends BaseCommand {
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
force: flags.boolean({ authority: Flags.string({
description: "overwrite outputFile if existing",
}),
authority: flags.string({
char: "a", char: "a",
description: "alternate keypair that is the authority for the aggregator", description: "alternate keypair that is the authority for the aggregator",
}), }),
@ -22,14 +19,10 @@ export default class AggregatorRemoveJob extends BaseCommand {
static args = [ static args = [
{ {
name: "aggregatorKey", name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account", description: "public key of the aggregator account",
}, },
{ {
name: "jobKey", name: "jobKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: description:
"public key of an existing job account to remove from an aggregator", "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"]; static examples = ["$ sbv2 aggregator:remove:job"];
async run() { async run() {
const { args, flags } = this.parse(AggregatorRemoveJob); const { args, flags } = await this.parse(AggregatorRemoveJob);
verifyProgramHasPayer(this.program); verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({ const aggregatorAccount = new AggregatorAccount({
program: this.program, program: this.program,
publicKey: args.aggregatorKey, publicKey: new PublicKey(args.aggregatorKey),
}); });
const aggregator = await aggregatorAccount.loadData(); const aggregator = await aggregatorAccount.loadData();
const authority = await this.loadAuthority( 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 { PublicKey } from "@solana/web3.js";
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2"; import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk"; import chalk from "chalk";
@ -10,7 +10,7 @@ export default class AggregatorSetAuthority extends BaseCommand {
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
currentAuthority: flags.string({ currentAuthority: Flags.string({
char: "a", char: "a",
description: "alternate keypair that is the authority for the aggregator", description: "alternate keypair that is the authority for the aggregator",
}), }),
@ -19,13 +19,10 @@ export default class AggregatorSetAuthority extends BaseCommand {
static args = [ static args = [
{ {
name: "aggregatorKey", name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account", description: "public key of the aggregator account",
}, },
{ {
name: "newAuthority", name: "newAuthority",
required: true,
description: "keypair path of new authority", description: "keypair path of new authority",
}, },
]; ];
@ -33,13 +30,13 @@ export default class AggregatorSetAuthority extends BaseCommand {
// static examples = ["$ sbv2 aggregator:set:authority"]; // static examples = ["$ sbv2 aggregator:set:authority"];
async run() { async run() {
const { args, flags } = this.parse(AggregatorSetAuthority); const { args, flags } = await this.parse(AggregatorSetAuthority);
const newAuthority = await loadKeypair(args.newAuthority); const newAuthority = await loadKeypair(args.newAuthority);
const aggregatorAccount = new AggregatorAccount({ const aggregatorAccount = new AggregatorAccount({
program: this.program, program: this.program,
publicKey: args.aggregatorKey, publicKey: new PublicKey(args.aggregatorKey),
}); });
const aggregator = await aggregatorAccount.loadData(); const aggregator = await aggregatorAccount.loadData();
const currentAuthority = await this.loadAuthority( 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 { PublicKey } from "@solana/web3.js";
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2"; import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk"; import chalk from "chalk";
@ -10,7 +10,7 @@ export default class AggregatorSetBatchSize extends BaseCommand {
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
authority: flags.string({ authority: Flags.string({
char: "a", char: "a",
description: "alternate keypair that is the authority for the aggregator", description: "alternate keypair that is the authority for the aggregator",
}), }),
@ -19,8 +19,6 @@ export default class AggregatorSetBatchSize extends BaseCommand {
static args = [ static args = [
{ {
name: "aggregatorKey", name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account", description: "public key of the aggregator account",
}, },
{ {
@ -33,7 +31,7 @@ export default class AggregatorSetBatchSize extends BaseCommand {
// static examples = ["$ sbv2 aggregator:set:authority"]; // static examples = ["$ sbv2 aggregator:set:authority"];
async run() { async run() {
const { args, flags } = this.parse(AggregatorSetBatchSize); const { args, flags } = await this.parse(AggregatorSetBatchSize);
verifyProgramHasPayer(this.program); verifyProgramHasPayer(this.program);
const batchSize = Number.parseInt(args.batchSize, 10); const batchSize = Number.parseInt(args.batchSize, 10);
@ -43,7 +41,7 @@ export default class AggregatorSetBatchSize extends BaseCommand {
const aggregatorAccount = new AggregatorAccount({ const aggregatorAccount = new AggregatorAccount({
program: this.program, program: this.program,
publicKey: args.aggregatorKey, publicKey: new PublicKey(args.aggregatorKey),
}); });
const aggregator = await aggregatorAccount.loadData(); 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 { PublicKey } from "@solana/web3.js";
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2"; import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk"; import chalk from "chalk";
@ -12,7 +12,7 @@ export default class AggregatorSetForceReportPeriod extends BaseCommand {
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
authority: flags.string({ authority: Flags.string({
char: "a", char: "a",
description: "alternate keypair that is the authority for the aggregator", description: "alternate keypair that is the authority for the aggregator",
}), }),
@ -21,14 +21,10 @@ export default class AggregatorSetForceReportPeriod extends BaseCommand {
static args = [ static args = [
{ {
name: "aggregatorKey", name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator", description: "public key of the aggregator",
}, },
{ {
name: "forceReportPeriod", name: "forceReportPeriod",
required: true,
parse: (value: string) => Number.parseInt(value),
description: description:
"Number of seconds for which, even if the variance threshold is not passed, accept new responses from oracles.", "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() { async run() {
const { args, flags } = this.parse(AggregatorSetForceReportPeriod); const { args, flags } = await this.parse(AggregatorSetForceReportPeriod);
verifyProgramHasPayer(this.program); verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({ const aggregatorAccount = new AggregatorAccount({
program: this.program, program: this.program,
publicKey: args.aggregatorKey, publicKey: new PublicKey(args.aggregatorKey),
}); });
const aggregator = await aggregatorAccount.loadData(); const aggregator = await aggregatorAccount.loadData();
const authority = await this.loadAuthority( 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 { PublicKey } from "@solana/web3.js";
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2"; import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk"; import chalk from "chalk";
@ -13,7 +13,7 @@ export default class AggregatorSetHistoryBuffer extends BaseCommand {
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
authority: flags.string({ authority: Flags.string({
char: "a", char: "a",
description: "alternate keypair that is the authority for the aggregator", description: "alternate keypair that is the authority for the aggregator",
}), }),
@ -22,14 +22,10 @@ export default class AggregatorSetHistoryBuffer extends BaseCommand {
static args = [ static args = [
{ {
name: "aggregatorKey", name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator to add to a crank", description: "public key of the aggregator to add to a crank",
}, },
{ {
name: "size", name: "size",
required: true,
parse: (value: string) => Number.parseInt(value, 10),
description: "size of history buffer", description: "size of history buffer",
}, },
]; ];
@ -39,12 +35,12 @@ export default class AggregatorSetHistoryBuffer extends BaseCommand {
]; ];
async run() { async run() {
const { args, flags } = this.parse(AggregatorSetHistoryBuffer); const { args, flags } = await this.parse(AggregatorSetHistoryBuffer);
verifyProgramHasPayer(this.program); verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({ const aggregatorAccount = new AggregatorAccount({
program: this.program, program: this.program,
publicKey: args.aggregatorKey, publicKey: new PublicKey(args.aggregatorKey),
}); });
const aggregator = await aggregatorAccount.loadData(); const aggregator = await aggregatorAccount.loadData();
const authority = await this.loadAuthority( 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 { PublicKey, Transaction } from "@solana/web3.js";
import { verifyProgramHasPayer } from "@switchboard-xyz/sbv2-utils";
import { import {
AggregatorAccount, AggregatorAccount,
OracleQueueAccount, OracleQueueAccount,
@ -9,7 +10,7 @@ import {
import Big from "big.js"; import Big from "big.js";
import chalk from "chalk"; import chalk from "chalk";
import BaseCommand from "../../../BaseCommand"; import BaseCommand from "../../../BaseCommand";
import { CHECK_ICON, verifyProgramHasPayer } from "../../../utils"; import { CHECK_ICON } from "../../../utils";
export default class AggregatorSet extends BaseCommand { export default class AggregatorSet extends BaseCommand {
static description = "set an aggregator's config"; static description = "set an aggregator's config";
@ -18,31 +19,31 @@ export default class AggregatorSet extends BaseCommand {
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
authority: flags.string({ authority: Flags.string({
char: "a", char: "a",
description: "alternate keypair that is the authority for the aggregator", description: "alternate keypair that is the authority for the aggregator",
}), }),
forceReportPeriod: flags.string({ forceReportPeriod: Flags.string({
description: description:
"Number of seconds for which, even if the variance threshold is not passed, accept new responses from oracles.", "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", // 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", description: "number of jobs that must respond before an oracle responds",
}), }),
minOracles: flags.string({ minOracles: Flags.string({
description: description:
"number of oracles that must respond before a value is accepted on-chain", "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", description: "public key of the new oracle queue",
}), }),
updateInterval: flags.string({ updateInterval: Flags.string({
description: "set an aggregator's minimum update delay", description: "set an aggregator's minimum update delay",
}), }),
varianceThreshold: flags.string({ varianceThreshold: Flags.string({
description: 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", "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 = [ static args = [
{ {
name: "aggregatorKey", name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator", description: "public key of the aggregator",
}, },
]; ];
@ -62,14 +61,14 @@ export default class AggregatorSet extends BaseCommand {
]; ];
async run() { async run() {
const { args, flags } = this.parse(AggregatorSet); const { args, flags } = await this.parse(AggregatorSet);
verifyProgramHasPayer(this.program); verifyProgramHasPayer(this.program);
const payerKeypair = programWallet(this.program); const payerKeypair = programWallet(this.program);
const aggregatorAccount = new AggregatorAccount({ const aggregatorAccount = new AggregatorAccount({
program: this.program, program: this.program,
publicKey: args.aggregatorKey, publicKey: new PublicKey(args.aggregatorKey),
}); });
const aggregator = await aggregatorAccount.loadData(); const aggregator = await aggregatorAccount.loadData();
const authority = await this.loadAuthority( const authority = await this.loadAuthority(
@ -114,7 +113,7 @@ export default class AggregatorSet extends BaseCommand {
// min oracles responses // min oracles responses
if (flags.minOracles) { if (flags.minOracles) {
const minOracles = Number.parseInt(flags.minOracles); const minOracles = Number.parseInt(flags.minOracles, 10);
txn.add( txn.add(
await this.program.methods await this.program.methods
.aggregatorSetMinOracles({ .aggregatorSetMinOracles({
@ -130,7 +129,7 @@ export default class AggregatorSet extends BaseCommand {
// min job responses // min job responses
if (flags.minJobs) { if (flags.minJobs) {
const minJobs = Number.parseInt(flags.minJobs); const minJobs = Number.parseInt(flags.minJobs, 10);
txn.add( txn.add(
await this.program.methods await this.program.methods
.aggregatorSetMinJobs({ .aggregatorSetMinJobs({
@ -167,7 +166,7 @@ export default class AggregatorSet extends BaseCommand {
txn.add( txn.add(
await this.program.methods await this.program.methods
.aggregatorSetForceReportPeriod({ .aggregatorSetForceReportPeriod({
forceReportPeriod: Number.parseInt(flags.forceReportPeriod), forceReportPeriod: Number.parseInt(flags.forceReportPeriod, 10),
}) })
.accounts({ .accounts({
aggregator: aggregatorAccount.publicKey, 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 { PublicKey } from "@solana/web3.js";
import { verifyProgramHasPayer } from "@switchboard-xyz/sbv2-utils";
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2"; import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk"; import chalk from "chalk";
import BaseCommand from "../../../BaseCommand"; import BaseCommand from "../../../BaseCommand";
import { CHECK_ICON, verifyProgramHasPayer } from "../../../utils"; import { CHECK_ICON } from "../../../utils";
export default class AggregatorSetMinJobResults extends BaseCommand { export default class AggregatorSetMinJobResults extends BaseCommand {
static description = static description =
@ -11,7 +12,7 @@ export default class AggregatorSetMinJobResults extends BaseCommand {
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
authority: flags.string({ authority: Flags.string({
char: "a", char: "a",
description: "alternate keypair that is the authority for the aggregator", description: "alternate keypair that is the authority for the aggregator",
}), }),
@ -20,13 +21,10 @@ export default class AggregatorSetMinJobResults extends BaseCommand {
static args = [ static args = [
{ {
name: "aggregatorKey", name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account", description: "public key of the aggregator account",
}, },
{ {
name: "minJobResults", name: "minJobResults",
required: true,
description: "number of jobs that must respond before an oracle responds", 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"]; // static examples = ["$ sbv2 aggregator:set:authority"];
async run() { async run() {
const { args, flags } = this.parse(AggregatorSetMinJobResults); const { args, flags } = await this.parse(AggregatorSetMinJobResults);
verifyProgramHasPayer(this.program); verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({ const aggregatorAccount = new AggregatorAccount({
program: this.program, program: this.program,
publicKey: args.aggregatorKey, publicKey: new PublicKey(args.aggregatorKey),
}); });
const aggregator = await aggregatorAccount.loadData(); const aggregator = await aggregatorAccount.loadData();
const authority = await this.loadAuthority( const authority = await this.loadAuthority(
@ -51,6 +49,7 @@ export default class AggregatorSetMinJobResults extends BaseCommand {
if (minJobResults <= 0 || minJobResults > 16) { if (minJobResults <= 0 || minJobResults > 16) {
throw new Error(`Invalid min job size (1 - 16), ${minJobResults}`); throw new Error(`Invalid min job size (1 - 16), ${minJobResults}`);
} }
if (minJobResults > aggregator.jobPubkeysSize) { if (minJobResults > aggregator.jobPubkeysSize) {
throw new Error( throw new Error(
`Min jobs ${minJobResults} is greater than current number of jobs ${aggregator.jobPubkeysSize} ` `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 { PublicKey } from "@solana/web3.js";
import { verifyProgramHasPayer } from "@switchboard-xyz/sbv2-utils";
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2"; import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk"; import chalk from "chalk";
import BaseCommand from "../../../BaseCommand"; import BaseCommand from "../../../BaseCommand";
import { CHECK_ICON, verifyProgramHasPayer } from "../../../utils"; import { CHECK_ICON } from "../../../utils";
export default class AggregatorSetMinOracleResults extends BaseCommand { export default class AggregatorSetMinOracleResults extends BaseCommand {
static description = static description =
@ -11,7 +12,7 @@ export default class AggregatorSetMinOracleResults extends BaseCommand {
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
authority: flags.string({ authority: Flags.string({
char: "a", char: "a",
description: "alternate keypair that is the authority for the aggregator", description: "alternate keypair that is the authority for the aggregator",
}), }),
@ -20,13 +21,10 @@ export default class AggregatorSetMinOracleResults extends BaseCommand {
static args = [ static args = [
{ {
name: "aggregatorKey", name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account", description: "public key of the aggregator account",
}, },
{ {
name: "minOracleResults", name: "minOracleResults",
required: true,
description: description:
"number of oracles that must respond before a value is accepted on-chain", "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"]; // static examples = ["$ sbv2 aggregator:set:authority"];
async run() { async run() {
const { args, flags } = this.parse(AggregatorSetMinOracleResults); const { args, flags } = await this.parse(AggregatorSetMinOracleResults);
verifyProgramHasPayer(this.program); verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({ const aggregatorAccount = new AggregatorAccount({
program: this.program, program: this.program,
publicKey: args.aggregatorKey, publicKey: new PublicKey(args.aggregatorKey),
}); });
const aggregator = await aggregatorAccount.loadData(); 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 { PublicKey } from "@solana/web3.js";
import { import {
AggregatorAccount, AggregatorAccount,
@ -13,7 +13,7 @@ export default class AggregatorSetQueue extends BaseCommand {
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
authority: flags.string({ authority: Flags.string({
char: "a", char: "a",
description: "alternate keypair that is the authority for the aggregator", description: "alternate keypair that is the authority for the aggregator",
}), }),
@ -22,31 +22,27 @@ export default class AggregatorSetQueue extends BaseCommand {
static args = [ static args = [
{ {
name: "aggregatorKey", name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator", description: "public key of the aggregator",
}, },
{ {
name: "queueKey", name: "queueKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the oracle queue", description: "public key of the oracle queue",
}, },
]; ];
async run() { async run() {
const { args, flags } = this.parse(AggregatorSetQueue); const { args, flags } = await this.parse(AggregatorSetQueue);
verifyProgramHasPayer(this.program); verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({ const aggregatorAccount = new AggregatorAccount({
program: this.program, program: this.program,
publicKey: args.aggregatorKey, publicKey: new PublicKey(args.aggregatorKey),
}); });
const aggregator = await aggregatorAccount.loadData(); const aggregator = await aggregatorAccount.loadData();
const oracleQueue = new OracleQueueAccount({ const oracleQueue = new OracleQueueAccount({
program: this.program, program: this.program,
publicKey: args.queueKey, publicKey: new PublicKey(args.queueKey),
}); });
const authority = await this.loadAuthority( 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 { PublicKey } from "@solana/web3.js";
import { verifyProgramHasPayer } from "@switchboard-xyz/sbv2-utils";
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2"; import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk"; import chalk from "chalk";
import BaseCommand from "../../../BaseCommand"; import BaseCommand from "../../../BaseCommand";
import { CHECK_ICON, verifyProgramHasPayer } from "../../../utils"; import { CHECK_ICON } from "../../../utils";
export default class AggregatorSetUpdateInterval extends BaseCommand { export default class AggregatorSetUpdateInterval extends BaseCommand {
static description = "set an aggregator's minimum update delay"; static description = "set an aggregator's minimum update delay";
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
authority: flags.string({ authority: Flags.string({
char: "a", char: "a",
description: "alternate keypair that is the authority for the aggregator", description: "alternate keypair that is the authority for the aggregator",
}), }),
@ -19,14 +20,10 @@ export default class AggregatorSetUpdateInterval extends BaseCommand {
static args = [ static args = [
{ {
name: "aggregatorKey", name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account", description: "public key of the aggregator account",
}, },
{ {
name: "updateInterval", name: "updateInterval",
required: true,
parse: (newInterval: string) => Number.parseInt(newInterval, 10),
description: "set an aggregator's minimum update delay", description: "set an aggregator's minimum update delay",
}, },
]; ];
@ -36,12 +33,12 @@ export default class AggregatorSetUpdateInterval extends BaseCommand {
]; ];
async run() { async run() {
const { args, flags } = this.parse(AggregatorSetUpdateInterval); const { args, flags } = await this.parse(AggregatorSetUpdateInterval);
verifyProgramHasPayer(this.program); verifyProgramHasPayer(this.program);
const aggregatorAccount = new AggregatorAccount({ const aggregatorAccount = new AggregatorAccount({
program: this.program, program: this.program,
publicKey: args.aggregatorKey, publicKey: new PublicKey(args.aggregatorKey),
}); });
const aggregator = await aggregatorAccount.loadData(); const aggregator = await aggregatorAccount.loadData();
@ -63,7 +60,7 @@ export default class AggregatorSetUpdateInterval extends BaseCommand {
); );
const txn = await aggregatorAccount.setUpdateInterval({ const txn = await aggregatorAccount.setUpdateInterval({
newInterval: args.updateInterval, newInterval: Number.parseInt(args.updateInterval, 10),
authority, authority,
}); });

View File

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

View File

@ -19,8 +19,6 @@ export default class AggregatorUpdate extends BaseCommand {
static args = [ static args = [
{ {
name: "aggregatorKey", name: "aggregatorKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the aggregator account to deserialize", description: "public key of the aggregator account to deserialize",
}, },
]; ];
@ -30,11 +28,11 @@ export default class AggregatorUpdate extends BaseCommand {
]; ];
async run() { async run() {
const { args } = this.parse(AggregatorUpdate); const { args } = await this.parse(AggregatorUpdate);
const aggregatorAccount = new AggregatorAccount({ const aggregatorAccount = new AggregatorAccount({
program: this.program, program: this.program,
publicKey: args.aggregatorKey, publicKey: new PublicKey(args.aggregatorKey),
}); });
const aggregator = await aggregatorAccount.loadData(); const aggregator = await aggregatorAccount.loadData();
@ -79,6 +77,7 @@ export default class AggregatorUpdate extends BaseCommand {
this.context.logger.info(error.toString()); this.context.logger.info(error.toString());
this.exit(0); this.exit(0);
} }
super.catch(error, "failed to open a new aggregator update round"); 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 BaseCommand from "../BaseCommand";
import OracleDeposit from "./oracle/deposit"; import OracleDeposit from "./oracle/deposit";
import OracleWithdraw from "./oracle/withdraw"; import OracleWithdraw from "./oracle/withdraw";
@ -16,7 +16,7 @@ export default class Config extends BaseCommand {
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
pubkey: flags.string({ pubkey: Flags.string({
description: description:
"command specific. override default account with provided public key", "command specific. override default account with provided public key",
}), }),
@ -39,7 +39,7 @@ export default class Config extends BaseCommand {
async init() { async init() {
await super.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.passThroughArguments = argv.slice(1);
this.baseCommand = args.baseCommand; this.baseCommand = args.baseCommand;
this.flags = flags; this.flags = flags;
@ -51,6 +51,7 @@ export default class Config extends BaseCommand {
await OracleWithdraw.run(this.passThroughArguments); await OracleWithdraw.run(this.passThroughArguments);
break; break;
} }
case "oracle:deposit": { case "oracle:deposit": {
await OracleDeposit.run(this.passThroughArguments); await OracleDeposit.run(this.passThroughArguments);
break; break;

View File

@ -1,5 +1,5 @@
import { chalkString } from "@switchboard-xyz/sbv2-utils";
import chalk from "chalk"; import chalk from "chalk";
import { chalkString } from "../../accounts/utils";
import BaseCommand from "../../BaseCommand"; import BaseCommand from "../../BaseCommand";
export default class ConfigPrint extends BaseCommand { export default class ConfigPrint extends BaseCommand {
@ -13,9 +13,9 @@ export default class ConfigPrint extends BaseCommand {
async run() { async run() {
const { devnet, mainnet } = this.cliConfig; const { devnet, mainnet } = this.cliConfig;
this.log(chalk.underline(chalk.blue("## Mainnet-Beta".padEnd(16))), "info"); this.log(chalk.underline(chalk.blue("## Mainnet-Beta".padEnd(16))));
this.log(chalkString("mainnet-rpc", mainnet.rpcUrl || "N/A"), "info"); this.log(chalkString("mainnet-rpc", mainnet.rpcUrl || "N/A"));
this.log(chalk.underline(chalk.blue("## Devnet".padEnd(16))), "info"); this.log(chalk.underline(chalk.blue("## Devnet".padEnd(16))));
this.log(chalkString("devnet-rpc", devnet.rpcUrl || "N/A"), "info"); 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 BaseCommand from "../../BaseCommand";
import { ConfigParameter } from "../../config";
export default class ConfigSet extends BaseCommand { export default class ConfigSet extends BaseCommand {
hidden = true; hidden = true;
@ -9,7 +8,7 @@ export default class ConfigSet extends BaseCommand {
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
reset: flags.boolean({ reset: Flags.boolean({
char: "r", char: "r",
description: "remove value or set to default rpc", description: "remove value or set to default rpc",
}), }),
@ -18,9 +17,7 @@ export default class ConfigSet extends BaseCommand {
static args = [ static args = [
{ {
name: "param", name: "param",
required: true,
options: ["devnet-rpc", "mainnet-rpc"], options: ["devnet-rpc", "mainnet-rpc"],
parse: (string_: string) => string_ as ConfigParameter,
description: "configuration parameter to set", description: "configuration parameter to set",
}, },
{ {
@ -31,7 +28,7 @@ export default class ConfigSet extends BaseCommand {
]; ];
async run() { async run() {
const { args, flags } = this.parse(ConfigSet); const { args, flags } = await this.parse(ConfigSet);
this.setConfig(args.param, flags.reset ? undefined : args.value); 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 * as anchor from "@project-serum/anchor";
import { PublicKey, SystemProgram } from "@solana/web3.js"; import { PublicKey, SystemProgram } from "@solana/web3.js";
import { import {
@ -11,23 +11,25 @@ import {
programWallet, programWallet,
} from "@switchboard-xyz/switchboard-v2"; } from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk"; import chalk from "chalk";
import BaseCommand from "../../../BaseCommand"; import BaseCommand from "../../BaseCommand";
import { CHECK_ICON } from "../../../utils"; import { CHECK_ICON } from "../../utils";
export default class QueueAddCrank extends BaseCommand { export default class QueueAddCrank extends BaseCommand {
static description = "add a crank to an existing oracle queue"; static description = "add a crank to an existing oracle queue";
static alias = ["queue:add:crank"];
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
name: flags.string({ name: Flags.string({
char: "n", char: "n",
description: "name of the crank for easier identification", description: "name of the crank for easier identification",
}), }),
maxRows: flags.integer({ maxRows: Flags.integer({
char: "r", char: "r",
description: "maximum number of rows a crank can support", description: "maximum number of rows a crank can support",
}), }),
queueAuthority: flags.string({ queueAuthority: Flags.string({
description: "alternative keypair to use for queue authority", description: "alternative keypair to use for queue authority",
}), }),
}; };
@ -35,8 +37,6 @@ export default class QueueAddCrank extends BaseCommand {
static args = [ static args = [
{ {
name: "queueKey", name: "queueKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the oracle queue to create a crank on", description: "public key of the oracle queue to create a crank on",
}, },
]; ];
@ -47,7 +47,7 @@ export default class QueueAddCrank extends BaseCommand {
]; ];
async run() { async run() {
const { args, flags } = this.parse(QueueAddCrank); const { args, flags } = await this.parse(QueueAddCrank);
verifyProgramHasPayer(this.program); verifyProgramHasPayer(this.program);
const payerKeypair = programWallet(this.program); const payerKeypair = programWallet(this.program);
@ -59,7 +59,7 @@ export default class QueueAddCrank extends BaseCommand {
const queueAccount = new OracleQueueAccount({ const queueAccount = new OracleQueueAccount({
program: this.program, program: this.program,
publicKey: args.queueKey, publicKey: new PublicKey(args.queueKey),
}); });
const queue = await queueAccount.loadData(); 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 { PublicKey } from "@solana/web3.js";
import { CrankAccount, CrankRow } from "@switchboard-xyz/switchboard-v2"; import { CrankAccount, CrankRow } from "@switchboard-xyz/switchboard-v2";
import * as fs from "fs"; import * as fs from "fs";
@ -10,8 +10,8 @@ export default class CrankList extends BaseCommand {
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
force: flags.boolean({ description: "overwrite output file if exists" }), force: Flags.boolean({ description: "overwrite output file if exists" }),
outputFile: flags.string({ outputFile: Flags.string({
char: "f", char: "f",
description: "output file to save aggregator pubkeys to", description: "output file to save aggregator pubkeys to",
}), }),
@ -20,14 +20,12 @@ export default class CrankList extends BaseCommand {
static args = [ static args = [
{ {
name: "crankKey", name: "crankKey",
required: true,
parse: (pubkey: string) => new PublicKey(pubkey),
description: "public key of the crank", description: "public key of the crank",
}, },
]; ];
async run() { async run() {
const { args, flags } = this.parse(CrankList); const { args, flags } = await this.parse(CrankList);
const outputFile = flags.outputFile const outputFile = flags.outputFile
? path.join(process.cwd(), flags.outputFile) ? path.join(process.cwd(), flags.outputFile)
@ -40,7 +38,7 @@ export default class CrankList extends BaseCommand {
const crankAccount = new CrankAccount({ const crankAccount = new CrankAccount({
program: this.program, program: this.program,
publicKey: args.crankKey, publicKey: new PublicKey(args.crankKey),
}); });
const crank = await crankAccount.loadData(); const crank = await crankAccount.loadData();

View File

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

View File

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