diff --git a/cli/src/OutputFileBaseCommand.ts b/cli/src/OutputFileBaseCommand.ts
new file mode 100644
index 0000000..d8ccab4
--- /dev/null
+++ b/cli/src/OutputFileBaseCommand.ts
@@ -0,0 +1,148 @@
+import { Flags } from "@oclif/core";
+import { Input } from "@oclif/parser";
+import * as anchor from "@project-serum/anchor";
+import { SwitchboardDecimal } from "@switchboard-xyz/switchboard-v2";
+import Big from "big.js";
+import fs from "fs";
+import path from "path";
+import BaseCommand from "./BaseCommand";
+
+abstract class OutputFileBaseCommand extends BaseCommand {
+ outputBasePath: string;
+
+ // outputTxtFile?: string;
+
+ outputJsonFile?: string;
+
+ outputCsvFile?: string;
+
+ static flags = {
+ ...BaseCommand.flags,
+ force: Flags.boolean({ description: "overwrite output file if exists" }),
+ outputFile: Flags.string({
+ char: "f",
+ description: "output file to save aggregator pubkeys to",
+ required: true,
+ }),
+ json: Flags.boolean({
+ description: "output aggregator accounts in json format",
+ }),
+ csv: Flags.boolean({
+ description: "output aggregator accounts in csv format",
+ }),
+ // txt: Flags.boolean({
+ // description: "output aggregator pubkeys in txt format",
+ // }),
+ };
+
+ async init() {
+ await super.init();
+ const { flags } = await this.parse((>this.constructor) as any);
+ BaseCommand.flags = flags as any;
+
+ const parsedPath = path.parse(
+ flags.outputFile.startsWith("/") || flags.outputFile.startsWith("C:")
+ ? flags.outputFile
+ : path.join(process.cwd(), flags.outputFile)
+ );
+ this.outputBasePath = path.join(parsedPath.dir, parsedPath.name);
+
+ if (parsedPath.ext === ".json" || flags.json) {
+ this.outputJsonFile = `${this.outputBasePath}.json`;
+ if (fs.existsSync(this.outputJsonFile) && !flags.force) {
+ throw new Error(
+ `output json file already exists: ${this.outputJsonFile}`
+ );
+ }
+ }
+
+ if (parsedPath.ext === ".csv" || flags.csv) {
+ this.outputCsvFile = `${this.outputBasePath}.csv`;
+ if (fs.existsSync(this.outputCsvFile) && !flags.force) {
+ throw new Error(
+ `output csv file already exists: ${this.outputCsvFile}`
+ );
+ }
+ }
+
+ if (!(this.outputJsonFile || this.outputCsvFile)) {
+ throw new Error(`no output format specified, try --json, or --csv`);
+ }
+ }
+
+ jsonReplacers(key: any, value: any) {
+ if (typeof value === "string") {
+ return value;
+ } else if (typeof value === "number") {
+ return value;
+ } else if (typeof value === "boolean") {
+ return value.toString();
+ } else {
+ if (value instanceof Big) {
+ return value.toString();
+ } else if (anchor.BN.isBN(value)) {
+ return value.toString(10);
+ } else if (
+ ("scale" in value && "mantissa" in value) ||
+ value instanceof SwitchboardDecimal
+ ) {
+ return new SwitchboardDecimal(value.mantissa, value.scale)
+ .toBig()
+ .toString();
+ }
+ }
+
+ return value;
+ }
+
+ save(rows?: T | T[], headers?: K[]) {
+ if (rows) {
+ this.saveJson(rows);
+ }
+ if (rows !== undefined && headers !== undefined) {
+ this.saveCsv(rows, headers);
+ }
+ }
+
+ saveJson(rows: T | T[]) {
+ if (this.outputJsonFile) {
+ fs.writeFileSync(
+ this.outputJsonFile,
+ JSON.stringify(rows, this.jsonReplacers, 2)
+ );
+ }
+ }
+
+ saveCsv(rows: T | T[], headers: K[]) {
+ if (this.outputCsvFile) {
+ const grid: string[][] = [];
+ grid.push(headers as string[]);
+ if (Array.isArray(rows)) {
+ rows.forEach((row) => {
+ const cols: string[] = [];
+ headers.forEach((col) => {
+ const val = row[col];
+ cols.push(
+ typeof val === "string" ? val : this.jsonReplacers(undefined, val)
+ );
+ });
+ grid.push(cols);
+ });
+ } else {
+ const cols: string[] = [];
+ headers.forEach((col) => {
+ const val = rows[col];
+ cols.push(
+ typeof val === "string" ? val : this.jsonReplacers(undefined, val)
+ );
+ });
+ grid.push(cols);
+ }
+
+ const lines = grid.map((col) => col.join(","));
+ fs.writeFileSync(this.outputCsvFile, lines.join("\n"));
+ }
+ }
+}
+
+export default OutputFileBaseCommand;
diff --git a/cli/src/commands/aggregator/save/history.ts b/cli/src/commands/aggregator/save/history.ts
new file mode 100644
index 0000000..249d96d
--- /dev/null
+++ b/cli/src/commands/aggregator/save/history.ts
@@ -0,0 +1,63 @@
+import { PublicKey } from "@solana/web3.js";
+import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
+import OutputFileBaseCommand from "../../../OutputFileBaseCommand";
+
+export default class AggregatorUpdate extends OutputFileBaseCommand {
+ static description = "request a new aggregator result from a set of oracles";
+
+ static flags = {
+ ...OutputFileBaseCommand.flags,
+ };
+
+ static args = [
+ {
+ name: "aggregatorKey",
+ description: "public key of the aggregator account to deserialize",
+ },
+ ];
+
+ static examples = [
+ "$ sbv2 aggregator:save:history --outputFile ../aggregator-history.json --csv",
+ ];
+
+ async run() {
+ const { args, flags } = await this.parse(AggregatorUpdate);
+
+ const aggregatorAccount = new AggregatorAccount({
+ program: this.program,
+ publicKey: new PublicKey(args.aggregatorKey),
+ });
+ const aggregator = await aggregatorAccount.loadData();
+
+ const history = await aggregatorAccount.loadHistory();
+
+ this.save(
+ history.map((r) => {
+ return {
+ timestamp: Number.parseInt(r.timestamp.toString(10)),
+ datetime: new Date(r.timestamp.toNumber() * 1000).toUTCString(),
+ value: r.value,
+ };
+ }),
+ ["timestamp", "datetime", "value"]
+ );
+
+ if (this.silent) {
+ return;
+ } else {
+ this.logger.log(`Files saved`);
+ }
+ }
+
+ async catch(error) {
+ // if (
+ // error instanceof AggregatorIllegalRoundOpenCall ||
+ // error.toString().includes("0x177d")
+ // ) {
+ // this.context.logger.info(error.toString());
+ // this.exit(0);
+ // }
+
+ super.catch(error, "failed to save aggregator history");
+ }
+}
diff --git a/cli/src/commands/sandbox.ts b/cli/src/commands/sandbox.ts
index fb3fac6..3420111 100644
--- a/cli/src/commands/sandbox.ts
+++ b/cli/src/commands/sandbox.ts
@@ -1,4 +1,5 @@
import { Flags } from "@oclif/core";
+import { PublicKey } from "@solana/web3.js";
import BaseCommand from "../BaseCommand";
export default class SandboxCommand extends BaseCommand {
@@ -70,110 +71,21 @@ export default class SandboxCommand extends BaseCommand {
// console.log(byteArray.length);
// console.log(`[${Uint8Array.from(historyKey.toBuffer()).toString()}]`);
- // const parseAddress = new PublicKey(
- // "DpoK8Zz69APV9ntjuY9C4LZCxANYMV56M2cbXEdkjxME"
- // );
- // console.log(`parseAddress: [${Uint8Array.from(parseAddress.toBuffer())}]`);
+ const parseAddress = new PublicKey(
+ "DpoK8Zz69APV9ntjuY9C4LZCxANYMV56M2cbXEdkjxME"
+ );
+ console.log(`parseAddress: [${Uint8Array.from(parseAddress.toBuffer())}]`);
- // const accountInfo = await this.program.provider.connection.getAccountInfo(
- // parseAddress
- // );
- // const byteArray = Uint8Array.from(accountInfo.data);
- // console.log(`parseAddress Data [${byteArray.length}]: [${byteArray}]`);
+ const accountInfo = await this.program.provider.connection.getAccountInfo(
+ parseAddress
+ );
+ const byteArray = Uint8Array.from(accountInfo.data);
+ console.log(`parseAddress Data [${byteArray.length}]: [${byteArray}]`);
- // const owner = accountInfo.owner;
- // console.log(`Owner: [${Uint8Array.from(owner.toBuffer())}]`);
+ const owner = accountInfo.owner;
+ console.log(`Owner: [${Uint8Array.from(owner.toBuffer())}]`);
- // console.log(accountInfo.lamports);
-
- // const jobData = Buffer.from(
- // OracleJob.encodeDelimited(
- // OracleJob.create({
- // tasks: [
- // OracleJob.Task.create({
- // httpTask: OracleJob.HttpTask.create({
- // url: "https://jsonplaceholder.typicode.com/todos/1",
- // }),
- // }),
- // ],
- // })
- // ).finish()
- // );
-
- // const jobLen = jobData.length;
- // console.log(`JobData Len: ${jobLen}`);
-
- // const bufferAccountSize =
- // this.program.account.bufferRelayerAccountData.size;
- // const bufferAccountSize = 2048;
- // const bufferRentExemption =
- // await this.program.provider.connection.getMinimumBalanceForRentExemption(
- // bufferAccountSize
- // );
-
- // console.log(
- // chalkString(
- // "Buffer",
- // `${bufferRentExemption} lamports, ${
- // bufferRentExemption / LAMPORTS_PER_SOL
- // } SOL, ${bufferAccountSize} bytes`
- // )
- // );
-
- // const escrowSize = spl.ACCOUNT_SIZE;
- // const escrowRentExemption =
- // await this.program.provider.connection.getMinimumBalanceForRentExemption(
- // escrowSize
- // );
- // console.log(
- // chalkString(
- // "Escrow",
- // `${escrowRentExemption} lamports, ${
- // escrowRentExemption / LAMPORTS_PER_SOL
- // } SOL, ${escrowSize} bytes`
- // )
- // );
-
- // const jobAccountSize =
- // this.program.account.jobAccountData.size + jobData.length;
- // const jobRentExemption =
- // await this.program.provider.connection.getMinimumBalanceForRentExemption(
- // jobAccountSize
- // );
-
- // console.log(
- // chalkString(
- // "Job",
- // `${jobRentExemption} lamports, ${
- // jobRentExemption / LAMPORTS_PER_SOL
- // } SOL, ${jobAccountSize} bytes`
- // )
- // );
-
- // const permissionAccountSize =
- // this.program.account.permissionAccountData.size;
- // const permissionRentExemption =
- // await this.program.provider.connection.getMinimumBalanceForRentExemption(
- // permissionAccountSize
- // );
- // console.log(
- // chalkString(
- // "Permission",
- // `${permissionRentExemption} lamports, ${
- // permissionRentExemption / LAMPORTS_PER_SOL
- // } SOL, ${permissionAccountSize} bytes`
- // )
- // );
-
- // const totalLamports =
- // bufferRentExemption +
- // escrowRentExemption +
- // jobRentExemption +
- // permissionRentExemption;
-
- // console.log(chalkString("Total Lamports", totalLamports));
-
- // console.log(chalkString("Total SOL", totalLamports / LAMPORTS_PER_SOL));
+ console.log(accountInfo.lamports);
}
async catch(error) {
diff --git a/libraries/protos/job_schemas.proto b/libraries/protos/job_schemas.proto
index 16e268e..ee3bf43 100644
--- a/libraries/protos/job_schemas.proto
+++ b/libraries/protos/job_schemas.proto
@@ -51,6 +51,10 @@ message OracleJob {
MAX = 2;
// Sum up all of the results.
SUM = 3;
+ // Average all of the results.
+ MEAN = 4;
+ // Grab the median of the results.
+ MEDIAN = 5;
}
// The technique that will be used to aggregate the results if walking the specified path
// returns multiple numerical results.
diff --git a/libraries/ts/package.json b/libraries/ts/package.json
index c1abb93..4179105 100644
--- a/libraries/ts/package.json
+++ b/libraries/ts/package.json
@@ -1,6 +1,6 @@
{
"name": "@switchboard-xyz/switchboard-v2",
- "version": "0.0.123-beta.4",
+ "version": "0.0.123",
"license": "MIT",
"author": "mitch@switchboard.xyz",
"description": "API wrapper for intergating with the Switchboardv2 program",
diff --git a/libraries/ts/src/index.ts b/libraries/ts/src/index.ts
index c4aca00..9adb3de 100644
--- a/libraries/ts/src/index.ts
+++ b/libraries/ts/src/index.ts
@@ -2,3 +2,13 @@
/*eslint-disable import/extensions */
export * from "./protos/index.js";
export * from "./sbv2.js";
+
+import protobuf from "protobufjs/minimal.js";
+
+// prevent enums from being converted to strings
+protobuf.util.toJSONOptions = {
+ longs: String,
+ enums: Number,
+ bytes: String,
+ json: true,
+};
diff --git a/libraries/ts/src/protos/index.d.ts b/libraries/ts/src/protos/index.d.ts
index cbdc503..eda5fd9 100644
--- a/libraries/ts/src/protos/index.d.ts
+++ b/libraries/ts/src/protos/index.d.ts
@@ -408,7 +408,9 @@ export namespace OracleJob {
NONE = 0,
MIN = 1,
MAX = 2,
- SUM = 3
+ SUM = 3,
+ MEAN = 4,
+ MEDIAN = 5
}
}
diff --git a/libraries/ts/src/protos/index.js b/libraries/ts/src/protos/index.js
index 24d15f9..6f1a1c1 100644
--- a/libraries/ts/src/protos/index.js
+++ b/libraries/ts/src/protos/index.js
@@ -896,6 +896,8 @@
case 1:
case 2:
case 3:
+ case 4:
+ case 5:
break;
}
return null;
@@ -932,6 +934,14 @@
case 3:
message.aggregationMethod = 3;
break;
+ case "MEAN":
+ case 4:
+ message.aggregationMethod = 4;
+ break;
+ case "MEDIAN":
+ case 5:
+ message.aggregationMethod = 5;
+ break;
}
return message;
};
@@ -979,6 +989,8 @@
* @property {number} MIN=1 MIN value
* @property {number} MAX=2 MAX value
* @property {number} SUM=3 SUM value
+ * @property {number} MEAN=4 MEAN value
+ * @property {number} MEDIAN=5 MEDIAN value
*/
JsonParseTask.AggregationMethod = (function() {
var valuesById = {}, values = Object.create(valuesById);
@@ -986,6 +998,8 @@
values[valuesById[1] = "MIN"] = 1;
values[valuesById[2] = "MAX"] = 2;
values[valuesById[3] = "SUM"] = 3;
+ values[valuesById[4] = "MEAN"] = 4;
+ values[valuesById[5] = "MEDIAN"] = 5;
return values;
})();
diff --git a/libraries/ts/src/sbv2.ts b/libraries/ts/src/sbv2.ts
index 6ee1055..fc19363 100644
--- a/libraries/ts/src/sbv2.ts
+++ b/libraries/ts/src/sbv2.ts
@@ -242,6 +242,10 @@ export class SwitchboardDecimal {
result.e = e;
return result;
}
+
+ toString() {
+ this.toBig().toString();
+ }
}
/**
diff --git a/libraries/ts/tsconfig.base.json b/libraries/ts/tsconfig.base.json
index 36deb9d..764fcb7 100644
--- a/libraries/ts/tsconfig.base.json
+++ b/libraries/ts/tsconfig.base.json
@@ -14,7 +14,7 @@
"esModuleInterop": true,
"resolveJsonModule": true,
"skipLibCheck": true,
- "types": ["mocha", "node", "long", "big.js"],
+ "types": ["mocha", "node", "long"],
"allowJs": true,
"checkJs": false,
// strict
diff --git a/yarn.lock b/yarn.lock
index a157fe2..a781759 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4170,6 +4170,48 @@
node-fetch "^3.2.6"
protobufjs "^6.11.3"
+"@switchboard-xyz/switchboard-v2@^0.0.121":
+ version "0.0.121"
+ resolved "https://registry.npmjs.org/@switchboard-xyz/switchboard-v2/-/switchboard-v2-0.0.121.tgz#5df38491feba9262a13fb071c86179f9acfdc865"
+ integrity sha512-IPA3QD/nThH57ekJaD3aov/5fa/JLETNb3TeHKx8mfRSE/P2b94nosvXsYiDpXE05v/zS4oDk6FD1YOO+fZXzQ==
+ dependencies:
+ "@project-serum/anchor" "^0.24.2"
+ "@solana/spl-governance" "^0.0.34"
+ "@solana/spl-token" "^0.2.0"
+ "@solana/web3.js" "^1.44.3"
+ "@types/big.js" "^6.1.4"
+ assert "^2.0.0"
+ big.js "^6.2.0"
+ bs58 "^4.0.1"
+ chan "^0.6.1"
+ crypto-js "^4.0.0"
+ glob "^8.0.3"
+ long "^4.0.0"
+ mocha "^9.1.1"
+ node-fetch "^3.2.6"
+ protobufjs "^6.11.3"
+
+"@switchboard-xyz/switchboard-v2@^0.0.122":
+ version "0.0.122"
+ resolved "https://registry.npmjs.org/@switchboard-xyz/switchboard-v2/-/switchboard-v2-0.0.122.tgz#11713cd909e17c52cab424f1c13f037a7eac89ec"
+ integrity sha512-LAjAEHq3VSRyJxGJj/FF9Q48tCST0+F56H2/nZ66UhpALucuKs1ppOlAKoqrFeTC4KYoQhcK2cE+B9WmTxA1pg==
+ dependencies:
+ "@project-serum/anchor" "^0.24.2"
+ "@solana/spl-governance" "^0.0.34"
+ "@solana/spl-token" "^0.2.0"
+ "@solana/web3.js" "^1.44.3"
+ "@types/big.js" "^6.1.4"
+ assert "^2.0.0"
+ big.js "^6.2.0"
+ bs58 "^4.0.1"
+ chan "^0.6.1"
+ crypto-js "^4.0.0"
+ glob "^8.0.3"
+ long "^4.0.0"
+ mocha "^9.1.1"
+ node-fetch "^3.2.6"
+ protobufjs "^6.11.3"
+
"@szmarczak/http-timer@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"