solana.js: test utilities

This commit is contained in:
Conner Gallagher 2023-01-04 12:20:32 -07:00
parent 82c3efbe45
commit 4be6ef79b2
4 changed files with 365 additions and 105 deletions

View File

@ -1,12 +1,12 @@
{ {
"name": "@switchboard-xyz/solana.js", "name": "@switchboard-xyz/solana.js",
"version": "2.0.68", "version": "2.0.76",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@switchboard-xyz/solana.js", "name": "@switchboard-xyz/solana.js",
"version": "2.0.68", "version": "2.0.76",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@project-serum/anchor": "^0.26.0", "@project-serum/anchor": "^0.26.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "@switchboard-xyz/solana.js", "name": "@switchboard-xyz/solana.js",
"version": "2.0.68", "version": "2.0.76",
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"description": "API wrapper for integrating with the Switchboard V2 program on Solana", "description": "API wrapper for integrating with the Switchboard V2 program on Solana",
@ -15,19 +15,7 @@
"import": "./lib/esm/index.js", "import": "./lib/esm/index.js",
"require": "./lib/cjs/index.js" "require": "./lib/cjs/index.js"
}, },
"./package.json": "./package.json", "./package.json": "./package.json"
"./idl/mainnet": {
"import": "./lib/esm/idl/mainnet.json",
"require": "./lib/cjs/idl/mainnet.json"
},
"./idl/devnet": {
"import": "./lib/esm/idl/devnet.json",
"require": "./lib/cjs/idl/devnet.json"
},
"./test": {
"import": "./lib/esm/test/index.js",
"require": "./lib/cjs/test/index.js"
}
}, },
"main": "lib/cjs/index.js", "main": "lib/cjs/index.js",
"module": "lib/esm/index.js", "module": "lib/esm/index.js",

View File

