solana.js: added fetchAccounts logic

This commit is contained in:
Conner Gallagher 2022-12-07 13:13:08 -07:00
parent 5d1e136cff
commit 3741fe8bab
16 changed files with 383 additions and 44 deletions

View File

@ -100,7 +100,8 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
this.program,
this.publicKey
);
if (data === null) throw new errors.AccountNotFoundError(this.publicKey);
if (data === null)
throw new errors.AccountNotFoundError('Aggregator', this.publicKey);
this.history = AggregatorHistoryBuffer.fromAggregator(this.program, data);
return data;
}
@ -1265,17 +1266,23 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
return txnSignature;
}
public async toAccountsJSON(
public async fetchAccounts(
_aggregator?: types.AggregatorAccountData,
_queueAccount?: QueueAccount,
_queue?: types.OracleQueueAccountData
): Promise<AggregatorAccountsJSON> {
): Promise<AggregatorAccounts> {
const aggregator = _aggregator ?? (await this.loadData());
const queueAccount =
_queueAccount ?? new QueueAccount(this.program, aggregator.queuePubkey);
const queue = _queue ?? (await queueAccount.loadData());
const { permissionAccount, leaseAccount, leaseEscrow } = this.getAccounts({
const {
permissionAccount,
permissionBump,
leaseAccount,
leaseEscrow,
leaseBump,
} = this.getAccounts({
queueAccount,
queueAuthority: queue.authority,
});
@ -1324,10 +1331,11 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
leaseEscrowAccountInfo.account
);
const jobs: (types.JobAccountDataJSON & {
const jobs: Array<{
publicKey: PublicKey;
data: types.JobAccountData;
tasks: Array<OracleJob.ITask>;
})[] = [];
}> = [];
accountInfos.map(accountInfo => {
if (!accountInfo || !accountInfo.account) {
throw new Error(`Failed to fetch JobAccount`);
@ -1336,31 +1344,74 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
const oracleJob = OracleJob.decodeDelimited(job.data);
jobs.push({
publicKey: accountInfo.publicKey,
...job.toJSON(),
data: job,
tasks: oracleJob.tasks,
});
});
return {
publicKey: this.publicKey,
...aggregator.toJSON(),
aggregator: {
publicKey: this.publicKey,
data: aggregator,
},
queue: {
publicKey: queueAccount.publicKey,
...queue.toJSON(),
data: queue,
},
permission: {
publicKey: permissionAccount.publicKey,
...permission.toJSON(),
bump: permissionBump,
data: permission,
},
lease: {
publicKey: leaseAccount.publicKey,
...lease.toJSON(),
bump: leaseBump,
data: lease,
balance: this.program.mint.fromTokenAmount(leaseEscrowAccount.amount),
},
jobs: jobs,
};
}
public async toAccountsJSON(
_aggregator?: types.AggregatorAccountData,
_queueAccount?: QueueAccount,
_queue?: types.OracleQueueAccountData
): Promise<AggregatorAccountsJSON> {
const accounts = await this.fetchAccounts(
_aggregator,
_queueAccount,
_queue
);
return {
publicKey: this.publicKey,
...accounts.aggregator.data.toJSON(),
queue: {
publicKey: accounts.queue.publicKey,
...accounts.queue.data.toJSON(),
},
permission: {
publicKey: accounts.permission.publicKey,
bump: accounts.permission.bump,
...accounts.permission.data.toJSON(),
},
lease: {
publicKey: accounts.lease.publicKey,
bump: accounts.lease.bump,
balance: accounts.lease.balance,
...accounts.lease.data.toJSON(),
},
jobs: accounts.jobs.map(j => {
return {
publicKey: j.publicKey,
...j.data.toJSON(),
tasks: j.tasks,
};
}),
};
}
setSlidingWindowInstruction(
payer: PublicKey,
params: {
@ -1617,8 +1668,11 @@ export interface AggregatorSaveResultParams {
export type AggregatorAccountsJSON = types.AggregatorAccountDataJSON & {
publicKey: PublicKey;
queue: types.OracleQueueAccountDataJSON & { publicKey: PublicKey };
permission: types.PermissionAccountDataJSON & { publicKey: PublicKey };
lease: types.LeaseAccountDataJSON & { publicKey: PublicKey } & {
permission: types.PermissionAccountDataJSON & {
bump: number;
publicKey: PublicKey;
};
lease: types.LeaseAccountDataJSON & { bump: number; publicKey: PublicKey } & {
balance: number;
};
jobs: Array<
@ -1628,3 +1682,29 @@ export type AggregatorAccountsJSON = types.AggregatorAccountDataJSON & {
}
>;
};
export type AggregatorAccounts = {
aggregator: {
publicKey: PublicKey;
data: types.AggregatorAccountData;
};
queue: {
publicKey: PublicKey;
data: types.OracleQueueAccountData;
};
permission: {
publicKey: PublicKey;
bump: number;
data: types.PermissionAccountData;
};
lease: {
publicKey: PublicKey;
bump: number;
balance: number;
data: types.LeaseAccountData;
};
jobs: Array<{
publicKey: PublicKey;
data: types.JobAccountData;
tasks: Array<OracleJob.ITask>;
}>;
};

View File

@ -1,4 +1,5 @@
import * as types from '../generated';
import * as borsh from '@project-serum/borsh'; // eslint-disable-line @typescript-eslint/no-unused-vars
import * as anchor from '@project-serum/anchor';
import { Account, OnAccountChangeCallback } from './account';
import * as errors from '../errors';
@ -119,7 +120,10 @@ export class AggregatorHistoryBuffer extends Account<
this.publicKey
);
if (bufferAccountInfo === null) {
throw new errors.AccountNotFoundError(this.publicKey);
throw new errors.AccountNotFoundError(
'Aggregator History',
this.publicKey
);
}
return AggregatorHistoryBuffer.decode(bufferAccountInfo.data);
}

View File

@ -90,7 +90,8 @@ export class BufferRelayerAccount extends Account<types.BufferRelayerAccountData
this.program,
this.publicKey
);
if (data === null) throw new errors.AccountNotFoundError(this.publicKey);
if (data === null)
throw new errors.AccountNotFoundError('Buffer Relayer', this.publicKey);
return data;
}

View File

@ -73,7 +73,8 @@ export class CrankAccount extends Account<types.CrankAccountData> {
this.program,
this.publicKey
);
if (data === null) throw new errors.AccountNotFoundError(this.publicKey);
if (data === null)
throw new errors.AccountNotFoundError('Crank', this.publicKey);
if (!this.dataBuffer) {
this.dataBuffer = CrankDataBuffer.fromCrank(this.program, data);
}

View File

@ -48,7 +48,10 @@ export class CrankDataBuffer extends Account<Array<types.CrankRow>> {
this.publicKey
);
if (accountInfo === null)
throw new errors.AccountNotFoundError(this.publicKey);
throw new errors.AccountNotFoundError(
'Crank Data Buffer',
this.publicKey
);
const data = CrankDataBuffer.decode(accountInfo);
return data;
}

View File

@ -12,3 +12,29 @@ export * from './programStateAccount';
export * from './queueAccount';
export * from './queueDataBuffer';
export * from './vrfAccount';
export const BUFFER_DISCRIMINATOR = Buffer.from([
42,
55,
46,
46,
45,
52,
78,
78, // BUFFERxx
]);
export type SwitchboardAccountTypes =
| 'Aggregator'
| 'AggregatorHistory'
| 'BufferRelayer'
| 'Crank'
| 'CrankBuffer'
| 'Job'
| 'Lease'
| 'Oracle'
| 'Permission'
| 'ProgramState'
| 'Queue'
| 'QueueBuffer'
| 'Vrf';

View File

@ -53,7 +53,8 @@ export class JobAccount extends Account<types.JobAccountData> {
*/
public async loadData(): Promise<types.JobAccountData> {
const data = await types.JobAccountData.fetch(this.program, this.publicKey);
if (data === null) throw new errors.AccountNotFoundError(this.publicKey);
if (data === null)
throw new errors.AccountNotFoundError('Job', this.publicKey);
return data;
}

View File

@ -73,7 +73,8 @@ export class LeaseAccount extends Account<types.LeaseAccountData> {
this.program,
this.publicKey
);
if (data === null) throw new errors.AccountNotFoundError(this.publicKey);
if (data === null)
throw new errors.AccountNotFoundError('Lease', this.publicKey);
return data;
}
@ -260,7 +261,7 @@ export class LeaseAccount extends Account<types.LeaseAccountData> {
escrow ?? this.program.mint.getAssociatedAddress(this.publicKey);
const escrowBalance = await this.program.mint.getBalance(escrowPubkey);
if (escrowBalance === null) {
throw new errors.AccountNotFoundError(escrowPubkey);
throw new errors.AccountNotFoundError('Lease Escrow', escrowPubkey);
}
return escrowBalance;
}
@ -283,7 +284,7 @@ export class LeaseAccount extends Account<types.LeaseAccountData> {
// decode aggregator
const aggregatorAccountInfo = accountInfos.shift();
if (!aggregatorAccountInfo) {
throw new errors.AccountNotFoundError(lease.aggregator);
throw new errors.AccountNotFoundError('Aggregator', lease.aggregator);
}
const aggregator: types.AggregatorAccountData = coder.decode(
AggregatorAccount.accountName,
@ -293,7 +294,7 @@ export class LeaseAccount extends Account<types.LeaseAccountData> {
// decode queue
const queueAccountInfo = accountInfos.shift();
if (!queueAccountInfo) {
throw new errors.AccountNotFoundError(lease.queue);
throw new errors.AccountNotFoundError('Queue', lease.queue);
}
const queue: types.OracleQueueAccountData = coder.decode(
QueueAccount.accountName,
@ -351,7 +352,7 @@ export class LeaseAccount extends Account<types.LeaseAccountData> {
);
const aggregatorAccountInfo = accountInfos.shift();
if (!aggregatorAccountInfo) {
throw new errors.AccountNotFoundError(lease.aggregator);
throw new errors.AccountNotFoundError('Aggregator', lease.aggregator);
}
const aggregator: types.AggregatorAccountData = coder.decode(
AggregatorAccount.accountName,
@ -363,7 +364,7 @@ export class LeaseAccount extends Account<types.LeaseAccountData> {
// decode queue
const queueAccountInfo = accountInfos.shift();
if (!queueAccountInfo) {
throw new errors.AccountNotFoundError(lease.queue);
throw new errors.AccountNotFoundError('Queue', lease.queue);
}
const jobWallets: Array<PublicKey> = [];
@ -483,13 +484,13 @@ export class LeaseAccount extends Account<types.LeaseAccountData> {
);
const aggregatorAccountInfo = accountInfos.shift();
if (!aggregatorAccountInfo) {
throw new errors.AccountNotFoundError(lease.aggregator);
throw new errors.AccountNotFoundError('Aggregator', lease.aggregator);
}
// decode queue
const queueAccountInfo = accountInfos.shift();
if (!queueAccountInfo) {
throw new errors.AccountNotFoundError(lease.queue);
throw new errors.AccountNotFoundError('Queue', lease.queue);
}
const leaseBump = LeaseAccount.fromSeed(

View File

@ -88,7 +88,8 @@ export class OracleAccount extends Account<types.OracleAccountData> {
this.program,
this.publicKey
);
if (data === null) throw new errors.AccountNotFoundError(this.publicKey);
if (data === null)
throw new errors.AccountNotFoundError('Oracle', this.publicKey);
return data;
}

View File

@ -181,7 +181,8 @@ export class PermissionAccount extends Account<types.PermissionAccountData> {
this.program,
this.publicKey
);
if (data === null) throw new errors.AccountNotFoundError(this.publicKey);
if (data === null)
throw new errors.AccountNotFoundError('Permissions', this.publicKey);
return data;
}

View File

@ -198,7 +198,8 @@ export class ProgramStateAccount extends Account<types.SbState> {
*/
public async loadData(): Promise<types.SbState> {
const data = await types.SbState.fetch(this.program, this.publicKey);
if (data === null) throw new errors.AccountNotFoundError(this.publicKey);
if (data === null)
throw new errors.AccountNotFoundError('Program State', this.publicKey);
return data;
}
/**

View File

@ -102,7 +102,8 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
this.program,
this.publicKey
);
if (data === null) throw new errors.AccountNotFoundError(this.publicKey);
if (data === null)
throw new errors.AccountNotFoundError('Queue', this.publicKey);
this.dataBuffer = new QueueDataBuffer(this.program, data.dataBuffer);
return data;
}

View File

@ -47,7 +47,10 @@ export class QueueDataBuffer extends Account<Array<PublicKey>> {
this.publicKey
);
if (accountInfo === null)
throw new errors.AccountNotFoundError(this.publicKey);
throw new errors.AccountNotFoundError(
'Oracle Queue Buffer',
this.publicKey
);
const data = QueueDataBuffer.decode(accountInfo);
return data;
}

View File

@ -71,7 +71,8 @@ export class VrfAccount extends Account<types.VrfAccountData> {
*/
async loadData(): Promise<types.VrfAccountData> {
const data = await types.VrfAccountData.fetch(this.program, this.publicKey);
if (data === null) throw new errors.AccountNotFoundError(this.publicKey);
if (data === null)
throw new errors.AccountNotFoundError('Vrf', this.publicKey);
return data;
}
@ -425,44 +426,76 @@ export class VrfAccount extends Account<types.VrfAccountData> {
};
}
public async toAccountsJSON(
public async fetchAccounts(
_vrf?: types.VrfAccountData,
_queueAccount?: QueueAccount,
_queue?: types.OracleQueueAccountData
): Promise<VrfAccountsJSON> {
): Promise<VrfAccounts> {
const vrf = _vrf ?? (await this.loadData());
const queueAccount =
_queueAccount ?? new QueueAccount(this.program, vrf.oracleQueue);
const queue = _queue ?? (await queueAccount.loadData());
const { permissionAccount } = this.getAccounts({
const { permissionAccount, permissionBump } = this.getAccounts({
queueAccount,
queueAuthority: queue.authority,
});
const permission = await permissionAccount.loadData();
const vrfEscrowBalance: number =
(await this.program.mint.getBalance(vrf.escrow)) ?? 0;
const vrfEscrow = await this.program.mint.getAccount(vrf.escrow);
if (!vrfEscrow) {
throw new errors.AccountNotFoundError('Vrf Escrow', vrf.escrow);
}
const vrfEscrowBalance: number = this.program.mint.fromTokenAmount(
vrfEscrow.amount
);
return {
publicKey: this.publicKey,
...vrf.toJSON(),
vrf: { publicKey: this.publicKey, data: vrf },
queue: {
publicKey: queueAccount.publicKey,
...queue.toJSON(),
data: queue,
},
permission: {
publicKey: permissionAccount.publicKey,
...permission.toJSON(),
bump: permissionBump,
data: permission,
},
escrow: {
publicKey: vrf.escrow,
data: vrfEscrow,
balance: vrfEscrowBalance,
},
};
}
public async toAccountsJSON(
_vrf?: types.VrfAccountData,
_queueAccount?: QueueAccount,
_queue?: types.OracleQueueAccountData
): Promise<VrfAccountsJSON> {
const accounts = await this.fetchAccounts(_vrf, _queueAccount, _queue);
return {
publicKey: this.publicKey,
...accounts.vrf.data.toJSON(),
queue: {
publicKey: accounts.queue.publicKey,
...accounts.queue.data.toJSON(),
},
permission: {
publicKey: accounts.permission.publicKey,
...accounts.permission.data.toJSON(),
},
escrow: {
publicKey: accounts.escrow.publicKey,
balance: accounts.escrow.balance,
},
};
}
/**
* Await for the next vrf result
*
@ -603,3 +636,24 @@ export type VrfAccountsJSON = Omit<types.VrfAccountDataJSON, 'escrow'> & {
permission: types.PermissionAccountDataJSON & { publicKey: PublicKey };
escrow: { publicKey: PublicKey; balance: number };
};
export type VrfAccounts = {
vrf: {
publicKey: PublicKey;
data: types.VrfAccountData;
};
queue: {
publicKey: PublicKey;
data: types.OracleQueueAccountData;
};
permission: {
publicKey: PublicKey;
bump: number;
data: types.PermissionAccountData;
};
escrow: {
publicKey: PublicKey;
data: spl.Account;
balance: number;
};
};

View File

@ -20,8 +20,8 @@ export class ExistingKeypair extends Error {
}
}
export class AccountNotFoundError extends Error {
constructor(publicKey: anchor.web3.PublicKey) {
super(`No account was found at the address: ${publicKey.toBase58()}`);
constructor(label: string, publicKey: anchor.web3.PublicKey) {
super(`Failed to find ${label} at address ${publicKey.toBase58()}`);
Object.setPrototypeOf(this, AccountNotFoundError.prototype);
}
}

View File

@ -14,6 +14,21 @@ import { TransactionObject } from './transaction';
import { SwitchboardEvents } from './switchboardEvents';
import { fromCode as fromSwitchboardCode } from './generated/errors/custom';
import { fromCode as fromAnchorCode } from './generated/errors/anchor';
import { ACCOUNT_DISCRIMINATOR_SIZE } from '@project-serum/anchor';
import {
AggregatorAccountData,
BufferRelayerAccountData,
CrankAccountData,
JobAccountData,
LeaseAccountData,
OracleAccountData,
OracleQueueAccountData,
PermissionAccountData,
SbState,
SlidingResultAccountData,
VrfAccountData,
} from './generated';
import { AggregatorAccount } from './accounts';
const DEVNET_GENESIS_HASH = 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG';
@ -408,6 +423,143 @@ export class SwitchboardProgram {
throw error;
}
}
async getProgramAccounts(): Promise<{
aggregators: Map<string, AggregatorAccountData>;
buffers: Map<string, Buffer>;
bufferRelayers: Map<string, BufferRelayerAccountData>;
cranks: Map<string, CrankAccountData>;
jobs: Map<string, JobAccountData>;
leases: Map<string, LeaseAccountData>;
oracles: Map<string, OracleAccountData>;
permissions: Map<string, PermissionAccountData>;
programState: Map<string, SbState>;
queues: Map<string, OracleQueueAccountData>;
slidingResult: Map<string, SlidingResultAccountData>;
vrfs: Map<string, VrfAccountData>;
}> {
const accountInfos: Array<AccountInfoResponse> =
await this.connection.getProgramAccounts(this.programId);
// buffer - [42, 55, 46, 46, 45, 52, 78, 78]
// bufferRelayer - [50, 35, 51, 115, 169, 219, 158, 52]
// lease - [55, 254, 208, 251, 164, 44, 150, 50]
// permissions - [77, 37, 177, 164, 38, 39, 34, 109]
// slidingResult - [91, 4, 83, 187, 102, 216, 153, 254]
// vrf - [101, 35, 62, 239, 103, 151, 6, 18]
// crank - [111, 81, 146, 73, 172, 180, 134, 209]
// job - [124, 69, 101, 195, 229, 218, 144, 63]
// oracles - [128, 30, 16, 241, 170, 73, 55, 54]
// sbState - [159, 42, 192, 191, 139, 62, 168, 28]
// queue - [164, 207, 200, 51, 199, 113, 35, 109]
// aggregator - [217, 230, 65, 101, 201, 162, 27, 125]
const discriminatorMap: Map<
Buffer,
Array<AccountInfoResponse>
> = accountInfos.reduce((map, account) => {
const discriminator = account.account.data.slice(
0,
ACCOUNT_DISCRIMINATOR_SIZE
);
if (map.has(discriminator)) {
const accounts = map.get(discriminator)!;
map.set(discriminator, [...accounts, account]);
} else {
map.set(discriminator, [account]);
}
return map;
}, new Map<Buffer, Array<AccountInfoResponse>>());
function decodeAccounts<T>(
accounts: Array<AccountInfoResponse>,
decode: (data: Buffer) => T
): Map<string, T> {
return accounts.reduce((map, account) => {
map.set(account.pubkey.toBase58(), decode(account.account.data));
return map;
}, new Map<string, T>());
}
const aggregators: Map<string, AggregatorAccountData> = decodeAccounts(
discriminatorMap.get(AggregatorAccountData.discriminator) ?? [],
AggregatorAccountData.decode
);
// TODO: Use aggregator.historyBuffer, crank.dataBuffer, queue.dataBuffer to filter these down and decode
const buffers: Map<string, Buffer> = (
discriminatorMap.get(sbv2.BUFFER_DISCRIMINATOR) ?? []
).reduce((map, buffer) => {
map.set(buffer.pubkey.toBase58(), buffer.account.data);
return map;
}, new Map<string, Buffer>());
const bufferRelayers: Map<string, BufferRelayerAccountData> =
decodeAccounts(
discriminatorMap.get(BufferRelayerAccountData.discriminator) ?? [],
BufferRelayerAccountData.decode
);
const cranks: Map<string, CrankAccountData> = decodeAccounts(
discriminatorMap.get(CrankAccountData.discriminator) ?? [],
CrankAccountData.decode
);
const jobs: Map<string, JobAccountData> = decodeAccounts(
discriminatorMap.get(JobAccountData.discriminator) ?? [],
JobAccountData.decode
);
const leases: Map<string, LeaseAccountData> = decodeAccounts(
discriminatorMap.get(LeaseAccountData.discriminator) ?? [],
LeaseAccountData.decode
);
const oracles: Map<string, OracleAccountData> = decodeAccounts(
discriminatorMap.get(OracleAccountData.discriminator) ?? [],
OracleAccountData.decode
);
const permissions: Map<string, PermissionAccountData> = decodeAccounts(
discriminatorMap.get(PermissionAccountData.discriminator) ?? [],
PermissionAccountData.decode
);
const programState: Map<string, SbState> = decodeAccounts(
discriminatorMap.get(SbState.discriminator) ?? [],
SbState.decode
);
const queues: Map<string, OracleQueueAccountData> = decodeAccounts(
discriminatorMap.get(OracleQueueAccountData.discriminator) ?? [],
OracleQueueAccountData.decode
);
const slidingResult: Map<string, SlidingResultAccountData> = decodeAccounts(
discriminatorMap.get(SlidingResultAccountData.discriminator) ?? [],
SlidingResultAccountData.decode
);
const vrfs: Map<string, VrfAccountData> = decodeAccounts(
discriminatorMap.get(VrfAccountData.discriminator) ?? [],
VrfAccountData.decode
);
return {
aggregators,
buffers,
bufferRelayers,
cranks,
jobs,
leases,
oracles,
permissions,
programState,
slidingResult,
queues,
vrfs,
};
}
}
export class AnchorWallet implements anchor.Wallet {
@ -428,3 +580,12 @@ export class AnchorWallet implements anchor.Wallet {
return txns.map(this.sign);
}
}
interface AccountInfoResponse {
pubkey: anchor.web3.PublicKey;
account: anchor.web3.AccountInfo<Buffer>;
}
interface DecodableAccount {
decode: (data: Buffer) => DecodableAccount;
}