solana.js: docs

This commit is contained in:
Conner Gallagher 2022-11-28 23:50:27 -07:00
parent 5d34a1d46f
commit 77085caa4a
3 changed files with 449 additions and 159 deletions

View File

@ -99,6 +99,94 @@ export interface AggregatorInitParams {
queueAuthority: PublicKey;
}
export type AggregatorSetConfigParams = Partial<{
name: Buffer;
metadata: Buffer;
batchSize: number;
minOracleResults: number;
minJobResults: number;
minUpdateDelaySeconds: number;
forceReportPeriod: number;
varianceThreshold: number;
}>;
export interface AggregatorSetQueueParams {
queueAccount: QueueAccount;
authority?: Keypair;
}
/**
* Parameters required to open an aggregator round
*/
export interface AggregatorOpenRoundParams {
/**
* The oracle queue from which oracles are assigned this update.
*/
oracleQueueAccount: QueueAccount;
/**
* The token wallet which will receive rewards for calling update on this feed.
*/
payoutWallet: PublicKey;
}
/**
* Parameters for creating and setting a history buffer for an aggregator
*/
export interface AggregatorSetHistoryBufferParams {
/*
* Authority keypair for the aggregator.
*/
authority?: Keypair;
/*
* Number of elements for the history buffer to fit.
*/
size: number;
}
/**
* Parameters for which oracles must submit for responding to update requests.
*/
export interface AggregatorSaveResultParams {
/**
* Index in the list of oracles in the aggregator assigned to this round update.
*/
oracleIdx: number;
/**
* Reports that an error occured and the oracle could not send a value.
*/
error: boolean;
/**
* Value the oracle is responding with for this update.
*/
value: Big;
/**
* The minimum value this oracle has seen this round for the jobs listed in the
* aggregator.
*/
minResponse: Big;
/**
* The maximum value this oracle has seen this round for the jobs listed in the
* aggregator.
*/
maxResponse: Big;
/**
* List of OracleJobs that were performed to produce this result.
*/
jobs: Array<OracleJob>;
/**
* Authority of the queue the aggregator is attached to.
*/
queueAuthority: PublicKey;
/**
* Program token mint.
*/
tokenMint: PublicKey;
/**
* List of parsed oracles.
*/
oracles: Array<any>;
}
export class AggregatorAccount extends Account<types.AggregatorAccountData> {
static accountName = 'AggregatorAccountData';
@ -131,6 +219,46 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
return data;
}
public getAccounts(params: {
queueAccount: QueueAccount;
queueAuthority: PublicKey;
}): {
queueAccount: QueueAccount;
permissionAccount: PermissionAccount;
permissionBump: number;
leaseAccount: LeaseAccount;
leaseBump: number;
leaseEscrow: PublicKey;
} {
const queueAccount = params.queueAccount;
const [permissionAccount, permissionBump] = PermissionAccount.fromSeed(
this.program,
params.queueAuthority,
queueAccount.publicKey,
this.publicKey
);
const [leaseAccount, leaseBump] = LeaseAccount.fromSeed(
this.program,
queueAccount.publicKey,
this.publicKey
);
const leaseEscrow = this.program.mint.getAssociatedAddress(
leaseAccount.publicKey
);
return {
queueAccount,
permissionAccount,
permissionBump,
leaseAccount,
leaseBump,
leaseEscrow,
};
}
public static async load(
program: SwitchboardProgram,
publicKey: PublicKey
@ -181,7 +309,7 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
* ```
* @param program The SwitchboardProgram.
* @param payer The account that will pay for the new accounts.
* @param params {@linkcode AggregatorInitParams}.
* @param params aggregator configuration parameters.
* @return {@linkcode TransactionObject} that will create the aggregatorAccount.
*/
public static async createInstruction(
@ -244,6 +372,27 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
];
}
/**
* Creates a transaction object with aggregatorInit instructions.
*
* Basic usage example:
*
* ```ts
* import {AggregatorAccount} from '@switchboard-xyz/solana.js';
* const [aggregatorInit, aggregatorAccount] = await AggregatorAccount.createInstruction(program, payer, {
* queueAccount,
* queueAuthority,
* batchSize: 5,
* minRequiredOracleResults: 3,
* minRequiredJobResults: 1,
* minUpdateDelaySeconds: 30,
* });
* const txnSignature = await program.signAndSend(aggregatorInit);
* ```
* @param program The SwitchboardProgram.
* @param params aggregator configuration parameters.
* @return Transaction signature and the newly created aggregatorAccount.
*/
public static async create(
program: SwitchboardProgram,
params: AggregatorInitParams
@ -318,7 +467,7 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
/**
* Get the latest confirmed value stored in the aggregator account.
* @return latest feed value
* @return latest feed value or null if not populated
*/
public async fetchLatestValue(): Promise<Big | null> {
const aggregator = await this.loadData();
@ -794,66 +943,21 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
permission?: [PermissionAccount, number];
}>
): Promise<TransactionObject> {
const aggregator = params.aggregator ?? (await this.loadData());
const queueAccount =
params.queueAccount ??
new QueueAccount(this.program, aggregator.queuePubkey);
new QueueAccount(this.program, (await this.loadData()).queuePubkey);
const queue = await queueAccount.loadData();
let queueAuthority = params.queueAuthority;
let queueDataBuffer = params.queueDataBuffer;
let queue = params.queue;
if (params.queue) {
queueAuthority = params.queue.authority;
queueDataBuffer = params.queue.dataBuffer;
}
if (!queueAuthority || !queueDataBuffer) {
queue = await queueAccount.loadData();
queueAuthority = queue.authority;
queueDataBuffer = queue.dataBuffer;
}
const [leaseAccount, leaseBump] =
params.lease ??
LeaseAccount.fromSeed(
this.program,
queueAccount.publicKey,
this.publicKey
);
let lease: types.LeaseAccountData;
try {
lease = await leaseAccount.loadData();
} catch (_) {
throw new Error(
'A requested lease pda account has not been initialized.'
);
}
const leaseEscrow =
params.leaseEscrow ??
lease.escrow ??
spl.getAssociatedTokenAddressSync(
this.program.mint.address,
leaseAccount.publicKey,
true
);
const [permissionAccount, permissionBump] =
params.permission ??
PermissionAccount.fromSeed(
this.program,
queueAuthority,
queueAccount.publicKey,
this.publicKey
);
let permission: types.PermissionAccountData;
try {
permission = await permissionAccount.loadData();
} catch (_) {
throw new Error(
'A requested aggregator permission pda account has not been initialized.'
);
}
const {
permissionAccount,
permissionBump,
leaseAccount,
leaseBump,
leaseEscrow,
} = this.getAccounts({
queueAccount: queueAccount,
queueAuthority: queue.authority,
});
const ixns: Array<TransactionInstruction> = [];
@ -882,13 +986,13 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
aggregator: this.publicKey,
lease: leaseAccount.publicKey,
oracleQueue: queueAccount.publicKey,
queueAuthority: queueAuthority,
queueAuthority: queue.authority,
permission: permissionAccount.publicKey,
escrow: leaseEscrow,
programState: this.program.programState.publicKey,
payoutWallet: payoutWallet,
tokenProgram: TOKEN_PROGRAM_ID,
dataBuffer: queueDataBuffer,
dataBuffer: queue.dataBuffer,
mint: this.program.mint.address,
}
)
@ -1018,7 +1122,8 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
for (let i = 0; i < aggregator.oracleRequestBatchSize; ++i) {
remainingAccounts.push(aggregator.currentRound.oraclePubkeysData[i]);
}
for (const oracle of params?.oracles ?? []) {
for (const oracle of params?.oracles ??
(await this.loadCurrentRoundOracles(aggregator)).map(a => a.state)) {
remainingAccounts.push(oracle.tokenAccount);
}
remainingAccounts.push(
@ -1062,41 +1167,16 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
mint = mint ?? queue.mint ?? spl.NATIVE_MINT;
}
const [leaseAccount, leaseBump] =
params.lease ??
LeaseAccount.fromSeed(
this.program,
queueAccount.publicKey,
this.publicKey
);
try {
await leaseAccount.loadData();
} catch (_) {
throw new Error(
'A requested lease pda account has not been initialized.'
);
}
const leaseEscrow = spl.getAssociatedTokenAddressSync(
mint,
leaseAccount.publicKey,
true
);
const [feedPermissionAccount, feedPermissionBump] =
params.feedPermission ??
PermissionAccount.fromSeed(
this.program,
queueAuthority,
queueAccount.publicKey,
this.publicKey
);
try {
await feedPermissionAccount.loadData();
} catch (_) {
throw new Error(
'A requested aggregator permission pda account has not been initialized.'
);
}
const {
permissionAccount,
permissionBump,
leaseAccount,
leaseBump,
leaseEscrow,
} = this.getAccounts({
queueAccount: queueAccount,
queueAuthority: queueAuthority,
});
const [oraclePermissionAccount, oraclePermissionBump] =
params.oraclePermission ??
@ -1119,7 +1199,7 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
const saveResultIxn = this.saveResultInstruction({
queueAccount,
queueAuthority,
feedPermission: [feedPermissionAccount, feedPermissionBump],
feedPermission: [permissionAccount, permissionBump],
jobs: params.jobs,
historyBuffer: historyBuffer.equals(PublicKey.default)
? undefined
@ -1136,13 +1216,15 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
lease: [leaseAccount, leaseBump],
leaseEscrow: leaseEscrow,
});
// add remaining accounts
saveResultIxn.keys.push(
...remainingAccounts.map((pubkey): AccountMeta => {
return { isSigner: false, isWritable: true, pubkey };
})
);
ixns.push(saveResultIxn);
ixns.push(saveResultIxn);
const saveResultTxn = new TransactionObject(
this.program.walletPubkey,
ixns,

View File

@ -1,8 +1,61 @@
import * as anchor from '@project-serum/anchor';
import { PublicKey } from '@solana/web3.js';
import * as spl from '@solana/spl-token';
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
import {
AccountMeta,
Keypair,
PublicKey,
SystemProgram,
TransactionSignature,
} from '@solana/web3.js';
import * as errors from '../errors';
import * as types from '../generated';
import { SwitchboardProgram } from '../program';
import { TransactionObject } from '../transaction';
import { Account } from './account';
import { AggregatorAccount } from './aggregatorAccount';
import { LeaseAccount } from './leaseAccount';
import { PermissionAccount } from './permissionAccount';
import { QueueAccount } from './queueAccount';
/**
* Parameters for initializing a CrankAccount
*/
export interface CrankInitParams {
/**
* OracleQueueAccount for which this crank is associated
*/
queueAccount: QueueAccount;
/**
* String specifying crank name
*/
name?: string;
/**
* String specifying crank metadata
*/
metadata?: String;
/**
* Optional max number of rows
*/
maxRows?: number;
/**
* Optional
*/
keypair?: Keypair;
}
/**
* Parameters for pushing an element into a CrankAccount.
*/
export interface CrankPushParams {
/**
* Specifies the aggregator to push onto the crank.
*/
aggregatorAccount: AggregatorAccount;
aggregator?: types.AggregatorAccountData;
queueAccount?: QueueAccount;
queue?: types.OracleQueueAccountData;
}
/**
* Parameters for popping an element from a CrankAccount.
@ -59,68 +112,209 @@ export class CrankAccount extends Account<types.CrankAccountData> {
return data;
}
// public async popInstruction(
// authority: PublicKey,
// params: CrankPopParams
// ): Promise<anchor.web3.TransactionInstruction> {
// const next = params.readyPubkeys ?? (await this.peakNextReady(5));
// if (next.length === 0) throw new Error('Crank is not ready to be turned.');
public static async createInstructions(
program: SwitchboardProgram,
payer: PublicKey,
params: CrankInitParams
): Promise<[TransactionObject, CrankAccount]> {
const crankAccount = params.keypair ?? Keypair.generate();
const buffer = anchor.web3.Keypair.generate();
const maxRows = params.maxRows ?? 500;
const crankSize = maxRows * 40 + 8;
// const remainingAccounts: PublicKey[] = [];
// const leaseBumpsMap: Record<string, number> = {};
// const permissionBumpsMap: Record<string, number> = {};
// const queueAccount = new OracleQueueAccount(
// /* program= */ this.program,
// /* queuePubkey= */ params.queuePubkey
// );
// for (const row of next) {
// const aggregatorAccount = new AggregatorAccount({
// program: this.program,
// publicKey: row,
// });
// const [leaseAccount, leaseBump] = LeaseAccount.fromSeed(
// this.program,
// queueAccount,
// aggregatorAccount
// );
// const [permissionAccount, permissionBump] = PermissionAccount.fromSeed(
// this.program,
// params.queueAuthority,
// params.queuePubkey,
// row
// );
// const escrow = await spl.getAssociatedTokenAddress(
// params.tokenMint,
// leaseAccount.publicKey,
// true
// );
// remainingAccounts.push(aggregatorAccount.publicKey);
// remainingAccounts.push(leaseAccount.publicKey);
// remainingAccounts.push(escrow);
// remainingAccounts.push(permissionAccount.publicKey);
// leaseBumpsMap.set(row.toBase58(), leaseBump);
// permissionBumpsMap.set(row.toBase58(), permissionBump);
// }
// remainingAccounts.sort((a: PublicKey, b: PublicKey) =>
// a.toBuffer().compare(b.toBuffer())
// );
const crankInit = new TransactionObject(
payer,
[
SystemProgram.createAccount({
fromPubkey: payer,
newAccountPubkey: buffer.publicKey,
space: crankSize,
lamports: await program.connection.getMinimumBalanceForRentExemption(
crankSize
),
programId: program.programId,
}),
types.crankInit(
program,
{
params: {
name: Buffer.from(params.name ?? '').slice(0, 32),
metadata: Buffer.from(params.metadata ?? '').slice(0, 64),
crankSize: maxRows,
},
},
{
crank: crankAccount.publicKey,
queue: params.queueAccount.publicKey,
buffer: buffer.publicKey,
systemProgram: SystemProgram.programId,
payer: payer,
}
),
],
[crankAccount, buffer]
);
// return types.crankPop(
// this.program.programId,
// {
// params: {
// stateBump,
// leaseBumps: Buffer.from(leaseBumps),
// permissionBumps: Buffer.from(permissionBumps),
// nonce: params.nonce ?? null,
// failOpenOnAccountMismatch,
// popIdx: params.popIdx ?? 0,
// },
// },
// { authority, queue: this.publicKey }
// );
// }
return [crankInit, new CrankAccount(program, crankAccount.publicKey)];
}
public static async create(
program: SwitchboardProgram,
params: CrankInitParams
): Promise<[TransactionSignature, CrankAccount]> {
const [crankInit, crankAccount] = await CrankAccount.createInstructions(
program,
program.walletPubkey,
params
);
const txnSignature = await program.signAndSend(crankInit);
return [txnSignature, crankAccount];
}
public async popInstruction(
payer: PublicKey,
params: CrankPopParams
): Promise<TransactionObject> {
const next = params.readyPubkeys ?? (await this.peakNextReady(5));
if (next.length === 0) throw new Error('Crank is not ready to be turned.');
const remainingAccounts: PublicKey[] = [];
const leaseBumpsMap: Map<string, number> = new Map();
const permissionBumpsMap: Map<string, number> = new Map();
const queueAccount = new QueueAccount(this.program, params.queuePubkey);
for (const row of next) {
const aggregatorAccount = new AggregatorAccount(this.program, row);
const {
leaseAccount,
leaseBump,
permissionAccount,
permissionBump,
leaseEscrow,
} = aggregatorAccount.getAccounts({
queueAccount: queueAccount,
queueAuthority: params.queueAuthority,
});
remainingAccounts.push(aggregatorAccount.publicKey);
remainingAccounts.push(leaseAccount.publicKey);
remainingAccounts.push(leaseEscrow);
remainingAccounts.push(permissionAccount.publicKey);
leaseBumpsMap.set(row.toBase58(), leaseBump);
permissionBumpsMap.set(row.toBase58(), permissionBump);
}
remainingAccounts.sort((a: PublicKey, b: PublicKey) =>
a.toBuffer().compare(b.toBuffer())
);
const toBumps = (bumpMap: Map<string, number>): Uint8Array => {
return new Uint8Array(Array.from(bumpMap.values()));
};
const ixn = types.crankPop(
this.program,
{
params: {
stateBump: this.program.programState.bump,
leaseBumps: toBumps(leaseBumpsMap),
permissionBumps: toBumps(permissionBumpsMap),
nonce: params.nonce ?? null,
failOpenOnAccountMismatch: null,
},
},
{
crank: this.publicKey,
oracleQueue: params.queuePubkey,
queueAuthority: params.queueAuthority,
programState: this.program.programState.publicKey,
payoutWallet: params.payoutWallet,
tokenProgram: TOKEN_PROGRAM_ID,
crankDataBuffer: params.crank.dataBuffer,
queueDataBuffer: params.queue.dataBuffer,
mint: this.program.mint.address,
}
);
ixn.keys.push(
...remainingAccounts.map((pubkey): AccountMeta => {
return { isSigner: false, isWritable: true, pubkey };
})
);
const crankPop = new TransactionObject(payer, [ixn], []);
return crankPop;
}
public async pop(params: CrankPopParams): Promise<TransactionSignature> {
const popTxn = await this.popInstruction(this.program.walletPubkey, params);
const txnSignature = await this.program.signAndSend(popTxn);
return txnSignature;
}
/**
* Pushes a new aggregator onto the crank.
* @param params The crank push parameters.
* @return TransactionSignature
*/
async pushInstruction(
payer: PublicKey,
params: CrankPushParams
): Promise<TransactionObject> {
const queueAccount =
params.queueAccount ??
new QueueAccount(this.program, (await this.loadData()).queuePubkey);
const queue = params.queue ?? (await queueAccount.loadData());
const { permissionAccount, permissionBump, leaseAccount, leaseEscrow } =
params.aggregatorAccount.getAccounts({
queueAccount: queueAccount,
queueAuthority: queue.authority,
});
return new TransactionObject(
payer,
[
types.crankPush(
this.program,
{
params: {
stateBump: this.program.programState.bump,
permissionBump: permissionBump,
notifiRef: null,
},
},
{
crank: this.publicKey,
aggregator: params.aggregatorAccount.publicKey,
oracleQueue: queueAccount.publicKey,
queueAuthority: queue.authority,
permission: permissionAccount.publicKey,
lease: leaseAccount.publicKey,
escrow: leaseEscrow,
programState: this.program.programState.publicKey,
dataBuffer: queue.dataBuffer,
}
),
],
[]
);
}
/**
* Pushes a new aggregator onto the crank.
* @param params The crank push parameters.
* @return TransactionSignature
*/
async push(params: CrankPushParams): Promise<TransactionSignature> {
const pushTxn = await this.pushInstruction(
this.program.walletPubkey,
params
);
const txnSignature = await this.program.signAndSend(pushTxn);
return txnSignature;
}
/**
* Get an array of the next aggregator pubkeys to be popped from the crank, limited by n
* @param num The limit of pubkeys to return.

View File

@ -18,6 +18,7 @@ import { SwitchboardProgram } from '../program';
import { TransactionObject } from '../transaction';
import { Account } from './account';
import { AggregatorAccount } from './aggregatorAccount';
import { CrankAccount } from './crankAccount';
import { JobAccount } from './jobAccount';
import { LeaseAccount } from './leaseAccount';
import { OracleAccount } from './oracleAccount';
@ -377,6 +378,8 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
authorWallet?: PublicKey;
feedKeypair?: Keypair;
authority?: Keypair;
} & {
crankPubkey?: PublicKey;
} & {
// lease params
loadAmount?: number;
@ -504,6 +507,17 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
post.push(addJobTxn);
}
if (params.crankPubkey) {
const crankAccount = new CrankAccount(this.program, params.crankPubkey);
post.push(
await crankAccount.pushInstruction(this.program.walletPubkey, {
aggregatorAccount: aggregatorAccount,
queueAccount: this,
queue,
})
);
}
const packed = TransactionObject.pack([
...TransactionObject.pack(pre),
...TransactionObject.pack(txns),