@ -56,6 +56,8 @@ import {
QueueDefinition, QueueDefinition,
VrfDefinition, VrfDefinition,
} from './types'; } from './types';
import path from 'path';
import fs from 'fs';
export const isKeypairString = (value: string): boolean => export const isKeypairString = (value: string): boolean =>
/^\[(\s)?[0-9]+((\s)?,(\s)?[0-9]+){31,}\]/.test(value); /^\[(\s)?[0-9]+((\s)?,(\s)?[0-9]+){31,}\]/.test(value);
@ -113,17 +115,122 @@ export class SwitchboardNetwork implements ISwitchboardNetwork {
vrfs: Array<VrfDefinition>; vrfs: Array<VrfDefinition>;
bufferRelayers: Array<BufferRelayerDefinition>; bufferRelayers: Array<BufferRelayerDefinition>;
crankMap: Map<string, CrankDefinition>;
oracleMap: Map<string, OracleDefinition>;
aggregatorMap: Map<string, AggregatorDefinition>;
vrfMap: Map<string, VrfDefinition>;
bufferRelayerMap: Map<string, BufferRelayerDefinition>;
constructor( constructor(
// readonly program: SwitchboardProgram, // readonly program: SwitchboardProgram,
fields: ISwitchboardNetwork fields: ISwitchboardNetwork
) { ) {
this.programState = fields.programState; this.programState = fields.programState;
this.queue = fields.queue; this.queue = fields.queue;
this.cranks = fields.cranks; this.cranks = fields.cranks;
this.crankMap = this.cranks.reduce((map, crank) => {
map.set(crank.account.publicKey.toBase58(), crank);
return map;
}, new Map<string, CrankDefinition>());
this.oracles = fields.oracles; this.oracles = fields.oracles;
this.oracleMap = this.oracles.reduce((map, oracle) => {
map.set(oracle.account.publicKey.toBase58(), oracle);
return map;
}, new Map<string, OracleDefinition>());
this.aggregators = fields.aggregators; this.aggregators = fields.aggregators;
this.aggregatorMap = this.aggregators.reduce((map, aggregator) => {
map.set(aggregator.account.publicKey.toBase58(), aggregator);
return map;
}, new Map<string, AggregatorDefinition>());
this.vrfs = fields.vrfs; this.vrfs = fields.vrfs;
this.vrfMap = this.vrfs.reduce((map, vrf) => {
map.set(vrf.account.publicKey.toBase58(), vrf);
return map;
}, new Map<string, VrfDefinition>());
this.bufferRelayers = fields.bufferRelayers; this.bufferRelayers = fields.bufferRelayers;
this.bufferRelayerMap = this.bufferRelayers.reduce((map, bufferRelayer) => {
map.set(bufferRelayer.account.publicKey.toBase58(), bufferRelayer);
return map;
}, new Map<string, BufferRelayerDefinition>());
}
get program(): SwitchboardProgram {
return this.queue.account.program;
}
getCrank(crankPubkey: PublicKey | string): CrankDefinition | undefined {
return this.crankMap.get(
typeof crankPubkey === 'string' ? crankPubkey : crankPubkey.toBase58()
);
}
getOracle(oraclePubkey: PublicKey | string): OracleDefinition | undefined {
return this.oracleMap.get(
typeof oraclePubkey === 'string' ? oraclePubkey : oraclePubkey.toBase58()
);
}
getAggregator(
aggregatorPubkey: PublicKey | string
): AggregatorDefinition | undefined {
return this.aggregatorMap.get(
typeof aggregatorPubkey === 'string'
? aggregatorPubkey
: aggregatorPubkey.toBase58()
);
}
getVrf(vrfPubkey: PublicKey | string): VrfDefinition | undefined {
return this.vrfMap.get(
typeof vrfPubkey === 'string' ? vrfPubkey : vrfPubkey.toBase58()
);
}
getBufferRelayer(
bufferRelayerPubkey: PublicKey | string
): BufferRelayerDefinition | undefined {
return this.bufferRelayerMap.get(
typeof bufferRelayerPubkey === 'string'
? bufferRelayerPubkey
: bufferRelayerPubkey.toBase58()
);
}
public static find(
program: SwitchboardProgram,
networkName = 'default',
switchboardDir = path.join(process.cwd(), '.switchboard')
): SwitchboardNetwork {
if (
!fs.existsSync(switchboardDir) ||
!fs.statSync(switchboardDir).isDirectory
) {
throw new Error(
`Failed to find switchboard directory: ${switchboardDir}`
);
}
const networkDir = path.join(switchboardDir, 'networks');
if (!fs.existsSync(networkDir) || !fs.statSync(networkDir).isDirectory) {
throw new Error(
`Failed to find switchboard network directory: ${networkDir}`
);
}
const networkFile = path.join(networkDir, `${networkName}.json`);
if (!fs.existsSync(networkFile) || !fs.statSync(networkFile).isFile) {
throw new Error(
`Failed to find switchboard network ${networkName}: ${networkFile}`
);
}
const obj: Record<string, any> = JSON.parse(
fs.readFileSync(networkFile, 'utf-8')
);
return SwitchboardNetwork.from(program, obj);
} }
/** /**
@ -720,92 +827,246 @@ export class SwitchboardNetwork implements ISwitchboardNetwork {
}); });
} }
// static from( static from(
// program: SwitchboardProgram, program: SwitchboardProgram,
// obj: Record<string, any> obj: Record<string, any>
// ): SwitchboardNetwork { ): SwitchboardNetwork {
// if (!('queue' in obj) || typeof obj.queue !== 'object') { const programState: ProgramStateDefinition = {
// throw new Error(`SwitchboardNetwork requires a queue object`); account: new ProgramStateAccount(program, program.programState.publicKey),
// } bump: program.programState.bump,
};
if (!('queue' in obj) || typeof obj.queue !== 'object') {
throw new Error(`SwitchboardNetwork requires a queue object`);
}
// let queueAccount: QueueAccount; let queueAccount: QueueAccount;
// if ('publicKey' in obj.queue) { if ('publicKey' in obj.queue) {
// queueAccount = new QueueAccount(program, obj.queue.publicKey); queueAccount = new QueueAccount(program, obj.queue.publicKey);
// } else if ( } else if (
// 'keypair' in obj.queue && 'keypair' in obj.queue &&
// typeof obj.queue.keypair === 'string' && typeof obj.queue.keypair === 'string' &&
// isKeypairString(obj.queue.keypair) isKeypairString(obj.queue.keypair)
// ) { ) {
// const queueKeypair = Keypair.fromSecretKey( const queueKeypair = Keypair.fromSecretKey(
// new Uint8Array(JSON.parse(obj.queue.keypair)) new Uint8Array(JSON.parse(obj.queue.keypair))
// ); );
// queueAccount = new QueueAccount(program, queueKeypair.publicKey); queueAccount = new QueueAccount(program, queueKeypair.publicKey);
// } else { } else {
// throw new Error(`Failed to load queue`); throw new Error(`Failed to load queue`);
// } }
const queue: QueueDefinition = {
account: queueAccount,
};
// const cranks: Array<CrankDefinition> = []; let queueAuthority: PublicKey;
// if ('cranks' in obj && Array.isArray(obj.cranks)) { if ('authority' in obj.queue && typeof obj.queue.authority === 'string') {
// for (const crank of obj.cranks ?? []) { queueAuthority = new PublicKey(obj.queue.authority);
// if ('publicKey' in crank) { } else if (
// const account = new CrankAccount(program, crank.publicKey); 'queueAuthorityKeypair' in obj.queue &&
// cranks.push({ typeof obj.queue.queueAuthorityKeypair === 'string' &&
// account, isKeypairString(obj.queue.queueAuthorityKeypair)
// }); ) {
// } else if ( const queueAuthorityKeypair = Keypair.fromSecretKey(
// 'keypair' in crank && new Uint8Array(JSON.parse(obj.queue.queueAuthorityKeypair))
// typeof crank.keypair === 'string' && );
// isKeypairString(crank.keypair) queueAuthority = queueAuthorityKeypair.publicKey;
// ) { } else {
// const keypair = Keypair.fromSecretKey( throw new Error(`Failed to load queue authority`);
// new Uint8Array(JSON.parse(crank.keypair)) }
// );
// const account = new CrankAccount(program, keypair.publicKey);
// cranks.push({
// account,
// });
// }
// }
// }
// const oracles: Array<OracleDefinition> = []; const cranks: Array<CrankDefinition> = [];
// if ('oracles' in obj && Array.isArray(obj.oracles)) { if ('cranks' in obj && Array.isArray(obj.cranks)) {
// for (const oracle of obj.oracles ?? []) { for (const crank of obj.cranks ?? []) {
// let account: OracleAccount | undefined = undefined; if ('publicKey' in crank) {
// if ('publicKey' in oracle) { const account = new CrankAccount(program, crank.publicKey);
// account = new OracleAccount(program, oracle.publicKey); cranks.push({
// } else if ( account,
// 'keypair' in oracle && });
// typeof oracle.keypair === 'string' && } else if (
// isKeypairString(oracle.keypair) 'keypair' in crank &&
// ) { typeof crank.keypair === 'string' &&
// const keypair = Keypair.fromSecretKey( isKeypairString(crank.keypair)
// new Uint8Array(JSON.parse(oracle.keypair)) ) {
// ); const keypair = Keypair.fromSecretKey(
// account = new OracleAccount(program, keypair.publicKey); new Uint8Array(JSON.parse(crank.keypair))
// } else if ( );
// 'stakingWalletKeypair' in oracle && const account = new CrankAccount(program, keypair.publicKey);
// typeof oracle.stakingWalletKeypair === 'string' && cranks.push({
// isKeypairString(oracle.stakingWalletKeypair) account,
// ) { });
// const keypair = Keypair.fromSecretKey( }
// new Uint8Array(JSON.parse(oracle.stakingWalletKeypair)) }
// ); }
// [account] = OracleAccount.fromSeed(
// program,
// queueAccount.publicKey,
// keypair.publicKey
// );
// }
// if (account) { const oracles: Array<OracleDefinition> = [];
// // const [permissionAccount, permissionBump] = PermissionAccount.fromSeed(program, queue.a) if ('oracles' in obj && Array.isArray(obj.oracles)) {
// } for (const oracle of obj.oracles ?? []) {
// } let account: OracleAccount | undefined = undefined;
// } if ('publicKey' in oracle) {
account = new OracleAccount(program, oracle.publicKey);
} else if (
'stakingWalletKeypair' in oracle &&
typeof oracle.stakingWalletKeypair === 'string' &&
isKeypairString(oracle.stakingWalletKeypair)
) {
const keypair = Keypair.fromSecretKey(
new Uint8Array(JSON.parse(oracle.stakingWalletKeypair))
);
[account] = OracleAccount.fromSeed(
program,
queueAccount.publicKey,
keypair.publicKey
);
}
// throw new Error(`Not finished yet`); if (account) {
// } const [permissionAccount, permissionBump] =
PermissionAccount.fromSeed(
program,
queueAuthority,
queueAccount.publicKey,
account.publicKey
);
oracles.push({
account,
permission: {
account: permissionAccount,
bump: permissionBump,
},
});
}
}
}
const aggregators: Array<AggregatorDefinition> = [];
if ('aggregators' in obj && Array.isArray(obj.aggregators)) {
for (const aggregator of obj.aggregators ?? []) {
let account: AggregatorAccount | undefined = undefined;
if ('publicKey' in aggregator) {
account = new AggregatorAccount(program, aggregator.publicKey);
} else if (
'keypair' in aggregator &&
typeof aggregator.keypair === 'string' &&
isKeypairString(aggregator.keypair)
) {
const keypair = Keypair.fromSecretKey(
new Uint8Array(JSON.parse(aggregator.keypair))
);
account = new AggregatorAccount(program, keypair.publicKey);
}
if (account) {
const [permissionAccount, permissionBump] =
PermissionAccount.fromSeed(
program,
queueAuthority,
queueAccount.publicKey,
account.publicKey
);
const [leaseAccount, leaseBump] = LeaseAccount.fromSeed(
program,
queueAccount.publicKey,
account.publicKey
);
aggregators.push({
account,
permission: {
account: permissionAccount,
bump: permissionBump,
},
lease: {
account: leaseAccount,
bump: leaseBump,
},
});
}
}
}
const vrfs: Array<VrfDefinition> = [];
if ('vrfs' in obj && Array.isArray(obj.vrfs)) {
for (const vrf of obj.vrfs ?? []) {
let account: VrfAccount | undefined = undefined;
if ('publicKey' in vrf) {
account = new VrfAccount(program, vrf.publicKey);
} else if (
'keypair' in vrf &&
typeof vrf.keypair === 'string' &&
isKeypairString(vrf.keypair)
) {
const keypair = Keypair.fromSecretKey(
new Uint8Array(JSON.parse(vrf.keypair))
);
account = new VrfAccount(program, keypair.publicKey);
}
if (account) {
const [permissionAccount, permissionBump] =
PermissionAccount.fromSeed(
program,
queueAuthority,
queueAccount.publicKey,
account.publicKey
);
vrfs.push({
account,
permission: {
account: permissionAccount,
bump: permissionBump,
},
});
}
}
}
const bufferRelayers: Array<BufferRelayerDefinition> = [];
if ('bufferRelayers' in obj && Array.isArray(obj.bufferRelayers)) {
for (const bufferRelayer of obj.bufferRelayers ?? []) {
let account: BufferRelayerAccount | undefined = undefined;
if ('publicKey' in bufferRelayer) {
account = new BufferRelayerAccount(program, bufferRelayer.publicKey);
} else if (
'keypair' in bufferRelayer &&
typeof bufferRelayer.keypair === 'string' &&
isKeypairString(bufferRelayer.keypair)
) {
const keypair = Keypair.fromSecretKey(
new Uint8Array(JSON.parse(bufferRelayer.keypair))
);
account = new BufferRelayerAccount(program, keypair.publicKey);
}
if (account) {
const [permissionAccount, permissionBump] =
PermissionAccount.fromSeed(
program,
queueAuthority,
queueAccount.publicKey,
account.publicKey
);
bufferRelayers.push({
account,
permission: {
account: permissionAccount,
bump: permissionBump,
},
});
}
}
}
return new SwitchboardNetwork({
programState,
queue,
oracles,
cranks,
aggregators,
vrfs,
bufferRelayers,
});
}
/** /**
* Load the associated accounts and states for a given {@linkcode QueueAccount}. * Load the associated accounts and states for a given {@linkcode QueueAccount}.
@ -1191,6 +1452,15 @@ export class LoadedSwitchboardNetwork implements ILoadedSwitchboardNetwork {
authority: vrf.state.authority.toBase58(), authority: vrf.state.authority.toBase58(),
state: { state: {
...vrf.state.toJSON(), ...vrf.state.toJSON(),
callback: {
...vrf.state.callback.toJSON(),
accounts: vrf.state.callback.accounts
.slice(0, vrf.state.callback.accountsLen)
.map(a => a.toJSON()),
ixData: `[${new Uint8Array(
vrf.state.callback.ixData.slice(0, vrf.state.callback.ixDataLen)
)}]`,
},
ebuf: undefined, ebuf: undefined,
builders: undefined, builders: undefined,
}, },

View File

@ -68,7 +68,7 @@ export class VrfJson implements IVrfJson {
static loadMultiple(object: Record<string, any>): Array<VrfJson> { static loadMultiple(object: Record<string, any>): Array<VrfJson> {
const vrfJsons: Array<VrfJson> = []; const vrfJsons: Array<VrfJson> = [];
if ('vrfs' in object && Array.isArray(object.aggregators)) { if ('vrfs' in object && Array.isArray(object.vrfs)) {
for (const vrf of object.vrfs) { for (const vrf of object.vrfs) {
vrfJsons.push(new VrfJson(vrf)); vrfJsons.push(new VrfJson(vrf));
} }
@ -81,7 +81,9 @@ export class VrfJson implements IVrfJson {
return { return {
callback: { callback: {
programId: this.callback.programId.toBase58(), programId: this.callback.programId.toBase58(),
accounts: this.callback.accounts.map(a => { accounts: this.callback.accounts
.filter(a => !a.pubkey.equals(PublicKey.default))
.map(a => {
return { return {
pubkey: a.pubkey.toBase58(), pubkey: a.pubkey.toBase58(),
isSigner: a.isSigner, isSigner: a.isSigner,