chore: removed deprecated javascript packages
This commit is contained in:
parent
77187a7c85
commit
ba5b6877a8
|
@ -29,8 +29,8 @@ Get your program ID and update `Anchor.toml` and `src/lib.rs` with your pubkey
|
|||
|
||||
```bash
|
||||
export ANCHOR_FEED_PARSER_PUBKEY=$(solana-keygen pubkey target/deploy/anchor_feed_parser-keypair.json)
|
||||
sed -i '' s/Fstf3oTcBxHMZFaoBzxk5oSkTh5HaAjxjh6zcgdZpNBb/"$ANCHOR_FEED_PARSER_PUBKEY"/g Anchor.toml
|
||||
sed -i '' s/Fstf3oTcBxHMZFaoBzxk5oSkTh5HaAjxjh6zcgdZpNBb/"$ANCHOR_FEED_PARSER_PUBKEY"/g src/lib.rs
|
||||
sed -i '' s/EE4NqG3B1N54tTbJ8VTtebdE88j4qjfVsbDXUUVFHegk/"$ANCHOR_FEED_PARSER_PUBKEY"/g Anchor.toml
|
||||
sed -i '' s/EE4NqG3B1N54tTbJ8VTtebdE88j4qjfVsbDXUUVFHegk/"$ANCHOR_FEED_PARSER_PUBKEY"/g src/lib.rs
|
||||
```
|
||||
|
||||
Then run Anchor test
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Switchboard
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,16 +0,0 @@
|
|||
# Switchboard V2 Feed Parser
|
||||
|
||||
Basic example showing how to load the Switchboard program and output the latest
|
||||
value
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
## Start
|
||||
|
||||
```
|
||||
yarn start
|
||||
```
|
|
@ -1,21 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
|
||||
// bundle d.ts declarations
|
||||
const { build } = require("estrella");
|
||||
|
||||
// Automatically exclude all node_modules from the bundled version
|
||||
const { nodeExternalsPlugin } = require("esbuild-node-externals");
|
||||
|
||||
build({
|
||||
entryPoints: ["./src/main.ts"],
|
||||
outfile: "dist/index.js",
|
||||
bundle: true,
|
||||
minify: true,
|
||||
platform: "node",
|
||||
target: "node16",
|
||||
tslint: true,
|
||||
sourcemap: "inline",
|
||||
plugins: [nodeExternalsPlugin()],
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -1,33 +0,0 @@
|
|||
{
|
||||
"name": "@switchboard-xyz/v2-feed-parser",
|
||||
"version": "1.0.0",
|
||||
"description": "switchboard v2 example demonstrating how to parse a data feed",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/switchboard-xyz/sbv2-solana",
|
||||
"directory": "javascript/feed-parser"
|
||||
},
|
||||
"homepage": "https://docs.switchboard.xyz",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"start": "ts-node src/main",
|
||||
"build": "shx rm -rf dist && ./esbuild.js",
|
||||
"test": "echo \"No test script for @switchboard-xyz/v2-feed-parser\" && exit 0"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@solana/web3.js": "^1.67.2",
|
||||
"@switchboard-xyz/solana.js": "file:../solana.js",
|
||||
"big.js": "^6.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/big.js": "^6.1.3",
|
||||
"esbuild-node-externals": "^1.4.1",
|
||||
"estrella": "^1.4.1",
|
||||
"shx": "^0.3.4",
|
||||
"ts-node": "^10.7.0",
|
||||
"typescript": "^4.6.3"
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/* eslint-disable unicorn/no-process-exit */
|
||||
import { clusterApiUrl, Connection, Keypair, PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
AggregatorAccount,
|
||||
SwitchboardProgram,
|
||||
} from "@switchboard-xyz/solana.js";
|
||||
|
||||
// SOL/USD Feed https://switchboard.xyz/explorer/2/GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR
|
||||
// Create your own feed here https://publish.switchboard.xyz/
|
||||
const switchboardFeed = new PublicKey(
|
||||
"GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR"
|
||||
);
|
||||
|
||||
async function main() {
|
||||
// load the switchboard program
|
||||
const program = await SwitchboardProgram.load(
|
||||
"devnet",
|
||||
new Connection(clusterApiUrl("devnet")),
|
||||
Keypair.fromSeed(new Uint8Array(32).fill(1)) // using dummy keypair since we wont be submitting any transactions
|
||||
);
|
||||
|
||||
// load the switchboard aggregator
|
||||
const aggregator = new AggregatorAccount(program, switchboardFeed);
|
||||
|
||||
// get the result
|
||||
const result = await aggregator.fetchLatestValue();
|
||||
console.log(`Switchboard Result: ${result}`);
|
||||
}
|
||||
|
||||
main().then(
|
||||
() => process.exit(),
|
||||
(error) => {
|
||||
console.error("Failed to parse Switchboard Feed");
|
||||
console.error(error);
|
||||
process.exit(-1);
|
||||
}
|
||||
);
|
|
@ -1,32 +0,0 @@
|
|||
{
|
||||
"ts-node": {
|
||||
// It is faster to skip typechecking.
|
||||
// Remove if you want ts-node to do typechecking.
|
||||
"transpileOnly": true,
|
||||
"files": true,
|
||||
"compilerOptions": {
|
||||
// compilerOptions specified here will override those declared below,
|
||||
// but *only* in ts-node. Useful if you want ts-node and tsc to use
|
||||
// different options with a single tsconfig.json.
|
||||
"module": "commonjs"
|
||||
}
|
||||
},
|
||||
"compilerOptions": {
|
||||
"target": "ES2019",
|
||||
"lib": ["es2019", "dom"],
|
||||
"module": "es2022",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"paths": {
|
||||
"@switchboard-xyz/solana.js": ["../solana.js"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["esbuild.js", "dist"],
|
||||
"references": [{ "path": "../solana.js" }],
|
||||
"files": ["src/main.ts"]
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
node_modules/**
|
|
@ -1,6 +0,0 @@
|
|||
FROM node:14
|
||||
WORKDIR /
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
CMD [ "npm", "run", "start" ]
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Switchboard
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,15 +0,0 @@
|
|||
# Lease Observer
|
||||
|
||||
Get PagerDuty alerts when a feed's lease is below `$PAGE_THRESHOLD`
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
## Start
|
||||
|
||||
```
|
||||
yarn start
|
||||
```
|
|
@ -1,15 +0,0 @@
|
|||
version: "3.3"
|
||||
services:
|
||||
switchboard:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
network_mode: host
|
||||
restart: always
|
||||
environment:
|
||||
- RPC_URL=YOUR_RPC_URL_HERE
|
||||
- CLUSTER=mainnet-beta
|
||||
- AGGREGATOR_KEY=3RfJxApwV2tYB5mArdD7aRbBk7P6BQCSSFQzR2GXUzA2
|
||||
- PAGERDUTY_EVENT_KEY=YOUR_PAGE_KEY_HERE
|
||||
- PAGE_THRESHOLD=10000 # LAMPORTS
|
||||
# - PAGE_THRESHOLD=1000000000 # LAMPORTS
|
|
@ -1,56 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
|
||||
// bundle d.ts declarations
|
||||
const { build, ts, tsconfig, dirname, glob, log } = require("estrella");
|
||||
|
||||
// Automatically exclude all node_modules from the bundled version
|
||||
const { nodeExternalsPlugin } = require("esbuild-node-externals");
|
||||
|
||||
build({
|
||||
entryPoints: ["./src/main.ts"],
|
||||
outfile: "dist/index.js",
|
||||
bundle: true,
|
||||
minify: true,
|
||||
platform: "node",
|
||||
target: "node16",
|
||||
tslint: true,
|
||||
sourcemap: "inline",
|
||||
plugins: [nodeExternalsPlugin()],
|
||||
onEnd(config) {
|
||||
// Generate type declaration files
|
||||
const dtsFilesOutdir = dirname(config.outfile);
|
||||
generateTypeDefs(tsconfig(config), config.entry, dtsFilesOutdir);
|
||||
},
|
||||
});
|
||||
|
||||
function generateTypeDefs(tscConfig, entryfiles, outdir) {
|
||||
const filenames = [
|
||||
...new Set(
|
||||
// eslint-disable-next-line unicorn/prefer-spread
|
||||
(Array.isArray(entryfiles) ? entryfiles : [entryfiles]).concat(
|
||||
tscConfig.include || []
|
||||
)
|
||||
),
|
||||
].filter(Boolean);
|
||||
// log.info("Generating type declaration files for", filenames.join(", "));
|
||||
const compilerOptions = {
|
||||
...tscConfig.compilerOptions,
|
||||
moduleResolution: undefined,
|
||||
declaration: true,
|
||||
outDir: outdir,
|
||||
};
|
||||
const program = ts.ts.createProgram(filenames, compilerOptions);
|
||||
const targetSourceFile = undefined;
|
||||
const writeFile = undefined;
|
||||
const cancellationToken = undefined;
|
||||
const emitOnlyDtsFiles = true;
|
||||
program.emit(
|
||||
targetSourceFile,
|
||||
writeFile,
|
||||
cancellationToken,
|
||||
emitOnlyDtsFiles
|
||||
);
|
||||
// log.info("Wrote", glob(outdir + "/*.d.ts").join(", "));
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,32 +0,0 @@
|
|||
{
|
||||
"name": "@switchboard-xyz/lease-observer",
|
||||
"version": "1.0.0",
|
||||
"description": "receive pager duty alerts when a switchboard v2 lease crosses a provided threshold",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/switchboard-xyz/sbv2-solana",
|
||||
"directory": "javascript/lease-observer"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "ts-node src/main.ts",
|
||||
"build": "rimraf lib && ./esbuild.js",
|
||||
"test": "echo \"No test script for @switchboard-xyz/lease-observer\" && exit 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@solana/web3.js": "1.33.0",
|
||||
"@switchboard-xyz/sbv2-utils": "^0.1.55",
|
||||
"@switchboard-xyz/switchboard-v2": "^0.0.174",
|
||||
"dotenv": "^16.0.1",
|
||||
"node-pagerduty": "^1.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.7.18",
|
||||
"esbuild-node-externals": "^1.4.1",
|
||||
"estrella": "^1.4.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.8.3"
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
declare module "node-pagerduty";
|
|
@ -1,70 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
import { sleep } from "@switchboard-xyz/sbv2-utils";
|
||||
import * as sbv2 from "@switchboard-xyz/switchboard-v2";
|
||||
import * as dotenv from "dotenv"; // should be loaded upon entry
|
||||
import { Pager } from "./pager";
|
||||
dotenv.config();
|
||||
|
||||
async function main() {
|
||||
if (!process.env.CLUSTER) {
|
||||
throw new Error(`Must provide $CLUSTER`);
|
||||
}
|
||||
const cluster = process.env.CLUSTER;
|
||||
if (cluster !== "devnet" && cluster !== "mainnet-beta") {
|
||||
throw new Error(`Invalid cluster ${cluster}`);
|
||||
}
|
||||
if (!process.env.RPC_URL) {
|
||||
throw new Error(`Must provide $RPC_URL`);
|
||||
}
|
||||
const program = await sbv2.loadSwitchboardProgram(
|
||||
cluster,
|
||||
new Connection(process.env.RPC_URL ?? "")
|
||||
);
|
||||
if (!process.env.AGGREGATOR_KEY) {
|
||||
throw new Error(`Must provide $AGGREGATOR_KEY`);
|
||||
}
|
||||
const aggregatorPubkey = new PublicKey(process.env.AGGREGATOR_KEY ?? "");
|
||||
const aggregatorAccount = new sbv2.AggregatorAccount({
|
||||
program,
|
||||
publicKey: aggregatorPubkey,
|
||||
});
|
||||
const pagerKey = process.env.PAGERDUTY_EVENT_KEY!;
|
||||
const pageThreshold: number = +process.env.PAGE_THRESHOLD!;
|
||||
const aggregator = await aggregatorAccount.loadData();
|
||||
const queueKey = aggregator.queuePubkey;
|
||||
const queueAccount = new sbv2.OracleQueueAccount({
|
||||
program,
|
||||
publicKey: queueKey,
|
||||
});
|
||||
const [leaseAccount] = sbv2.LeaseAccount.fromSeed(
|
||||
program,
|
||||
queueAccount,
|
||||
aggregatorAccount
|
||||
);
|
||||
while (true) {
|
||||
const balance = await leaseAccount.getBalance();
|
||||
console.log(`${aggregatorPubkey.toBase58()} balance ${balance}`);
|
||||
if (balance < pageThreshold) {
|
||||
await Pager.sendEvent(
|
||||
pagerKey,
|
||||
"critical",
|
||||
`Switchboard feed ${aggregatorPubkey.toBase58()} is running low on funds.`,
|
||||
{
|
||||
balance,
|
||||
}
|
||||
);
|
||||
}
|
||||
await sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
main().then(
|
||||
() => {
|
||||
process.exit();
|
||||
},
|
||||
(err) => {
|
||||
console.error(err);
|
||||
process.exit(0);
|
||||
}
|
||||
);
|
|
@ -1,38 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
|
||||
import PdClient from "node-pagerduty";
|
||||
import * as os from "os";
|
||||
|
||||
export class Pager {
|
||||
static async sendEvent(
|
||||
routingKey: string,
|
||||
severity: string,
|
||||
summary: string,
|
||||
customDetails: any = {}
|
||||
): Promise<void> {
|
||||
const pdClient = new PdClient(routingKey);
|
||||
Object.assign(customDetails, {
|
||||
source: os.hostname(),
|
||||
group: process.env.CLUSTER ?? "",
|
||||
client: os.hostname(),
|
||||
});
|
||||
const payload = {
|
||||
payload: {
|
||||
summary: summary,
|
||||
timestamp: new Date().toISOString(),
|
||||
source: os.hostname(),
|
||||
severity: severity,
|
||||
group: process.env.CLUSTER ?? "",
|
||||
custom_details: customDetails,
|
||||
},
|
||||
routing_key: routingKey,
|
||||
event_action: "trigger",
|
||||
client: os.hostname(),
|
||||
};
|
||||
console.info("Event sending to pagerduty:", payload);
|
||||
await pdClient.events.sendEvent(payload);
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
{
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "ES2019",
|
||||
"alwaysStrict": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitReturns": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"resolveJsonModule": true,
|
||||
"declarationMap": true
|
||||
}
|
||||
},
|
||||
"compilerOptions": {
|
||||
"target": "ES2019",
|
||||
"lib": ["es2019", "dom"],
|
||||
"module": "es2022",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": false,
|
||||
"paths": {
|
||||
"@switchboard-xyz/switchboard-v2": ["../switchboard-v2"],
|
||||
"@switchboard-xyz/sbv2-utils": ["../sbv2-utils"],
|
||||
"@solana/spl-token": ["./node_modules/@solana/spl-token"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"references": [{ "path": "../switchboard-v2" }, { "path": "../sbv2-utils" }],
|
||||
"files": ["src/main.ts"]
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
node_modules
|
||||
lib
|
|
@ -1 +0,0 @@
|
|||
*.tsbuildinfo
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Switchboard
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,42 +0,0 @@
|
|||
# Switchboard V2 Lite
|
||||
|
||||
A lightweight library to decode and parse aggregator accounts
|
||||
|
||||
[](https://www.npmjs.com/package/@switchboard-xyz/sbv2-lite)
|
||||
[](https://twitter.com/switchboardxyz)
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
npm i @switchboard-xyz/sbv2-lite
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
import SwitchboardProgram from "@switchboard-xyz/sbv2-lite";
|
||||
|
||||
//
|
||||
|
||||
const sbv2 = await SwitchboardProgram.loadDevnet();
|
||||
|
||||
// SOL_USD Aggregator https://switchboard.xyz/explorer
|
||||
const solAggregator = new anchor.web3.PublicKey(
|
||||
"GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR"
|
||||
);
|
||||
|
||||
const accountInfo = await sbv2.program.provider.connection.getAccountInfo(
|
||||
solAggregator
|
||||
);
|
||||
if (!accountInfo) {
|
||||
throw new Error(`failed to fetch account info`);
|
||||
}
|
||||
|
||||
// Get latest value if its been updated in the last 300 seconds
|
||||
const latestResult = sbv2.decodeLatestAggregatorValue(accountInfo, 300);
|
||||
if (latestResult === null) {
|
||||
throw new Error(`failed to fetch latest result for aggregator`);
|
||||
}
|
||||
console.log(`latestResult: ${latestResult}`);
|
||||
// latestResult: 105.673205
|
||||
```
|
File diff suppressed because it is too large
Load Diff
|
@ -1,54 +0,0 @@
|
|||
{
|
||||
"name": "@switchboard-xyz/sbv2-lite",
|
||||
"version": "0.1.6",
|
||||
"description": "",
|
||||
"private": false,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/switchboard-xyz/sbv2-solana",
|
||||
"directory": "javascript/sbv2-lite"
|
||||
},
|
||||
"homepage": "https://docs.switchboard.xyz",
|
||||
"files": [
|
||||
"lib",
|
||||
"src"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./lib/esm/index.js",
|
||||
"require": "./lib/cjs/index.js"
|
||||
}
|
||||
},
|
||||
"main": "lib/cjs/index.js",
|
||||
"module": "lib/esm/index.js",
|
||||
"types": "lib/cjs/index.d.ts",
|
||||
"scripts": {
|
||||
"docgen": "typedoc --entryPoints src/index.ts --out ../../website/static/api/ts-lite",
|
||||
"test": "env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha -r ts-node/register 'tests/**/*.ts'",
|
||||
"build": "shx rm -rf lib && tsc && tsc -p tsconfig.cjs.json",
|
||||
"watch": "tsc -p tsconfig.cjs.json --watch",
|
||||
"prepublishOnly": "yarn build && yarn test"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@coral-xyz/anchor": "^0.26.0",
|
||||
"big.js": "^6.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/big.js": "^6.1.3",
|
||||
"@types/chai": "^4.3.1",
|
||||
"@types/mocha": "^9.1.0",
|
||||
"@types/node": "^18.7.18",
|
||||
"assert": "^2.0.0",
|
||||
"chai": "^4.3.6",
|
||||
"mocha": "^9.2.2",
|
||||
"shx": "^0.3.4",
|
||||
"ts-node": "^10.7.0",
|
||||
"typedoc": "^0.23.8",
|
||||
"typescript": "^4.6.3"
|
||||
},
|
||||
"pre-commit": [
|
||||
"build"
|
||||
]
|
||||
}
|
|
@ -1,238 +0,0 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import Big from "big.js";
|
||||
|
||||
export class AnchorWallet implements anchor.Wallet {
|
||||
constructor(readonly payer: anchor.web3.Keypair) {
|
||||
this.payer = payer;
|
||||
}
|
||||
|
||||
async signTransaction(
|
||||
tx: anchor.web3.Transaction
|
||||
): Promise<anchor.web3.Transaction> {
|
||||
tx.partialSign(this.payer);
|
||||
return tx;
|
||||
}
|
||||
|
||||
async signAllTransactions(
|
||||
txs: anchor.web3.Transaction[]
|
||||
): Promise<anchor.web3.Transaction[]> {
|
||||
return txs.map((t) => {
|
||||
t.partialSign(this.payer);
|
||||
return t;
|
||||
});
|
||||
}
|
||||
|
||||
get publicKey(): anchor.web3.PublicKey {
|
||||
return this.payer.publicKey;
|
||||
}
|
||||
}
|
||||
|
||||
/** A Switchboard V2 wrapper to assist in decoding onchain accounts */
|
||||
export default class SwitchboardProgram {
|
||||
/**
|
||||
* Switchboard Devnet Program ID
|
||||
* SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f
|
||||
*/
|
||||
public static devnetPid = new anchor.web3.PublicKey(
|
||||
"SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f"
|
||||
);
|
||||
|
||||
/**
|
||||
* Switchboard Mainnet Program ID
|
||||
* SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f
|
||||
*/
|
||||
public static mainnetPid = new anchor.web3.PublicKey(
|
||||
"SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f"
|
||||
);
|
||||
|
||||
/**
|
||||
* Default confirmation options for fetching Solana data
|
||||
*/
|
||||
public static defaultConfirmOptions: anchor.web3.ConfirmOptions = {
|
||||
commitment: "confirmed",
|
||||
};
|
||||
|
||||
/**
|
||||
* Switchboard Anchor program object
|
||||
*/
|
||||
public program: anchor.Program;
|
||||
|
||||
/**
|
||||
* Selected Solana cluster
|
||||
*/
|
||||
public cluster: "devnet" | "mainnet-beta";
|
||||
|
||||
constructor(program: anchor.Program, cluster: "devnet" | "mainnet-beta") {
|
||||
this.program = program;
|
||||
this.cluster = cluster;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Switchboard devnet program
|
||||
* @param connection optional connection object if not using the default endpoints
|
||||
* @param confirmOptions optional confirmation options. defaults to commitment level 'confirmed'
|
||||
*/
|
||||
public static async loadDevnet(
|
||||
connection = new anchor.web3.Connection(
|
||||
anchor.web3.clusterApiUrl("devnet")
|
||||
),
|
||||
confirmOptions = SwitchboardProgram.defaultConfirmOptions
|
||||
): Promise<SwitchboardProgram> {
|
||||
const provider = new anchor.AnchorProvider(
|
||||
connection,
|
||||
new AnchorWallet(
|
||||
anchor.web3.Keypair.fromSeed(new Uint8Array(32).fill(1))
|
||||
),
|
||||
confirmOptions
|
||||
);
|
||||
|
||||
const anchorIdl = await anchor.Program.fetchIdl(
|
||||
SwitchboardProgram.devnetPid,
|
||||
provider
|
||||
);
|
||||
if (!anchorIdl) {
|
||||
throw new Error(
|
||||
`failed to read devnet idl for ${SwitchboardProgram.devnetPid}`
|
||||
);
|
||||
}
|
||||
|
||||
const program = new anchor.Program(
|
||||
anchorIdl,
|
||||
SwitchboardProgram.devnetPid,
|
||||
provider
|
||||
);
|
||||
|
||||
return new SwitchboardProgram(program, "devnet");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Switchboard mainnet-beta program
|
||||
* @param connection optional connection object if not using the default endpoints
|
||||
* @param confirmOptions optional confirmation options. defaults to commitment level 'confirmed'
|
||||
*/
|
||||
public static async loadMainnet(
|
||||
connection = new anchor.web3.Connection(
|
||||
anchor.web3.clusterApiUrl("mainnet-beta")
|
||||
),
|
||||
confirmOptions = SwitchboardProgram.defaultConfirmOptions
|
||||
): Promise<SwitchboardProgram> {
|
||||
const provider = new anchor.AnchorProvider(
|
||||
connection,
|
||||
new AnchorWallet(
|
||||
anchor.web3.Keypair.fromSeed(new Uint8Array(32).fill(1))
|
||||
),
|
||||
confirmOptions
|
||||
);
|
||||
|
||||
const anchorIdl = await anchor.Program.fetchIdl(
|
||||
SwitchboardProgram.mainnetPid,
|
||||
provider
|
||||
);
|
||||
if (!anchorIdl) {
|
||||
throw new Error(
|
||||
`failed to read devnet idl for ${SwitchboardProgram.mainnetPid}`
|
||||
);
|
||||
}
|
||||
|
||||
const program = new anchor.Program(
|
||||
anchorIdl,
|
||||
SwitchboardProgram.mainnetPid,
|
||||
provider
|
||||
);
|
||||
|
||||
return new SwitchboardProgram(program, "mainnet-beta");
|
||||
}
|
||||
|
||||
/** Parse an aggregators account data and return the latest confirmed result if valid
|
||||
* @param aggregator an aggregators deserialized account data
|
||||
* @param maxStaleness the maximum duration in seconds before a result is considered invalid. Defaults to 0 which ignores any checks
|
||||
* @returns latest confirmed result as a big.js or null if the latest confirmed round has insufficient oracle responses or data is too stale
|
||||
*/
|
||||
private getLatestAggregatorValue(
|
||||
aggregator: any,
|
||||
maxStaleness = 0
|
||||
): Big | null {
|
||||
if ((aggregator.latestConfirmedRound?.numSuccess ?? 0) === 0) {
|
||||
return null;
|
||||
}
|
||||
if (maxStaleness !== 0) {
|
||||
const now = new anchor.BN(Date.now() / 1000);
|
||||
const latestRoundTimestamp: anchor.BN =
|
||||
aggregator.latestConfirmedRound.roundOpenTimestamp;
|
||||
const staleness = now.sub(latestRoundTimestamp);
|
||||
if (staleness.gt(new anchor.BN(maxStaleness))) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const mantissa = new Big(
|
||||
aggregator.latestConfirmedRound.result.mantissa.toString()
|
||||
);
|
||||
const scale = aggregator.latestConfirmedRound.result.scale;
|
||||
const oldDp = Big.DP;
|
||||
Big.DP = 20;
|
||||
const result: Big = mantissa.div(new Big(10).pow(scale));
|
||||
Big.DP = oldDp;
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Fetch and decode an aggregator account
|
||||
* @param aggregatorPubkey the aggregator's public key
|
||||
* @param commitment optional connection commitment level
|
||||
* @returns deserialized aggregator account, as specified by the Switchboard IDL
|
||||
*/
|
||||
public async fetchAggregator(
|
||||
aggregatorPubkey: anchor.web3.PublicKey,
|
||||
commitment?: anchor.web3.Commitment
|
||||
): Promise<any> {
|
||||
const aggregator: any =
|
||||
await this.program.account.aggregatorAccountData?.fetch(
|
||||
aggregatorPubkey,
|
||||
commitment
|
||||
);
|
||||
aggregator.ebuf = undefined;
|
||||
return aggregator;
|
||||
}
|
||||
|
||||
/** Fetch and decode an aggregator's latest confirmed value if valid
|
||||
* @param aggregatorPubkey the aggregator's public key
|
||||
* @param commitment optional connection commitment level
|
||||
* @param maxStaleness the maximum duration in seconds before a result is considered invalid. Defaults to 0 which ignores any checks
|
||||
* @returns latest confirmed result as a big.js or null if the latest confirmed round has insufficient oracle responses or data is too stale
|
||||
*/
|
||||
public async fetchAggregatorLatestValue(
|
||||
aggregatorPubkey: anchor.web3.PublicKey,
|
||||
commitment?: anchor.web3.Commitment,
|
||||
maxStaleness = 0
|
||||
): Promise<Big | null> {
|
||||
const aggregator = await this.fetchAggregator(aggregatorPubkey, commitment);
|
||||
return this.getLatestAggregatorValue(aggregator, maxStaleness);
|
||||
}
|
||||
|
||||
/** Decode an aggregator's account info
|
||||
* @param accountInfo the aggregatror's account info
|
||||
* @returns deserialized aggregator account, as specified by the Switchboard IDL
|
||||
*/
|
||||
public decodeAggregator(accountInfo: anchor.web3.AccountInfo<Buffer>): any {
|
||||
const coder = new anchor.BorshAccountsCoder(this.program.idl);
|
||||
const aggregator: any = coder.decode(
|
||||
"AggregatorAccountData",
|
||||
accountInfo?.data
|
||||
);
|
||||
aggregator.ebuf = undefined;
|
||||
return aggregator;
|
||||
}
|
||||
|
||||
/** Decode an aggregator and get the latest confirmed round
|
||||
* @param accountInfo the aggregator's account info
|
||||
* @param maxStaleness the maximum duration in seconds before a result is considered invalid. Defaults to 0 which ignores any checks
|
||||
* @returns latest confirmed result as a big.js or null if the latest confirmed round has insufficient oracle responses or data is too stale
|
||||
*/
|
||||
public decodeLatestAggregatorValue(
|
||||
accountInfo: anchor.web3.AccountInfo<Buffer>,
|
||||
maxStaleness = 0
|
||||
): Big | null {
|
||||
const aggregator = this.decodeAggregator(accountInfo);
|
||||
return this.getLatestAggregatorValue(aggregator, maxStaleness);
|
||||
}
|
||||
}
|
|
@ -1,248 +0,0 @@
|
|||
import * as anchor from "@project-serum/anchor";
|
||||
import { strict as assert } from "assert";
|
||||
import Big from "big.js";
|
||||
import SwitchboardProgram from "../src";
|
||||
|
||||
describe("Switchboard V2 Lite Test", () => {
|
||||
let sbv2: SwitchboardProgram;
|
||||
|
||||
before(async () => {
|
||||
// TODO: Add try catch block to check devnet environment accounts
|
||||
sbv2 = await SwitchboardProgram.loadMainnet();
|
||||
});
|
||||
|
||||
// it("fetches and decodes SOL/USD mainnet aggregator", async () => {
|
||||
// const accountInfo = await sbv2.program.provider.connection.getAccountInfo(
|
||||
// solAggregatorPubkey
|
||||
// );
|
||||
// if (!accountInfo) {
|
||||
// throw new Error(`failed to fetch account info`);
|
||||
// }
|
||||
|
||||
// // Get latest value if its been updated in the last 300 seconds
|
||||
// const latestResult = sbv2.decodeLatestAggregatorValue(accountInfo, 300);
|
||||
// if (latestResult === null) {
|
||||
// throw new Error(`failed to fetch latest result for aggregator`);
|
||||
// }
|
||||
// assert(latestResult instanceof Big, "latest result is not a big.js object");
|
||||
// assert(
|
||||
// latestResult.toNumber() >= 0,
|
||||
// "latest result is less than or equal to 0"
|
||||
// );
|
||||
// });
|
||||
|
||||
it("decodes SOL/USD aggregator", async () => {
|
||||
const aggregator = sbv2.decodeAggregator(solAggregatorAccountInfo);
|
||||
|
||||
const latestResult = sbv2.decodeLatestAggregatorValue(
|
||||
solAggregatorAccountInfo
|
||||
);
|
||||
if (latestResult === null) {
|
||||
throw new Error(`failed to fetch latest result for aggregator`);
|
||||
}
|
||||
assert(latestResult instanceof Big, "latest result is not a big.js object");
|
||||
assert(
|
||||
latestResult.toNumber() === 104.967865328125,
|
||||
"latest result is not equal to expected value of 104.967865328125"
|
||||
);
|
||||
});
|
||||
|
||||
it("fails to decode stale aggregator", async () => {
|
||||
const latestResult = sbv2.decodeLatestAggregatorValue(
|
||||
solAggregatorAccountInfo,
|
||||
300
|
||||
);
|
||||
assert(
|
||||
latestResult === null,
|
||||
"aggregator should return null if value is more than 300s old"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const solAggregatorAccountInfo: anchor.web3.AccountInfo<Buffer> = {
|
||||
data: Buffer.from([
|
||||
217, 230, 65, 101, 201, 162, 27, 125, 83, 79, 76, 95, 85, 83, 68, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
254, 83, 137, 67, 40, 165, 83, 155, 80, 27, 250, 104, 141, 83, 132, 233, 58,
|
||||
21, 213, 37, 206, 133, 134, 11, 167, 160, 205, 213, 5, 110, 232, 148, 33,
|
||||
220, 163, 22, 231, 144, 81, 75, 91, 144, 77, 37, 153, 183, 156, 217, 79, 99,
|
||||
143, 60, 146, 199, 29, 200, 242, 61, 250, 67, 81, 245, 189, 99, 3, 0, 0, 0,
|
||||
2, 0, 0, 0, 1, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 88, 96, 98, 0, 0, 0, 0, 0,
|
||||
232, 48, 130, 157, 109, 192, 148, 28, 222, 236, 197, 193, 185, 28, 120, 114,
|
||||
223, 110, 18, 68, 255, 63, 82, 34, 211, 20, 254, 125, 247, 195, 0, 190, 2,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 216, 184, 200, 7, 0, 0, 0, 0, 108, 88, 96, 98, 0, 0,
|
||||
0, 0, 226, 211, 65, 91, 173, 186, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0,
|
||||
0, 0, 221, 178, 249, 215, 167, 146, 206, 248, 90, 0, 0, 0, 0, 0, 28, 0, 0,
|
||||
0, 111, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 45, 134,
|
||||
16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 32, 199, 151, 80,
|
||||
108, 73, 163, 195, 33, 198, 249, 191, 21, 162, 83, 134, 106, 247, 198, 82,
|
||||
36, 86, 177, 202, 241, 190, 232, 37, 55, 41, 29, 163, 222, 100, 183, 122,
|
||||
190, 54, 183, 109, 12, 229, 164, 70, 125, 112, 97, 247, 188, 14, 113, 24,
|
||||
176, 15, 175, 208, 157, 159, 107, 244, 41, 209, 227, 55, 231, 82, 60, 73, 4,
|
||||
36, 138, 53, 16, 25, 166, 74, 76, 144, 202, 226, 183, 187, 50, 183, 15, 179,
|
||||
60, 128, 97, 204, 166, 164, 161, 196, 113, 97, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 163, 37, 143, 62, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105,
|
||||
228, 200, 58, 140, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
216, 184, 200, 7, 0, 0, 0, 0, 108, 88, 96, 98, 0, 0, 0, 0, 226, 211, 65, 91,
|
||||
173, 186, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 221, 178, 249,
|
||||
215, 167, 146, 206, 248, 90, 0, 0, 0, 0, 0, 28, 0, 0, 0, 111, 0, 16, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 45, 134, 16, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 32, 199, 151, 80, 108, 73, 163, 195, 33,
|
||||
198, 249, 191, 21, 162, 83, 134, 106, 247, 198, 82, 36, 86, 177, 202, 241,
|
||||
190, 232, 37, 55, 41, 29, 163, 222, 100, 183, 122, 190, 54, 183, 109, 12,
|
||||
229, 164, 70, 125, 112, 97, 247, 188, 14, 113, 24, 176, 15, 175, 208, 157,
|
||||
159, 107, 244, 41, 209, 227, 55, 231, 82, 60, 73, 4, 36, 138, 53, 16, 25,
|
||||
166, 74, 76, 144, 202, 226, 183, 187, 50, 183, 15, 179, 60, 128, 97, 204,
|
||||
166, 164, 161, 196, 113, 97, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
163, 37, 143, 62, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105, 228, 200, 58, 140,
|
||||
9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
212, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 212, 48, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 142, 45, 185, 18, 195, 245, 198, 216,
|
||||
233, 244, 18, 45, 14, 178, 93, 34, 177, 51, 25, 116, 102, 136, 188, 191,
|
||||
157, 71, 47, 158, 238, 28, 135, 110, 82, 134, 175, 95, 239, 3, 109, 92, 42,
|
||||
64, 56, 180, 32, 227, 236, 51, 157, 192, 153, 42, 190, 45, 255, 202, 12,
|
||||
242, 92, 15, 11, 14, 177, 185, 33, 246, 102, 130, 36, 106, 21, 161, 247,
|
||||
205, 155, 14, 124, 142, 3, 168, 151, 84, 181, 87, 173, 190, 106, 59, 132,
|
||||
136, 154, 229, 166, 218, 27, 254, 15, 12, 81, 31, 191, 153, 61, 184, 173,
|
||||
48, 160, 244, 41, 218, 75, 26, 56, 127, 150, 233, 41, 239, 214, 129, 194,
|
||||
98, 70, 104, 108, 76, 201, 199, 1, 114, 138, 67, 214, 204, 45, 81, 248, 201,
|
||||
102, 170, 130, 118, 159, 46, 111, 203, 207, 41, 179, 92, 83, 44, 137, 83,
|
||||
37, 172, 244, 190, 204, 148, 44, 144, 244, 196, 3, 215, 109, 102, 136, 14,
|
||||
91, 35, 45, 207, 101, 215, 32, 16, 32, 145, 151, 95, 213, 34, 67, 159, 141,
|
||||
241, 95, 34, 37, 27, 250, 181, 234, 77, 97, 0, 151, 71, 1, 11, 13, 80, 72,
|
||||
110, 160, 244, 210, 106, 163, 148, 141, 44, 186, 37, 238, 148, 24, 174, 95,
|
||||
4, 43, 72, 73, 105, 234, 83, 27, 132, 156, 157, 168, 18, 141, 66, 18, 10,
|
||||
180, 34, 74, 131, 207, 12, 238, 147, 14, 127, 12, 189, 235, 223, 218, 207,
|
||||
136, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 145, 18, 6, 17, 49, 126, 4, 68, 220, 147, 77,
|
||||
236, 138, 17, 39, 208, 107, 24, 211, 125, 170, 224, 128, 61, 223, 155, 37,
|
||||
149, 160, 11, 221, 174, 189, 225, 195, 114, 46, 165, 44, 213, 21, 155, 29,
|
||||
127, 14, 92, 73, 55, 26, 92, 23, 240, 56, 241, 200, 215, 196, 7, 201, 132,
|
||||
3, 99, 100, 225, 234, 208, 159, 177, 21, 205, 255, 192, 147, 93, 2, 96, 63,
|
||||
242, 200, 155, 213, 219, 243, 20, 253, 69, 114, 48, 237, 214, 59, 37, 39,
|
||||
77, 198, 171, 28, 164, 16, 10, 131, 26, 12, 190, 150, 68, 188, 48, 252, 199,
|
||||
156, 19, 116, 207, 255, 225, 136, 26, 230, 90, 112, 2, 0, 229, 167, 169,
|
||||
171, 197, 211, 123, 234, 255, 110, 0, 86, 32, 135, 64, 158, 98, 179, 86, 87,
|
||||
140, 208, 163, 129, 164, 90, 15, 76, 168, 62, 27, 170, 36, 159, 225, 233,
|
||||
167, 162, 101, 176, 19, 52, 14, 195, 66, 178, 53, 218, 155, 89, 174, 252,
|
||||
122, 165, 166, 113, 184, 60, 102, 207, 206, 3, 120, 64, 107, 228, 234, 49,
|
||||
189, 236, 79, 136, 142, 206, 151, 242, 234, 167, 95, 96, 237, 99, 153, 188,
|
||||
43, 63, 191, 63, 185, 119, 37, 39, 1, 114, 48, 47, 174, 23, 83, 57, 99, 58,
|
||||
84, 135, 120, 90, 129, 130, 178, 93, 98, 36, 16, 7, 22, 86, 238, 55, 78,
|
||||
184, 132, 246, 30, 109, 138, 157, 70, 169, 207, 203, 214, 171, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 8, 0, 0, 0, 211, 99, 128, 93, 12, 140, 85, 191, 58, 26, 142, 190, 248,
|
||||
72, 216, 45, 94, 39, 1, 153, 200, 155, 92, 125, 159, 237, 129, 71, 26, 9,
|
||||
215, 164, 236, 129, 16, 81, 18, 162, 87, 214, 29, 244, 207, 95, 19, 238, 10,
|
||||
27, 1, 145, 151, 200, 197, 52, 59, 79, 42, 126, 200, 132, 106, 226, 44, 26,
|
||||
193, 211, 158, 16, 56, 168, 103, 76, 155, 255, 160, 102, 203, 171, 43, 147,
|
||||
5, 136, 255, 154, 90, 206, 174, 147, 168, 217, 240, 103, 28, 252, 117, 64,
|
||||
241, 135, 8, 125, 137, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 90,
|
||||
184, 200, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
]),
|
||||
executable: false,
|
||||
lamports: 27693840,
|
||||
owner: SwitchboardProgram.mainnetPid,
|
||||
rentEpoch: 302,
|
||||
};
|
||||
|
||||
const solAggregatorPubkey = new anchor.web3.PublicKey(
|
||||
"GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR"
|
||||
);
|
|
@ -1,30 +0,0 @@
|
|||
{
|
||||
"include": ["./src/**/*"],
|
||||
"compilerOptions": {
|
||||
"target": "es2019",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
// strict
|
||||
"strict": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitAny": false,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitReturns": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"paths": {
|
||||
"@switchboard-xyz/switchboard-v2": ["../switchboard-v2"]
|
||||
}
|
||||
},
|
||||
"references": [{ "path": "../switchboard-v2" }]
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2019",
|
||||
"outDir": "lib/cjs/",
|
||||
"rootDir": "./src"
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "es2022",
|
||||
"target": "es2019",
|
||||
"outDir": "lib/esm/",
|
||||
"rootDir": "./src"
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
*.tsbuildinfo
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Switchboard
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,13 +0,0 @@
|
|||
# Sbv2-Utils
|
||||
|
||||
[](https://github.com/switchboard-xyz/sbv2-solana/tree/main/libraries/sbv2-utils)
|
||||
[](https://www.npmjs.com/package/@switchboard-xyz/sbv2-utils)
|
||||
[](https://twitter.com/switchboardxyz)
|
||||
|
||||
A library of utility functions to interact with the Switchboardv2 program
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
npm i @switchboard-xyz/sbv2-utils
|
||||
```
|
File diff suppressed because it is too large
Load Diff
|
@ -1,63 +0,0 @@
|
|||
{
|
||||
"name": "@switchboard-xyz/sbv2-utils",
|
||||
"version": "0.1.55",
|
||||
"description": "some basic utility functions when working with switchboard-v2",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/switchboard-xyz/sbv2-solana",
|
||||
"directory": "javascript/sbv2-utils"
|
||||
},
|
||||
"homepage": "https://docs.switchboard.xyz",
|
||||
"files": [
|
||||
"lib",
|
||||
"src",
|
||||
"package.json"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./lib/esm/index.js",
|
||||
"require": "./lib/cjs/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "lib/cjs/index.js",
|
||||
"module": "lib/esm/index.js",
|
||||
"types": "lib/cjs/index.d.ts",
|
||||
"scripts": {
|
||||
"docgen": "typedoc --entryPoints src/index.ts --out ../../website/static/api/sbv2-utils",
|
||||
"pretest": "yarn build",
|
||||
"test": "ts-mocha -p ./tsconfig.cjs.json -t 1000000 ./tests/*.tests.ts",
|
||||
"build": "shx rm -rf lib && tsc && tsc -p tsconfig.cjs.json",
|
||||
"watch": "tsc -p tsconfig.cjs.json --watch",
|
||||
"prepublishOnly": "shx rm -rf lib && yarn build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coral-xyz/anchor": "^0.26.0",
|
||||
"@orca-so/sdk": "^1.2.24",
|
||||
"@saberhq/token-utils": "^1.13.32",
|
||||
"@solana/spl-token-v3": "npm:@solana/spl-token@0.3.6",
|
||||
"@solana/web3.js": "^1.66.2",
|
||||
"@switchboard-xyz/common": "^2.1.29",
|
||||
"@switchboard-xyz/switchboard-v2": "^0.0.174",
|
||||
"big.js": "^6.2.1",
|
||||
"bn.js": "^5.2.1",
|
||||
"chalk": "4",
|
||||
"decimal.js": "^10.3.1",
|
||||
"dotenv": "^16.0.1",
|
||||
"mocha": "^9.1.1",
|
||||
"toml": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/big.js": "^6.1.5",
|
||||
"@types/bn.js": "^5.1.1",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/node": "^17.0.45",
|
||||
"shx": "^0.3.4",
|
||||
"ts-mocha": "^9.0.2",
|
||||
"typedoc": "^0.23.8",
|
||||
"typescript": "^4.6.3"
|
||||
},
|
||||
"gitHead": "9ee13d31a3577767061bd90f57fe4629b9a89e1a"
|
||||
}
|
|
@ -1,183 +0,0 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
/*eslint-disable import/extensions */
|
||||
import { findProgramAddressSync } from "@coral-xyz/anchor/dist/cjs/utils/pubkey.js";
|
||||
import {
|
||||
Cluster,
|
||||
clusterApiUrl,
|
||||
Connection,
|
||||
Keypair,
|
||||
PublicKey,
|
||||
} from "@solana/web3.js";
|
||||
import { AnchorWallet } from "@switchboard-xyz/switchboard-v2";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import toml from "toml";
|
||||
import { DEFAULT_KEYPAIR } from "./const.js";
|
||||
import { NoPayerKeypairProvided } from "./errors.js";
|
||||
|
||||
export function programWallet(program: anchor.Program): Keypair {
|
||||
return ((program.provider as anchor.AnchorProvider).wallet as AnchorWallet)
|
||||
.payer;
|
||||
}
|
||||
|
||||
/** Return the default anchor.AnchorProvider that will fail if a transaction is sent. This is used to avoid accidentally loading a
|
||||
* valid keypair from the anchor environment defaults.
|
||||
* @param connection a Solana connection object for a given Solana cluster and endpoint
|
||||
* @return the anchor.AnchorProvider object
|
||||
* */
|
||||
export const getDefaultProvider = (
|
||||
connection: Connection
|
||||
): anchor.AnchorProvider => {
|
||||
return new anchor.AnchorProvider(
|
||||
connection,
|
||||
new AnchorWallet(DEFAULT_KEYPAIR),
|
||||
anchor.AnchorProvider.defaultOptions()
|
||||
);
|
||||
};
|
||||
|
||||
/** Get the program data address for a given programId
|
||||
* @param programId the programId for a given on-chain program
|
||||
* @return the publicKey of the address holding the upgradeable program buffer
|
||||
*/
|
||||
export const getProgramDataAddress = (programId: PublicKey): PublicKey => {
|
||||
return findProgramAddressSync(
|
||||
[programId.toBytes()],
|
||||
new PublicKey("BPFLoaderUpgradeab1e11111111111111111111111")
|
||||
)[0];
|
||||
};
|
||||
|
||||
/** Get the IDL address for a given programId
|
||||
* @param programId the programId for a given on-chain program
|
||||
* @return the publicKey of the IDL address
|
||||
*/
|
||||
export const getIdlAddress = async (
|
||||
programId: PublicKey
|
||||
): Promise<PublicKey> => {
|
||||
const base = (await PublicKey.findProgramAddress([], programId))[0];
|
||||
return PublicKey.createWithSeed(base, "anchor:idl", programId);
|
||||
};
|
||||
|
||||
export const programHasPayer = (program: anchor.Program): boolean => {
|
||||
const payer = programWallet(program);
|
||||
return !payer.publicKey.equals(DEFAULT_KEYPAIR.publicKey);
|
||||
};
|
||||
|
||||
export const getProgramPayer = (program: anchor.Program): Keypair => {
|
||||
const wallet = programWallet(program);
|
||||
if (programHasPayer(program)) {
|
||||
return wallet;
|
||||
}
|
||||
throw new NoPayerKeypairProvided();
|
||||
};
|
||||
|
||||
export const verifyProgramHasPayer = (program: anchor.Program): void => {
|
||||
if (programHasPayer(program)) {
|
||||
return;
|
||||
}
|
||||
throw new NoPayerKeypairProvided();
|
||||
};
|
||||
|
||||
export function getAnchorWalletPath(parsedToml?: any): string {
|
||||
let tomlData: any;
|
||||
if (parsedToml) {
|
||||
tomlData = parsedToml;
|
||||
} else {
|
||||
const tomlPath = path.join(process.cwd(), "Anchor.toml");
|
||||
if (!fs.existsSync(tomlPath)) {
|
||||
throw new Error(`failed to find Anchor.toml`);
|
||||
}
|
||||
tomlData = toml.parse(fs.readFileSync(tomlPath, "utf8"));
|
||||
}
|
||||
|
||||
const walletPath = tomlData.provider.wallet;
|
||||
if (!walletPath) {
|
||||
throw new Error(`Failed to read wallet path`);
|
||||
}
|
||||
return walletPath;
|
||||
}
|
||||
|
||||
export function getAnchorCluster(parsedToml?: any): string {
|
||||
let tomlData: any;
|
||||
if (parsedToml) {
|
||||
tomlData = parsedToml;
|
||||
} else {
|
||||
const tomlPath = path.join(process.cwd(), "Anchor.toml");
|
||||
if (!fs.existsSync(tomlPath)) {
|
||||
throw new Error(`failed to find Anchor.toml`);
|
||||
}
|
||||
tomlData = toml.parse(fs.readFileSync(tomlPath, "utf8"));
|
||||
}
|
||||
|
||||
const cluster = tomlData.provider.cluster;
|
||||
if (!cluster) {
|
||||
throw new Error(`Failed to read Anchor.toml cluster`);
|
||||
}
|
||||
return cluster;
|
||||
}
|
||||
|
||||
export function loadPid(programKeypairPath: string): PublicKey {
|
||||
if (!fs.existsSync(programKeypairPath)) {
|
||||
console.log(programKeypairPath);
|
||||
throw new Error(`Could not find keypair. Have you run 'anchor build'?`);
|
||||
}
|
||||
const programKeypair = Keypair.fromSecretKey(
|
||||
new Uint8Array(JSON.parse(fs.readFileSync(programKeypairPath, "utf8")))
|
||||
);
|
||||
return programKeypair.publicKey;
|
||||
}
|
||||
|
||||
export function getWorkspace(
|
||||
programName: string,
|
||||
programPath: string
|
||||
): anchor.Program {
|
||||
const tomlPath = path.join(programPath, "Anchor.toml");
|
||||
if (!fs.existsSync(tomlPath)) {
|
||||
throw new Error(`failed to find Anchor.toml`);
|
||||
}
|
||||
const tomlData = toml.parse(fs.readFileSync(tomlPath, "utf8"));
|
||||
|
||||
const cluster: Cluster | "localnet" = tomlData.provider.cluster;
|
||||
const wallet = Keypair.fromSecretKey(
|
||||
Buffer.from(
|
||||
JSON.parse(
|
||||
fs.readFileSync(tomlData.provider.wallet, {
|
||||
encoding: "utf-8",
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
const programKeypairPath = path.join(
|
||||
programPath,
|
||||
`target/deploy/${programName.replace("-", "_")}-keypair.json`
|
||||
);
|
||||
|
||||
let programId: PublicKey;
|
||||
switch (cluster) {
|
||||
case "localnet":
|
||||
programId = new PublicKey(tomlData.programs.localnet[programName]);
|
||||
break;
|
||||
case "devnet":
|
||||
programId = new PublicKey(tomlData.programs.devnet[programName]);
|
||||
break;
|
||||
case "mainnet-beta":
|
||||
programId = new PublicKey(tomlData.programs.mainnet[programName]);
|
||||
break;
|
||||
default:
|
||||
programId = loadPid(programKeypairPath);
|
||||
}
|
||||
|
||||
const programIdlPath = path.join(
|
||||
programPath,
|
||||
`target/idl/${programName.replace("-", "_")}.json`
|
||||
);
|
||||
|
||||
const idl: anchor.Idl = JSON.parse(fs.readFileSync(programIdlPath, "utf-8"));
|
||||
const url =
|
||||
cluster === "localnet" ? "http://localhost:8899" : clusterApiUrl(cluster);
|
||||
const provider = new anchor.AnchorProvider(
|
||||
new Connection(url, { commitment: "confirmed" }),
|
||||
new AnchorWallet(wallet),
|
||||
{ commitment: "confirmed" }
|
||||
);
|
||||
return new anchor.Program(idl, programId, provider);
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
/** Sleep for a given number of milliseconds
|
||||
* @param ms number of milliseconds to sleep for
|
||||
* @return a promise that resolves when the sleep interval has elapsed
|
||||
*/
|
||||
export const sleep = (ms: number): Promise<any> =>
|
||||
new Promise((s) => setTimeout(s, ms));
|
||||
|
||||
/** Returns a promise that resolves successfully if returned before the given timeout has elapsed.
|
||||
* @param ms the number of milliseconds before the promise expires
|
||||
* @param promise the promise to wait for
|
||||
* @param timeoutError the error to throw if the promise expires
|
||||
* @return the promise result
|
||||
*/
|
||||
export async function promiseWithTimeout<T>(
|
||||
ms: number,
|
||||
promise: Promise<T>,
|
||||
timeoutError = new Error("timeoutError")
|
||||
): Promise<T> {
|
||||
// create a promise that rejects in milliseconds
|
||||
const timeout = new Promise<never>((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(timeoutError);
|
||||
}, ms);
|
||||
});
|
||||
|
||||
return Promise.race<T>([promise, timeout]);
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import type { OrcaU64 } from "@orca-so/sdk";
|
||||
import type {
|
||||
Fraction,
|
||||
Price,
|
||||
TokenAmount as SaberTokenAmount,
|
||||
} from "@saberhq/token-utils";
|
||||
import type { TokenAmount } from "@solana/web3.js";
|
||||
import { SwitchboardDecimal } from "@switchboard-xyz/switchboard-v2";
|
||||
import Big from "big.js";
|
||||
import Decimal from "decimal.js";
|
||||
|
||||
export class BigUtils {
|
||||
static safeDiv(number_: Big, denominator: Big, decimals = 20): Big {
|
||||
const oldDp = Big.DP;
|
||||
Big.DP = decimals;
|
||||
const result = number_.div(denominator);
|
||||
Big.DP = oldDp;
|
||||
return result;
|
||||
}
|
||||
|
||||
static safeMul(...n: Big[]): Big {
|
||||
if (n.length === 0) {
|
||||
throw new Error(`need to provide elements to multiply ${n}`);
|
||||
}
|
||||
|
||||
let result = new Big(1);
|
||||
for (const x of n) {
|
||||
result = result.mul(x);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static safeNthRoot(big: Big, nthRoot: number, decimals = 20): Big {
|
||||
if (nthRoot <= 0) {
|
||||
throw new Error(`cannot take the nth root of a negative number`);
|
||||
}
|
||||
|
||||
const oldDp = Big.DP;
|
||||
Big.DP = decimals;
|
||||
|
||||
const decimal = BigUtils.toDecimal(big);
|
||||
const frac = new Decimal(1).div(nthRoot);
|
||||
const root: Decimal =
|
||||
big.s === -1
|
||||
? decimal.abs().pow(frac).mul(new Decimal(big.s))
|
||||
: decimal.pow(frac);
|
||||
|
||||
const result: Big = BigUtils.fromDecimal(root);
|
||||
|
||||
Big.DP = oldDp;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static safeSqrt(n: Big, decimals = 20): Big {
|
||||
const oldDp = Big.DP;
|
||||
Big.DP = decimals;
|
||||
const result = n.sqrt();
|
||||
Big.DP = oldDp;
|
||||
return result;
|
||||
}
|
||||
|
||||
static safePow(n: Big, exp: number, decimals = 20): Big {
|
||||
const oldDp = Big.DP;
|
||||
Big.DP = decimals;
|
||||
|
||||
const oldPrecision = Decimal.precision;
|
||||
Decimal.set({ precision: decimals });
|
||||
const base = BigUtils.toDecimal(n);
|
||||
const value = base.pow(exp);
|
||||
const result = BigUtils.fromDecimal(value);
|
||||
Decimal.set({ precision: oldPrecision });
|
||||
|
||||
Big.DP = oldDp;
|
||||
return result;
|
||||
}
|
||||
|
||||
static fromBN(n: anchor.BN, decimals = 0): Big {
|
||||
const big = new SwitchboardDecimal(n, decimals).toBig();
|
||||
// assert(n.cmp(new anchor.BN(big.toFixed())) === 0);
|
||||
return big;
|
||||
}
|
||||
|
||||
static toDecimal(big: Big, decimals = 20): Decimal {
|
||||
const decimal = new Decimal(big.toFixed(decimals, 0));
|
||||
// assert(decimal.toFixed() === big.toFixed());
|
||||
return decimal;
|
||||
// const b = new Big(big);
|
||||
|
||||
// const decimal = new Decimal(0);
|
||||
// (decimal as any).d = groupArray(b.c);
|
||||
// (decimal as any).e = b.e;
|
||||
// (decimal as any).s = b.s;
|
||||
|
||||
// console.log(`toDecimal: ${big.toString()} => ${decimal.toString()}`);
|
||||
// return decimal;
|
||||
}
|
||||
|
||||
static fromDecimal(decimal: Decimal, decimals = 20): Big {
|
||||
if (decimal.isNaN()) {
|
||||
throw new TypeError(`cannot convert NaN decimal.js to Big.js`);
|
||||
}
|
||||
|
||||
if (!decimal.isFinite()) {
|
||||
throw new TypeError(`cannot convert INF decimal.js to Big.js`);
|
||||
}
|
||||
|
||||
const big = new Big(decimal.toFixed(decimals, 0));
|
||||
// assert(big.toFixed() === decimal.toFixed());
|
||||
return big;
|
||||
// const d = new Decimal(decimal);
|
||||
|
||||
// const big = new Big(0);
|
||||
// console.log(`fromDecimal (${d.toString()}) d.d ${d.d}`);
|
||||
// big.c = splitToDigits(d.d);
|
||||
// big.e = d.e;
|
||||
// big.s = d.s;
|
||||
|
||||
// console.log(`fromDecimal: ${decimal.toString()} => ${big.toString()}`);
|
||||
// return big;
|
||||
}
|
||||
|
||||
static fromOrcaU64(u64: OrcaU64): Big {
|
||||
return BigUtils.fromBN(new anchor.BN(u64.value), u64.scale);
|
||||
}
|
||||
|
||||
static fromSaberTokenAmount(token: SaberTokenAmount): Big {
|
||||
return BigUtils.fromBN(
|
||||
new anchor.BN(token.toU64()),
|
||||
token.token.info.decimals
|
||||
);
|
||||
}
|
||||
|
||||
static fromTokenAmount(token: TokenAmount): Big {
|
||||
return BigUtils.fromBN(new anchor.BN(token.amount), token.decimals);
|
||||
}
|
||||
|
||||
static fromPrice(price: Price | Fraction): Big {
|
||||
const numerator = new Big(price.numerator.toString());
|
||||
const denominator = new Big(price.denominator.toString());
|
||||
return BigUtils.safeDiv(numerator, denominator);
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import { Keypair, PublicKey } from "@solana/web3.js";
|
||||
|
||||
export const DEFAULT_PUBKEY = new PublicKey("11111111111111111111111111111111");
|
||||
|
||||
export const DEFAULT_KEYPAIR = Keypair.fromSeed(new Uint8Array(32).fill(1));
|
|
@ -1,37 +0,0 @@
|
|||
import type * as anchor from "@coral-xyz/anchor";
|
||||
|
||||
const padTime = (number_: number): string => {
|
||||
return number_.toString().padStart(2, "0");
|
||||
};
|
||||
|
||||
/** Convert a Date object to YYYY-MM-DD */
|
||||
export function toDateString(d: Date | undefined): string {
|
||||
if (d)
|
||||
return `${d.getFullYear()}-${padTime(d.getMonth() + 1)}-${padTime(
|
||||
d.getDate()
|
||||
)} L`;
|
||||
return "";
|
||||
}
|
||||
|
||||
/** Convert an anchor.BN timestamp to YYYY-MM-DD */
|
||||
export function anchorBNtoDateString(ts: anchor.BN): string {
|
||||
if (!ts.toNumber()) return "N/A";
|
||||
return toDateString(new Date(ts.toNumber() * 1000));
|
||||
}
|
||||
|
||||
/** Convert a Date object to YYYY-MM-DD HH:mm:ss */
|
||||
export function toDateTimeString(d: Date | undefined): string {
|
||||
if (d)
|
||||
return `${d.getFullYear()}-${padTime(d.getMonth() + 1)}-${padTime(
|
||||
d.getDate()
|
||||
)} ${padTime(d.getHours())}:${padTime(d.getMinutes())}:${padTime(
|
||||
d.getSeconds()
|
||||
)} L`;
|
||||
return "";
|
||||
}
|
||||
|
||||
/** Convert an anchor.BN timestamp to YYYY-MM-DD HH:mm:ss */
|
||||
export function anchorBNtoDateTimeString(ts: anchor.BN): string {
|
||||
if (!ts.toNumber()) return "N/A";
|
||||
return toDateTimeString(new Date(ts.toNumber() * 1000));
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
export class NoPayerKeypairProvided extends Error {
|
||||
constructor(message = "no payer keypair provided") {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, NoPayerKeypairProvided.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidSwitchboardAccount extends Error {
|
||||
constructor(message = "failed to match account type by discriminator") {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, InvalidSwitchboardAccount.prototype);
|
||||
}
|
||||
}
|
|
@ -1,372 +0,0 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import * as spl from "@solana/spl-token-v3";
|
||||
import {
|
||||
AggregatorAccount,
|
||||
AggregatorInitParams,
|
||||
JobAccount,
|
||||
LeaseAccount,
|
||||
OracleQueueAccount,
|
||||
packInstructions,
|
||||
PermissionAccount,
|
||||
ProgramStateAccount,
|
||||
programWallet,
|
||||
signTransactions,
|
||||
SwitchboardDecimal,
|
||||
} from "@switchboard-xyz/switchboard-v2";
|
||||
import Big from "big.js";
|
||||
import { promiseWithTimeout } from "./async.js";
|
||||
|
||||
export async function awaitOpenRound(
|
||||
aggregatorAccount: AggregatorAccount,
|
||||
queueAccount: OracleQueueAccount,
|
||||
payerTokenWallet: anchor.web3.PublicKey,
|
||||
expectedValue: Big | undefined = undefined,
|
||||
timeout = 30
|
||||
): Promise<Big> {
|
||||
// call open round and wait for new value
|
||||
const accountsCoder = new anchor.BorshAccountsCoder(
|
||||
aggregatorAccount.program.idl
|
||||
);
|
||||
|
||||
let accountWs: number;
|
||||
const awaitUpdatePromise = new Promise(
|
||||
(resolve: (value: Big) => void, reject: (reason?: string) => void) => {
|
||||
accountWs = aggregatorAccount.program.provider.connection.onAccountChange(
|
||||
aggregatorAccount?.publicKey ?? anchor.web3.PublicKey.default,
|
||||
async (accountInfo) => {
|
||||
const aggregator = accountsCoder.decode(
|
||||
"AggregatorAccountData",
|
||||
accountInfo.data
|
||||
);
|
||||
const latestResult = await aggregatorAccount.getLatestValue(
|
||||
aggregator
|
||||
);
|
||||
if (!latestResult) {
|
||||
return;
|
||||
}
|
||||
if (!expectedValue) {
|
||||
resolve(latestResult);
|
||||
} else if (latestResult?.eq(expectedValue)) {
|
||||
resolve(latestResult);
|
||||
} else {
|
||||
reject(
|
||||
`Value mismatch, expected ${expectedValue}, received ${latestResult}`
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const updatedValuePromise = promiseWithTimeout(
|
||||
timeout * 1000,
|
||||
awaitUpdatePromise,
|
||||
new Error(`aggregator failed to update in ${timeout} seconds`)
|
||||
).finally(() => {
|
||||
if (accountWs) {
|
||||
aggregatorAccount.program.provider.connection.removeAccountChangeListener(
|
||||
accountWs
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await aggregatorAccount.openRound({
|
||||
oracleQueueAccount: queueAccount,
|
||||
payoutWallet: payerTokenWallet,
|
||||
});
|
||||
|
||||
const result = await updatedValuePromise;
|
||||
|
||||
if (!result) {
|
||||
throw new Error(`failed to update aggregator`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function signAndConfirmTransactions(
|
||||
program: anchor.Program,
|
||||
transactions: anchor.web3.Transaction[]
|
||||
) {
|
||||
const signedTxs = await (
|
||||
program.provider as anchor.AnchorProvider
|
||||
).wallet.signAllTransactions(transactions);
|
||||
for (const transaction of signedTxs) {
|
||||
// console.log(`Blockhash: ${transaction.recentBlockhash}`);
|
||||
const sig = await program.provider.connection.sendRawTransaction(
|
||||
transaction.serialize(),
|
||||
{ skipPreflight: false, maxRetries: 10 }
|
||||
);
|
||||
await program.provider.connection.confirmTransaction(sig);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createAggregator(
|
||||
program: anchor.Program,
|
||||
queueAccount: OracleQueueAccount,
|
||||
params: AggregatorInitParams,
|
||||
jobs: [JobAccount, number][],
|
||||
fundLeaseAmount = new anchor.BN(0)
|
||||
): Promise<AggregatorAccount> {
|
||||
const req = await createAggregatorReq(
|
||||
program,
|
||||
queueAccount,
|
||||
params,
|
||||
jobs,
|
||||
fundLeaseAmount
|
||||
);
|
||||
const { blockhash } = await program.provider.connection.getLatestBlockhash();
|
||||
const packedTxns = packInstructions(
|
||||
req.ixns,
|
||||
programWallet(program).publicKey,
|
||||
blockhash
|
||||
);
|
||||
const signedTxns = signTransactions(
|
||||
packedTxns,
|
||||
req.signers as anchor.web3.Keypair[]
|
||||
);
|
||||
await signAndConfirmTransactions(program, signedTxns);
|
||||
return req.account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve information about the payer's associated token account. If it does not exist, an
|
||||
* instruction to create it will be returned with the account's {@linkcode PublicKey}.
|
||||
*/
|
||||
async function getPayerTokenAccount(
|
||||
connection: anchor.web3.Connection,
|
||||
payer: anchor.web3.PublicKey,
|
||||
mint: anchor.web3.PublicKey
|
||||
): Promise<{
|
||||
/**
|
||||
* The {@linkcode PublicKey} of the associated token account for this payer.
|
||||
*/
|
||||
publicKey: anchor.web3.PublicKey;
|
||||
/**
|
||||
* If the token account doesn't currently exist on-chain, it needs to be created using this ixn.
|
||||
*/
|
||||
ixn?: anchor.web3.TransactionInstruction;
|
||||
}> {
|
||||
const publicKey = await spl.getAssociatedTokenAddress(
|
||||
mint,
|
||||
payer,
|
||||
undefined,
|
||||
spl.TOKEN_PROGRAM_ID,
|
||||
spl.ASSOCIATED_TOKEN_PROGRAM_ID
|
||||
);
|
||||
const accountExists = await connection
|
||||
.getAccountInfo(publicKey)
|
||||
.then((info) => info !== null)
|
||||
.catch(() => false);
|
||||
|
||||
return {
|
||||
publicKey,
|
||||
ixn: accountExists
|
||||
? undefined // Account exists, so theres no need to create it.
|
||||
: spl.createAssociatedTokenAccountInstruction(
|
||||
payer,
|
||||
publicKey,
|
||||
payer,
|
||||
mint,
|
||||
spl.TOKEN_PROGRAM_ID,
|
||||
spl.ASSOCIATED_TOKEN_PROGRAM_ID
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export async function createAggregatorReq(
|
||||
program: anchor.Program,
|
||||
queueAccount: OracleQueueAccount,
|
||||
params: AggregatorInitParams,
|
||||
jobs: [JobAccount, number][],
|
||||
fundLeaseAmount = new anchor.BN(0),
|
||||
payerPubkey = programWallet(program as any).publicKey
|
||||
): Promise<{
|
||||
ixns: anchor.web3.TransactionInstruction[];
|
||||
signers: anchor.web3.Signer[];
|
||||
account: AggregatorAccount;
|
||||
}> {
|
||||
const queue = await queueAccount.loadData();
|
||||
const mint = await queueAccount.loadMint();
|
||||
|
||||
// Aggregator params
|
||||
const aggregatorKeypair = params.keypair ?? anchor.web3.Keypair.generate();
|
||||
const authority = params.authority ?? payerPubkey;
|
||||
const size = program.account.aggregatorAccountData.size;
|
||||
const [programStateAccount, stateBump] =
|
||||
ProgramStateAccount.fromSeed(program);
|
||||
const state = await programStateAccount.loadData();
|
||||
const aggregatorAccount = new AggregatorAccount({
|
||||
program,
|
||||
publicKey: aggregatorKeypair.publicKey,
|
||||
});
|
||||
|
||||
// Permission params
|
||||
const [permissionAccount, permissionBump] = PermissionAccount.fromSeed(
|
||||
program,
|
||||
queue.authority,
|
||||
queueAccount.publicKey,
|
||||
aggregatorKeypair.publicKey
|
||||
);
|
||||
|
||||
// Lease params
|
||||
const [leaseAccount, leaseBump] = LeaseAccount.fromSeed(
|
||||
program,
|
||||
queueAccount,
|
||||
aggregatorAccount
|
||||
);
|
||||
const leaseEscrow = await spl.getAssociatedTokenAddress(
|
||||
mint.address,
|
||||
leaseAccount.publicKey,
|
||||
true,
|
||||
spl.TOKEN_PROGRAM_ID,
|
||||
spl.ASSOCIATED_TOKEN_PROGRAM_ID
|
||||
);
|
||||
|
||||
// const jobPubkeys: Array<PublicKey> = [];
|
||||
// const jobWallets: Array<PublicKey> = [];
|
||||
// const walletBumps: Array<number> = [];
|
||||
// for (const idx in jobs) {
|
||||
// const [jobWallet, bump] = anchor.utils.publicKey.findProgramAddressSync(
|
||||
// [
|
||||
// payerKeypair.publicKey.toBuffer(),
|
||||
// spl.TOKEN_PROGRAM_ID.toBuffer(),
|
||||
// mint.address.toBuffer(),
|
||||
// ],
|
||||
// spl.ASSOCIATED_TOKEN_PROGRAM_ID
|
||||
// );
|
||||
// jobPubkeys.push(jobs[idx].publicKey);
|
||||
// jobWallets.push(jobWallet);
|
||||
// walletBumps.push(bump);
|
||||
// }
|
||||
|
||||
const ixns: anchor.web3.TransactionInstruction[] = [];
|
||||
|
||||
// Check if the user has created a user token account. If not, they'll need to do that first.
|
||||
const payerTokenAcct = await getPayerTokenAccount(
|
||||
program.provider.connection,
|
||||
payerPubkey,
|
||||
mint.address
|
||||
);
|
||||
if (payerTokenAcct.ixn) {
|
||||
ixns.push(payerTokenAcct.ixn);
|
||||
}
|
||||
|
||||
// TODO: if fundLeaseAmount, check payer has enough funds
|
||||
|
||||
ixns.push(
|
||||
...([
|
||||
// allocate aggregator account
|
||||
anchor.web3.SystemProgram.createAccount({
|
||||
fromPubkey: programWallet(program).publicKey,
|
||||
newAccountPubkey: aggregatorKeypair.publicKey,
|
||||
space: size,
|
||||
lamports:
|
||||
await program.provider.connection.getMinimumBalanceForRentExemption(
|
||||
size
|
||||
),
|
||||
programId: program.programId,
|
||||
}),
|
||||
// create aggregator
|
||||
await program.methods
|
||||
.aggregatorInit({
|
||||
name: (params.name ?? Buffer.from("")).slice(0, 32),
|
||||
metadata: (params.metadata ?? Buffer.from("")).slice(0, 128),
|
||||
batchSize: params.batchSize,
|
||||
minOracleResults: params.minRequiredOracleResults,
|
||||
minJobResults: params.minRequiredJobResults,
|
||||
minUpdateDelaySeconds: params.minUpdateDelaySeconds,
|
||||
varianceThreshold: SwitchboardDecimal.fromBig(
|
||||
new Big(params.varianceThreshold ?? 0)
|
||||
),
|
||||
forceReportPeriod: params.forceReportPeriod ?? new anchor.BN(0),
|
||||
expiration: params.expiration ?? new anchor.BN(0),
|
||||
stateBump,
|
||||
})
|
||||
.accounts({
|
||||
aggregator: aggregatorKeypair.publicKey,
|
||||
authority,
|
||||
queue: params.queueAccount.publicKey,
|
||||
// authorWallet: params.authorWallet ?? state.tokenVault,
|
||||
programState: programStateAccount.publicKey,
|
||||
})
|
||||
.instruction(),
|
||||
await program.methods
|
||||
.permissionInit({})
|
||||
.accounts({
|
||||
permission: permissionAccount.publicKey,
|
||||
authority: queue.authority,
|
||||
granter: queueAccount.publicKey,
|
||||
grantee: aggregatorKeypair.publicKey,
|
||||
payer: payerPubkey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
})
|
||||
.instruction(),
|
||||
payerPubkey.equals(queue.authority)
|
||||
? await program.methods
|
||||
.permissionSet({
|
||||
permission: { permitOracleQueueUsage: null },
|
||||
enable: true,
|
||||
})
|
||||
.accounts({
|
||||
permission: permissionAccount.publicKey,
|
||||
authority: queue.authority,
|
||||
})
|
||||
.instruction()
|
||||
: undefined,
|
||||
spl.createAssociatedTokenAccountInstruction(
|
||||
payerPubkey,
|
||||
leaseEscrow,
|
||||
leaseAccount.publicKey,
|
||||
mint.address
|
||||
),
|
||||
await program.methods
|
||||
.leaseInit({
|
||||
loadAmount: fundLeaseAmount,
|
||||
stateBump,
|
||||
leaseBump,
|
||||
withdrawAuthority: payerPubkey,
|
||||
walletBumps: Buffer.from([]),
|
||||
})
|
||||
.accounts({
|
||||
programState: programStateAccount.publicKey,
|
||||
lease: leaseAccount.publicKey,
|
||||
queue: queueAccount.publicKey,
|
||||
aggregator: aggregatorAccount.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
funder: payerTokenAcct.publicKey,
|
||||
payer: payerPubkey,
|
||||
tokenProgram: spl.TOKEN_PROGRAM_ID,
|
||||
escrow: leaseEscrow,
|
||||
owner: payerPubkey,
|
||||
mint: mint.address,
|
||||
})
|
||||
// .remainingAccounts(
|
||||
// jobPubkeys.concat(jobWallets).map((pubkey: PublicKey) => {
|
||||
// return { isSigner: false, isWritable: true, pubkey };
|
||||
// })
|
||||
// )
|
||||
.instruction(),
|
||||
...(await Promise.all(
|
||||
jobs.map(async ([jobAccount, weight]) => {
|
||||
return program.methods
|
||||
.aggregatorAddJob({
|
||||
weight,
|
||||
})
|
||||
.accounts({
|
||||
aggregator: aggregatorKeypair.publicKey,
|
||||
authority,
|
||||
job: jobAccount.publicKey,
|
||||
})
|
||||
.instruction();
|
||||
})
|
||||
)),
|
||||
].filter(Boolean) as anchor.web3.TransactionInstruction[])
|
||||
);
|
||||
|
||||
return {
|
||||
ixns: ixns,
|
||||
signers: [aggregatorKeypair],
|
||||
account: aggregatorAccount,
|
||||
};
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
export * from "./anchor.js";
|
||||
export * from "./async.js";
|
||||
export * from "./big.js";
|
||||
export * from "./const.js";
|
||||
export * from "./date.js";
|
||||
export * from "./errors.js";
|
||||
export * from "./feed.js";
|
||||
export * from "./json.js";
|
||||
export * from "./nonce.js";
|
||||
export * from "./print.js";
|
||||
export * from "./switchboard.js";
|
||||
export * from "./test/index.js";
|
||||
export * from "./token.js";
|
||||
export * from "./transaction.js";
|
|
@ -1,51 +0,0 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { SwitchboardDecimal } from "@switchboard-xyz/switchboard-v2";
|
||||
import Big from "big.js";
|
||||
import BN from "bn.js";
|
||||
|
||||
function big2NumberOrString(big: Big): number | string {
|
||||
const oldStrict = Big.strict;
|
||||
Big.strict = true;
|
||||
try {
|
||||
const num = big.toNumber();
|
||||
Big.strict = oldStrict;
|
||||
return num;
|
||||
} catch {}
|
||||
Big.strict = oldStrict;
|
||||
return big.toString();
|
||||
}
|
||||
|
||||
export function jsonReplacers(key: any, value: any): any {
|
||||
if (typeof value === "string" || typeof value === "number") {
|
||||
return value;
|
||||
}
|
||||
// BN
|
||||
if (BN.isBN(value)) {
|
||||
return value.toNumber();
|
||||
}
|
||||
if (
|
||||
value instanceof SwitchboardDecimal ||
|
||||
(value &&
|
||||
typeof value === "object" &&
|
||||
"mantissa" in value &&
|
||||
"scale" in value)
|
||||
) {
|
||||
const swbDecimal = new SwitchboardDecimal(value.mantissa, value.scale);
|
||||
return big2NumberOrString(swbDecimal.toBig());
|
||||
}
|
||||
// big.js
|
||||
if (value instanceof Big) {
|
||||
return big2NumberOrString(value);
|
||||
}
|
||||
// pubkey
|
||||
if (value instanceof PublicKey) {
|
||||
return value.toBase58();
|
||||
}
|
||||
// bigint
|
||||
if (typeof value === "bigint") {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
// Fall through for nested objects
|
||||
return value;
|
||||
}
|
|
@ -1,230 +0,0 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import {
|
||||
AccountInfo,
|
||||
NonceAccount,
|
||||
NONCE_ACCOUNT_LENGTH,
|
||||
PublicKey,
|
||||
SystemProgram,
|
||||
} from "@solana/web3.js";
|
||||
import type { OracleAccount } from "@switchboard-xyz/switchboard-v2";
|
||||
import assert from "assert";
|
||||
import crypto from "crypto";
|
||||
|
||||
export interface OracleNonceAccounts {
|
||||
heartbeatNonce?: PublicKey;
|
||||
unwrapStakeNonce?: PublicKey;
|
||||
queueNonces: PublicKey[];
|
||||
}
|
||||
|
||||
export function getNoncePubkeyFromSeed(
|
||||
oracleAccount: OracleAccount,
|
||||
authority: PublicKey,
|
||||
baseSeed: string
|
||||
): [PublicKey, string] {
|
||||
const seed = `${baseSeed}-${oracleAccount.publicKey.toBase58()}`;
|
||||
const seedHashBuffer = crypto.createHash("sha256").update(seed).digest();
|
||||
assert(seedHashBuffer.byteLength === 32);
|
||||
const seedHashString = seedHashBuffer.toString("hex").slice(0, 32);
|
||||
const derivedPubkey = anchor.utils.publicKey.createWithSeedSync(
|
||||
authority,
|
||||
seedHashString,
|
||||
SystemProgram.programId
|
||||
);
|
||||
return [derivedPubkey, seedHashString];
|
||||
}
|
||||
|
||||
export function nonceAccountExists(accountInfo?: AccountInfo<Buffer>): boolean {
|
||||
if (accountInfo && accountInfo.data) {
|
||||
const nonceAccount = decodeNonceAccount(accountInfo);
|
||||
if (nonceAccount.nonce) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function getOracleHeartbeatNonceAccount(
|
||||
oracleAccount: OracleAccount,
|
||||
authority: PublicKey
|
||||
): Promise<PublicKey | null> {
|
||||
const [heartbeatNoncePubkey, heartbeatNonceSeed] = getNoncePubkeyFromSeed(
|
||||
oracleAccount,
|
||||
authority,
|
||||
"OracleHeartbeat"
|
||||
);
|
||||
const accountInfo =
|
||||
await oracleAccount.program.provider.connection.getAccountInfo(
|
||||
heartbeatNoncePubkey
|
||||
);
|
||||
if (nonceAccountExists(accountInfo ?? undefined)) {
|
||||
return heartbeatNoncePubkey;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function getOracleStakeUnwrapNonceAccount(
|
||||
oracleAccount: OracleAccount,
|
||||
authority: PublicKey
|
||||
): Promise<PublicKey | null> {
|
||||
const [heartbeatNoncePubkey, heartbeatNonceSeed] = getNoncePubkeyFromSeed(
|
||||
oracleAccount,
|
||||
authority,
|
||||
"UnwrapStakeAccount"
|
||||
);
|
||||
const accountInfo =
|
||||
await oracleAccount.program.provider.connection.getAccountInfo(
|
||||
heartbeatNoncePubkey
|
||||
);
|
||||
if (nonceAccountExists(accountInfo ?? undefined)) {
|
||||
return heartbeatNoncePubkey;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function getOracleNonceQueueAccounts(
|
||||
oracleAccount: OracleAccount,
|
||||
authority: PublicKey,
|
||||
queueSize = 1000
|
||||
): Promise<PublicKey[]> {
|
||||
// const queueBaseSeeds: string[] = Array.from(Array(queueSize).keys()).map(
|
||||
// (n) => `NonceQueue-${n.toString().padStart(5, "0")}`
|
||||
// );
|
||||
// const noncePubkeyWithSeeds: [PublicKey, string][] = queueBaseSeeds.map(
|
||||
// (seed) => getNoncePubkeyFromSeed(oracleAccount, authority, seed)
|
||||
// );
|
||||
// const pubkeyChunks: [PublicKey, string][][] = sliceIntoChunks(
|
||||
// noncePubkeyWithSeeds,
|
||||
// 100
|
||||
// );
|
||||
// const nonceAccountInfos: {
|
||||
// accountInfo: AccountInfo<Buffer>;
|
||||
// pubkey: PublicKey;
|
||||
// baseSeed: string;
|
||||
// }[] = (
|
||||
// await Promise.all(
|
||||
// pubkeyChunks.map(async (chunk) => {
|
||||
// const accountInfos =
|
||||
// await oracleAccount.program.provider.connection.getMultipleAccountsInfo(
|
||||
// chunk.map((i) => i[0])
|
||||
// );
|
||||
// return accountInfos.map((accountInfo, idx) => {
|
||||
// return {
|
||||
// accountInfo,
|
||||
// pubkey: chunk[idx][0],
|
||||
// baseSeed: chunk[idx][1],
|
||||
// };
|
||||
// });
|
||||
// })
|
||||
// )
|
||||
// ).flat();
|
||||
|
||||
const queueBaseSeeds: string[] = Array.from(Array(queueSize).keys()).map(
|
||||
(n) => `NonceQueue-${n.toString().padStart(5, "0")}`
|
||||
);
|
||||
|
||||
// [derivedPubkey, fullSeed, baseSeed]
|
||||
const noncePubkeyWithSeeds: {
|
||||
pubkey: PublicKey;
|
||||
fullSeed: string;
|
||||
baseSeed: string;
|
||||
}[] = queueBaseSeeds.map((baseSeed) => {
|
||||
const [derivedPubkey, fullSeed] = getNoncePubkeyFromSeed(
|
||||
oracleAccount,
|
||||
authority,
|
||||
baseSeed
|
||||
);
|
||||
return {
|
||||
pubkey: derivedPubkey,
|
||||
fullSeed,
|
||||
baseSeed,
|
||||
};
|
||||
});
|
||||
|
||||
const pubkeyChunks: {
|
||||
pubkey: PublicKey;
|
||||
fullSeed: string;
|
||||
baseSeed: string;
|
||||
}[][] = sliceIntoChunks(noncePubkeyWithSeeds, 100);
|
||||
|
||||
const nonceAccountInfos: {
|
||||
accountInfo: AccountInfo<Buffer>;
|
||||
pubkey?: PublicKey;
|
||||
fullSeed?: string;
|
||||
baseSeed?: string;
|
||||
}[] = (
|
||||
await Promise.all(
|
||||
pubkeyChunks.map(async (chunk, chunkIdx) => {
|
||||
const accountInfos = (
|
||||
await oracleAccount.program.provider.connection.getMultipleAccountsInfo(
|
||||
chunk.map((i) => i.pubkey).filter(Boolean) as PublicKey[]
|
||||
)
|
||||
).filter(Boolean) as AccountInfo<Buffer>[];
|
||||
return accountInfos.map((accountInfo, idx) => {
|
||||
return {
|
||||
...chunk[idx],
|
||||
accountInfo,
|
||||
};
|
||||
});
|
||||
})
|
||||
)
|
||||
).flat();
|
||||
|
||||
const nonceQueuePubkeys = nonceAccountInfos
|
||||
.map((nonce, i) => {
|
||||
if (nonceAccountExists(nonce.accountInfo ?? undefined)) {
|
||||
return nonce.pubkey;
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
.filter(Boolean) as PublicKey[];
|
||||
return nonceQueuePubkeys;
|
||||
}
|
||||
|
||||
export async function getOracleNonceAccounts(
|
||||
oracleAccount: OracleAccount
|
||||
): Promise<OracleNonceAccounts> {
|
||||
const oracle = await oracleAccount.loadData();
|
||||
const heartbeatNonce = await getOracleHeartbeatNonceAccount(
|
||||
oracleAccount,
|
||||
oracle.oracleAuthority
|
||||
);
|
||||
const unwrapStakeNonce = await getOracleStakeUnwrapNonceAccount(
|
||||
oracleAccount,
|
||||
oracle.oracleAuthority
|
||||
);
|
||||
const queueNonces = await getOracleNonceQueueAccounts(
|
||||
oracleAccount,
|
||||
oracle.oracleAuthority
|
||||
);
|
||||
|
||||
return {
|
||||
heartbeatNonce: heartbeatNonce ?? undefined,
|
||||
unwrapStakeNonce: unwrapStakeNonce ?? undefined,
|
||||
queueNonces,
|
||||
};
|
||||
}
|
||||
|
||||
// slice an array into chunks with a max size
|
||||
function sliceIntoChunks<T>(arr: Array<T>, chunkSize: number): T[][] {
|
||||
const res: T[][] = [[]];
|
||||
for (let i = 0; i < arr.length; i += chunkSize) {
|
||||
const chunk = arr.slice(i, i + chunkSize);
|
||||
res.push(chunk);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export function decodeNonceAccount(info: AccountInfo<Buffer>): NonceAccount {
|
||||
if (info === null) {
|
||||
throw new Error("FAILED_TO_FIND_ACCOUNT");
|
||||
}
|
||||
if (!info.owner.equals(SystemProgram.programId)) {
|
||||
throw new Error("INVALID_ACCOUNT_OWNER");
|
||||
}
|
||||
if (info.data.length != NONCE_ACCOUNT_LENGTH) {
|
||||
throw new Error(`Invalid account size`);
|
||||
}
|
||||
|
||||
const data = Buffer.from(info.data);
|
||||
return NonceAccount.fromAccountData(data);
|
||||
}
|
|
@ -1,830 +0,0 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import { AccountMeta, PublicKey, TokenAmount } from "@solana/web3.js";
|
||||
import { OracleJob } from "@switchboard-xyz/common";
|
||||
import {
|
||||
AggregatorAccount,
|
||||
BufferRelayerAccount,
|
||||
CrankAccount,
|
||||
CrankRow,
|
||||
JobAccount,
|
||||
LeaseAccount,
|
||||
OracleAccount,
|
||||
OracleQueueAccount,
|
||||
PermissionAccount,
|
||||
ProgramStateAccount,
|
||||
SwitchboardDecimal,
|
||||
SwitchboardPermissionValue,
|
||||
VrfAccount,
|
||||
} from "@switchboard-xyz/switchboard-v2";
|
||||
import Big from "big.js";
|
||||
import chalk from "chalk";
|
||||
import { getIdlAddress, getProgramDataAddress } from "./anchor.js";
|
||||
import { anchorBNtoDateTimeString } from "./date.js";
|
||||
import { InvalidSwitchboardAccount } from "./errors.js";
|
||||
import type { SwitchboardAccountType } from "./switchboard.js";
|
||||
|
||||
export const chalkString = (
|
||||
label: string,
|
||||
value: string | number | boolean | PublicKey | Big | anchor.BN,
|
||||
padding = 16
|
||||
): string => {
|
||||
let valueString = "";
|
||||
if (typeof value === "string") {
|
||||
valueString = value;
|
||||
} else if (typeof value === "number") {
|
||||
valueString = value.toString();
|
||||
} else if (typeof value === "boolean") {
|
||||
valueString = value.toString();
|
||||
} else if (value instanceof PublicKey) {
|
||||
if (PublicKey.default.equals(value)) {
|
||||
valueString = "N/A";
|
||||
} else {
|
||||
valueString = value.toString();
|
||||
}
|
||||
} else if (value !== undefined) {
|
||||
valueString = value.toString();
|
||||
}
|
||||
return `${chalk.blue(label.padEnd(padding, " "))}${chalk.yellow(
|
||||
valueString
|
||||
)}`;
|
||||
};
|
||||
|
||||
// JSON.stringify: Object => String
|
||||
export const pubKeyConverter = (key: any, value: any): any => {
|
||||
if (value instanceof PublicKey || key.toLowerCase().endsWith("publickey")) {
|
||||
return value.toString() ?? "";
|
||||
}
|
||||
if (value instanceof Uint8Array) {
|
||||
return `[${value.toString()}]`;
|
||||
}
|
||||
if (value instanceof anchor.BN) {
|
||||
return value.toString();
|
||||
}
|
||||
if (value instanceof Big) {
|
||||
return value.toString();
|
||||
}
|
||||
if (value instanceof SwitchboardDecimal) {
|
||||
return new Big(value.mantissa.toString())
|
||||
.div(new Big(10).pow(value.scale))
|
||||
.toString();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
export const tokenAmountString = (value: TokenAmount): string => {
|
||||
return `${value.uiAmountString ?? ""} (${value.amount})`;
|
||||
};
|
||||
|
||||
/* eslint-disable no-control-regex */
|
||||
export const buffer2string = (buf: Buffer | string | ArrayBuffer): string => {
|
||||
return Buffer.from(buf as any)
|
||||
.toString("utf8")
|
||||
.replace(/\u0000/g, ""); // removes padding from onchain fixed sized buffers
|
||||
};
|
||||
|
||||
export const toPermissionString = (
|
||||
permission: SwitchboardPermissionValue
|
||||
): string => {
|
||||
switch (permission) {
|
||||
case SwitchboardPermissionValue.PERMIT_ORACLE_HEARTBEAT:
|
||||
return "PERMIT_ORACLE_HEARTBEAT";
|
||||
case SwitchboardPermissionValue.PERMIT_ORACLE_QUEUE_USAGE:
|
||||
return "PERMIT_ORACLE_QUEUE_USAGE";
|
||||
case SwitchboardPermissionValue.PERMIT_VRF_REQUESTS:
|
||||
return "PERMIT_VRF_REQUESTS";
|
||||
default:
|
||||
return "NONE";
|
||||
}
|
||||
};
|
||||
|
||||
export type VrfStatusString =
|
||||
| ""
|
||||
| "StatusNone"
|
||||
| "StatusRequesting"
|
||||
| "StatusVerifying"
|
||||
| "StatusVerified"
|
||||
| "StatusCallbackSuccess"
|
||||
| "StatusVerifyFailure";
|
||||
|
||||
export const toVrfStatusString = (
|
||||
status: Record<string, unknown>
|
||||
): VrfStatusString => {
|
||||
try {
|
||||
if ("statusNone" in status) {
|
||||
return "StatusNone";
|
||||
}
|
||||
if ("statusRequesting" in status) {
|
||||
return "StatusRequesting";
|
||||
}
|
||||
if ("statusVerifying" in status) {
|
||||
return "StatusVerifying";
|
||||
}
|
||||
if ("statusVerified" in status) {
|
||||
return "StatusVerified";
|
||||
}
|
||||
if ("statusCallbackSuccess" in status) {
|
||||
return "StatusCallbackSuccess";
|
||||
}
|
||||
if ("statusVerifyFailure" in status) {
|
||||
return "StatusVerifyFailure";
|
||||
}
|
||||
} catch {}
|
||||
return "";
|
||||
};
|
||||
|
||||
export async function prettyPrintProgramState(
|
||||
programState: ProgramStateAccount,
|
||||
accountData?: any,
|
||||
printIdlAddress = false,
|
||||
printDataAddress = false,
|
||||
SPACING = 24
|
||||
): Promise<string> {
|
||||
const data = accountData ?? (await programState.loadData());
|
||||
|
||||
let outputString = "";
|
||||
outputString += chalk.underline(
|
||||
chalkString("## SbState", programState.publicKey, SPACING) + "\r\n"
|
||||
);
|
||||
outputString += chalkString("authority", data.authority, SPACING) + "\r\n";
|
||||
outputString += chalkString("tokenMint", data.tokenMint, SPACING) + "\r\n";
|
||||
outputString += chalkString("tokenVault", data.tokenVault, SPACING) + "\r\n";
|
||||
outputString += chalkString("daoMint", data.daoMint, SPACING);
|
||||
|
||||
if (printIdlAddress) {
|
||||
const idlAddress = await getIdlAddress(programState.program.programId);
|
||||
outputString += "\r\n" + chalkString("idlAddress", idlAddress, SPACING);
|
||||
}
|
||||
if (printDataAddress) {
|
||||
const dataAddress = getProgramDataAddress(programState.program.programId);
|
||||
outputString +=
|
||||
"\r\n" + chalkString("programDataAddress", dataAddress, SPACING);
|
||||
}
|
||||
|
||||
return outputString;
|
||||
}
|
||||
|
||||
export async function prettyPrintOracle(
|
||||
oracleAccount: OracleAccount,
|
||||
accountData?: any,
|
||||
printPermissions = false,
|
||||
SPACING = 24
|
||||
): Promise<string> {
|
||||
const data = accountData ?? (await oracleAccount.loadData());
|
||||
const oracleTokenAmount =
|
||||
await oracleAccount.program.provider.connection.getTokenAccountBalance(
|
||||
data.tokenAccount
|
||||
);
|
||||
|
||||
let outputString = "";
|
||||
|
||||
outputString += chalk.underline(
|
||||
chalkString("## Oracle", oracleAccount.publicKey, SPACING) + "\r\n"
|
||||
);
|
||||
outputString +=
|
||||
chalkString("name", buffer2string(data.name as any), SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("metadata", buffer2string(data.metadata as any), SPACING) +
|
||||
"\r\n";
|
||||
outputString +=
|
||||
chalkString(
|
||||
"balance",
|
||||
tokenAmountString(oracleTokenAmount.value),
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("oracleAuthority", data.oracleAuthority, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("tokenAccount", data.tokenAccount, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("queuePubkey", data.queuePubkey, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString(
|
||||
"lastHeartbeat",
|
||||
anchorBNtoDateTimeString(data.lastHeartbeat),
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
outputString += chalkString("numInUse", data.numInUse, SPACING) + "\r\n";
|
||||
outputString += chalkString(
|
||||
"metrics",
|
||||
JSON.stringify(data.metrics, undefined, 2),
|
||||
SPACING
|
||||
);
|
||||
|
||||
if (printPermissions) {
|
||||
let permissionAccount: PermissionAccount;
|
||||
try {
|
||||
const queueAccount = new OracleQueueAccount({
|
||||
program: oracleAccount.program,
|
||||
publicKey: data.queuePubkey,
|
||||
});
|
||||
const queue = await queueAccount.loadData();
|
||||
[permissionAccount] = PermissionAccount.fromSeed(
|
||||
oracleAccount.program,
|
||||
queue.authority,
|
||||
queueAccount.publicKey,
|
||||
oracleAccount.publicKey
|
||||
);
|
||||
const permissionData = await permissionAccount.loadData();
|
||||
outputString +=
|
||||
"\r\n" +
|
||||
(await prettyPrintPermissions(permissionAccount, permissionData));
|
||||
} catch {
|
||||
outputString += `\r\nFailed to load permission account. Has it been created yet?`;
|
||||
}
|
||||
}
|
||||
|
||||
return outputString;
|
||||
}
|
||||
|
||||
export async function prettyPrintPermissions(
|
||||
permissionAccount: PermissionAccount,
|
||||
accountData?: any,
|
||||
SPACING = 24
|
||||
): Promise<string> {
|
||||
const data = accountData ?? (await permissionAccount.loadData());
|
||||
|
||||
let outputString = "";
|
||||
|
||||
outputString += chalk.underline(
|
||||
chalkString("## Permission", permissionAccount.publicKey, SPACING) + "\r\n"
|
||||
);
|
||||
outputString += chalkString("authority", data.authority, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("permissions", toPermissionString(data.permissions), SPACING) +
|
||||
"\r\n";
|
||||
outputString += chalkString("granter", data.granter, SPACING) + "\r\n";
|
||||
outputString += chalkString("grantee", data.grantee, SPACING) + "\r\n";
|
||||
outputString += chalkString(
|
||||
"expiration",
|
||||
anchorBNtoDateTimeString(data.expiration),
|
||||
SPACING
|
||||
);
|
||||
return outputString;
|
||||
}
|
||||
|
||||
export async function prettyPrintQueue(
|
||||
queueAccount: OracleQueueAccount,
|
||||
accountData?: any,
|
||||
printOracles = false,
|
||||
SPACING = 30
|
||||
): Promise<string> {
|
||||
const data = accountData ?? (await queueAccount.loadData());
|
||||
|
||||
const varianceToleranceMultiplier = SwitchboardDecimal.from(
|
||||
data.varianceToleranceMultiplier
|
||||
).toBig();
|
||||
|
||||
let outputString = "";
|
||||
|
||||
outputString += chalk.underline(
|
||||
chalkString("## Queue", queueAccount.publicKey, SPACING) + "\r\n"
|
||||
);
|
||||
outputString +=
|
||||
chalkString("name", buffer2string(data.name as any), SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("metadata", buffer2string(data.metadata as any), SPACING) +
|
||||
"\r\n";
|
||||
outputString +=
|
||||
chalkString("oracleBuffer", data.dataBuffer, SPACING) + "\r\n";
|
||||
outputString += chalkString("authority", data.authority, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("oracleTimeout", data.oracleTimeout, SPACING) + "\r\n";
|
||||
outputString += chalkString("reward", data.reward, SPACING) + "\r\n";
|
||||
outputString += chalkString("minStake", data.minStake, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("slashingEnabled", data.slashingEnabled, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString(
|
||||
"consecutiveFeedFailureLimit",
|
||||
data.consecutiveFeedFailureLimit.toString(),
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
outputString +=
|
||||
chalkString(
|
||||
"consecutiveOracleFailureLimit",
|
||||
data.consecutiveOracleFailureLimit.toString(),
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
outputString +=
|
||||
chalkString(
|
||||
"varianceToleranceMultiplier",
|
||||
varianceToleranceMultiplier,
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
outputString +=
|
||||
chalkString(
|
||||
"feedProbationPeriod",
|
||||
data.feedProbationPeriod.toString(),
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
outputString +=
|
||||
chalkString(
|
||||
"unpermissionedFeedsEnabled",
|
||||
data.unpermissionedFeedsEnabled.toString(),
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
outputString +=
|
||||
chalkString(
|
||||
"unpermissionedVrfEnabled",
|
||||
data.unpermissionedVrfEnabled.toString(),
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
outputString += chalkString(
|
||||
"enableBufferRelayers",
|
||||
data.enableBufferRelayers?.toString() ?? "",
|
||||
SPACING
|
||||
);
|
||||
|
||||
if (printOracles && data.queue) {
|
||||
outputString += chalk.underline(
|
||||
chalkString("\r\n## Oracles", " ".repeat(32), SPACING) + "\r\n"
|
||||
);
|
||||
outputString += (data.queue as PublicKey[])
|
||||
.filter((pubkey) => !PublicKey.default.equals(pubkey))
|
||||
.map((pubkey) => pubkey.toString())
|
||||
.join("\n");
|
||||
|
||||
// (data.queue as PublicKey[]).forEach(
|
||||
// (row, index) =>
|
||||
// (outputString +=
|
||||
// chalkString(`# ${index + 1},`, row.toString(), SPACING) + "\r\n")
|
||||
// );
|
||||
}
|
||||
|
||||
return outputString;
|
||||
}
|
||||
|
||||
export async function prettyPrintLease(
|
||||
leaseAccount: LeaseAccount,
|
||||
accountData?: any,
|
||||
SPACING = 24
|
||||
): Promise<string> {
|
||||
const data = accountData ?? (await leaseAccount.loadData());
|
||||
|
||||
const escrowTokenAmount =
|
||||
await leaseAccount.program.provider.connection.getTokenAccountBalance(
|
||||
data.escrow
|
||||
);
|
||||
const balance = Number.parseInt(escrowTokenAmount.value.amount, 10);
|
||||
|
||||
let outputString = "";
|
||||
|
||||
outputString += chalk.underline(
|
||||
chalkString("## Lease", leaseAccount.publicKey, SPACING) + "\r\n"
|
||||
);
|
||||
outputString += chalkString("escrow", data.escrow, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString(
|
||||
"escrowBalance",
|
||||
tokenAmountString(escrowTokenAmount.value),
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("withdrawAuthority", data.withdrawAuthority, SPACING) + "\r\n";
|
||||
outputString += chalkString("queue", data.queue, SPACING) + "\r\n";
|
||||
outputString += chalkString("aggregator", data.aggregator, SPACING) + "\r\n";
|
||||
outputString += chalkString("isActive", data.isActive, SPACING);
|
||||
|
||||
return outputString;
|
||||
}
|
||||
|
||||
export async function prettyPrintJob(
|
||||
jobAccount: JobAccount,
|
||||
accountData?: any,
|
||||
SPACING = 24
|
||||
): Promise<string> {
|
||||
const data = accountData ?? (await jobAccount.loadData());
|
||||
|
||||
let outputString = "";
|
||||
|
||||
outputString += chalk.underline(
|
||||
chalkString("## Job", jobAccount.publicKey, SPACING) + "\r\n"
|
||||
);
|
||||
outputString +=
|
||||
chalkString("name", buffer2string(data.name as any), SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("metadata", buffer2string(data.metadata as any), SPACING) +
|
||||
"\r\n";
|
||||
outputString += chalkString("authority", data.authority, SPACING) + "\r\n";
|
||||
outputString += chalkString("expiration", data.expiration, SPACING) + "\r\n";
|
||||
outputString += chalkString(
|
||||
"tasks",
|
||||
JSON.stringify(OracleJob.decodeDelimited(data.data).tasks, undefined, 2),
|
||||
SPACING
|
||||
);
|
||||
|
||||
return outputString;
|
||||
}
|
||||
|
||||
// TODO: Add rest of fields
|
||||
export async function prettyPrintAggregator(
|
||||
aggregatorAccount: AggregatorAccount,
|
||||
accountData?: any,
|
||||
printPermissions = false,
|
||||
printLease = false,
|
||||
printJobs = false,
|
||||
SPACING = 24
|
||||
): Promise<string> {
|
||||
const data = accountData ?? (await aggregatorAccount.loadData());
|
||||
|
||||
const result = SwitchboardDecimal.from(data.latestConfirmedRound.result)
|
||||
.toBig()
|
||||
.toString();
|
||||
|
||||
const resultTimestamp = anchorBNtoDateTimeString(
|
||||
data.latestConfirmedRound.roundOpenTimestamp ?? new anchor.BN(0)
|
||||
);
|
||||
|
||||
const varianceThreshold = parseFloat(
|
||||
SwitchboardDecimal.from(data.varianceThreshold).toBig().toString()
|
||||
).toFixed(2);
|
||||
|
||||
let outputString = "";
|
||||
outputString += chalk.underline(
|
||||
chalkString(
|
||||
"## Aggregator",
|
||||
aggregatorAccount.publicKey ?? PublicKey.default,
|
||||
SPACING
|
||||
) + "\r\n"
|
||||
);
|
||||
|
||||
outputString +=
|
||||
chalkString(
|
||||
"latestResult",
|
||||
`${result} (${resultTimestamp ?? ""})`,
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("name", buffer2string(data.name as any), SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("metadata", buffer2string(data.metadata as any), SPACING) +
|
||||
"\r\n";
|
||||
outputString += chalkString("authority", data.authority, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("queuePubkey", data.queuePubkey, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("crankPubkey", data.crankPubkey, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("historyBufferPublicKey", data.historyBuffer, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString(
|
||||
"authorWallet",
|
||||
data.authorWallet ?? PublicKey.default,
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("minUpdateDelaySeconds", data.minUpdateDelaySeconds, SPACING) +
|
||||
"\r\n";
|
||||
outputString +=
|
||||
chalkString("jobPubkeysSize", data.jobPubkeysSize, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("minJobResults", data.minJobResults, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString(
|
||||
"oracleRequestBatchSize",
|
||||
data.oracleRequestBatchSize,
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("minOracleResults", data.minOracleResults, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("varianceThreshold", `${varianceThreshold} %`, SPACING) +
|
||||
"\r\n";
|
||||
outputString +=
|
||||
chalkString("forceReportPeriod", data.forceReportPeriod, SPACING) + "\r\n";
|
||||
outputString += chalkString("isLocked", data.isLocked, SPACING);
|
||||
|
||||
if (printPermissions) {
|
||||
let permissionAccount: PermissionAccount;
|
||||
try {
|
||||
const queueAccount = new OracleQueueAccount({
|
||||
program: aggregatorAccount.program,
|
||||
publicKey: data.queuePubkey,
|
||||
});
|
||||
const queue = await queueAccount.loadData();
|
||||
[permissionAccount] = PermissionAccount.fromSeed(
|
||||
aggregatorAccount.program,
|
||||
queue.authority,
|
||||
queueAccount.publicKey,
|
||||
aggregatorAccount.publicKey ?? PublicKey.default
|
||||
);
|
||||
const permissionData = await permissionAccount.loadData();
|
||||
outputString +=
|
||||
"\r\n" +
|
||||
(await prettyPrintPermissions(permissionAccount, permissionData));
|
||||
} catch {
|
||||
outputString += `\r\nFailed to load permission account. Has it been created yet?`;
|
||||
}
|
||||
}
|
||||
|
||||
if (printLease) {
|
||||
let leaseAccount: LeaseAccount;
|
||||
try {
|
||||
const queueAccount = new OracleQueueAccount({
|
||||
program: aggregatorAccount.program,
|
||||
publicKey: data.queuePubkey,
|
||||
});
|
||||
const { authority } = await queueAccount.loadData();
|
||||
[leaseAccount] = LeaseAccount.fromSeed(
|
||||
aggregatorAccount.program,
|
||||
queueAccount,
|
||||
aggregatorAccount
|
||||
);
|
||||
const leaseData = await leaseAccount.loadData();
|
||||
outputString +=
|
||||
"\r\n" + (await prettyPrintLease(leaseAccount, leaseData));
|
||||
} catch {
|
||||
outputString += `\r\nFailed to load lease account. Has it been created yet?`;
|
||||
}
|
||||
}
|
||||
|
||||
if (printJobs) {
|
||||
const jobKeys: PublicKey[] = (data.jobPubkeysData as PublicKey[]).filter(
|
||||
(pubkey) => !PublicKey.default.equals(pubkey)
|
||||
);
|
||||
for await (const jobKey of jobKeys) {
|
||||
const jobAccount = new JobAccount({
|
||||
program: aggregatorAccount.program,
|
||||
publicKey: jobKey,
|
||||
});
|
||||
outputString += "\r\n" + (await prettyPrintJob(jobAccount));
|
||||
}
|
||||
}
|
||||
|
||||
return outputString;
|
||||
}
|
||||
|
||||
export async function prettyPrintVrf(
|
||||
vrfAccount: VrfAccount,
|
||||
accountData?: any,
|
||||
printPermissions = false,
|
||||
SPACING = 24
|
||||
): Promise<string> {
|
||||
const data = accountData ?? (await vrfAccount.loadData());
|
||||
const escrowTokenAmount =
|
||||
await vrfAccount.program.provider.connection.getTokenAccountBalance(
|
||||
data.escrow
|
||||
);
|
||||
|
||||
let outputString = "";
|
||||
outputString += chalk.underline(
|
||||
chalkString("## VRF", vrfAccount.publicKey, SPACING) + "\r\n"
|
||||
);
|
||||
outputString += chalkString("authority", data.authority, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("oracleQueue", data.oracleQueue, SPACING) + "\r\n";
|
||||
outputString += chalkString("escrow", data.escrow, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString(
|
||||
"escrowBalance",
|
||||
tokenAmountString(escrowTokenAmount.value),
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
|
||||
outputString += chalkString("batchSize", data.batchSize, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString(
|
||||
"callback",
|
||||
JSON.stringify(
|
||||
{
|
||||
...data.callback,
|
||||
accounts: data.callback.accounts.filter(
|
||||
(a: AccountMeta) => !a.pubkey.equals(PublicKey.default)
|
||||
),
|
||||
ixData: `[${data.callback.ixData
|
||||
.slice(0, data.callback.ixDataLen)
|
||||
.map((n) => n.toString())
|
||||
.join(",")}]`,
|
||||
},
|
||||
undefined,
|
||||
2
|
||||
),
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
outputString += chalkString("counter", data.counter, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("status", toVrfStatusString(data.status), SPACING) + "\r\n";
|
||||
outputString += chalkString(
|
||||
"latestResult",
|
||||
JSON.stringify(
|
||||
{
|
||||
status: toVrfStatusString(data.builders[0]?.status) ?? "",
|
||||
verified: data.builders[0]?.verified ?? "",
|
||||
txRemaining: data.builders[0]?.txRemaining ?? "",
|
||||
producer: data.builders[0]?.producer.toString() ?? "",
|
||||
reprProof: data.builders[0].reprProof
|
||||
? `[${data.builders[0].reprProof.map((value) => value.toString())}]`
|
||||
: "",
|
||||
reprProofHex: data.builders[0].reprProof
|
||||
? Buffer.from(data.builders[0].reprProof).toString("hex")
|
||||
: "",
|
||||
currentRound: {
|
||||
result: data.currentRound.result
|
||||
? `[${data.currentRound.result.map((value) => value.toString())}]`
|
||||
: "",
|
||||
alpha: data.currentRound.alpha
|
||||
? `[${data.currentRound.alpha.map((value) => value.toString())}]`
|
||||
: "",
|
||||
alphaHex: Buffer.from(data.currentRound.alpha).toString("hex"),
|
||||
requestSlot: data.currentRound?.requestSlot?.toString() ?? "",
|
||||
requestTimestamp: anchorBNtoDateTimeString(
|
||||
data.currentRound.requestTimestamp
|
||||
),
|
||||
numVerified: data.currentRound.numVerified.toString(),
|
||||
},
|
||||
},
|
||||
undefined,
|
||||
2
|
||||
),
|
||||
SPACING
|
||||
);
|
||||
|
||||
if (printPermissions) {
|
||||
let permissionAccount: PermissionAccount;
|
||||
try {
|
||||
const queueAccount = new OracleQueueAccount({
|
||||
program: vrfAccount.program,
|
||||
publicKey: data.oracleQueue,
|
||||
});
|
||||
const queue = await queueAccount.loadData();
|
||||
[permissionAccount] = PermissionAccount.fromSeed(
|
||||
vrfAccount.program,
|
||||
queue.authority,
|
||||
queueAccount.publicKey,
|
||||
vrfAccount.publicKey
|
||||
);
|
||||
const permissionData = await permissionAccount.loadData();
|
||||
outputString +=
|
||||
"\r\n" +
|
||||
(await prettyPrintPermissions(permissionAccount, permissionData));
|
||||
} catch {
|
||||
outputString += `\r\nFailed to load permission account. Has it been created yet?`;
|
||||
}
|
||||
}
|
||||
|
||||
return outputString;
|
||||
}
|
||||
|
||||
export async function prettyPrintCrank(
|
||||
crankAccount: CrankAccount,
|
||||
accountData?: any,
|
||||
printRows = false,
|
||||
SPACING = 24
|
||||
): Promise<string> {
|
||||
const data = accountData ?? (await crankAccount.loadData());
|
||||
|
||||
let outputString = "";
|
||||
|
||||
outputString += chalk.underline(
|
||||
chalkString("## Crank", crankAccount.publicKey, SPACING) + "\r\n"
|
||||
);
|
||||
outputString +=
|
||||
chalkString("name", buffer2string(data.name as any), SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("metadata", buffer2string(data.metadata as any), SPACING) +
|
||||
"\r\n";
|
||||
outputString +=
|
||||
chalkString("queuePubkey", data.queuePubkey, SPACING) + "\r\n";
|
||||
outputString += chalkString("dataBuffer", data.dataBuffer, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString(
|
||||
"Size",
|
||||
`${(data.pqData as CrankRow[]).length
|
||||
.toString()
|
||||
.padStart(4)} / ${data.maxRows.toString().padEnd(4)}`,
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
|
||||
if (printRows) {
|
||||
outputString += chalk.underline(
|
||||
chalkString("## Crank Buffer", data.dataBuffer, SPACING) + "\r\n"
|
||||
);
|
||||
const rowStrings = data.pqData.map((row) => {
|
||||
return `${anchorBNtoDateTimeString(row.nextTimestamp as anchor.BN).padEnd(
|
||||
16
|
||||
)} - ${(row.pubkey as PublicKey).toString()}`;
|
||||
});
|
||||
outputString = outputString.concat(...rowStrings.join("\n"));
|
||||
|
||||
// const feedNames: string[] = [];
|
||||
// for await (const row of data.pqData) {
|
||||
// const agg = new AggregatorAccount({
|
||||
// program: crankAccount.program,
|
||||
// publicKey: row.pubkey,
|
||||
// });
|
||||
// const aggData = await agg.loadData();
|
||||
// const aggName = buffer2string(aggData.name as any);
|
||||
// feedNames.push(`${(row.pubkey as PublicKey).toString()} # ${aggName}`);
|
||||
// }
|
||||
|
||||
// outputString = outputString.concat("\n", ...feedNames.join("\n"));
|
||||
}
|
||||
return outputString;
|
||||
}
|
||||
|
||||
export async function prettyPrintBufferRelayer(
|
||||
bufferRelayerAccount: BufferRelayerAccount,
|
||||
accountData?: any,
|
||||
printJob = false,
|
||||
SPACING = 24
|
||||
): Promise<string> {
|
||||
const data = accountData ?? (await bufferRelayerAccount.loadData());
|
||||
|
||||
let outputString = "";
|
||||
|
||||
outputString += chalk.underline(
|
||||
chalkString("## BufferRelayer", bufferRelayerAccount.publicKey, SPACING) +
|
||||
"\r\n"
|
||||
);
|
||||
outputString +=
|
||||
chalkString("name", buffer2string(data.name as any), SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("queuePubkey", data.queuePubkey, SPACING) + "\r\n";
|
||||
outputString += chalkString("escrow", data.escrow, SPACING) + "\r\n";
|
||||
outputString += chalkString("authority", data.authority, SPACING) + "\r\n";
|
||||
outputString += chalkString("jobPubkey", data.jobPubkey, SPACING) + "\r\n";
|
||||
outputString +=
|
||||
chalkString("minUpdateDelaySeconds", data.minUpdateDelaySeconds, SPACING) +
|
||||
"\r\n";
|
||||
|
||||
const result = data.result as number[];
|
||||
outputString +=
|
||||
chalkString(
|
||||
"result",
|
||||
`[${result.map((r) => r.toString()).join(",")}]`,
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
outputString +=
|
||||
chalkString(
|
||||
"currentRound",
|
||||
JSON.stringify(data.currentRound, pubKeyConverter, 2),
|
||||
SPACING
|
||||
) + "\r\n";
|
||||
|
||||
if (printJob) {
|
||||
const jobAccount = new JobAccount({
|
||||
program: bufferRelayerAccount.program,
|
||||
publicKey: data.jobPubkey,
|
||||
});
|
||||
outputString += "\r\n" + (await prettyPrintJob(jobAccount));
|
||||
}
|
||||
|
||||
return outputString;
|
||||
}
|
||||
|
||||
export async function prettyPrintSwitchboardAccount(
|
||||
program: anchor.Program,
|
||||
publicKey: PublicKey,
|
||||
accountType: SwitchboardAccountType
|
||||
): Promise<string> {
|
||||
switch (accountType) {
|
||||
case JobAccount.accountName: {
|
||||
const job = new JobAccount({ program, publicKey });
|
||||
return prettyPrintJob(job);
|
||||
}
|
||||
case AggregatorAccount.accountName: {
|
||||
const aggregator = new AggregatorAccount({ program, publicKey });
|
||||
return prettyPrintAggregator(aggregator, undefined);
|
||||
}
|
||||
case OracleAccount.accountName: {
|
||||
const oracle = new OracleAccount({ program, publicKey });
|
||||
return prettyPrintOracle(oracle, undefined);
|
||||
}
|
||||
case PermissionAccount.accountName: {
|
||||
const permission = new PermissionAccount({ program, publicKey });
|
||||
return prettyPrintPermissions(permission, undefined);
|
||||
}
|
||||
case LeaseAccount.accountName: {
|
||||
const lease = new LeaseAccount({ program, publicKey });
|
||||
return prettyPrintLease(lease, undefined);
|
||||
}
|
||||
case OracleQueueAccount.accountName: {
|
||||
const queue = new OracleQueueAccount({ program, publicKey });
|
||||
return prettyPrintQueue(queue, undefined);
|
||||
}
|
||||
case CrankAccount.accountName: {
|
||||
const crank = new CrankAccount({ program, publicKey });
|
||||
return prettyPrintCrank(crank, undefined);
|
||||
}
|
||||
case "SbState":
|
||||
case ProgramStateAccount.accountName: {
|
||||
const [programState] = ProgramStateAccount.fromSeed(program);
|
||||
return prettyPrintProgramState(programState);
|
||||
}
|
||||
case VrfAccount.accountName: {
|
||||
const vrfAccount = new VrfAccount({ program, publicKey });
|
||||
return prettyPrintVrf(vrfAccount, undefined);
|
||||
}
|
||||
case BufferRelayerAccount.accountName: {
|
||||
const bufferRelayerAccount = new BufferRelayerAccount({
|
||||
program,
|
||||
publicKey,
|
||||
});
|
||||
return prettyPrintBufferRelayer(bufferRelayerAccount, undefined);
|
||||
}
|
||||
case "BUFFERxx": {
|
||||
return `Found buffer account but dont know which one`;
|
||||
}
|
||||
}
|
||||
throw new InvalidSwitchboardAccount();
|
||||
}
|
|
@ -1,303 +0,0 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import * as spl from "@solana/spl-token-v3";
|
||||
import {
|
||||
Keypair,
|
||||
PublicKey,
|
||||
SystemProgram,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import {
|
||||
CrankAccount,
|
||||
OracleAccount,
|
||||
OracleQueueAccount,
|
||||
PermissionAccount,
|
||||
ProgramStateAccount,
|
||||
programWallet,
|
||||
SwitchboardDecimal,
|
||||
} from "@switchboard-xyz/switchboard-v2";
|
||||
import Big from "big.js";
|
||||
import { chalkString } from "./print.js";
|
||||
import { packAndSend } from "./transaction.js";
|
||||
|
||||
export interface CreateQueueParams {
|
||||
authority?: PublicKey;
|
||||
name?: string;
|
||||
metadata?: string;
|
||||
minStake: anchor.BN;
|
||||
reward: anchor.BN;
|
||||
crankSize?: number;
|
||||
oracleTimeout?: number;
|
||||
numOracles?: number;
|
||||
unpermissionedFeeds?: boolean;
|
||||
unpermissionedVrf?: boolean;
|
||||
enableBufferRelayers?: boolean;
|
||||
}
|
||||
|
||||
export interface CreateQueueResponse {
|
||||
queueAccount: OracleQueueAccount;
|
||||
crankPubkey: PublicKey;
|
||||
oracles: PublicKey[];
|
||||
}
|
||||
|
||||
export async function createQueue(
|
||||
program: anchor.Program,
|
||||
params: CreateQueueParams,
|
||||
queueSize = 500,
|
||||
authorityKeypair = programWallet(program)
|
||||
): Promise<CreateQueueResponse> {
|
||||
const payerKeypair = programWallet(program);
|
||||
|
||||
const [programStateAccount, stateBump] =
|
||||
ProgramStateAccount.fromSeed(program);
|
||||
const mint = await spl.getMint(
|
||||
program.provider.connection,
|
||||
spl.NATIVE_MINT,
|
||||
undefined,
|
||||
spl.TOKEN_PROGRAM_ID
|
||||
);
|
||||
|
||||
const ixns: (TransactionInstruction | TransactionInstruction[])[] = [];
|
||||
const signers: Keypair[] = [payerKeypair, authorityKeypair];
|
||||
|
||||
try {
|
||||
await programStateAccount.loadData();
|
||||
} catch {
|
||||
const vaultKeypair = anchor.web3.Keypair.generate();
|
||||
ixns.push([
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: payerKeypair.publicKey,
|
||||
newAccountPubkey: vaultKeypair.publicKey,
|
||||
lamports:
|
||||
await program.provider.connection.getMinimumBalanceForRentExemption(
|
||||
spl.AccountLayout.span
|
||||
),
|
||||
space: spl.AccountLayout.span,
|
||||
programId: spl.TOKEN_PROGRAM_ID,
|
||||
}),
|
||||
spl.createInitializeAccountInstruction(
|
||||
vaultKeypair.publicKey,
|
||||
mint.address,
|
||||
payerKeypair.publicKey,
|
||||
spl.TOKEN_PROGRAM_ID
|
||||
),
|
||||
await program.methods
|
||||
.programInit({
|
||||
stateBump,
|
||||
})
|
||||
.accounts({
|
||||
state: programStateAccount.publicKey,
|
||||
authority: payerKeypair.publicKey,
|
||||
tokenMint: mint.address,
|
||||
vault: vaultKeypair.publicKey,
|
||||
payer: payerKeypair.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
tokenProgram: spl.TOKEN_PROGRAM_ID,
|
||||
daoMint: mint.address,
|
||||
})
|
||||
.instruction(),
|
||||
]);
|
||||
signers.push(vaultKeypair);
|
||||
}
|
||||
|
||||
const queueKeypair = anchor.web3.Keypair.generate();
|
||||
const queueBuffer = anchor.web3.Keypair.generate();
|
||||
const queueBufferSize = queueSize * 32 + 8;
|
||||
|
||||
const queueAccount = new OracleQueueAccount({
|
||||
program: program,
|
||||
publicKey: queueKeypair.publicKey,
|
||||
});
|
||||
|
||||
console.debug(chalkString("OracleQueue", queueKeypair.publicKey));
|
||||
console.debug(chalkString("OracleBuffer", queueBuffer.publicKey));
|
||||
|
||||
const crankKeypair = anchor.web3.Keypair.generate();
|
||||
const crankBuffer = anchor.web3.Keypair.generate();
|
||||
const crankSize = params.crankSize ? params.crankSize * 40 + 8 : 0;
|
||||
|
||||
console.debug(chalkString("CrankAccount", crankKeypair.publicKey));
|
||||
console.debug(chalkString("CrankBuffer", crankBuffer.publicKey));
|
||||
|
||||
const crankAccount = new CrankAccount({
|
||||
program: program,
|
||||
publicKey: crankKeypair.publicKey,
|
||||
});
|
||||
|
||||
ixns.push(
|
||||
anchor.web3.SystemProgram.createAccount({
|
||||
fromPubkey: payerKeypair.publicKey,
|
||||
newAccountPubkey: queueBuffer.publicKey,
|
||||
space: queueBufferSize,
|
||||
lamports:
|
||||
await program.provider.connection.getMinimumBalanceForRentExemption(
|
||||
queueBufferSize
|
||||
),
|
||||
programId: program.programId,
|
||||
}),
|
||||
await program.methods
|
||||
.oracleQueueInit({
|
||||
name: Buffer.from(params.name ?? "").slice(0, 32),
|
||||
metadata: Buffer.from("").slice(0, 64),
|
||||
reward: params.reward ? new anchor.BN(params.reward) : new anchor.BN(0),
|
||||
minStake: params.minStake
|
||||
? new anchor.BN(params.minStake)
|
||||
: new anchor.BN(0),
|
||||
// feedProbationPeriod: 0,
|
||||
oracleTimeout: params.oracleTimeout,
|
||||
slashingEnabled: false,
|
||||
varianceToleranceMultiplier: SwitchboardDecimal.fromBig(new Big(2)),
|
||||
authority: authorityKeypair.publicKey,
|
||||
// consecutiveFeedFailureLimit: new anchor.BN(1000),
|
||||
// consecutiveOracleFailureLimit: new anchor.BN(1000),
|
||||
minimumDelaySeconds: 5,
|
||||
queueSize: queueSize,
|
||||
unpermissionedFeeds: params.unpermissionedFeeds ?? false,
|
||||
unpermissionedVrf: params.unpermissionedVrf ?? false,
|
||||
enableBufferRelayers: params.enableBufferRelayers ?? false,
|
||||
})
|
||||
.accounts({
|
||||
oracleQueue: queueKeypair.publicKey,
|
||||
authority: authorityKeypair.publicKey,
|
||||
buffer: queueBuffer.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
payer: payerKeypair.publicKey,
|
||||
mint: mint.address,
|
||||
})
|
||||
.instruction(),
|
||||
anchor.web3.SystemProgram.createAccount({
|
||||
fromPubkey: payerKeypair.publicKey,
|
||||
newAccountPubkey: crankBuffer.publicKey,
|
||||
space: crankSize,
|
||||
lamports:
|
||||
await program.provider.connection.getMinimumBalanceForRentExemption(
|
||||
crankSize
|
||||
),
|
||||
programId: program.programId,
|
||||
}),
|
||||
await program.methods
|
||||
.crankInit({
|
||||
name: Buffer.from("Crank").slice(0, 32),
|
||||
metadata: Buffer.from("").slice(0, 64),
|
||||
crankSize: params.crankSize,
|
||||
})
|
||||
.accounts({
|
||||
crank: crankKeypair.publicKey,
|
||||
queue: queueKeypair.publicKey,
|
||||
buffer: crankBuffer.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
payer: payerKeypair.publicKey,
|
||||
})
|
||||
.instruction()
|
||||
);
|
||||
signers.push(queueKeypair, queueBuffer, crankKeypair, crankBuffer);
|
||||
|
||||
const finalTransactions: (
|
||||
| TransactionInstruction
|
||||
| TransactionInstruction[]
|
||||
)[] = [];
|
||||
|
||||
const oracleAccounts = await Promise.all(
|
||||
Array.from(Array(params.numOracles).keys()).map(async (n) => {
|
||||
const name = `Oracle-${n + 1}`;
|
||||
const tokenWalletKeypair = anchor.web3.Keypair.generate();
|
||||
const [oracleAccount, oracleBump] = OracleAccount.fromSeed(
|
||||
program,
|
||||
queueAccount,
|
||||
tokenWalletKeypair.publicKey
|
||||
);
|
||||
|
||||
console.debug(chalkString(name, oracleAccount.publicKey));
|
||||
|
||||
const [permissionAccount, permissionBump] = PermissionAccount.fromSeed(
|
||||
program,
|
||||
authorityKeypair.publicKey,
|
||||
queueAccount.publicKey,
|
||||
oracleAccount.publicKey
|
||||
);
|
||||
console.debug(
|
||||
chalkString(`Permission-${n + 1}`, permissionAccount.publicKey)
|
||||
);
|
||||
|
||||
finalTransactions.push([
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: payerKeypair.publicKey,
|
||||
newAccountPubkey: tokenWalletKeypair.publicKey,
|
||||
lamports:
|
||||
await program.provider.connection.getMinimumBalanceForRentExemption(
|
||||
spl.AccountLayout.span
|
||||
),
|
||||
space: spl.AccountLayout.span,
|
||||
programId: spl.TOKEN_PROGRAM_ID,
|
||||
}),
|
||||
spl.createInitializeAccountInstruction(
|
||||
tokenWalletKeypair.publicKey,
|
||||
mint.address,
|
||||
programStateAccount.publicKey,
|
||||
spl.TOKEN_PROGRAM_ID
|
||||
),
|
||||
await program.methods
|
||||
.oracleInit({
|
||||
name: Buffer.from(name).slice(0, 32),
|
||||
metadata: Buffer.from("").slice(0, 128),
|
||||
stateBump,
|
||||
oracleBump,
|
||||
})
|
||||
.accounts({
|
||||
oracle: oracleAccount.publicKey,
|
||||
oracleAuthority: authorityKeypair.publicKey,
|
||||
queue: queueKeypair.publicKey,
|
||||
wallet: tokenWalletKeypair.publicKey,
|
||||
programState: programStateAccount.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
payer: payerKeypair.publicKey,
|
||||
})
|
||||
.instruction(),
|
||||
await program.methods
|
||||
.permissionInit({})
|
||||
.accounts({
|
||||
permission: permissionAccount.publicKey,
|
||||
authority: authorityKeypair.publicKey,
|
||||
granter: queueAccount.publicKey,
|
||||
grantee: oracleAccount.publicKey,
|
||||
payer: payerKeypair.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
})
|
||||
.instruction(),
|
||||
await program.methods
|
||||
.permissionSet({
|
||||
permission: { permitOracleHeartbeat: null },
|
||||
enable: true,
|
||||
})
|
||||
.accounts({
|
||||
permission: permissionAccount.publicKey,
|
||||
authority: authorityKeypair.publicKey,
|
||||
})
|
||||
.instruction(),
|
||||
]);
|
||||
signers.push(tokenWalletKeypair);
|
||||
return {
|
||||
oracleAccount,
|
||||
name,
|
||||
permissionAccount,
|
||||
tokenWalletKeypair,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const createAccountSignatures = await packAndSend(
|
||||
program,
|
||||
[ixns, finalTransactions],
|
||||
signers,
|
||||
payerKeypair.publicKey
|
||||
);
|
||||
|
||||
// const result = await program.provider.connection.confirmTransaction(
|
||||
// createAccountSignatures[-1]
|
||||
// );
|
||||
|
||||
return {
|
||||
queueAccount,
|
||||
crankPubkey: crankAccount.publicKey,
|
||||
oracles: oracleAccounts.map((o) => o.oracleAccount.publicKey) ?? [],
|
||||
};
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import { ACCOUNT_DISCRIMINATOR_SIZE } from "@coral-xyz/anchor";
|
||||
import type { PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
AggregatorAccount,
|
||||
BufferRelayerAccount,
|
||||
CrankAccount,
|
||||
JobAccount,
|
||||
LeaseAccount,
|
||||
OracleAccount,
|
||||
OracleQueueAccount,
|
||||
PermissionAccount,
|
||||
ProgramStateAccount,
|
||||
VrfAccount,
|
||||
} from "@switchboard-xyz/switchboard-v2";
|
||||
import { InvalidSwitchboardAccount } from "./errors.js";
|
||||
|
||||
export const SWITCHBOARD_ACCOUNT_TYPES = [
|
||||
JobAccount.accountName,
|
||||
AggregatorAccount.accountName,
|
||||
OracleAccount.accountName,
|
||||
OracleQueueAccount.accountName,
|
||||
PermissionAccount.accountName,
|
||||
LeaseAccount.accountName,
|
||||
ProgramStateAccount.accountName,
|
||||
VrfAccount.accountName,
|
||||
CrankAccount.accountName,
|
||||
BufferRelayerAccount.accountName,
|
||||
"SbState",
|
||||
"BUFFERxx",
|
||||
] as const;
|
||||
|
||||
export type SwitchboardAccount =
|
||||
| JobAccount
|
||||
| AggregatorAccount
|
||||
| OracleAccount
|
||||
| OracleQueueAccount
|
||||
| PermissionAccount
|
||||
| LeaseAccount
|
||||
| ProgramStateAccount
|
||||
| VrfAccount
|
||||
| CrankAccount
|
||||
| BufferRelayerAccount;
|
||||
|
||||
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),
|
||||
])
|
||||
);
|
||||
|
||||
// should also check if pubkey is a token account
|
||||
export const findAccountType = async (
|
||||
program: anchor.Program,
|
||||
publicKey: PublicKey
|
||||
): Promise<SwitchboardAccountType> => {
|
||||
const account = await program.provider.connection.getAccountInfo(publicKey);
|
||||
if (!account) {
|
||||
throw new Error(`failed to fetch account info for ${publicKey}`);
|
||||
}
|
||||
|
||||
const accountDiscriminator = account.data.slice(
|
||||
0,
|
||||
ACCOUNT_DISCRIMINATOR_SIZE
|
||||
);
|
||||
|
||||
for (const [name, discriminator] of SWITCHBOARD_DISCRIMINATOR_MAP.entries()) {
|
||||
if (Buffer.compare(accountDiscriminator, discriminator) === 0) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidSwitchboardAccount();
|
||||
};
|
||||
|
||||
export const loadSwitchboardAccount = async (
|
||||
program: anchor.Program,
|
||||
publicKey: PublicKey
|
||||
): Promise<[SwitchboardAccountType, SwitchboardAccount]> => {
|
||||
const accountType = await findAccountType(program, publicKey);
|
||||
switch (accountType) {
|
||||
case JobAccount.accountName: {
|
||||
return [accountType, new JobAccount({ program, publicKey })];
|
||||
}
|
||||
case AggregatorAccount.accountName: {
|
||||
return [accountType, new AggregatorAccount({ program, publicKey })];
|
||||
}
|
||||
case OracleAccount.accountName: {
|
||||
return [accountType, new OracleAccount({ program, publicKey })];
|
||||
}
|
||||
case PermissionAccount.accountName: {
|
||||
return [accountType, new PermissionAccount({ program, publicKey })];
|
||||
}
|
||||
case LeaseAccount.accountName: {
|
||||
return [accountType, new LeaseAccount({ program, publicKey })];
|
||||
}
|
||||
case OracleQueueAccount.accountName: {
|
||||
return [accountType, new OracleQueueAccount({ program, publicKey })];
|
||||
}
|
||||
case CrankAccount.accountName: {
|
||||
return [accountType, new CrankAccount({ program, publicKey })];
|
||||
}
|
||||
case "SbState":
|
||||
case ProgramStateAccount.accountName: {
|
||||
return [accountType, new ProgramStateAccount({ program, publicKey })];
|
||||
}
|
||||
case VrfAccount.accountName: {
|
||||
return [accountType, new VrfAccount({ program, publicKey })];
|
||||
}
|
||||
case BufferRelayerAccount.accountName: {
|
||||
return [accountType, new BufferRelayerAccount({ program, publicKey })];
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidSwitchboardAccount();
|
||||
};
|
|
@ -1,450 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/no-shadow */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
import * as anchor from "@coral-xyz/anchor";
|
||||
import * as spl from "@solana/spl-token-v3";
|
||||
import { Keypair, PublicKey } from "@solana/web3.js";
|
||||
import { OracleJob } from "@switchboard-xyz/common";
|
||||
import * as sbv2 from "@switchboard-xyz/switchboard-v2";
|
||||
import Big from "big.js";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { sleep } from "../async.js";
|
||||
import { awaitOpenRound, createAggregator } from "../feed.js";
|
||||
import { transferWrappedSol } from "../token.js";
|
||||
|
||||
export interface ISwitchboardTestContext {
|
||||
program: anchor.Program;
|
||||
mint: spl.Mint;
|
||||
payerTokenWallet: PublicKey;
|
||||
queue: sbv2.OracleQueueAccount;
|
||||
oracle?: sbv2.OracleAccount;
|
||||
}
|
||||
|
||||
export class SwitchboardTestContext implements ISwitchboardTestContext {
|
||||
program: anchor.Program;
|
||||
|
||||
mint: spl.Mint;
|
||||
|
||||
payerTokenWallet: PublicKey;
|
||||
|
||||
queue: sbv2.OracleQueueAccount;
|
||||
|
||||
oracle?: sbv2.OracleAccount;
|
||||
|
||||
constructor(ctx: ISwitchboardTestContext) {
|
||||
this.program = ctx.program;
|
||||
this.mint = ctx.mint;
|
||||
this.payerTokenWallet = ctx.payerTokenWallet;
|
||||
this.queue = ctx.queue;
|
||||
this.oracle = ctx.oracle;
|
||||
}
|
||||
|
||||
/** Load the associated token wallet for the given payer with a prefunded balance
|
||||
* @param program anchor program
|
||||
* @param mint the switchboard mint address
|
||||
* @param tokenAmount number of tokens to populate in switchboard mint's associated token account
|
||||
*/
|
||||
static async getOrCreateSwitchboardWallet(
|
||||
program: anchor.Program,
|
||||
mint: spl.Mint,
|
||||
tokenAmount: number
|
||||
): Promise<PublicKey> {
|
||||
const payerKeypair = sbv2.programWallet(program);
|
||||
|
||||
if (tokenAmount <= 0) {
|
||||
return spl.getAssociatedTokenAddress(
|
||||
mint.address,
|
||||
payerKeypair.publicKey
|
||||
);
|
||||
}
|
||||
|
||||
const associatedTokenAccount = await spl.getOrCreateAssociatedTokenAccount(
|
||||
program.provider.connection,
|
||||
payerKeypair,
|
||||
mint.address,
|
||||
payerKeypair.publicKey
|
||||
);
|
||||
|
||||
if (tokenAmount <= associatedTokenAccount.amount) {
|
||||
return associatedTokenAccount.address;
|
||||
}
|
||||
|
||||
const amountNeeded = tokenAmount - Number(associatedTokenAccount.amount);
|
||||
if (amountNeeded <= 0) {
|
||||
return associatedTokenAccount.address;
|
||||
}
|
||||
|
||||
const balance = await program.provider.connection.getBalance(
|
||||
payerKeypair.publicKey
|
||||
);
|
||||
|
||||
if (amountNeeded > balance) {
|
||||
throw new Error(
|
||||
`Payer account does not enough balance to fund new token account, need ${amountNeeded}, have ${balance}`
|
||||
);
|
||||
}
|
||||
|
||||
const finalBalance = await transferWrappedSol(
|
||||
program.provider.connection,
|
||||
payerKeypair,
|
||||
amountNeeded
|
||||
);
|
||||
|
||||
return associatedTokenAccount.address;
|
||||
}
|
||||
|
||||
/** Load SwitchboardTestContext using a specified queue
|
||||
* @param provider anchor Provider containing connection and payer Keypair
|
||||
* @param queueKey the oracle queue to load
|
||||
* @param tokenAmount number of tokens to populate in switchboard mint's associated token account
|
||||
*/
|
||||
static async loadDevnetQueue(
|
||||
provider: anchor.AnchorProvider,
|
||||
queueKey = "uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX",
|
||||
tokenAmount = 0
|
||||
) {
|
||||
const payerKeypair = (provider.wallet as sbv2.AnchorWallet).payer;
|
||||
|
||||
const balance = await provider.connection.getBalance(
|
||||
payerKeypair.publicKey
|
||||
);
|
||||
if (!balance) {
|
||||
try {
|
||||
await provider.connection.requestAirdrop(
|
||||
payerKeypair.publicKey,
|
||||
1_000_000_000
|
||||
);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
let program: anchor.Program;
|
||||
try {
|
||||
program = await sbv2.loadSwitchboardProgram(
|
||||
"devnet",
|
||||
provider.connection,
|
||||
payerKeypair
|
||||
);
|
||||
} catch (error: any) {
|
||||
throw new Error(
|
||||
`Failed to load the SBV2 program for the given cluster, ${error.message}`
|
||||
);
|
||||
}
|
||||
let queue: sbv2.OracleQueueAccount;
|
||||
let queueData: any;
|
||||
try {
|
||||
queue = new sbv2.OracleQueueAccount({
|
||||
program,
|
||||
publicKey: new PublicKey(queueKey),
|
||||
});
|
||||
queueData = await queue.loadData();
|
||||
if (queueData.queue.length < 1) {
|
||||
throw new Error(`OracleQueue has no active oracles heartbeating`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(
|
||||
`Failed to load the SBV2 queue for the given cluster, ${error.message}`
|
||||
);
|
||||
}
|
||||
let mint: spl.Mint;
|
||||
try {
|
||||
mint = await queue.loadMint();
|
||||
} catch (error: any) {
|
||||
throw new Error(
|
||||
`Failed to load the SBV2 mint for the given cluster, ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
const payerTokenWallet =
|
||||
await SwitchboardTestContext.getOrCreateSwitchboardWallet(
|
||||
program,
|
||||
mint,
|
||||
tokenAmount
|
||||
);
|
||||
|
||||
return new SwitchboardTestContext({
|
||||
program,
|
||||
queue,
|
||||
mint,
|
||||
payerTokenWallet,
|
||||
});
|
||||
}
|
||||
|
||||
/** Recursively loop through directories and return the filepath of switchboard.env
|
||||
* @param envFileName alternative filename to search for. defaults to switchboard.env
|
||||
* @returns the filepath for a switchboard env file to load
|
||||
*/
|
||||
public static findSwitchboardEnv(envFileName = "switchboard.env"): string {
|
||||
const NotFoundError = new Error(
|
||||
"failed to find switchboard.env file in current directory recursively"
|
||||
);
|
||||
let retryCount = 5;
|
||||
|
||||
let currentDirectory = process.cwd();
|
||||
while (retryCount > 0) {
|
||||
// look for switchboard.env
|
||||
try {
|
||||
const currentPath = path.join(currentDirectory, envFileName);
|
||||
if (fs.existsSync(currentPath)) {
|
||||
return currentPath;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
// look for .switchboard directory
|
||||
try {
|
||||
const localSbvPath = path.join(currentDirectory, ".switchboard");
|
||||
if (fs.existsSync(localSbvPath)) {
|
||||
const localSbvEnvPath = path.join(localSbvPath, envFileName);
|
||||
if (fs.existsSync(localSbvEnvPath)) {
|
||||
return localSbvEnvPath;
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
currentDirectory = path.join(currentDirectory, "../");
|
||||
|
||||
--retryCount;
|
||||
}
|
||||
|
||||
throw NotFoundError;
|
||||
}
|
||||
|
||||
/** Load SwitchboardTestContext from an env file containing $SWITCHBOARD_PROGRAM_ID, $ORACLE_QUEUE, $AGGREGATOR
|
||||
* @param provider anchor Provider containing connection and payer Keypair
|
||||
* @param filePath filesystem path to env file
|
||||
* @param tokenAmount number of tokens to populate in switchboard mint's associated token account
|
||||
*/
|
||||
public static async loadFromEnv(
|
||||
provider: anchor.AnchorProvider,
|
||||
filePath = SwitchboardTestContext.findSwitchboardEnv(),
|
||||
tokenAmount = 0
|
||||
): Promise<SwitchboardTestContext> {
|
||||
require("dotenv").config({ path: filePath });
|
||||
if (!process.env.SWITCHBOARD_PROGRAM_ID) {
|
||||
throw new Error(`your env file must have $SWITCHBOARD_PROGRAM_ID set`);
|
||||
}
|
||||
|
||||
const payerKeypair = (provider.wallet as sbv2.AnchorWallet).payer;
|
||||
|
||||
const balance = await provider.connection.getBalance(
|
||||
payerKeypair.publicKey
|
||||
);
|
||||
if (!balance) {
|
||||
try {
|
||||
const airdropSignature = await provider.connection.requestAirdrop(
|
||||
payerKeypair.publicKey,
|
||||
1_000_000_000
|
||||
);
|
||||
await provider.connection.confirmTransaction(airdropSignature);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const SWITCHBOARD_PID = new PublicKey(process.env.SWITCHBOARD_PROGRAM_ID);
|
||||
const switchboardIdl = await anchor.Program.fetchIdl(
|
||||
SWITCHBOARD_PID,
|
||||
provider
|
||||
);
|
||||
if (!switchboardIdl) {
|
||||
throw new Error(`failed to load Switchboard IDL`);
|
||||
}
|
||||
const switchboardProgram = new anchor.Program(
|
||||
switchboardIdl,
|
||||
SWITCHBOARD_PID,
|
||||
provider
|
||||
);
|
||||
|
||||
if (!process.env.ORACLE_QUEUE) {
|
||||
throw new Error(`your env file must have $ORACLE_QUEUE set`);
|
||||
}
|
||||
const SWITCHBOARD_QUEUE = new PublicKey(process.env.ORACLE_QUEUE);
|
||||
const queue = new sbv2.OracleQueueAccount({
|
||||
program: switchboardProgram,
|
||||
publicKey: SWITCHBOARD_QUEUE,
|
||||
});
|
||||
|
||||
const oracle = process.env.ORACLE
|
||||
? new sbv2.OracleAccount({
|
||||
program: switchboardProgram,
|
||||
publicKey: new PublicKey(process.env.ORACLE),
|
||||
})
|
||||
: undefined;
|
||||
|
||||
let mint: spl.Mint;
|
||||
try {
|
||||
mint = await queue.loadMint();
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
throw new Error(
|
||||
`Failed to load the SBV2 mint for the given cluster, ${error}`
|
||||
);
|
||||
}
|
||||
|
||||
const payerTokenWallet =
|
||||
await SwitchboardTestContext.getOrCreateSwitchboardWallet(
|
||||
switchboardProgram,
|
||||
mint,
|
||||
tokenAmount
|
||||
);
|
||||
|
||||
const context: ISwitchboardTestContext = {
|
||||
program: switchboardProgram,
|
||||
mint: mint,
|
||||
payerTokenWallet,
|
||||
queue,
|
||||
oracle,
|
||||
};
|
||||
|
||||
return new SwitchboardTestContext(context);
|
||||
}
|
||||
|
||||
/** Create a static data feed that resolves to an expected value */
|
||||
public async createStaticFeed(
|
||||
value: number,
|
||||
timeout = 30
|
||||
): Promise<sbv2.AggregatorAccount> {
|
||||
const payerKeypair = sbv2.programWallet(this.program);
|
||||
|
||||
const staticJob = await sbv2.JobAccount.create(this.program, {
|
||||
name: Buffer.from(`Value ${value}`),
|
||||
authority: this.payerTokenWallet,
|
||||
data: Buffer.from(
|
||||
OracleJob.encodeDelimited(
|
||||
OracleJob.create({
|
||||
tasks: [
|
||||
OracleJob.Task.create({
|
||||
valueTask: OracleJob.ValueTask.create({
|
||||
value,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
})
|
||||
).finish()
|
||||
),
|
||||
});
|
||||
|
||||
const aggregatorAccount = await createAggregator(
|
||||
this.program,
|
||||
this.queue,
|
||||
{
|
||||
batchSize: 1,
|
||||
minRequiredJobResults: 1,
|
||||
minRequiredOracleResults: 1,
|
||||
minUpdateDelaySeconds: 5,
|
||||
queueAccount: this.queue,
|
||||
authorWallet: this.payerTokenWallet,
|
||||
authority: payerKeypair.publicKey,
|
||||
},
|
||||
[[staticJob, 1]]
|
||||
);
|
||||
|
||||
const aggValue = await awaitOpenRound(
|
||||
aggregatorAccount,
|
||||
this.queue,
|
||||
this.payerTokenWallet,
|
||||
new Big(value),
|
||||
timeout
|
||||
);
|
||||
|
||||
return aggregatorAccount;
|
||||
}
|
||||
|
||||
/** Update a feed to a single job that resolves to a new expected value
|
||||
* @param aggregatorAccount the aggregator to change a job definition for
|
||||
* @param value the new expected value
|
||||
* @param timeout how long to wait for the oracle to update the aggregator's latestRound result
|
||||
*/
|
||||
public async updateStaticFeed(
|
||||
aggregatorAccount: sbv2.AggregatorAccount,
|
||||
value: number,
|
||||
timeout = 30
|
||||
): Promise<void> {
|
||||
const payerKeypair = sbv2.programWallet(this.program);
|
||||
const aggregator = await aggregatorAccount.loadData();
|
||||
const expectedValue = new Big(value);
|
||||
|
||||
const queue = await this.queue.loadData();
|
||||
|
||||
// remove all existing jobs
|
||||
const existingJobs: sbv2.JobAccount[] = aggregator.jobPubkeysData
|
||||
// eslint-disable-next-line array-callback-return
|
||||
.filter((jobKey: PublicKey) => {
|
||||
if (!jobKey.equals(PublicKey.default)) {
|
||||
return jobKey;
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
.filter((item: PublicKey | undefined) => item !== undefined)
|
||||
.map(
|
||||
(jobKey: PublicKey) =>
|
||||
new sbv2.JobAccount({
|
||||
program: this.program,
|
||||
publicKey: jobKey,
|
||||
})
|
||||
);
|
||||
await Promise.all(
|
||||
existingJobs.map((job) => aggregatorAccount.removeJob(job, payerKeypair))
|
||||
);
|
||||
|
||||
// add new static job
|
||||
const staticJob = await sbv2.JobAccount.create(this.program, {
|
||||
name: Buffer.from(`Value ${value}`),
|
||||
authority: Keypair.generate().publicKey,
|
||||
data: Buffer.from(
|
||||
OracleJob.encodeDelimited(
|
||||
OracleJob.create({
|
||||
tasks: [
|
||||
OracleJob.Task.create({
|
||||
valueTask: OracleJob.ValueTask.create({
|
||||
value,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
})
|
||||
).finish()
|
||||
),
|
||||
});
|
||||
await aggregatorAccount.addJob(staticJob, payerKeypair);
|
||||
|
||||
const aggValue = await awaitOpenRound(
|
||||
aggregatorAccount,
|
||||
this.queue,
|
||||
this.payerTokenWallet,
|
||||
expectedValue,
|
||||
timeout
|
||||
);
|
||||
}
|
||||
|
||||
/** Checks whether the queue has any active oracles heartbeating */
|
||||
public async isQueueReady(): Promise<boolean> {
|
||||
const queueData = await this.queue.loadData();
|
||||
return queueData.queue.length > 0;
|
||||
}
|
||||
|
||||
/** Awaits the specified timeout for an oracle to start heartbeating on the queue
|
||||
* @param timeout number of seconds to wait for an oracle to start heartbeating
|
||||
*/
|
||||
public async oracleHeartbeat(timeout = 30): Promise<void> {
|
||||
const delay = Math.ceil(timeout / 10) * 1000;
|
||||
let retryCount = 10;
|
||||
while (retryCount) {
|
||||
try {
|
||||
if (await this.isQueueReady()) {
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
if (
|
||||
!(error instanceof Error) ||
|
||||
!error.toString().includes("connection refused")
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
await sleep(delay);
|
||||
--retryCount;
|
||||
}
|
||||
if (timeout <= 0) {
|
||||
throw new Error(
|
||||
`Timed out waiting for the OracleQueue to have an active oracle heartbeating`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,471 +0,0 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import { clusterApiUrl, Connection, Keypair, PublicKey } from "@solana/web3.js";
|
||||
import * as sbv2 from "@switchboard-xyz/switchboard-v2";
|
||||
import {
|
||||
CrankAccount,
|
||||
OracleAccount,
|
||||
PermissionAccount,
|
||||
ProgramStateAccount,
|
||||
} from "@switchboard-xyz/switchboard-v2";
|
||||
import chalk from "chalk";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { getIdlAddress, getProgramDataAddress } from "../anchor.js";
|
||||
import { anchorBNtoDateString } from "../date.js";
|
||||
import { createQueue } from "../queue.js";
|
||||
import { getOrCreateSwitchboardTokenAccount } from "../token.js";
|
||||
|
||||
export const LATEST_DOCKER_VERSION = "dev-v2-07-18-22";
|
||||
|
||||
export interface ISwitchboardTestEnvironment {
|
||||
programId: PublicKey;
|
||||
programDataAddress: PublicKey;
|
||||
idlAddress: PublicKey;
|
||||
programState: PublicKey;
|
||||
switchboardVault: PublicKey;
|
||||
switchboardMint: PublicKey;
|
||||
tokenWallet: PublicKey;
|
||||
queue: PublicKey;
|
||||
queueAuthority: PublicKey;
|
||||
queueBuffer: PublicKey;
|
||||
crank: PublicKey;
|
||||
crankBuffer: PublicKey;
|
||||
oracle: PublicKey;
|
||||
oracleAuthority: PublicKey;
|
||||
oracleEscrow: PublicKey;
|
||||
oraclePermissions: PublicKey;
|
||||
payerKeypairPath: string;
|
||||
|
||||
// allow a map of public keys to include in clone script
|
||||
additionalClonedAccounts?: Record<string, PublicKey>;
|
||||
}
|
||||
|
||||
/** Contains all of the necessary devnet Switchboard accounts to clone to localnet */
|
||||
export class SwitchboardTestEnvironment implements ISwitchboardTestEnvironment {
|
||||
programId: PublicKey;
|
||||
|
||||
programDataAddress: PublicKey;
|
||||
|
||||
idlAddress: PublicKey;
|
||||
|
||||
programState: PublicKey;
|
||||
|
||||
switchboardVault: PublicKey;
|
||||
|
||||
switchboardMint: PublicKey;
|
||||
|
||||
tokenWallet: PublicKey;
|
||||
|
||||
queue: PublicKey;
|
||||
|
||||
queueAuthority: PublicKey;
|
||||
|
||||
queueBuffer: PublicKey;
|
||||
|
||||
crank: PublicKey;
|
||||
|
||||
crankBuffer: PublicKey;
|
||||
|
||||
oracle: PublicKey;
|
||||
|
||||
oracleAuthority: PublicKey;
|
||||
|
||||
oracleEscrow: PublicKey;
|
||||
|
||||
oraclePermissions: PublicKey;
|
||||
|
||||
payerKeypairPath: string;
|
||||
|
||||
additionalClonedAccounts: Record<string, PublicKey>;
|
||||
|
||||
constructor(ctx: ISwitchboardTestEnvironment) {
|
||||
this.programId = ctx.programId;
|
||||
this.programDataAddress = ctx.programDataAddress;
|
||||
this.idlAddress = ctx.idlAddress;
|
||||
this.programState = ctx.programState;
|
||||
this.switchboardVault = ctx.switchboardVault;
|
||||
this.switchboardMint = ctx.switchboardMint;
|
||||
this.tokenWallet = ctx.tokenWallet;
|
||||
this.queue = ctx.queue;
|
||||
this.queueAuthority = ctx.queueAuthority;
|
||||
this.queueBuffer = ctx.queueBuffer;
|
||||
this.crank = ctx.crank;
|
||||
this.crankBuffer = ctx.crankBuffer;
|
||||
this.oracle = ctx.oracle;
|
||||
this.oracleAuthority = ctx.oracleAuthority;
|
||||
this.oracleEscrow = ctx.oracleEscrow;
|
||||
this.oraclePermissions = ctx.oraclePermissions;
|
||||
this.payerKeypairPath = ctx.payerKeypairPath;
|
||||
this.additionalClonedAccounts = ctx.additionalClonedAccounts ?? {};
|
||||
}
|
||||
|
||||
private getAccountCloneString(): string {
|
||||
const accounts = Object.keys(this).map((key) => {
|
||||
// iterate over additionalClonedAccounts and collect pubkeys
|
||||
if (typeof this[key] === "string") {
|
||||
return;
|
||||
}
|
||||
if (key === "additionalClonedAccounts" && this[key]) {
|
||||
const additionalPubkeys = Object.values(this.additionalClonedAccounts);
|
||||
const cloneStrings = additionalPubkeys.map(
|
||||
(pubkey) => `--clone ${pubkey.toBase58()} \`# ${key}\``
|
||||
);
|
||||
return cloneStrings.join(`\\\n`);
|
||||
}
|
||||
|
||||
return `--clone ${(this[key] as PublicKey).toBase58()} \`# ${key}\` `;
|
||||
});
|
||||
|
||||
return accounts.filter(Boolean).join(`\\\n`);
|
||||
}
|
||||
|
||||
public toJSON(): ISwitchboardTestEnvironment {
|
||||
return {
|
||||
programId: this.programId,
|
||||
programDataAddress: this.programDataAddress,
|
||||
idlAddress: this.idlAddress,
|
||||
programState: this.programState,
|
||||
switchboardVault: this.switchboardVault,
|
||||
switchboardMint: this.switchboardMint,
|
||||
tokenWallet: this.tokenWallet,
|
||||
queue: this.queue,
|
||||
queueAuthority: this.queueAuthority,
|
||||
queueBuffer: this.queueBuffer,
|
||||
crank: this.crank,
|
||||
crankBuffer: this.crankBuffer,
|
||||
oracle: this.oracle,
|
||||
oracleAuthority: this.oracleAuthority,
|
||||
oracleEscrow: this.oracleEscrow,
|
||||
oraclePermissions: this.oraclePermissions,
|
||||
payerKeypairPath: this.payerKeypairPath,
|
||||
additionalClonedAccounts: this.additionalClonedAccounts,
|
||||
};
|
||||
}
|
||||
|
||||
/** Write switchboard test environment to filesystem */
|
||||
public writeAll(outputDir: string): void {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
this.writeEnv(outputDir);
|
||||
this.writeJSON(outputDir);
|
||||
this.writeScripts(outputDir);
|
||||
this.writeDockerCompose(outputDir);
|
||||
this.writeAnchorToml(outputDir);
|
||||
}
|
||||
|
||||
/** Write the env file to filesystem */
|
||||
public writeEnv(filePath: string): void {
|
||||
const ENV_FILE_PATH = path.join(filePath, "switchboard.env");
|
||||
let fileStr = "";
|
||||
fileStr += `SWITCHBOARD_PROGRAM_ID="${this.programId.toBase58()}"\n`;
|
||||
fileStr += `SWITCHBOARD_PROGRAM_DATA_ADDRESS="${this.programDataAddress.toBase58()}"\n`;
|
||||
fileStr += `SWITCHBOARD_IDL_ADDRESS="${this.idlAddress.toBase58()}"\n`;
|
||||
fileStr += `SWITCHBOARD_PROGRAM_STATE="${this.programState.toBase58()}"\n`;
|
||||
fileStr += `SWITCHBOARD_VAULT="${this.switchboardVault.toBase58()}"\n`;
|
||||
fileStr += `SWITCHBOARD_MINT="${this.switchboardMint.toBase58()}"\n`;
|
||||
fileStr += `TOKEN_WALLET="${this.tokenWallet.toBase58()}"\n`;
|
||||
fileStr += `ORACLE_QUEUE="${this.queue.toBase58()}"\n`;
|
||||
fileStr += `ORACLE_QUEUE_AUTHORITY="${this.queueAuthority.toBase58()}"\n`;
|
||||
fileStr += `ORACLE_QUEUE_BUFFER="${this.queueBuffer.toBase58()}"\n`;
|
||||
fileStr += `CRANK="${this.crank.toBase58()}"\n`;
|
||||
fileStr += `CRANK_BUFFER="${this.crankBuffer.toBase58()}"\n`;
|
||||
fileStr += `ORACLE="${this.oracle.toBase58()}"\n`;
|
||||
fileStr += `ORACLE_AUTHORITY="${this.oracleAuthority.toBase58()}"\n`;
|
||||
fileStr += `ORACLE_ESCROW="${this.oracleEscrow.toBase58()}"\n`;
|
||||
fileStr += `ORACLE_PERMISSIONS="${this.oraclePermissions.toBase58()}"\n`;
|
||||
// fileStr += `SWITCHBOARD_ACCOUNTS="${this.getAccountCloneString()}"\n`;
|
||||
// TODO: Write additionalClonedAccounts to env file
|
||||
fs.writeFileSync(ENV_FILE_PATH, fileStr);
|
||||
console.log(
|
||||
`${chalk.green("Env File saved to:")} ${ENV_FILE_PATH.replace(
|
||||
process.cwd(),
|
||||
"."
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
public writeJSON(outputDir: string): void {
|
||||
const JSON_FILE_PATH = path.join(outputDir, "switchboard.json");
|
||||
fs.writeFileSync(
|
||||
JSON_FILE_PATH,
|
||||
JSON.stringify(
|
||||
this.toJSON(),
|
||||
(key, value) => {
|
||||
if (value instanceof PublicKey) {
|
||||
return value.toBase58();
|
||||
}
|
||||
return value;
|
||||
},
|
||||
2
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public writeScripts(outputDir: string): void {
|
||||
const LOCAL_VALIDATOR_SCRIPT = path.join(
|
||||
outputDir,
|
||||
"start-local-validator.sh"
|
||||
);
|
||||
// create bash script to startup local validator with appropriate accounts cloned
|
||||
const baseValidatorCommand = `solana-test-validator -r --ledger .anchor/test-ledger --mint ${this.oracleAuthority.toBase58()} --bind-address 0.0.0.0 --url ${clusterApiUrl(
|
||||
"devnet"
|
||||
)} --rpc-port 8899 `;
|
||||
const cloneAccountsString = this.getAccountCloneString();
|
||||
const startValidatorCommand = `${baseValidatorCommand} ${cloneAccountsString}`;
|
||||
fs.writeFileSync(
|
||||
LOCAL_VALIDATOR_SCRIPT,
|
||||
|
||||
`#!/bin/bash\n\nmkdir -p .anchor/test-ledger\n\n${startValidatorCommand}`
|
||||
);
|
||||
fs.chmodSync(LOCAL_VALIDATOR_SCRIPT, "755");
|
||||
console.log(
|
||||
`${chalk.green("Bash script saved to:")} ${LOCAL_VALIDATOR_SCRIPT.replace(
|
||||
process.cwd(),
|
||||
"."
|
||||
)}`
|
||||
);
|
||||
|
||||
// create bash script to start local oracle
|
||||
const ORACLE_SCRIPT = path.join(outputDir, "start-oracle.sh");
|
||||
// const startOracleCommand = `docker-compose -f docker-compose.switchboard.yml up`;
|
||||
fs.writeFileSync(
|
||||
ORACLE_SCRIPT,
|
||||
`#!/usr/bin/env bash
|
||||
|
||||
script_dir=$( cd -- "$( dirname -- "\${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
docker-compose -f "$script_dir"/docker-compose.switchboard.yml up
|
||||
`
|
||||
// `#!/bin/bash\n\n${startOracleCommand}`
|
||||
);
|
||||
fs.chmodSync(ORACLE_SCRIPT, "755");
|
||||
console.log(
|
||||
`${chalk.green("Bash script saved to:")} ${ORACLE_SCRIPT.replace(
|
||||
process.cwd(),
|
||||
"."
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
public writeDockerCompose(outputDir: string): void {
|
||||
const DOCKER_COMPOSE_FILEPATH = path.join(
|
||||
outputDir,
|
||||
"docker-compose.switchboard.yml"
|
||||
);
|
||||
const dockerComposeString = `version: "3.3"
|
||||
services:
|
||||
oracle:
|
||||
image: "switchboardlabs/node:\${SBV2_ORACLE_VERSION:-${LATEST_DOCKER_VERSION}}" # https://hub.docker.com/r/switchboardlabs/node/tags
|
||||
network_mode: host
|
||||
restart: always
|
||||
secrets:
|
||||
- PAYER_SECRETS
|
||||
environment:
|
||||
- VERBOSE=1
|
||||
- LIVE=1
|
||||
- CLUSTER=\${CLUSTER:-localnet}
|
||||
- HEARTBEAT_INTERVAL=30 # Seconds
|
||||
- ORACLE_KEY=${this.oracle.toBase58()}
|
||||
# - RPC_URL=\${RPC_URL}
|
||||
secrets:
|
||||
PAYER_SECRETS:
|
||||
file: ${this.payerKeypairPath}
|
||||
`;
|
||||
fs.writeFileSync(DOCKER_COMPOSE_FILEPATH, dockerComposeString);
|
||||
console.log(
|
||||
`${chalk.green(
|
||||
"Docker-Compose saved to:"
|
||||
)} ${DOCKER_COMPOSE_FILEPATH.replace(process.cwd(), ".")}`
|
||||
);
|
||||
}
|
||||
|
||||
public writeAnchorToml(outputDir: string): void {
|
||||
const ANCHOR_TOML_FILEPATH = path.join(
|
||||
outputDir,
|
||||
"Anchor.switchboard.toml"
|
||||
);
|
||||
const anchorTomlString = `[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "${this.payerKeypairPath}"
|
||||
|
||||
[test]
|
||||
startup_wait = 10000
|
||||
|
||||
[test.validator]
|
||||
url = "https://api.devnet.solana.com"
|
||||
|
||||
[[test.validator.clone]] # programID
|
||||
address = "${this.programId}"
|
||||
|
||||
[[test.validator.clone]] # idlAddress
|
||||
address = "${this.idlAddress}"
|
||||
|
||||
[[test.validator.clone]] # programState
|
||||
address = "${this.programState}"
|
||||
|
||||
[[test.validator.clone]] # switchboardVault
|
||||
address = "${this.switchboardVault}"
|
||||
|
||||
[[test.validator.clone]] # tokenWallet
|
||||
address = "${this.tokenWallet}"
|
||||
|
||||
[[test.validator.clone]] # queue
|
||||
address = "${this.queue}"
|
||||
|
||||
[[test.validator.clone]] # queueAuthority
|
||||
address = "${this.queueAuthority}"
|
||||
|
||||
[[test.validator.clone]] # queueBuffer
|
||||
address = "${this.queueBuffer}"
|
||||
|
||||
[[test.validator.clone]] # crank
|
||||
address = "${this.crank}"
|
||||
|
||||
[[test.validator.clone]] # crankBuffer
|
||||
address = "${this.crankBuffer}"
|
||||
|
||||
[[test.validator.clone]] # oracle
|
||||
address = "${this.oracle}"
|
||||
|
||||
[[test.validator.clone]] # oracleAuthority
|
||||
address = "${this.oracleAuthority}"
|
||||
|
||||
[[test.validator.clone]] # oracleEscrow
|
||||
address = "${this.oracleEscrow}"
|
||||
|
||||
[[test.validator.clone]] # oraclePermissions
|
||||
address = "${this.oraclePermissions}"
|
||||
`;
|
||||
|
||||
fs.writeFileSync(ANCHOR_TOML_FILEPATH, anchorTomlString);
|
||||
console.log(
|
||||
`${chalk.green("Anchor.toml saved to:")} ${ANCHOR_TOML_FILEPATH.replace(
|
||||
process.cwd(),
|
||||
"."
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
/** Build a devnet environment to later clone to localnet */
|
||||
static async create(
|
||||
payerKeypairPath: string,
|
||||
additionalClonedAccounts?: Record<string, PublicKey>,
|
||||
alternateProgramId?: PublicKey
|
||||
): Promise<SwitchboardTestEnvironment> {
|
||||
const fullKeypairPath =
|
||||
payerKeypairPath.charAt(0) === "/"
|
||||
? payerKeypairPath
|
||||
: path.join(process.cwd(), payerKeypairPath);
|
||||
if (!fs.existsSync(fullKeypairPath)) {
|
||||
throw new Error("Failed to find payer keypair path");
|
||||
}
|
||||
const payerKeypair = Keypair.fromSecretKey(
|
||||
new Uint8Array(
|
||||
JSON.parse(
|
||||
fs.readFileSync(fullKeypairPath, {
|
||||
encoding: "utf-8",
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
const connection = new Connection(clusterApiUrl("devnet"), {
|
||||
commitment: "confirmed",
|
||||
});
|
||||
|
||||
const programId = alternateProgramId ?? sbv2.getSwitchboardPid("devnet");
|
||||
const wallet = new sbv2.AnchorWallet(payerKeypair);
|
||||
const provider = new anchor.AnchorProvider(connection, wallet, {});
|
||||
|
||||
const anchorIdl = await anchor.Program.fetchIdl(programId, provider);
|
||||
if (!anchorIdl) {
|
||||
throw new Error(`failed to read idl for ${programId}`);
|
||||
}
|
||||
|
||||
const switchboardProgram = new anchor.Program(
|
||||
anchorIdl,
|
||||
programId,
|
||||
provider
|
||||
);
|
||||
|
||||
const programDataAddress = getProgramDataAddress(
|
||||
switchboardProgram.programId
|
||||
);
|
||||
const idlAddress = await getIdlAddress(switchboardProgram.programId);
|
||||
|
||||
const queueResponse = await createQueue(
|
||||
switchboardProgram,
|
||||
{
|
||||
authority: payerKeypair.publicKey,
|
||||
name: "Test Queue",
|
||||
metadata: `created ${anchorBNtoDateString(
|
||||
new anchor.BN(Math.floor(Date.now() / 1000))
|
||||
)}`,
|
||||
minStake: new anchor.BN(0),
|
||||
reward: new anchor.BN(0),
|
||||
crankSize: 10,
|
||||
oracleTimeout: 180,
|
||||
numOracles: 1,
|
||||
unpermissionedFeeds: true,
|
||||
unpermissionedVrf: true,
|
||||
enableBufferRelayers: true,
|
||||
},
|
||||
10
|
||||
);
|
||||
|
||||
const queueAccount = queueResponse.queueAccount;
|
||||
const queue = await queueAccount.loadData();
|
||||
|
||||
const [programStateAccount, stateBump] =
|
||||
ProgramStateAccount.fromSeed(switchboardProgram);
|
||||
const programState = await programStateAccount.loadData();
|
||||
|
||||
const mint = await queueAccount.loadMint();
|
||||
|
||||
const payerSwitchboardWallet = await getOrCreateSwitchboardTokenAccount(
|
||||
switchboardProgram,
|
||||
mint
|
||||
);
|
||||
|
||||
const crankAccount = new CrankAccount({
|
||||
program: switchboardProgram,
|
||||
publicKey: queueResponse.crankPubkey,
|
||||
});
|
||||
const crank = await crankAccount.loadData();
|
||||
|
||||
const oracleAccount = new OracleAccount({
|
||||
program: switchboardProgram,
|
||||
publicKey: queueResponse.oracles[0],
|
||||
});
|
||||
const oracle = await oracleAccount.loadData();
|
||||
|
||||
const [permissionAccount] = PermissionAccount.fromSeed(
|
||||
switchboardProgram,
|
||||
queue.authority,
|
||||
queueAccount.publicKey,
|
||||
oracleAccount.publicKey
|
||||
);
|
||||
const permission = await permissionAccount.loadData();
|
||||
|
||||
const ctx: ISwitchboardTestEnvironment = {
|
||||
programId: switchboardProgram.programId,
|
||||
programDataAddress,
|
||||
idlAddress,
|
||||
programState: programStateAccount.publicKey,
|
||||
switchboardVault: programState.tokenVault,
|
||||
switchboardMint: mint.address,
|
||||
tokenWallet: payerSwitchboardWallet,
|
||||
queue: queueResponse.queueAccount.publicKey,
|
||||
queueAuthority: queue.authority,
|
||||
queueBuffer: queue.dataBuffer,
|
||||
crank: crankAccount.publicKey,
|
||||
crankBuffer: crank.dataBuffer,
|
||||
oracle: oracleAccount.publicKey,
|
||||
oracleAuthority: oracle.oracleAuthority,
|
||||
oracleEscrow: oracle.tokenAccount,
|
||||
oraclePermissions: permissionAccount.publicKey,
|
||||
payerKeypairPath: fullKeypairPath,
|
||||
additionalClonedAccounts,
|
||||
};
|
||||
|
||||
return new SwitchboardTestEnvironment(ctx);
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
export * from "./context.js";
|
||||
export * from "./env.js";
|
|
@ -1,112 +0,0 @@
|
|||
import type * as anchor from "@coral-xyz/anchor";
|
||||
import * as spl from "@solana/spl-token-v3";
|
||||
import {
|
||||
Connection,
|
||||
Keypair,
|
||||
PublicKey,
|
||||
sendAndConfirmTransaction,
|
||||
SystemProgram,
|
||||
Transaction,
|
||||
} from "@solana/web3.js";
|
||||
import {
|
||||
ProgramStateAccount,
|
||||
programWallet,
|
||||
} from "@switchboard-xyz/switchboard-v2";
|
||||
|
||||
export const getOrCreateSwitchboardTokenAccount = async (
|
||||
program: anchor.Program,
|
||||
switchboardMint?: spl.Mint,
|
||||
payer = programWallet(program)
|
||||
): Promise<PublicKey> => {
|
||||
const getAssociatedAddress = async (mint: spl.Mint): Promise<PublicKey> => {
|
||||
const tokenAccount = await spl.getOrCreateAssociatedTokenAccount(
|
||||
program.provider.connection,
|
||||
payer,
|
||||
mint.address,
|
||||
payer.publicKey,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
spl.TOKEN_PROGRAM_ID,
|
||||
spl.ASSOCIATED_TOKEN_PROGRAM_ID
|
||||
);
|
||||
return tokenAccount.address;
|
||||
};
|
||||
|
||||
let mint = switchboardMint;
|
||||
if (mint) {
|
||||
return getAssociatedAddress(mint);
|
||||
}
|
||||
const [programState] = ProgramStateAccount.fromSeed(program);
|
||||
mint = await programState.getTokenMint();
|
||||
if (mint) {
|
||||
return getAssociatedAddress(mint);
|
||||
}
|
||||
|
||||
throw new Error(`failed to get associated token account`);
|
||||
};
|
||||
|
||||
export async function transferWrappedSol(
|
||||
connection: Connection,
|
||||
payerKeypair: Keypair,
|
||||
amount: number
|
||||
): Promise<number> {
|
||||
const payerBalance = await connection.getBalance(payerKeypair.publicKey);
|
||||
if (payerBalance < amount) {
|
||||
throw new Error(
|
||||
`TransferWrappedSolError: Payer has insufficient funds, need ${amount}, have ${payerBalance}`
|
||||
);
|
||||
}
|
||||
const payerAssociatedWallet = (
|
||||
await spl.getOrCreateAssociatedTokenAccount(
|
||||
connection,
|
||||
payerKeypair,
|
||||
spl.NATIVE_MINT,
|
||||
payerKeypair.publicKey
|
||||
)
|
||||
).address;
|
||||
|
||||
// create new account to temporarily hold wrapped funds
|
||||
const ephemeralAccount = Keypair.generate();
|
||||
const ephemeralWallet = await spl.getAssociatedTokenAddress(
|
||||
spl.NATIVE_MINT,
|
||||
ephemeralAccount.publicKey,
|
||||
false
|
||||
);
|
||||
|
||||
const tx = new Transaction().add(
|
||||
spl.createAssociatedTokenAccountInstruction(
|
||||
payerKeypair.publicKey,
|
||||
ephemeralWallet,
|
||||
ephemeralAccount.publicKey,
|
||||
spl.NATIVE_MINT
|
||||
),
|
||||
SystemProgram.transfer({
|
||||
fromPubkey: payerKeypair.publicKey,
|
||||
toPubkey: ephemeralWallet,
|
||||
lamports: amount,
|
||||
}),
|
||||
spl.createSyncNativeInstruction(ephemeralWallet),
|
||||
spl.createTransferInstruction(
|
||||
ephemeralWallet,
|
||||
payerAssociatedWallet,
|
||||
ephemeralAccount.publicKey,
|
||||
amount,
|
||||
[payerKeypair, ephemeralAccount]
|
||||
),
|
||||
spl.createCloseAccountInstruction(
|
||||
ephemeralWallet,
|
||||
payerKeypair.publicKey,
|
||||
ephemeralAccount.publicKey,
|
||||
[payerKeypair, ephemeralAccount]
|
||||
)
|
||||
);
|
||||
|
||||
const txn = await sendAndConfirmTransaction(connection, tx, [
|
||||
payerKeypair,
|
||||
ephemeralAccount,
|
||||
]);
|
||||
|
||||
const finalBalance = await spl.getAccount(connection, payerAssociatedWallet);
|
||||
return Number(finalBalance.amount);
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import {
|
||||
ConfirmOptions,
|
||||
Connection,
|
||||
Keypair,
|
||||
PublicKey,
|
||||
TransactionInstruction,
|
||||
TransactionSignature,
|
||||
} from "@solana/web3.js";
|
||||
import {
|
||||
packInstructions,
|
||||
signTransactions,
|
||||
} from "@switchboard-xyz/switchboard-v2";
|
||||
|
||||
export async function packAndSend(
|
||||
program: anchor.Program,
|
||||
ixnsBatches: (TransactionInstruction | TransactionInstruction[])[][],
|
||||
signers: Keypair[],
|
||||
feePayer: PublicKey
|
||||
): Promise<TransactionSignature[]> {
|
||||
const signatures: Promise<TransactionSignature>[] = [];
|
||||
|
||||
for await (const batch of ixnsBatches) {
|
||||
const { blockhash } =
|
||||
await program.provider.connection.getLatestBlockhash();
|
||||
|
||||
const packedTransactions = packInstructions(batch, feePayer, blockhash);
|
||||
const signedTransactions = signTransactions(packedTransactions, signers);
|
||||
const signedTxs = await (
|
||||
program.provider as anchor.AnchorProvider
|
||||
).wallet.signAllTransactions(signedTransactions);
|
||||
|
||||
for (let k = 0; k < packedTransactions.length; k += 1) {
|
||||
const tx = signedTxs[k]!;
|
||||
const rawTx = tx.serialize();
|
||||
signatures.push(
|
||||
sendAndConfirmRawTransaction(program.provider.connection, rawTx, {
|
||||
maxRetries: 10,
|
||||
commitment: "processed",
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
throw error;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(signatures);
|
||||
}
|
||||
|
||||
return Promise.all(signatures);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send and confirm a raw transaction
|
||||
*
|
||||
* If `commitment` option is not specified, defaults to 'max' commitment.
|
||||
*/
|
||||
export async function sendAndConfirmRawTransaction(
|
||||
connection: Connection,
|
||||
rawTransaction: Buffer,
|
||||
options: ConfirmOptions
|
||||
): Promise<TransactionSignature> {
|
||||
const sendOptions = options && {
|
||||
skipPreflight: options.skipPreflight,
|
||||
preflightCommitment: options.preflightCommitment || options.commitment,
|
||||
};
|
||||
const signature: TransactionSignature = await connection.sendRawTransaction(
|
||||
rawTransaction,
|
||||
sendOptions
|
||||
);
|
||||
const status = (
|
||||
await connection.confirmTransaction(
|
||||
signature as any,
|
||||
options.commitment || "max"
|
||||
)
|
||||
).value;
|
||||
|
||||
if (status.err) {
|
||||
throw new Error(
|
||||
`Raw transaction ${signature} failed (${JSON.stringify(status)})`
|
||||
);
|
||||
}
|
||||
|
||||
return signature;
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { VrfAccount } from "@switchboard-xyz/switchboard-v2";
|
||||
import { promiseWithTimeout } from "./async.js";
|
||||
|
||||
export async function awaitOpenRound(
|
||||
vrfAccount: VrfAccount,
|
||||
counter: anchor.BN,
|
||||
timeout = 30
|
||||
): Promise<Buffer> {
|
||||
// call open round and wait for new value
|
||||
const accountsCoder = new anchor.BorshAccountsCoder(vrfAccount.program.idl);
|
||||
|
||||
let accountWs: number;
|
||||
const awaitUpdatePromise = new Promise((resolve: (value: Buffer) => void) => {
|
||||
accountWs = vrfAccount.program.provider.connection.onAccountChange(
|
||||
vrfAccount?.publicKey ?? PublicKey.default,
|
||||
async (accountInfo) => {
|
||||
const vrf = accountsCoder.decode("VrfAccountData", accountInfo.data);
|
||||
if (!counter.eq(vrf.counter)) {
|
||||
return;
|
||||
}
|
||||
if (vrf.result.every((val) => val === 0)) {
|
||||
return;
|
||||
}
|
||||
resolve(vrf.result as Buffer);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const updatedValuePromise = promiseWithTimeout(
|
||||
timeout * 1000,
|
||||
awaitUpdatePromise,
|
||||
new Error(`vrf failed to update in ${timeout} seconds`)
|
||||
).finally(() => {
|
||||
if (accountWs) {
|
||||
vrfAccount.program.provider.connection.removeAccountChangeListener(
|
||||
accountWs
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const result = await updatedValuePromise;
|
||||
|
||||
if (!result) {
|
||||
throw new Error(`failed to update VRF`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
|
@ -1,185 +0,0 @@
|
|||
import * as anchor from "@project-serum/anchor";
|
||||
import {
|
||||
AnchorWallet,
|
||||
JobAccount,
|
||||
OracleJob,
|
||||
} from "@switchboard-xyz/switchboard-v2";
|
||||
// import fs from "fs";
|
||||
import "mocha";
|
||||
import {
|
||||
awaitOpenRound,
|
||||
createAggregator,
|
||||
sleep,
|
||||
SwitchboardTestContext,
|
||||
} from "../lib/cjs";
|
||||
|
||||
describe("Feed tests", () => {
|
||||
const payer = anchor.web3.Keypair.generate();
|
||||
|
||||
// const payer = anchor.web3.Keypair.fromSecretKey(
|
||||
// new Uint8Array(
|
||||
// JSON.parse(fs.readFileSync("../../../payer-keypair.json", "utf8"))
|
||||
// )
|
||||
// );
|
||||
|
||||
let switchboard: SwitchboardTestContext;
|
||||
|
||||
before(async () => {
|
||||
try {
|
||||
const localnetConnection = new anchor.web3.Connection(
|
||||
"http://localhost:8899"
|
||||
);
|
||||
const provider = new anchor.AnchorProvider(
|
||||
localnetConnection,
|
||||
new AnchorWallet(payer),
|
||||
{ commitment: "confirmed" }
|
||||
);
|
||||
switchboard = await SwitchboardTestContext.loadFromEnv(provider);
|
||||
console.log("local env detected");
|
||||
return;
|
||||
} catch (error: any) {}
|
||||
|
||||
try {
|
||||
const devnetConnection = new anchor.web3.Connection(
|
||||
"https://switchboard.devnet.rpcpool.com/f9fe774d81ba4527a418f5b19477"
|
||||
);
|
||||
const provider = new anchor.AnchorProvider(
|
||||
devnetConnection,
|
||||
new AnchorWallet(payer),
|
||||
{ commitment: "confirmed" }
|
||||
);
|
||||
// const airdropSignature = await devnetConnection.requestAirdrop(
|
||||
// payer.publicKey,
|
||||
// anchor.web3.LAMPORTS_PER_SOL
|
||||
// );
|
||||
// await devnetConnection.confirmTransaction(airdropSignature);
|
||||
switchboard = await SwitchboardTestContext.loadDevnetQueue(
|
||||
provider,
|
||||
"uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX"
|
||||
);
|
||||
console.log("devnet detected");
|
||||
return;
|
||||
} catch (error: any) {
|
||||
// console.log(`Error: SBV2 Devnet - ${error.message}`);
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (!switchboard) {
|
||||
// If fails, throw error
|
||||
throw new Error(
|
||||
`Failed to load the SwitchboardTestContext from devnet or from a switchboard.env file`
|
||||
);
|
||||
}
|
||||
|
||||
const airdropSignature =
|
||||
await switchboard.program.provider.connection.requestAirdrop(
|
||||
payer.publicKey,
|
||||
anchor.web3.LAMPORTS_PER_SOL
|
||||
);
|
||||
await switchboard.program.provider.connection.confirmTransaction(
|
||||
airdropSignature
|
||||
);
|
||||
});
|
||||
|
||||
it("Creates a switchboard feed", async () => {
|
||||
const job1 = await JobAccount.create(switchboard.program, {
|
||||
name: Buffer.from("Job1"),
|
||||
authority: payer.publicKey,
|
||||
data: Buffer.from(
|
||||
OracleJob.encodeDelimited(
|
||||
OracleJob.create({
|
||||
tasks: [
|
||||
OracleJob.Task.create({
|
||||
httpTask: OracleJob.HttpTask.create({
|
||||
url: `https://ftx.us/api/markets/SOL_USD`,
|
||||
}),
|
||||
}),
|
||||
OracleJob.Task.create({
|
||||
jsonParseTask: OracleJob.JsonParseTask.create({
|
||||
path: "$.result.price",
|
||||
}),
|
||||
}),
|
||||
],
|
||||
})
|
||||
).finish()
|
||||
),
|
||||
});
|
||||
|
||||
const job2 = await JobAccount.create(switchboard.program, {
|
||||
name: Buffer.from("Job1"),
|
||||
authority: payer.publicKey,
|
||||
data: Buffer.from(
|
||||
OracleJob.encodeDelimited(
|
||||
OracleJob.create({
|
||||
tasks: [
|
||||
OracleJob.Task.create({
|
||||
httpTask: OracleJob.HttpTask.create({
|
||||
url: "https://www.binance.com/api/v3/ticker/price?symbol=SOLUSDT",
|
||||
}),
|
||||
}),
|
||||
OracleJob.Task.create({
|
||||
jsonParseTask: OracleJob.JsonParseTask.create({
|
||||
path: "$.price",
|
||||
}),
|
||||
}),
|
||||
OracleJob.Task.create({
|
||||
multiplyTask: OracleJob.MultiplyTask.create({
|
||||
aggregatorPubkey:
|
||||
"ETAaeeuQBwsh9mC2gCov9WdhJENZuffRMXY2HgjCcSL9",
|
||||
}),
|
||||
}),
|
||||
],
|
||||
})
|
||||
).finish()
|
||||
),
|
||||
});
|
||||
|
||||
let retryCount = 10;
|
||||
while (retryCount) {
|
||||
try {
|
||||
await job1.loadData();
|
||||
await job2.loadData();
|
||||
break;
|
||||
} catch {
|
||||
await sleep(1000);
|
||||
--retryCount;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const newAggregator = await createAggregator(
|
||||
switchboard.program,
|
||||
switchboard.queue,
|
||||
{
|
||||
name: Buffer.from("Test Feed"),
|
||||
batchSize: 1,
|
||||
minRequiredOracleResults: 1,
|
||||
minRequiredJobResults: 1,
|
||||
minUpdateDelaySeconds: 10,
|
||||
queueAccount: switchboard.queue,
|
||||
},
|
||||
[
|
||||
[job1, 1],
|
||||
[job2, 1],
|
||||
],
|
||||
new anchor.BN(12500 * 5)
|
||||
);
|
||||
|
||||
console.log(`Created Aggregator: ${newAggregator.publicKey}`);
|
||||
|
||||
// call openRound
|
||||
const value = await awaitOpenRound(
|
||||
newAggregator,
|
||||
switchboard.queue,
|
||||
switchboard.payerTokenWallet,
|
||||
undefined,
|
||||
45
|
||||
);
|
||||
|
||||
console.log(`Aggregator Value: ${value.toString()}`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,34 +0,0 @@
|
|||
{
|
||||
"include": ["./src/**/*"],
|
||||
"exclude": ["tests/**/*", "lib", "node_modules"],
|
||||
"files": ["./src/index.ts"],
|
||||
"compilerOptions": {
|
||||
"target": "es2019",
|
||||
"moduleResolution": "node",
|
||||
"composite": true,
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["mocha", "node"],
|
||||
// strict
|
||||
"strict": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitAny": false,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitReturns": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"paths": {
|
||||
"@switchboard-xyz/switchboard-v2": ["../switchboard-v2"]
|
||||
}
|
||||
},
|
||||
"references": [{ "path": "../switchboard-v2" }]
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2019",
|
||||
"outDir": "lib/cjs/",
|
||||
"rootDir": "./src"
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "es2022",
|
||||
"target": "es2019",
|
||||
"outDir": "lib/esm/",
|
||||
"rootDir": "./src",
|
||||
"paths": {
|
||||
"@solana/spl-token": [
|
||||
"./node_modules/@solana/spl-token"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Build output
|
||||
dist
|
||||
javascript/*/docs
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
.yarn
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
# dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
#docs
|
||||
# docs
|
||||
# docs/**
|
|
@ -1 +0,0 @@
|
|||
*.tsbuildinfo
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Switchboard
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,82 +0,0 @@
|
|||
# Switchboard-v2 API module
|
||||
|
||||
<!--- https://badgen.net/npm/v/@switchboard-xyz/switchboardv2-cli --->
|
||||
|
||||
<!--- [](https://github.com/switchboard-xyz/switchboardv2-cli/commit/) --->
|
||||
|
||||
[](https://github.com/switchboard-xyz/sbv2-solana/tree/main/libraries/ts)
|
||||
[](https://www.npmjs.com/package/@switchboard-xyz/switchboard-v2)
|
||||
[](https://twitter.com/switchboardxyz)
|
||||
|
||||
A library of utility functions to interact with the Switchboardv2 program
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
npm i @switchboard-xyz/switchboard-v2
|
||||
```
|
||||
|
||||
## Creating Feeds
|
||||
|
||||
```ts
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import { clusterApiUrl, Connection, Keypair, PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
AggregatorAccount,
|
||||
OracleQueueAccount,
|
||||
loadSwitchboardProgram,
|
||||
} from "@switchboard-xyz/switchboard-v2";
|
||||
|
||||
const payerKeypair = Keypair.fromSecretKey(
|
||||
JSON.parse(fs.readFileSync("../keypair-path.json", "utf-8"))
|
||||
);
|
||||
const program = await loadSwitchboardProgram(
|
||||
"devnet",
|
||||
new Connection(clusterApiUrl("devnet")),
|
||||
payerKeypair
|
||||
);
|
||||
const queueAccount = new OracleQueueAccount({
|
||||
program: program,
|
||||
// devnet permissionless queue
|
||||
publicKey: new PublicKey("uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX"),
|
||||
});
|
||||
|
||||
const aggregatorAccount = await AggregatorAccount.create(program, {
|
||||
name: Buffer.from("FeedName"),
|
||||
batchSize: 6,
|
||||
minRequiredJobResults: 1,
|
||||
minRequiredOracleResults: 1,
|
||||
minUpdateDelaySeconds: 30,
|
||||
queueAccount,
|
||||
});
|
||||
```
|
||||
|
||||
### Updating Feeds
|
||||
|
||||
```ts
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import {
|
||||
AggregatorAccount,
|
||||
OracleQueueAccount,
|
||||
} from "@switchboard-xyz/switchboard-v2";
|
||||
|
||||
const program: anchor.Program;
|
||||
const queueAccount: OracleQueueAccount;
|
||||
|
||||
await aggregatorAccount.openRound({
|
||||
oracleQueueAccount: queueAccount,
|
||||
payoutWallet: tokenAccount,
|
||||
});
|
||||
```
|
||||
|
||||
### Reading Feeds
|
||||
|
||||
```ts
|
||||
import { AggregatorAccount } from "@switchboard-xyz/switchboard-v2";
|
||||
import { Big } from "big.js";
|
||||
|
||||
const aggregatorAccount: AggregatorAccount;
|
||||
const result: Big = await aggregatorAccount.getLatestValue();
|
||||
|
||||
console.log(result.toNumber());
|
||||
```
|
File diff suppressed because it is too large
Load Diff
|
@ -1,65 +0,0 @@
|
|||
{
|
||||
"name": "@switchboard-xyz/switchboard-v2",
|
||||
"version": "0.0.174",
|
||||
"license": "MIT",
|
||||
"author": "mitch@switchboard.xyz",
|
||||
"description": "API wrapper for intergating with the Switchboardv2 program",
|
||||
"keywords": [
|
||||
"oracle",
|
||||
"solana",
|
||||
"Defi"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/switchboard-xyz/sbv2-solana",
|
||||
"directory": "javascript/switchboard-v2"
|
||||
},
|
||||
"homepage": "https://docs.switchboard.xyz",
|
||||
"files": [
|
||||
"lib",
|
||||
"src",
|
||||
"package.json"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./lib/esm/index.js",
|
||||
"require": "./lib/cjs/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "lib/cjs/index.js",
|
||||
"module": "lib/esm/index.js",
|
||||
"types": "lib/cjs/index.d.ts",
|
||||
"scripts": {
|
||||
"docgen": "typedoc --entryPoints src/index.ts --out ../../website/static/api/ts",
|
||||
"build:cjs": "tsc -p tsconfig.cjs.json",
|
||||
"build:esm": "tsc",
|
||||
"build": "yarn build:cjs && yarn build:esm",
|
||||
"watch": "tsc -p tsconfig.cjs.json --watch",
|
||||
"test": "ts-mocha -p ./tsconfig.cjs.json --require ts-node/register -t 1000000 ./tests/*.tests.ts",
|
||||
"lint": "eslint --fix-dry-run --ext .ts src/**/*.ts",
|
||||
"prepublishOnly": "shx rm -rf lib && yarn build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@solana/spl-governance": "^0.0.34"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coral-xyz/anchor": "^0.26.0",
|
||||
"@solana/spl-governance": "^0.0.34",
|
||||
"@solana/spl-token-v3": "npm:@solana/spl-token@0.3.6",
|
||||
"@solana/web3.js": "^1.66.2",
|
||||
"@switchboard-xyz/common": "^2.1.7",
|
||||
"big.js": "^6.2.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mocha": "^9.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/big.js": "^6.1.6",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/node": "^17.0.45",
|
||||
"shx": "^0.3.4",
|
||||
"ts-mocha": "^9.0.2",
|
||||
"typescript": "^4.7"
|
||||
},
|
||||
"gitHead": "9ee13d31a3577767061bd90f57fe4629b9a89e1a"
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export * from "./sbv2.js";
|
File diff suppressed because it is too large
Load Diff
|
@ -1,54 +0,0 @@
|
|||
import * as anchor from "@project-serum/anchor";
|
||||
import { strict as assert } from "assert";
|
||||
import Big from "big.js";
|
||||
import "mocha";
|
||||
import * as sbv2 from "../src";
|
||||
|
||||
describe("Decimal tests", () => {
|
||||
it("Converts a SwitchboardDecimal to a Big", async () => {
|
||||
let sbd = new sbv2.SwitchboardDecimal(new anchor.BN(8675309), 3);
|
||||
assert(sbd.toBig().toNumber() === 8675.309);
|
||||
|
||||
sbd = new sbv2.SwitchboardDecimal(new anchor.BN(-5000), 3);
|
||||
assert(sbd.toBig().toNumber() === -5);
|
||||
|
||||
sbd = new sbv2.SwitchboardDecimal(new anchor.BN(0), 0);
|
||||
assert(sbd.toBig().toNumber() === 0);
|
||||
});
|
||||
|
||||
it("Converts a Big to a SwitchboardDecimal", async () => {
|
||||
let b = Big(100.25);
|
||||
let sbd = sbv2.SwitchboardDecimal.fromBig(b);
|
||||
assert(sbd.mantissa.eq(new anchor.BN(10025, 10)));
|
||||
assert(sbd.scale === 2);
|
||||
|
||||
b = Big(10.025);
|
||||
sbd = sbv2.SwitchboardDecimal.fromBig(b);
|
||||
assert(sbd.mantissa.eq(new anchor.BN(10025, 10)));
|
||||
assert(sbd.scale === 3);
|
||||
|
||||
b = Big(0.10025);
|
||||
sbd = sbv2.SwitchboardDecimal.fromBig(b);
|
||||
assert(sbd.mantissa.eq(new anchor.BN(10025, 10)));
|
||||
assert(sbd.scale === 5);
|
||||
|
||||
b = Big(0);
|
||||
sbd = sbv2.SwitchboardDecimal.fromBig(b);
|
||||
assert(sbd.mantissa.eq(new anchor.BN(0, 10)));
|
||||
assert(sbd.scale === 0);
|
||||
|
||||
b = Big(-270.4);
|
||||
sbd = sbv2.SwitchboardDecimal.fromBig(b);
|
||||
assert(sbd.mantissa.eq(new anchor.BN(-2704, 10)));
|
||||
assert(sbd.scale === 1);
|
||||
});
|
||||
|
||||
it("Converts a SwitchboardDecimal back and forth", async () => {
|
||||
const big = new Big(4.847);
|
||||
let sbd = sbv2.SwitchboardDecimal.fromBig(big);
|
||||
assert(sbd.toBig().toNumber() === 4.847);
|
||||
|
||||
sbd = sbv2.SwitchboardDecimal.fromBig(sbd.toBig());
|
||||
assert(sbd.toBig().toNumber() === 4.847);
|
||||
});
|
||||
});
|
|
@ -1,18 +0,0 @@
|
|||
import { Keypair } from "@solana/web3.js";
|
||||
import { assert } from "console";
|
||||
import "mocha";
|
||||
import { loadSwitchboardProgram, programWallet } from "../src";
|
||||
|
||||
describe("Wallet tests", () => {
|
||||
it("Get program wallet", async () => {
|
||||
const defaultKeypair = Keypair.fromSeed(new Uint8Array(32).fill(1));
|
||||
const keypair = Keypair.generate();
|
||||
const program = await loadSwitchboardProgram("devnet", undefined, keypair);
|
||||
|
||||
const getKeypair = programWallet(program);
|
||||
assert(
|
||||
keypair.publicKey.equals(getKeypair.publicKey),
|
||||
"Program Wallet does not match generated keypair"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"include": ["./src/**/*"],
|
||||
"exclude": ["tests/**/*", "lib", "node_modules"],
|
||||
"files": ["src/index.ts"],
|
||||
"compilerOptions": {
|
||||
"target": "es2019",
|
||||
"moduleResolution": "node",
|
||||
"composite": true,
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["mocha", "node"],
|
||||
"allowJs": true,
|
||||
"checkJs": false,
|
||||
// strict
|
||||
"strict": false,
|
||||
"noImplicitAny": false
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2019",
|
||||
"outDir": "lib/cjs/",
|
||||
"rootDir": "./src"
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "es2022",
|
||||
"target": "es2019",
|
||||
"outDir": "lib/esm/",
|
||||
"rootDir": "./src"
|
||||
}
|
||||
}
|
|
@ -4,9 +4,9 @@ members = [
|
|||
]
|
||||
|
||||
[programs.localnet]
|
||||
anchor_feed_parser = "Fstf3oTcBxHMZFaoBzxk5oSkTh5HaAjxjh6zcgdZpNBb"
|
||||
anchor_feed_parser = "EE4NqG3B1N54tTbJ8VTtebdE88j4qjfVsbDXUUVFHegk"
|
||||
[programs.devnet]
|
||||
anchor_feed_parser = "Fstf3oTcBxHMZFaoBzxk5oSkTh5HaAjxjh6zcgdZpNBb"
|
||||
anchor_feed_parser = "EE4NqG3B1N54tTbJ8VTtebdE88j4qjfVsbDXUUVFHegk"
|
||||
|
||||
[provider]
|
||||
# cluster = "devnet"
|
||||
|
|
|
@ -27,8 +27,8 @@ Get your program ID and update `Anchor.toml` and `src/lib.rs` with your pubkey
|
|||
|
||||
```bash
|
||||
export ANCHOR_FEED_PARSER_PUBKEY=$(solana-keygen pubkey target/deploy/anchor_feed_parser-keypair.json)
|
||||
sed -i '' s/Fstf3oTcBxHMZFaoBzxk5oSkTh5HaAjxjh6zcgdZpNBb/"$ANCHOR_FEED_PARSER_PUBKEY"/g Anchor.toml
|
||||
sed -i '' s/Fstf3oTcBxHMZFaoBzxk5oSkTh5HaAjxjh6zcgdZpNBb/"$ANCHOR_FEED_PARSER_PUBKEY"/g src/lib.rs
|
||||
sed -i '' s/EE4NqG3B1N54tTbJ8VTtebdE88j4qjfVsbDXUUVFHegk/"$ANCHOR_FEED_PARSER_PUBKEY"/g Anchor.toml
|
||||
sed -i '' s/EE4NqG3B1N54tTbJ8VTtebdE88j4qjfVsbDXUUVFHegk/"$ANCHOR_FEED_PARSER_PUBKEY"/g src/lib.rs
|
||||
```
|
||||
|
||||
Then run Anchor test
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"directory": "programs/anchor-feed-parser"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node ../build.js anchor-feed-parser Fstf3oTcBxHMZFaoBzxk5oSkTh5HaAjxjh6zcgdZpNBb",
|
||||
"build": "node ../build.js anchor-feed-parser EE4NqG3B1N54tTbJ8VTtebdE88j4qjfVsbDXUUVFHegk",
|
||||
"lint": "eslint --ext .js,.json,.ts 'src/**' --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
@ -3,7 +3,7 @@ use anchor_lang::solana_program::clock;
|
|||
use std::convert::TryInto;
|
||||
pub use switchboard_v2::{AggregatorAccountData, SwitchboardDecimal, SWITCHBOARD_PROGRAM_ID};
|
||||
|
||||
declare_id!("Fstf3oTcBxHMZFaoBzxk5oSkTh5HaAjxjh6zcgdZpNBb");
|
||||
declare_id!("EE4NqG3B1N54tTbJ8VTtebdE88j4qjfVsbDXUUVFHegk");
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(params: ReadResultParams)]
|
||||
|
|
Loading…
Reference in New Issue