sbv2-solana/javascript/solana.js/test/utilts.ts

246 lines
6.4 KiB
TypeScript

import * as sbv2 from '../src';
import fs from 'fs';
import os from 'os';
import path from 'path';
import {
clusterApiUrl,
Connection,
Keypair,
LAMPORTS_PER_SOL,
PublicKey,
} from '@solana/web3.js';
import dotenv from 'dotenv';
import {
AggregatorAccount,
CreateQueueFeedParams,
QueueAccount,
SBV2_DEVNET_PID,
SBV2_MAINNET_PID,
TransactionObject,
} from '../src';
import { OracleJob } from '@switchboard-xyz/common';
dotenv.config();
type SolanaCluster = 'localnet' | 'devnet' | 'mainnet-beta';
export const sleep = (ms: number): Promise<any> =>
new Promise(s => setTimeout(s, ms));
export const DEFAULT_KEYPAIR_PATH = path.join(
os.homedir(),
'.config/solana/id.json'
);
export interface TestContext {
cluster: SolanaCluster;
program: sbv2.SwitchboardProgram;
payer: Keypair;
toUrl: (signature: string) => string;
}
export function isLocalnet(): boolean {
if (process.env.SOLANA_LOCALNET) {
switch (process.env.SOLANA_LOCALNET) {
case '1':
case 'true':
case 'localnet': {
return true;
}
}
}
return false;
}
export function getCluster(): SolanaCluster {
if (process.env.SOLANA_CLUSTER) {
const cluster = String(process.env.SOLANA_CLUSTER);
if (
cluster === 'localnet' ||
cluster === 'devnet' ||
cluster === 'mainnet-beta'
) {
return cluster;
} else {
throw new Error(
`SOLANA_CLUSTER must be localnet, devnet, or mainnet-beta`
);
}
}
if (isLocalnet()) {
return 'localnet';
}
return 'devnet';
}
export function getProgramId(cluster: SolanaCluster): PublicKey {
if (process.env.SWITCHBOARD_PROGRAM_ID) {
return new PublicKey(process.env.SWITCHBOARD_PROGRAM_ID);
}
if (cluster === 'mainnet-beta') {
return SBV2_MAINNET_PID;
}
return SBV2_DEVNET_PID;
}
export function getRpcUrl(cluster: SolanaCluster): string {
if (process.env.SOLANA_RPC_URL) {
return String(process.env.SOLANA_RPC_URL);
}
if (cluster === 'localnet') {
return 'http://localhost:8899';
}
return clusterApiUrl(cluster);
}
export async function setupTest(): Promise<TestContext> {
const cluster = getCluster();
const payer: Keypair = fs.existsSync(DEFAULT_KEYPAIR_PATH)
? Keypair.fromSecretKey(
new Uint8Array(
JSON.parse(fs.readFileSync(DEFAULT_KEYPAIR_PATH, 'utf8'))
)
)
: Keypair.generate();
const programId = getProgramId(cluster);
const program = await sbv2.SwitchboardProgram.load(
cluster,
new Connection(getRpcUrl(cluster), { commitment: 'confirmed' }),
payer,
programId
);
// request airdrop if low on funds
const payerBalance = await program.connection.getBalance(payer.publicKey);
if (payerBalance === 0) {
const airdropTxn = await program.connection.requestAirdrop(
payer.publicKey,
1 * LAMPORTS_PER_SOL
);
console.log(`Airdrop requested: ${airdropTxn}`);
await program.connection.confirmTransaction(airdropTxn);
}
// Check if programStateAccount exists
try {
const programState = await program.connection.getAccountInfo(
program.programState.publicKey
);
if (!programState || programState.data === null) {
await sbv2.ProgramStateAccount.getOrCreate(program);
}
} catch (e) {
console.error(e);
}
await program.mint.getOrCreateAssociatedUser(program.walletPubkey);
return {
cluster,
program,
payer,
toUrl: signature =>
cluster === 'localnet'
? `https://explorer.solana.com/tx/${signature}?cluster=custom&customUrl=http%3A%2F%2Flocalhost%3A8899`
: `https://explorer.solana.com/tx/${signature}${
cluster === 'devnet' ? '?cluster=devnet' : ''
}`,
};
}
export async function createFeed(
queueAccount: QueueAccount,
feedConfig?: Partial<CreateQueueFeedParams>
): Promise<AggregatorAccount> {
const [aggregatorAccount] = await queueAccount.createFeed({
name: feedConfig?.name ?? `Aggregator`,
queueAuthority: feedConfig?.queueAuthority,
batchSize: feedConfig?.batchSize ?? 1,
minRequiredOracleResults: feedConfig?.minRequiredOracleResults ?? 1,
minRequiredJobResults: feedConfig?.minRequiredOracleResults ?? 1,
minUpdateDelaySeconds: feedConfig?.minUpdateDelaySeconds ?? 10,
fundAmount: feedConfig?.fundAmount ?? 0,
enable: feedConfig?.enable ?? true,
jobs:
feedConfig?.jobs && feedConfig?.jobs.length > 0
? feedConfig?.jobs
: [
{
weight: 2,
data: OracleJob.encodeDelimited(
OracleJob.fromObject({
tasks: [
{
valueTask: {
value: 1,
},
},
],
})
).finish(),
},
],
});
return aggregatorAccount;
}
export async function createFeeds(
queueAccount: QueueAccount,
numFeeds: number,
feedConfig?: Partial<CreateQueueFeedParams>
): Promise<Array<AggregatorAccount>> {
const aggregators: Array<AggregatorAccount> = [];
const txns: Array<Array<TransactionObject>> = [];
for (const i of Array.from(Array(numFeeds).keys())) {
const [aggregatorAccount, txn] = await queueAccount.createFeedInstructions(
queueAccount.program.walletPubkey,
{
name: feedConfig?.name ?? `Aggregator-${i + 1}`,
queueAuthority: feedConfig?.queueAuthority,
batchSize: feedConfig?.batchSize ?? 1,
minRequiredOracleResults: feedConfig?.minRequiredOracleResults ?? 1,
minRequiredJobResults: feedConfig?.minRequiredOracleResults ?? 1,
minUpdateDelaySeconds: feedConfig?.minUpdateDelaySeconds ?? 10,
fundAmount: feedConfig?.fundAmount ?? 0,
enable: feedConfig?.enable ?? true,
jobs:
feedConfig?.jobs && feedConfig?.jobs.length > 0
? feedConfig?.jobs
: [
{
weight: 2,
data: OracleJob.encodeDelimited(
OracleJob.fromObject({
tasks: [
{
valueTask: {
value: 1,
},
},
],
})
).finish(),
},
],
}
);
aggregators.push(aggregatorAccount);
txns.push(txn);
}
await queueAccount.program.signAndSendAll(
TransactionObject.pack(txns.flat())
);
return aggregators;
}