fix: removed oracle import from solana.js for browser compatibility
This commit is contained in:
parent
dd7ab4bce7
commit
b9f796a51d
|
@ -7,7 +7,7 @@
|
|||
"editor.wordWrap": "on",
|
||||
"[typescript]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true
|
||||
"source.organizeImports": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@switchboard-xyz/solana.js",
|
||||
"version": "2.1.1",
|
||||
"version": "2.1.2",
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"description": "API wrapper for integrating with the Switchboard V2 program on Solana",
|
||||
|
@ -46,7 +46,6 @@
|
|||
"@solana/spl-token": "^0.3.6",
|
||||
"@solana/web3.js": "^1.73.0",
|
||||
"@switchboard-xyz/common": "^2.1.33",
|
||||
"@switchboard-xyz/oracle": "^2.1.13",
|
||||
"dotenv": "^16.0.3",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
|
|
|
@ -7,22 +7,22 @@ import {
|
|||
QueueAccount,
|
||||
SwitchboardNetwork,
|
||||
SwitchboardProgram,
|
||||
} from '..';
|
||||
} from '.';
|
||||
|
||||
import { AnchorProvider } from '@coral-xyz/anchor';
|
||||
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
|
||||
import {
|
||||
IOracleConfig,
|
||||
NodeOracle,
|
||||
ReleaseChannel,
|
||||
ReleaseChannelVersion,
|
||||
} from '@switchboard-xyz/oracle';
|
||||
// import {
|
||||
// IOracleConfig,
|
||||
// NodeOracle,
|
||||
// ReleaseChannel,
|
||||
// ReleaseChannelVersion,
|
||||
// } from '@switchboard-xyz/oracle';
|
||||
import fs from 'fs';
|
||||
import _ from 'lodash';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
export type NodeConfig = IOracleConfig & Partial<ReleaseChannelVersion>;
|
||||
// export type NodeConfig = IOracleConfig & Partial<ReleaseChannelVersion>;
|
||||
|
||||
export function findAnchorTomlWallet(workingDir = process.cwd()): string {
|
||||
let numDirs = 3;
|
||||
|
@ -58,7 +58,7 @@ export function findAnchorTomlWallet(workingDir = process.cwd()): string {
|
|||
throw new Error(`Failed to find wallet path in Anchor.toml`);
|
||||
}
|
||||
|
||||
export type SwitchboardTestContextV2Init = Omit<
|
||||
export type SwitchboardTestContextInit = Omit<
|
||||
Omit<NetworkInitParams, 'authority'>,
|
||||
'oracles'
|
||||
> & {
|
||||
|
@ -69,7 +69,7 @@ export type SwitchboardTestContextV2Init = Omit<
|
|||
// oraclePubkey: Ei4HcqRQtf6TfwbuRXKRwCtt8PDXhmq9NhYLWpoh23xp
|
||||
|
||||
// TIP: Do NOT define an authority and defaul to Anchor.toml wallet
|
||||
export const DEFAULT_LOCALNET_NETWORK: SwitchboardTestContextV2Init = {
|
||||
export const DEFAULT_LOCALNET_NETWORK: SwitchboardTestContextInit = {
|
||||
name: 'Localnet Queue',
|
||||
metadata: 'Localnet Metadata',
|
||||
keypair: Keypair.fromSecretKey(
|
||||
|
@ -112,8 +112,8 @@ export const DEFAULT_LOCALNET_NETWORK: SwitchboardTestContextV2Init = {
|
|||
},
|
||||
};
|
||||
|
||||
export class SwitchboardTestContextV2 {
|
||||
_oracle?: NodeOracle;
|
||||
export class SwitchboardTestContext {
|
||||
// _oracle?: NodeOracle;
|
||||
|
||||
constructor(
|
||||
readonly network: LoadedSwitchboardNetwork,
|
||||
|
@ -134,10 +134,10 @@ export class SwitchboardTestContextV2 {
|
|||
|
||||
static async load(
|
||||
connection: Connection,
|
||||
networkInitParams?: Partial<SwitchboardTestContextV2Init>,
|
||||
networkInitParams?: Partial<SwitchboardTestContextInit>,
|
||||
walletPath?: string,
|
||||
programId?: PublicKey
|
||||
): Promise<SwitchboardTestContextV2> {
|
||||
): Promise<SwitchboardTestContext> {
|
||||
const walletFsPath = walletPath ?? findAnchorTomlWallet();
|
||||
const wallet = Keypair.fromSecretKey(
|
||||
new Uint8Array(JSON.parse(fs.readFileSync(walletFsPath, 'utf-8')))
|
||||
|
@ -187,7 +187,7 @@ export class SwitchboardTestContextV2 {
|
|||
}
|
||||
|
||||
const network = await SwitchboardNetwork.fromQueue(queueAccount);
|
||||
return new SwitchboardTestContextV2(network, walletFsPath);
|
||||
return new SwitchboardTestContext(network, walletFsPath);
|
||||
}
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch {}
|
||||
|
@ -204,15 +204,15 @@ export class SwitchboardTestContextV2 {
|
|||
throw new Error(`Anchor wallet pubkey mismatch`);
|
||||
}
|
||||
}
|
||||
return new SwitchboardTestContextV2(loadedNetwork, walletFsPath);
|
||||
return new SwitchboardTestContext(loadedNetwork, walletFsPath);
|
||||
}
|
||||
|
||||
static async loadFromProvider(
|
||||
provider: AnchorProvider,
|
||||
networkInitParams?: Partial<SwitchboardTestContextV2Init>,
|
||||
networkInitParams?: Partial<SwitchboardTestContextInit>,
|
||||
programId?: PublicKey
|
||||
): Promise<SwitchboardTestContextV2> {
|
||||
const switchboard = await SwitchboardTestContextV2.load(
|
||||
): Promise<SwitchboardTestContext> {
|
||||
const switchboard = await SwitchboardTestContext.load(
|
||||
provider.connection,
|
||||
networkInitParams,
|
||||
undefined,
|
||||
|
@ -223,92 +223,92 @@ export class SwitchboardTestContextV2 {
|
|||
|
||||
static async initFromProvider(
|
||||
provider: AnchorProvider,
|
||||
networkInitParams?: Partial<SwitchboardTestContextV2Init>,
|
||||
oracleParams?: Partial<IOracleConfig>,
|
||||
networkInitParams?: Partial<SwitchboardTestContextInit>,
|
||||
// oracleParams?: Partial<IOracleConfig>,
|
||||
programId?: PublicKey
|
||||
): Promise<SwitchboardTestContextV2> {
|
||||
const switchboard = await SwitchboardTestContextV2.loadFromProvider(
|
||||
): Promise<SwitchboardTestContext> {
|
||||
const switchboard = await SwitchboardTestContext.loadFromProvider(
|
||||
provider,
|
||||
networkInitParams,
|
||||
programId
|
||||
);
|
||||
await switchboard.start(oracleParams);
|
||||
// await switchboard.start(oracleParams);
|
||||
return switchboard;
|
||||
}
|
||||
|
||||
static async init(
|
||||
connection: Connection,
|
||||
networkInitParams?: Partial<SwitchboardTestContextV2Init>,
|
||||
oracleParams?: Partial<IOracleConfig>,
|
||||
networkInitParams?: Partial<SwitchboardTestContextInit>,
|
||||
// oracleParams?: Partial<IOracleConfig>,
|
||||
walletPath?: string,
|
||||
programId?: PublicKey
|
||||
): Promise<SwitchboardTestContextV2> {
|
||||
const switchboard = await SwitchboardTestContextV2.load(
|
||||
): Promise<SwitchboardTestContext> {
|
||||
const switchboard = await SwitchboardTestContext.load(
|
||||
connection,
|
||||
networkInitParams,
|
||||
walletPath,
|
||||
programId
|
||||
);
|
||||
await switchboard.start(oracleParams);
|
||||
// await switchboard.start(oracleParams);
|
||||
return switchboard;
|
||||
}
|
||||
|
||||
async start(
|
||||
oracleParams?: Partial<NodeConfig> | ReleaseChannel,
|
||||
timeout = 60
|
||||
) {
|
||||
const releaseChannel: ReleaseChannel =
|
||||
typeof oracleParams === 'string' &&
|
||||
(oracleParams === 'testnet' ||
|
||||
oracleParams === 'mainnet' ||
|
||||
oracleParams === 'latest')
|
||||
? oracleParams
|
||||
: 'testnet';
|
||||
// async start(
|
||||
// oracleParams?: Partial<NodeConfig> | ReleaseChannel,
|
||||
// timeout = 60
|
||||
// ) {
|
||||
// const releaseChannel: ReleaseChannel =
|
||||
// typeof oracleParams === 'string' &&
|
||||
// (oracleParams === 'testnet' ||
|
||||
// oracleParams === 'mainnet' ||
|
||||
// oracleParams === 'latest')
|
||||
// ? oracleParams
|
||||
// : 'testnet';
|
||||
|
||||
const baseConfig: NodeConfig = {
|
||||
chain: 'solana',
|
||||
releaseChannel: releaseChannel,
|
||||
network:
|
||||
this.program.cluster === 'mainnet-beta'
|
||||
? 'mainnet'
|
||||
: this.program.cluster,
|
||||
rpcUrl: this.program.connection.rpcEndpoint,
|
||||
oracleKey: this.oracle.publicKey.toBase58(),
|
||||
secretPath: this.walletPath,
|
||||
envVariables: {
|
||||
VERBOSE: '1',
|
||||
DEBUG: '1',
|
||||
DISABLE_NONE_QUEUE: '1',
|
||||
DISABLE_METRICS: '1',
|
||||
},
|
||||
};
|
||||
// const baseConfig: NodeConfig = {
|
||||
// chain: 'solana',
|
||||
// releaseChannel: releaseChannel,
|
||||
// network:
|
||||
// this.program.cluster === 'mainnet-beta'
|
||||
// ? 'mainnet'
|
||||
// : this.program.cluster,
|
||||
// rpcUrl: this.program.connection.rpcEndpoint,
|
||||
// oracleKey: this.oracle.publicKey.toBase58(),
|
||||
// secretPath: this.walletPath,
|
||||
// envVariables: {
|
||||
// VERBOSE: '1',
|
||||
// DEBUG: '1',
|
||||
// DISABLE_NONE_QUEUE: '1',
|
||||
// DISABLE_METRICS: '1',
|
||||
// },
|
||||
// };
|
||||
|
||||
const config: NodeConfig =
|
||||
typeof oracleParams === 'string'
|
||||
? baseConfig
|
||||
: _.merge(baseConfig, oracleParams);
|
||||
// const config: NodeConfig =
|
||||
// typeof oracleParams === 'string'
|
||||
// ? baseConfig
|
||||
// : _.merge(baseConfig, oracleParams);
|
||||
|
||||
this._oracle = await NodeOracle.fromReleaseChannel({
|
||||
...config,
|
||||
releaseChannel: releaseChannel,
|
||||
chain: 'solana',
|
||||
});
|
||||
// this._oracle = await NodeOracle.fromReleaseChannel({
|
||||
// ...config,
|
||||
// releaseChannel: releaseChannel,
|
||||
// chain: 'solana',
|
||||
// });
|
||||
|
||||
console.log(`Starting Switchboard oracle ...`);
|
||||
// console.log(`Starting Switchboard oracle ...`);
|
||||
|
||||
await this._oracle.startAndAwait(timeout);
|
||||
}
|
||||
// await this._oracle.startAndAwait(timeout);
|
||||
// }
|
||||
|
||||
stop() {
|
||||
if (this._oracle) {
|
||||
const stopped = this._oracle.stop();
|
||||
if (!stopped) {
|
||||
console.error(`Failed to stop docker oracle`);
|
||||
// stop() {
|
||||
// if (this._oracle) {
|
||||
// const stopped = this._oracle.stop();
|
||||
// if (!stopped) {
|
||||
// console.error(`Failed to stop docker oracle`);
|
||||
|
||||
this._oracle.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
// this._oracle.kill();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
public static findAnchorTomlWallet(workingDir = process.cwd()): string {
|
||||
try {
|
||||
|
@ -326,3 +326,5 @@ export class SwitchboardTestContextV2 {
|
|||
return loadKeypair(keypairPath);
|
||||
}
|
||||
}
|
||||
|
||||
export { SwitchboardTestContext as SwitchboardTestContextV2 };
|
|
@ -9,6 +9,6 @@ export * from './SwitchboardError';
|
|||
export * from './SwitchboardEvents';
|
||||
export * from './SwitchboardNetwork';
|
||||
export * from './SwitchboardProgram';
|
||||
export * from './test';
|
||||
export * from './SwitchboardTestContext';
|
||||
export * from './TransactionObject';
|
||||
export * from './utils';
|
||||
|
|
|
@ -1,740 +0,0 @@
|
|||
import { AggregatorAccount, JobAccount, QueueAccount } from '../accounts';
|
||||
import {
|
||||
DEVNET_GENESIS_HASH,
|
||||
SWITCHBOARD_LABS_DEVNET_PERMISSIONLESS_QUEUE,
|
||||
} from '../const';
|
||||
import { AggregatorAccountData } from '../generated';
|
||||
import { Mint } from '../mint';
|
||||
import { SwitchboardNetwork } from '../SwitchboardNetwork';
|
||||
import { AnchorWallet, SwitchboardProgram } from '../SwitchboardProgram';
|
||||
import { TransactionObject } from '../TransactionObject';
|
||||
|
||||
import * as anchor from '@coral-xyz/anchor';
|
||||
import { clusterApiUrl, Connection, Keypair, PublicKey } from '@solana/web3.js';
|
||||
import { BNtoDateTimeString, OracleJob } from '@switchboard-xyz/common';
|
||||
import * as dotenv from 'dotenv';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export const LATEST_DOCKER_VERSION = 'dev-v2-RC_01_05_23_03_24';
|
||||
|
||||
/** 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 PublicKey.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 class SwitchboardTestContext {
|
||||
constructor(
|
||||
readonly program: SwitchboardProgram,
|
||||
readonly queue: QueueAccount,
|
||||
readonly payerTokenWallet: PublicKey
|
||||
) {}
|
||||
|
||||
public static async load(
|
||||
provider: anchor.AnchorProvider,
|
||||
params?: {
|
||||
tokenAmount?: number;
|
||||
queueKey?: PublicKey | string;
|
||||
filePath?: string;
|
||||
}
|
||||
): Promise<SwitchboardTestContext> {
|
||||
// fetch genesis hash
|
||||
|
||||
const genesisHash = await provider.connection.getGenesisHash();
|
||||
if (genesisHash === DEVNET_GENESIS_HASH) {
|
||||
// if queueKey is defined should we bother loading the local env?
|
||||
|
||||
// first try to load the local env
|
||||
try {
|
||||
const testContext = await SwitchboardTestContext.loadFromEnv(
|
||||
provider,
|
||||
params?.filePath ?? undefined,
|
||||
params?.tokenAmount ?? undefined
|
||||
);
|
||||
// verify the oracle is heartbeating
|
||||
const oracles = await testContext.queue.loadActiveOracleAccounts();
|
||||
if (oracles.length > 0) {
|
||||
return testContext;
|
||||
}
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (error) {}
|
||||
|
||||
// fallback to the devnet permissionless queue
|
||||
try {
|
||||
const testContext = await SwitchboardTestContext.loadDevnetQueue(
|
||||
provider,
|
||||
params?.queueKey ?? undefined,
|
||||
params?.tokenAmount ?? undefined
|
||||
);
|
||||
// verify the oracle is heartbeating
|
||||
const oracles = await testContext.queue.loadActiveOracleAccounts();
|
||||
if (oracles.length > 0) {
|
||||
return testContext;
|
||||
}
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (error) {}
|
||||
|
||||
throw new Error(
|
||||
`Failed to load a Switchboard environment from a local file or the devnet permissionless queue`
|
||||
);
|
||||
} else {
|
||||
const testContext = await SwitchboardTestContext.loadFromEnv(
|
||||
provider,
|
||||
params?.filePath ?? undefined,
|
||||
params?.tokenAmount ?? undefined
|
||||
);
|
||||
return testContext;
|
||||
}
|
||||
}
|
||||
|
||||
/** 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: PublicKey | string = SWITCHBOARD_LABS_DEVNET_PERMISSIONLESS_QUEUE,
|
||||
tokenAmount = 0
|
||||
) {
|
||||
const payerKeypair = (provider.wallet as AnchorWallet).payer;
|
||||
|
||||
const balance = await provider.connection.getBalance(
|
||||
payerKeypair.publicKey
|
||||
);
|
||||
if (!balance) {
|
||||
try {
|
||||
await provider.connection.requestAirdrop(
|
||||
payerKeypair.publicKey,
|
||||
1_000_000_000
|
||||
);
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const program = await SwitchboardProgram.load(
|
||||
'devnet',
|
||||
provider.connection,
|
||||
payerKeypair
|
||||
).catch(error => {
|
||||
throw new Error(
|
||||
`Failed to load the SBV2 program for the given cluster, ${error.message}`
|
||||
);
|
||||
});
|
||||
|
||||
const queueAccount = new QueueAccount(
|
||||
program,
|
||||
typeof queueKey === 'string' ? new PublicKey(queueKey) : queueKey
|
||||
);
|
||||
try {
|
||||
await queueAccount.loadData();
|
||||
const oracles = await queueAccount.loadOracles();
|
||||
if (oracles.length < 1) {
|
||||
throw new Error(`OracleQueue has no active oracles heartbeating`);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to load the SBV2 queue for the given cluster, ${
|
||||
(error as any).message
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
const [userTokenAmount] = await program.mint.getOrCreateWrappedUser(
|
||||
program.walletPubkey,
|
||||
{ fundUpTo: tokenAmount }
|
||||
);
|
||||
|
||||
return new SwitchboardTestContext(program, queueAccount, userTokenAmount);
|
||||
}
|
||||
|
||||
/** 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;
|
||||
}
|
||||
// eslint-disable-next-line no-empty
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line no-empty
|
||||
} 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> {
|
||||
// eslint-disable-next-line node/no-unpublished-require
|
||||
dotenv.config({ path: filePath });
|
||||
if (!process.env.SWITCHBOARD_PROGRAM_ID) {
|
||||
throw new Error(`your env file must have $SWITCHBOARD_PROGRAM_ID set`);
|
||||
}
|
||||
if (!process.env.ORACLE_QUEUE) {
|
||||
throw new Error(`your env file must have $ORACLE_QUEUE set`);
|
||||
}
|
||||
|
||||
const program = await SwitchboardProgram.load(
|
||||
'devnet',
|
||||
provider.connection,
|
||||
(provider.wallet as AnchorWallet).payer,
|
||||
new PublicKey(process.env.SWITCHBOARD_PROGRAM_ID)
|
||||
);
|
||||
|
||||
const balance = await provider.connection.getBalance(program.walletPubkey);
|
||||
if (!balance) {
|
||||
try {
|
||||
const airdropSignature = await provider.connection.requestAirdrop(
|
||||
program.walletPubkey,
|
||||
1_000_000_000
|
||||
);
|
||||
await provider.connection.confirmTransaction(airdropSignature);
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const queueAccount = new QueueAccount(
|
||||
program,
|
||||
new PublicKey(process.env.ORACLE_QUEUE)
|
||||
);
|
||||
|
||||
const [userTokenAmount] = await program.mint.getOrCreateWrappedUser(
|
||||
program.walletPubkey,
|
||||
{ fundUpTo: tokenAmount }
|
||||
);
|
||||
|
||||
return new SwitchboardTestContext(program, queueAccount, userTokenAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a static data feed that resolves to an expected value
|
||||
* @param value - the static value the feed will resolve to
|
||||
* @param timeout - the number of milliseconds to wait before timing out
|
||||
*/
|
||||
public async createStaticFeed(
|
||||
value: number,
|
||||
timeout = 30000
|
||||
): Promise<[AggregatorAccount, AggregatorAccountData]> {
|
||||
const [aggregatorAccount] = await this.queue.createFeed({
|
||||
name: `Value ${value}`,
|
||||
batchSize: 1,
|
||||
minRequiredOracleResults: 1,
|
||||
minRequiredJobResults: 1,
|
||||
minUpdateDelaySeconds: 10,
|
||||
enable: true,
|
||||
queueAuthorityPubkey: this.program.walletPubkey,
|
||||
jobs: [
|
||||
{
|
||||
data: OracleJob.encodeDelimited(
|
||||
OracleJob.create({
|
||||
tasks: [
|
||||
OracleJob.Task.create({
|
||||
valueTask: OracleJob.ValueTask.create({
|
||||
value,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
})
|
||||
).finish(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const [state] = await aggregatorAccount.openRoundAndAwaitResult(
|
||||
undefined,
|
||||
timeout
|
||||
);
|
||||
|
||||
return [aggregatorAccount, state];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing aggregator that resolves to a new static value then await the new result
|
||||
* @params aggregatorAccount - the aggregator account to modify
|
||||
* @param value - the static value the feed will resolve to
|
||||
* @param timeout - the number of milliseconds to wait before timing out
|
||||
*/
|
||||
public async updateStaticFeed(
|
||||
aggregatorAccount: AggregatorAccount,
|
||||
value: number,
|
||||
timeout = 30000
|
||||
): Promise<[AggregatorAccount, AggregatorAccountData]> {
|
||||
const aggregator = await aggregatorAccount.loadData();
|
||||
|
||||
const [jobAccount, jobInit] = JobAccount.createInstructions(
|
||||
this.program,
|
||||
this.program.walletPubkey,
|
||||
{
|
||||
data: OracleJob.encodeDelimited(
|
||||
OracleJob.create({
|
||||
tasks: [
|
||||
OracleJob.Task.create({
|
||||
valueTask: OracleJob.ValueTask.create({
|
||||
value,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
})
|
||||
).finish(),
|
||||
}
|
||||
);
|
||||
|
||||
const oldJobKeys = aggregator.jobPubkeysData.filter(
|
||||
pubkey => !pubkey.equals(PublicKey.default)
|
||||
);
|
||||
|
||||
const oldJobs: Array<[JobAccount, number]> = oldJobKeys.map((pubkey, i) => [
|
||||
new JobAccount(this.program, pubkey),
|
||||
i,
|
||||
]);
|
||||
|
||||
const removeJobTxns = oldJobs.map(job =>
|
||||
aggregatorAccount.removeJobInstruction(this.program.walletPubkey, {
|
||||
job: job[0],
|
||||
jobIdx: job[1],
|
||||
})
|
||||
);
|
||||
|
||||
const addJobTxn = aggregatorAccount.addJobInstruction(
|
||||
this.program.walletPubkey,
|
||||
{ job: jobAccount }
|
||||
);
|
||||
|
||||
const txns = TransactionObject.pack([
|
||||
...jobInit,
|
||||
...removeJobTxns,
|
||||
addJobTxn,
|
||||
]);
|
||||
await this.program.signAndSendAll(txns);
|
||||
|
||||
const [state] = await aggregatorAccount.openRoundAndAwaitResult(
|
||||
undefined,
|
||||
timeout
|
||||
);
|
||||
|
||||
return [aggregatorAccount, state];
|
||||
}
|
||||
|
||||
static async createEnvironment(
|
||||
payerKeypairPath: string,
|
||||
alternateProgramId?: PublicKey
|
||||
): Promise<SwitchboardTestEnvironment> {
|
||||
const fullKeypairPath =
|
||||
payerKeypairPath.startsWith('/') || payerKeypairPath.startsWith('C:')
|
||||
? 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 program = await SwitchboardProgram.load(
|
||||
'devnet',
|
||||
connection,
|
||||
payerKeypair,
|
||||
alternateProgramId
|
||||
);
|
||||
|
||||
const [userTokenWallet] = await program.mint.getOrCreateWrappedUser(
|
||||
program.walletPubkey,
|
||||
{ amount: 0 }
|
||||
);
|
||||
|
||||
const programDataAddress = getProgramDataAddress(program.programId);
|
||||
const idlAddress = await getIdlAddress(program.programId);
|
||||
|
||||
// use pre-generated keypairs so we dont need to rely on account loading
|
||||
const dataBufferKeypair = Keypair.generate();
|
||||
const crankBufferKeypair = Keypair.generate();
|
||||
const oracleStakingWalletKeypair = Keypair.generate();
|
||||
|
||||
const [accounts] = await SwitchboardNetwork.create(program, {
|
||||
name: 'Test Queue',
|
||||
metadata: `created ${BNtoDateTimeString(
|
||||
new anchor.BN(Math.floor(Date.now() / 1000))
|
||||
)}`,
|
||||
authority: payerKeypair,
|
||||
reward: 0,
|
||||
minStake: 0,
|
||||
queueSize: 10,
|
||||
dataBufferKeypair: dataBufferKeypair,
|
||||
unpermissionedFeeds: true,
|
||||
unpermissionedVrf: true,
|
||||
enableBufferRelayers: true,
|
||||
slashingEnabled: false,
|
||||
cranks: [
|
||||
{
|
||||
name: 'Test Crank',
|
||||
maxRows: 100,
|
||||
dataBufferKeypair: crankBufferKeypair,
|
||||
},
|
||||
],
|
||||
oracles: [
|
||||
{
|
||||
name: 'Test Oracle',
|
||||
enable: true,
|
||||
stakingWalletKeypair: oracleStakingWalletKeypair,
|
||||
queueAuthorityPubkey: program.walletPubkey,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const crank = accounts.cranks.shift();
|
||||
if (!crank) {
|
||||
throw new Error(`Failed to create the crank`);
|
||||
}
|
||||
|
||||
const oracle = accounts.oracles.shift();
|
||||
if (!oracle) {
|
||||
throw new Error(`Failed to create the oracle`);
|
||||
}
|
||||
|
||||
// async load the accounts
|
||||
const programState = await accounts.programState.account.loadData();
|
||||
|
||||
return new SwitchboardTestEnvironment({
|
||||
switchboardProgramId: program.programId,
|
||||
switchboardProgramDataAddress: programDataAddress,
|
||||
switchboardIdlAddress: idlAddress,
|
||||
switchboardProgramState: accounts.programState.account.publicKey,
|
||||
switchboardVault: programState.tokenVault,
|
||||
switchboardMint: programState.tokenMint.equals(PublicKey.default)
|
||||
? Mint.native
|
||||
: programState.tokenMint,
|
||||
tokenWallet: userTokenWallet,
|
||||
oracleQueue: accounts.queue.account.publicKey,
|
||||
oracleQueueAuthority: program.walletPubkey,
|
||||
oracleQueueBuffer: dataBufferKeypair.publicKey,
|
||||
crank: crank.account.publicKey,
|
||||
crankBuffer: crankBufferKeypair.publicKey,
|
||||
oracle: oracle.account.publicKey,
|
||||
oracleAuthority: program.walletPubkey,
|
||||
oracleEscrow: oracleStakingWalletKeypair.publicKey,
|
||||
oraclePermissions: oracle.permission.account.publicKey,
|
||||
payerKeypairPath: fullKeypairPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface ISwitchboardTestEnvironment {
|
||||
switchboardProgramId: PublicKey;
|
||||
switchboardProgramDataAddress: PublicKey;
|
||||
switchboardIdlAddress: PublicKey;
|
||||
switchboardProgramState: PublicKey;
|
||||
switchboardVault: PublicKey;
|
||||
switchboardMint: PublicKey;
|
||||
tokenWallet: PublicKey;
|
||||
oracleQueue: PublicKey;
|
||||
oracleQueueAuthority: PublicKey;
|
||||
oracleQueueBuffer: PublicKey;
|
||||
crank: PublicKey;
|
||||
crankBuffer: PublicKey;
|
||||
oracle: PublicKey;
|
||||
oracleAuthority: PublicKey;
|
||||
oracleEscrow: PublicKey;
|
||||
oraclePermissions: PublicKey;
|
||||
payerKeypairPath: string;
|
||||
}
|
||||
|
||||
export class SwitchboardTestEnvironment implements ISwitchboardTestEnvironment {
|
||||
switchboardProgramId: PublicKey;
|
||||
switchboardProgramDataAddress: PublicKey;
|
||||
switchboardIdlAddress: PublicKey;
|
||||
switchboardProgramState: PublicKey;
|
||||
switchboardVault: PublicKey;
|
||||
switchboardMint: PublicKey;
|
||||
tokenWallet: PublicKey;
|
||||
oracleQueue: PublicKey;
|
||||
oracleQueueAuthority: PublicKey;
|
||||
oracleQueueBuffer: PublicKey;
|
||||
crank: PublicKey;
|
||||
crankBuffer: PublicKey;
|
||||
oracle: PublicKey;
|
||||
oracleAuthority: PublicKey;
|
||||
oracleEscrow: PublicKey;
|
||||
oraclePermissions: PublicKey;
|
||||
payerKeypairPath: string;
|
||||
|
||||
constructor(ctx: ISwitchboardTestEnvironment) {
|
||||
this.switchboardProgramId = ctx.switchboardProgramId;
|
||||
this.switchboardProgramDataAddress = ctx.switchboardProgramDataAddress;
|
||||
this.switchboardIdlAddress = ctx.switchboardIdlAddress;
|
||||
this.switchboardProgramState = ctx.switchboardProgramState;
|
||||
this.switchboardVault = ctx.switchboardVault;
|
||||
this.switchboardMint = ctx.switchboardMint;
|
||||
this.tokenWallet = ctx.tokenWallet;
|
||||
this.oracleQueue = ctx.oracleQueue;
|
||||
this.oracleQueueAuthority = ctx.oracleQueueAuthority;
|
||||
this.oracleQueueBuffer = ctx.oracleQueueBuffer;
|
||||
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;
|
||||
}
|
||||
|
||||
public get envFileString(): string {
|
||||
// const fileString = Object.keys(this)
|
||||
// .map(key => {
|
||||
// if (this[key] instanceof PublicKey) {
|
||||
// return `${camelToUpperCaseWithUnderscores(key)}="${this[
|
||||
// key
|
||||
// ].toBase58()}"`;
|
||||
// }
|
||||
// return;
|
||||
// })
|
||||
// .filter(Boolean)
|
||||
// .join('\n');
|
||||
return `SWITCHBOARD_PROGRAM_ID="${this.switchboardProgramId.toBase58()}"
|
||||
SWITCHBOARD_PROGRAM_DATA_ADDRESS="${this.switchboardProgramDataAddress.toBase58()}"
|
||||
SWITCHBOARD_IDL_ADDRESS="${this.switchboardIdlAddress.toBase58()}"
|
||||
SWITCHBOARD_PROGRAM_STATE="${this.switchboardProgramState.toBase58()}"
|
||||
SWITCHBOARD_VAULT="${this.switchboardVault.toBase58()}"
|
||||
SWITCHBOARD_MINT="${this.switchboardMint.toBase58()}"
|
||||
TOKEN_WALLET="${this.tokenWallet.toBase58()}"
|
||||
ORACLE_QUEUE="${this.oracleQueue.toBase58()}"
|
||||
ORACLE_QUEUE_AUTHORITY="${this.oracleQueueAuthority.toBase58()}"
|
||||
ORACLE_QUEUE_BUFFER="${this.oracleQueueBuffer.toBase58()}"
|
||||
CRANK="${this.crank.toBase58()}"
|
||||
CRANK_BUFFER="${this.crankBuffer.toBase58()}"
|
||||
ORACLE="${this.oracle.toBase58()}"
|
||||
ORACLE_AUTHORITY="${this.oracleAuthority.toBase58()}"
|
||||
ORACLE_ESCROW="${this.oracleEscrow.toBase58()}"
|
||||
ORACLE_PERMISSIONS="${this.oraclePermissions.toBase58()}"`;
|
||||
}
|
||||
|
||||
public get anchorToml(): string {
|
||||
return [
|
||||
`
|
||||
[test]
|
||||
startup_wait = 10000
|
||||
|
||||
[test.validator]
|
||||
url = "https://api.devnet.solana.com"
|
||||
`,
|
||||
Object.keys(this)
|
||||
.map(key => {
|
||||
if (this[key] instanceof PublicKey) {
|
||||
return `[[test.validator.clone]] # ${key}\naddress = "${this[key]}"`;
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n\n'),
|
||||
].join('\n\n');
|
||||
}
|
||||
|
||||
public get accountCloneString(): string {
|
||||
const accounts = Object.keys(this).map(key => {
|
||||
if (typeof this[key] === 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
return `--clone ${(this[key] as PublicKey).toBase58()} \`# ${key}\` `;
|
||||
});
|
||||
|
||||
return accounts.filter(Boolean).join(`\\\n`);
|
||||
}
|
||||
|
||||
public get dockerCompose(): string {
|
||||
return `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
|
||||
- CLUSTER=\${CLUSTER:-localnet}
|
||||
- HEARTBEAT_INTERVAL=30 # Seconds
|
||||
- ORACLE_KEY=${this.oracle.toBase58()}
|
||||
- TASK_RUNNER_SOLANA_RPC=${clusterApiUrl('mainnet-beta')}
|
||||
# - RPC_URL=\${RPC_URL}
|
||||
secrets:
|
||||
PAYER_SECRETS:
|
||||
file: ${this.payerKeypairPath}`;
|
||||
}
|
||||
|
||||
public get localValidatorScript(): string {
|
||||
return `#!/bin/bash
|
||||
|
||||
mkdir -p .anchor/test-ledger
|
||||
|
||||
solana-test-validator -r --ledger .anchor/test-ledger --mint ${this.oracleAuthority.toBase58()} --bind-address 0.0.0.0 --url ${clusterApiUrl(
|
||||
'devnet'
|
||||
)} --rpc-port 8899 ${this.accountCloneString}`;
|
||||
}
|
||||
|
||||
public get startOracleScript(): string {
|
||||
return `#!/usr/bin/env bash
|
||||
|
||||
script_dir=$( cd -- "$( dirname -- "\${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
docker-compose -f "$script_dir"/docker-compose.switchboard.yml up`;
|
||||
}
|
||||
|
||||
public toJSON(): ISwitchboardTestEnvironment {
|
||||
return {
|
||||
switchboardProgramId: this.switchboardProgramId,
|
||||
switchboardProgramDataAddress: this.switchboardProgramDataAddress,
|
||||
switchboardIdlAddress: this.switchboardIdlAddress,
|
||||
switchboardProgramState: this.switchboardProgramState,
|
||||
switchboardVault: this.switchboardVault,
|
||||
switchboardMint: this.switchboardMint,
|
||||
tokenWallet: this.tokenWallet,
|
||||
oracleQueue: this.oracleQueue,
|
||||
oracleQueueAuthority: this.oracleQueueAuthority,
|
||||
oracleQueueBuffer: this.oracleQueueBuffer,
|
||||
crank: this.crank,
|
||||
crankBuffer: this.crankBuffer,
|
||||
oracle: this.oracle,
|
||||
oracleAuthority: this.oracleAuthority,
|
||||
oracleEscrow: this.oracleEscrow,
|
||||
oraclePermissions: this.oraclePermissions,
|
||||
payerKeypairPath: this.payerKeypairPath,
|
||||
};
|
||||
}
|
||||
|
||||
/** 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');
|
||||
fs.writeFileSync(ENV_FILE_PATH, this.envFileString);
|
||||
}
|
||||
|
||||
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 {
|
||||
// create script to start local validator with accounts cloned
|
||||
const LOCAL_VALIDATOR_SCRIPT = path.join(
|
||||
outputDir,
|
||||
'start-local-validator.sh'
|
||||
);
|
||||
fs.writeFileSync(LOCAL_VALIDATOR_SCRIPT, this.localValidatorScript);
|
||||
fs.chmodSync(LOCAL_VALIDATOR_SCRIPT, '755');
|
||||
|
||||
// create bash script to start local oracle
|
||||
const ORACLE_SCRIPT = path.join(outputDir, 'start-oracle.sh');
|
||||
fs.writeFileSync(ORACLE_SCRIPT, this.startOracleScript);
|
||||
fs.chmodSync(ORACLE_SCRIPT, '755');
|
||||
}
|
||||
|
||||
public writeDockerCompose(outputDir: string): void {
|
||||
const DOCKER_COMPOSE_FILEPATH = path.join(
|
||||
outputDir,
|
||||
'docker-compose.switchboard.yml'
|
||||
);
|
||||
fs.writeFileSync(DOCKER_COMPOSE_FILEPATH, this.dockerCompose);
|
||||
}
|
||||
|
||||
public writeAnchorToml(outputDir: string) {
|
||||
const ANCHOR_TOML_FILEPATH = path.join(
|
||||
outputDir,
|
||||
'Anchor.switchboard.toml'
|
||||
);
|
||||
fs.writeFileSync(ANCHOR_TOML_FILEPATH, this.anchorToml);
|
||||
}
|
||||
}
|
||||
|
||||
export function camelToUpperCaseWithUnderscores(str: string): string {
|
||||
// Use a regular expression to match any uppercase or lowercase letters followed by uppercase letters
|
||||
// and replace them with a matched group (the uppercase or lowercase letters) followed by an underscore
|
||||
// and the uppercase letter
|
||||
return (
|
||||
str
|
||||
.replace(/([a-z]+)([A-Z])/g, (_, p1, p2) => p1 + '_' + p2)
|
||||
// Make the entire string uppercase
|
||||
.toUpperCase()
|
||||
);
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
export * from './SwitchboardTestContext';
|
||||
export * from './SwitchboardTestContextV2';
|
|
@ -59,7 +59,6 @@ importers:
|
|||
'@solana/web3.js': ^1.73.0
|
||||
'@switchboard-xyz/common': ^2.1.33
|
||||
'@switchboard-xyz/eslint-config': latest
|
||||
'@switchboard-xyz/oracle': ^2.1.13
|
||||
'@types/chai': ^4.3.4
|
||||
'@types/lodash': ^4.14.191
|
||||
'@types/mocha': ^10.0.0
|
||||
|
@ -85,7 +84,6 @@ importers:
|
|||
'@solana/spl-token': 0.3.6_@solana+web3.js@1.73.0
|
||||
'@solana/web3.js': 1.73.0
|
||||
'@switchboard-xyz/common': 2.1.33
|
||||
'@switchboard-xyz/oracle': 2.1.13
|
||||
dotenv: 16.0.3
|
||||
lodash: 4.17.21
|
||||
devDependencies:
|
||||
|
@ -113,6 +111,7 @@ importers:
|
|||
'@coral-xyz/anchor': ^0.27.0
|
||||
'@solana/web3.js': ^1.73.3
|
||||
'@switchboard-xyz/common': ^2.1.33
|
||||
'@switchboard-xyz/oracle': ^2.1.13
|
||||
'@switchboard-xyz/solana.js': workspace:*
|
||||
'@types/chai': ^4.3.0
|
||||
'@types/mocha': ^9.0.0
|
||||
|
@ -128,6 +127,7 @@ importers:
|
|||
'@coral-xyz/anchor': 0.27.0
|
||||
'@solana/web3.js': 1.73.3
|
||||
'@switchboard-xyz/common': 2.1.33
|
||||
'@switchboard-xyz/oracle': 2.1.13
|
||||
'@switchboard-xyz/solana.js': link:../../javascript/solana.js
|
||||
node-fetch: 2.6.7
|
||||
devDependencies:
|
||||
|
@ -146,6 +146,7 @@ importers:
|
|||
'@coral-xyz/anchor': ^0.27.0
|
||||
'@solana/web3.js': ^1.73.3
|
||||
'@switchboard-xyz/common': ^2.1.33
|
||||
'@switchboard-xyz/oracle': ^2.1.13
|
||||
'@switchboard-xyz/solana.js': workspace:*
|
||||
'@types/chai': ^4.3.0
|
||||
'@types/mocha': ^9.0.0
|
||||
|
@ -159,6 +160,7 @@ importers:
|
|||
'@coral-xyz/anchor': 0.27.0
|
||||
'@solana/web3.js': 1.73.3
|
||||
'@switchboard-xyz/common': 2.1.33
|
||||
'@switchboard-xyz/oracle': 2.1.13
|
||||
'@switchboard-xyz/solana.js': link:../../javascript/solana.js
|
||||
devDependencies:
|
||||
'@types/chai': 4.3.4
|
||||
|
@ -174,6 +176,7 @@ importers:
|
|||
specifiers:
|
||||
'@coral-xyz/anchor': ^0.27.0
|
||||
'@switchboard-xyz/common': ^2.1.33
|
||||
'@switchboard-xyz/oracle': ^2.1.13
|
||||
'@switchboard-xyz/solana.js': workspace:*
|
||||
'@types/bn.js': ^5.1.0
|
||||
'@types/chai': ^4.3.0
|
||||
|
@ -186,6 +189,7 @@ importers:
|
|||
dependencies:
|
||||
'@coral-xyz/anchor': 0.27.0
|
||||
'@switchboard-xyz/common': 2.1.33
|
||||
'@switchboard-xyz/oracle': 2.1.13
|
||||
'@switchboard-xyz/solana.js': link:../../javascript/solana.js
|
||||
devDependencies:
|
||||
'@types/bn.js': 5.1.1
|
||||
|
@ -205,6 +209,7 @@ importers:
|
|||
'@solana/spl-token': ^0.3.6
|
||||
'@solana/web3.js': ^1.73.3
|
||||
'@switchboard-xyz/common': ^2.1.33
|
||||
'@switchboard-xyz/oracle': ^2.1.13
|
||||
'@switchboard-xyz/solana.js': workspace:*
|
||||
'@types/chai': ^4.3.0
|
||||
'@types/mocha': ^9.0.0
|
||||
|
@ -231,6 +236,7 @@ importers:
|
|||
'@solana/spl-token': 0.3.6_@solana+web3.js@1.73.3
|
||||
'@solana/web3.js': 1.73.3
|
||||
'@switchboard-xyz/common': 2.1.33
|
||||
'@switchboard-xyz/oracle': 2.1.13
|
||||
'@switchboard-xyz/solana.js': link:../../javascript/solana.js
|
||||
chalk: 4.1.2
|
||||
dotenv: 16.0.3
|
||||
|
@ -257,6 +263,7 @@ importers:
|
|||
'@coral-xyz/anchor': ^0.27.0
|
||||
'@solana/web3.js': ^1.73.3
|
||||
'@switchboard-xyz/common': ^2.1.33
|
||||
'@switchboard-xyz/oracle': ^2.1.13
|
||||
'@switchboard-xyz/solana.js': workspace:*
|
||||
'@types/chai': ^4.3.0
|
||||
'@types/mocha': ^9.0.0
|
||||
|
@ -270,6 +277,7 @@ importers:
|
|||
'@coral-xyz/anchor': 0.27.0
|
||||
'@solana/web3.js': 1.73.3
|
||||
'@switchboard-xyz/common': 2.1.33
|
||||
'@switchboard-xyz/oracle': 2.1.13
|
||||
'@switchboard-xyz/solana.js': link:../../javascript/solana.js
|
||||
devDependencies:
|
||||
'@types/chai': 4.3.4
|
||||
|
@ -1022,7 +1030,7 @@ packages:
|
|||
dependencies:
|
||||
'@types/http-cache-semantics': 4.0.1
|
||||
'@types/keyv': 3.1.4
|
||||
'@types/node': 18.11.18
|
||||
'@types/node': 17.0.45
|
||||
'@types/responselike': 1.0.0
|
||||
dev: false
|
||||
|
||||
|
@ -1050,7 +1058,7 @@ packages:
|
|||
/@types/keyv/3.1.4:
|
||||
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
|
||||
dependencies:
|
||||
'@types/node': 18.11.18
|
||||
'@types/node': 17.0.45
|
||||
dev: false
|
||||
|
||||
/@types/lodash/4.14.191:
|
||||
|
@ -1081,7 +1089,6 @@ packages:
|
|||
|
||||
/@types/node/17.0.45:
|
||||
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
|
||||
dev: true
|
||||
|
||||
/@types/node/18.11.18:
|
||||
resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==}
|
||||
|
@ -1097,7 +1104,7 @@ packages:
|
|||
/@types/responselike/1.0.0:
|
||||
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
|
||||
dependencies:
|
||||
'@types/node': 18.11.18
|
||||
'@types/node': 17.0.45
|
||||
dev: false
|
||||
|
||||
/@types/semver/7.3.13:
|
||||
|
@ -1113,7 +1120,7 @@ packages:
|
|||
resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@types/node': 18.11.18
|
||||
'@types/node': 17.0.45
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"@coral-xyz/anchor": "^0.27.0",
|
||||
"@solana/web3.js": "^1.73.3",
|
||||
"@switchboard-xyz/common": "^2.1.33",
|
||||
"@switchboard-xyz/oracle": "^2.1.13",
|
||||
"@switchboard-xyz/solana.js": "workspace:*",
|
||||
"node-fetch": "^2.6"
|
||||
},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import { OracleJob, sleep } from "@switchboard-xyz/common";
|
||||
import { SwitchboardTestContextV2 } from "@switchboard-xyz/solana.js";
|
||||
import { NodeOracle } from "@switchboard-xyz/oracle";
|
||||
import { SwitchboardTestContext } from "@switchboard-xyz/solana.js";
|
||||
import fetch from "node-fetch";
|
||||
import { AnchorBufferParser } from "../target/types/anchor_buffer_parser";
|
||||
|
||||
|
@ -11,13 +12,14 @@ describe("anchor-buffer-parser test", () => {
|
|||
const bufferParserProgram: anchor.Program<AnchorBufferParser> =
|
||||
anchor.workspace.AnchorBufferParser;
|
||||
|
||||
let switchboard: SwitchboardTestContextV2;
|
||||
let switchboard: SwitchboardTestContext;
|
||||
let oracle: NodeOracle;
|
||||
|
||||
before(async () => {
|
||||
switchboard = await SwitchboardTestContextV2.loadFromProvider(provider, {
|
||||
switchboard = await SwitchboardTestContext.loadFromProvider(provider, {
|
||||
// You can provide a keypair to so the PDA schemes dont change between test runs
|
||||
name: "Test Queue",
|
||||
keypair: SwitchboardTestContextV2.loadKeypair("~/.keypairs/queue.json"),
|
||||
keypair: SwitchboardTestContext.loadKeypair("~/.keypairs/queue.json"),
|
||||
queueSize: 10,
|
||||
reward: 0,
|
||||
minStake: 0,
|
||||
|
@ -28,18 +30,33 @@ describe("anchor-buffer-parser test", () => {
|
|||
oracle: {
|
||||
name: "Test Oracle",
|
||||
enable: true,
|
||||
stakingWalletKeypair: SwitchboardTestContextV2.loadKeypair(
|
||||
stakingWalletKeypair: SwitchboardTestContext.loadKeypair(
|
||||
"~/.keypairs/oracleWallet.json"
|
||||
),
|
||||
},
|
||||
});
|
||||
await switchboard.start();
|
||||
|
||||
oracle = await NodeOracle.fromReleaseChannel({
|
||||
chain: "solana",
|
||||
releaseChannel: "testnet",
|
||||
network: "localnet", // disables production capabilities like monitoring and alerts
|
||||
rpcUrl: switchboard.program.connection.rpcEndpoint,
|
||||
oracleKey: switchboard.oracle.publicKey.toBase58(),
|
||||
secretPath: switchboard.walletPath,
|
||||
silent: false, // set to true to suppress oracle logs in the console
|
||||
envVariables: {
|
||||
VERBOSE: "1",
|
||||
DEBUG: "1",
|
||||
DISABLE_NONCE_QUEUE: "1",
|
||||
DISABLE_METRICS: "1",
|
||||
},
|
||||
});
|
||||
|
||||
await oracle.startAndAwait();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
if (switchboard) {
|
||||
switchboard.stop();
|
||||
}
|
||||
after(() => {
|
||||
oracle?.stop();
|
||||
});
|
||||
|
||||
it("Create and read buffer account", async () => {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"@coral-xyz/anchor": "^0.27.0",
|
||||
"@solana/web3.js": "^1.73.3",
|
||||
"@switchboard-xyz/common": "^2.1.33",
|
||||
"@switchboard-xyz/oracle": "^2.1.13",
|
||||
"@switchboard-xyz/solana.js": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -2,12 +2,11 @@ import * as anchor from "@coral-xyz/anchor";
|
|||
import { OracleJob, sleep } from "@switchboard-xyz/common";
|
||||
import {
|
||||
AggregatorAccount,
|
||||
SwitchboardTestContextV2,
|
||||
SwitchboardTestContext,
|
||||
} from "@switchboard-xyz/solana.js";
|
||||
import assert from "assert";
|
||||
import chai from "chai";
|
||||
import { AnchorFeedParser } from "../target/types/anchor_feed_parser";
|
||||
const expect = chai.expect;
|
||||
import { NodeOracle } from "@switchboard-xyz/oracle";
|
||||
|
||||
describe("anchor-feed-parser test", () => {
|
||||
const provider = anchor.AnchorProvider.env();
|
||||
|
@ -16,14 +15,16 @@ describe("anchor-feed-parser test", () => {
|
|||
const feedParserProgram: anchor.Program<AnchorFeedParser> =
|
||||
anchor.workspace.AnchorFeedParser;
|
||||
|
||||
let switchboard: SwitchboardTestContextV2;
|
||||
let switchboard: SwitchboardTestContext;
|
||||
let oracle: NodeOracle;
|
||||
|
||||
let aggregatorAccount: AggregatorAccount;
|
||||
|
||||
before(async () => {
|
||||
switchboard = await SwitchboardTestContextV2.loadFromProvider(provider, {
|
||||
switchboard = await SwitchboardTestContext.loadFromProvider(provider, {
|
||||
// You can provide a keypair to so the PDA schemes dont change between test runs
|
||||
name: "Test Queue",
|
||||
keypair: SwitchboardTestContextV2.loadKeypair("~/.keypairs/queue.json"),
|
||||
keypair: SwitchboardTestContext.loadKeypair("~/.keypairs/queue.json"),
|
||||
queueSize: 10,
|
||||
reward: 0,
|
||||
minStake: 0,
|
||||
|
@ -34,18 +35,33 @@ describe("anchor-feed-parser test", () => {
|
|||
oracle: {
|
||||
name: "Test Oracle",
|
||||
enable: true,
|
||||
stakingWalletKeypair: SwitchboardTestContextV2.loadKeypair(
|
||||
stakingWalletKeypair: SwitchboardTestContext.loadKeypair(
|
||||
"~/.keypairs/oracleWallet.json"
|
||||
),
|
||||
},
|
||||
});
|
||||
await switchboard.start();
|
||||
|
||||
oracle = await NodeOracle.fromReleaseChannel({
|
||||
chain: "solana",
|
||||
releaseChannel: "testnet",
|
||||
network: "localnet", // disables production capabilities like monitoring and alerts
|
||||
rpcUrl: switchboard.program.connection.rpcEndpoint,
|
||||
oracleKey: switchboard.oracle.publicKey.toBase58(),
|
||||
secretPath: switchboard.walletPath,
|
||||
silent: false, // set to true to suppress oracle logs in the console
|
||||
envVariables: {
|
||||
VERBOSE: "1",
|
||||
DEBUG: "1",
|
||||
DISABLE_NONCE_QUEUE: "1",
|
||||
DISABLE_METRICS: "1",
|
||||
},
|
||||
});
|
||||
|
||||
await oracle.startAndAwait();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
if (switchboard) {
|
||||
switchboard.stop();
|
||||
}
|
||||
after(() => {
|
||||
oracle?.stop();
|
||||
});
|
||||
|
||||
it("Creates a static feed that resolves to 100", async () => {
|
||||
|
|
|
@ -44,4 +44,4 @@ address = "7hkp1xfPBcD2t1vZMoWWQPzipHVcXeLAAaiGXdPSfDie"
|
|||
address="GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR"
|
||||
|
||||
[[test.validator.clone]] # sbv2 SOL/USD History Buffer
|
||||
address="7LLvRhMs73FqcLkA8jvEE1AM2mYZXTmqfUv8GAEurymx"
|
||||
address="9GPTMZmtNU61ULAZoGxDZmnZoWeF8zvBmKp4WZY6Ln6j"
|
|
@ -15,6 +15,7 @@
|
|||
"dependencies": {
|
||||
"@coral-xyz/anchor": "^0.27.0",
|
||||
"@switchboard-xyz/common": "^2.1.33",
|
||||
"@switchboard-xyz/oracle": "^2.1.13",
|
||||
"@switchboard-xyz/solana.js": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -4,15 +4,16 @@ import { Program } from "@coral-xyz/anchor";
|
|||
import { OracleJob } from "@switchboard-xyz/common";
|
||||
import {
|
||||
AggregatorAccount,
|
||||
SwitchboardTestContextV2,
|
||||
SwitchboardTestContext,
|
||||
} from "@switchboard-xyz/solana.js";
|
||||
import { AnchorHistoryParser } from "../target/types/anchor_history_parser";
|
||||
import { NodeOracle } from "@switchboard-xyz/oracle";
|
||||
|
||||
export const AGGREGATOR_PUBKEY: anchor.web3.PublicKey =
|
||||
new anchor.web3.PublicKey("GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR");
|
||||
|
||||
export const HISTORY_BUFFER_PUBKEY: anchor.web3.PublicKey =
|
||||
new anchor.web3.PublicKey("7LLvRhMs73FqcLkA8jvEE1AM2mYZXTmqfUv8GAEurymx");
|
||||
new anchor.web3.PublicKey("9GPTMZmtNU61ULAZoGxDZmnZoWeF8zvBmKp4WZY6Ln6j");
|
||||
|
||||
export const sleep = (ms: number): Promise<any> =>
|
||||
new Promise((s) => setTimeout(s, ms));
|
||||
|
@ -28,13 +29,14 @@ describe("anchor-history-parser", () => {
|
|||
let aggregatorAccount: AggregatorAccount;
|
||||
let historyBuffer: anchor.web3.PublicKey;
|
||||
|
||||
let switchboard: SwitchboardTestContextV2;
|
||||
let switchboard: SwitchboardTestContext;
|
||||
let oracle: NodeOracle;
|
||||
|
||||
before(async () => {
|
||||
switchboard = await SwitchboardTestContextV2.loadFromProvider(provider, {
|
||||
switchboard = await SwitchboardTestContext.loadFromProvider(provider, {
|
||||
// You can provide a keypair to so the PDA schemes dont change between test runs
|
||||
name: "Test Queue",
|
||||
keypair: SwitchboardTestContextV2.loadKeypair("~/.keypairs/queue.json"),
|
||||
keypair: SwitchboardTestContext.loadKeypair("~/.keypairs/queue.json"),
|
||||
queueSize: 10,
|
||||
reward: 0,
|
||||
minStake: 0,
|
||||
|
@ -45,18 +47,33 @@ describe("anchor-history-parser", () => {
|
|||
oracle: {
|
||||
name: "Test Oracle",
|
||||
enable: true,
|
||||
stakingWalletKeypair: SwitchboardTestContextV2.loadKeypair(
|
||||
stakingWalletKeypair: SwitchboardTestContext.loadKeypair(
|
||||
"~/.keypairs/oracleWallet.json"
|
||||
),
|
||||
},
|
||||
});
|
||||
await switchboard.start();
|
||||
|
||||
oracle = await NodeOracle.fromReleaseChannel({
|
||||
chain: "solana",
|
||||
releaseChannel: "testnet",
|
||||
network: "localnet", // disables production capabilities like monitoring and alerts
|
||||
rpcUrl: switchboard.program.connection.rpcEndpoint,
|
||||
oracleKey: switchboard.oracle.publicKey.toBase58(),
|
||||
secretPath: switchboard.walletPath,
|
||||
silent: false, // set to true to suppress oracle logs in the console
|
||||
envVariables: {
|
||||
VERBOSE: "1",
|
||||
DEBUG: "1",
|
||||
DISABLE_NONCE_QUEUE: "1",
|
||||
DISABLE_METRICS: "1",
|
||||
},
|
||||
});
|
||||
|
||||
await oracle.startAndAwait();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
if (switchboard) {
|
||||
switchboard.stop();
|
||||
}
|
||||
after(() => {
|
||||
oracle?.stop();
|
||||
});
|
||||
|
||||
/** Example showing how to create a new data feed with a history buffer storing 200k samples.
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"@solana/spl-token": "^0.3.6",
|
||||
"@solana/web3.js": "^1.73.3",
|
||||
"@switchboard-xyz/common": "^2.1.33",
|
||||
"@switchboard-xyz/oracle": "^2.1.13",
|
||||
"@switchboard-xyz/solana.js": "workspace:*",
|
||||
"chalk": "^4.1.2",
|
||||
"dotenv": "^16.0.1",
|
||||
|
|
|
@ -17,12 +17,13 @@ import {
|
|||
PermissionAccount,
|
||||
QueueAccount,
|
||||
SwitchboardProgram,
|
||||
SwitchboardTestContextV2,
|
||||
SwitchboardTestContext,
|
||||
SWITCHBOARD_LABS_DEVNET_PERMISSIONLESS_QUEUE,
|
||||
types,
|
||||
} from "@switchboard-xyz/solana.js";
|
||||
|
||||
import { AnchorVrfParser } from "../target/types/anchor_vrf_parser";
|
||||
import { NodeOracle } from "@switchboard-xyz/oracle";
|
||||
|
||||
describe("anchor-vrf-parser test", () => {
|
||||
const provider = AnchorProvider.env();
|
||||
|
@ -56,7 +57,9 @@ describe("anchor-vrf-parser test", () => {
|
|||
ixData: vrfIxCoder.encode("updateResult", ""), // pass any params for instruction here
|
||||
};
|
||||
|
||||
let switchboard: SwitchboardTestContextV2;
|
||||
let switchboard: SwitchboardTestContext;
|
||||
let oracle: NodeOracle;
|
||||
|
||||
let queueAccount: QueueAccount;
|
||||
let queue: types.OracleQueueAccountData;
|
||||
|
||||
|
@ -70,10 +73,10 @@ describe("anchor-vrf-parser test", () => {
|
|||
SWITCHBOARD_LABS_DEVNET_PERMISSIONLESS_QUEUE
|
||||
);
|
||||
} else {
|
||||
switchboard = await SwitchboardTestContextV2.loadFromProvider(provider, {
|
||||
switchboard = await SwitchboardTestContext.loadFromProvider(provider, {
|
||||
// You can provide a keypair to so the PDA schemes dont change between test runs
|
||||
name: "Test Queue",
|
||||
keypair: SwitchboardTestContextV2.loadKeypair("~/.keypairs/queue.json"),
|
||||
keypair: SwitchboardTestContext.loadKeypair("~/.keypairs/queue.json"),
|
||||
queueSize: 10,
|
||||
reward: 0,
|
||||
minStake: 0,
|
||||
|
@ -84,19 +87,36 @@ describe("anchor-vrf-parser test", () => {
|
|||
oracle: {
|
||||
name: "Test Oracle",
|
||||
enable: true,
|
||||
stakingWalletKeypair: SwitchboardTestContextV2.loadKeypair(
|
||||
stakingWalletKeypair: SwitchboardTestContext.loadKeypair(
|
||||
"~/.keypairs/oracleWallet.json"
|
||||
),
|
||||
},
|
||||
});
|
||||
queueAccount = switchboard.queue;
|
||||
queue = await queueAccount.loadData();
|
||||
await switchboard.start();
|
||||
|
||||
oracle = await NodeOracle.fromReleaseChannel({
|
||||
chain: "solana",
|
||||
releaseChannel: "testnet",
|
||||
network: "localnet", // disables production capabilities like monitoring and alerts
|
||||
rpcUrl: switchboard.program.connection.rpcEndpoint,
|
||||
oracleKey: switchboard.oracle.publicKey.toBase58(),
|
||||
secretPath: switchboard.walletPath,
|
||||
silent: false, // set to true to suppress oracle logs in the console
|
||||
envVariables: {
|
||||
VERBOSE: "1",
|
||||
DEBUG: "1",
|
||||
DISABLE_NONCE_QUEUE: "1",
|
||||
DISABLE_METRICS: "1",
|
||||
},
|
||||
});
|
||||
|
||||
await oracle.startAndAwait();
|
||||
}
|
||||
});
|
||||
|
||||
after(() => {
|
||||
switchboard?.stop();
|
||||
oracle?.stop();
|
||||
});
|
||||
|
||||
it("Creates a vrfClient account", async () => {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"@coral-xyz/anchor": "^0.27.0",
|
||||
"@solana/web3.js": "^1.73.3",
|
||||
"@switchboard-xyz/common": "^2.1.33",
|
||||
"@switchboard-xyz/oracle": "^2.1.13",
|
||||
"@switchboard-xyz/solana.js": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -6,9 +6,10 @@ import {
|
|||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { OracleJob, sleep } from "@switchboard-xyz/common";
|
||||
import { NodeOracle } from "@switchboard-xyz/oracle";
|
||||
import {
|
||||
AggregatorAccount,
|
||||
SwitchboardTestContextV2,
|
||||
SwitchboardTestContext,
|
||||
} from "@switchboard-xyz/solana.js";
|
||||
import assert from "assert";
|
||||
import fs from "fs";
|
||||
|
@ -37,13 +38,14 @@ describe("native-feed-parser test", () => {
|
|||
anchor.setProvider(provider);
|
||||
|
||||
let aggregatorAccount: AggregatorAccount;
|
||||
let switchboard: SwitchboardTestContextV2;
|
||||
let switchboard: SwitchboardTestContext;
|
||||
let oracle: NodeOracle;
|
||||
|
||||
before(async () => {
|
||||
switchboard = await SwitchboardTestContextV2.loadFromProvider(provider, {
|
||||
switchboard = await SwitchboardTestContext.loadFromProvider(provider, {
|
||||
// You can provide a keypair to so the PDA schemes dont change between test runs
|
||||
name: "Test Queue",
|
||||
keypair: SwitchboardTestContextV2.loadKeypair("~/.keypairs/queue.json"),
|
||||
keypair: SwitchboardTestContext.loadKeypair("~/.keypairs/queue.json"),
|
||||
queueSize: 10,
|
||||
reward: 0,
|
||||
minStake: 0,
|
||||
|
@ -54,18 +56,33 @@ describe("native-feed-parser test", () => {
|
|||
oracle: {
|
||||
name: "Test Oracle",
|
||||
enable: true,
|
||||
stakingWalletKeypair: SwitchboardTestContextV2.loadKeypair(
|
||||
stakingWalletKeypair: SwitchboardTestContext.loadKeypair(
|
||||
"~/.keypairs/oracleWallet.json"
|
||||
),
|
||||
},
|
||||
});
|
||||
await switchboard.start();
|
||||
|
||||
oracle = await NodeOracle.fromReleaseChannel({
|
||||
chain: "solana",
|
||||
releaseChannel: "testnet",
|
||||
network: "localnet", // disables production capabilities like monitoring and alerts
|
||||
rpcUrl: switchboard.program.connection.rpcEndpoint,
|
||||
oracleKey: switchboard.oracle.publicKey.toBase58(),
|
||||
secretPath: switchboard.walletPath,
|
||||
silent: false, // set to true to suppress oracle logs in the console
|
||||
envVariables: {
|
||||
VERBOSE: "1",
|
||||
DEBUG: "1",
|
||||
DISABLE_NONCE_QUEUE: "1",
|
||||
DISABLE_METRICS: "1",
|
||||
},
|
||||
});
|
||||
|
||||
await oracle.startAndAwait();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
if (switchboard) {
|
||||
switchboard.stop();
|
||||
}
|
||||
after(() => {
|
||||
oracle?.stop();
|
||||
});
|
||||
|
||||
it("Read SOL/USD Feed", async () => {
|
||||
|
|
Loading…
Reference in New Issue