solana.js: doc comments and misc cleanup
This commit is contained in:
parent
e5e641ed28
commit
a76f7db7ac
|
@ -25,6 +25,7 @@ target
|
|||
.crates
|
||||
|
||||
# Misc
|
||||
.DS_STORE
|
||||
.keypairs
|
||||
secrets
|
||||
*-keypair*.json
|
||||
|
|
|
@ -5,7 +5,6 @@ import * as errors from '../errors';
|
|||
import Big from 'big.js';
|
||||
import { SwitchboardProgram } from '../program';
|
||||
import {
|
||||
AccountInfo,
|
||||
AccountMeta,
|
||||
Commitment,
|
||||
Keypair,
|
||||
|
@ -30,6 +29,9 @@ import { AggregatorHistoryBuffer } from './aggregatorHistoryBuffer';
|
|||
* Account type holding a data feed's update configuration, job accounts, and its current result.
|
||||
*
|
||||
* Data: {@linkcode types.AggregatorAccountData}
|
||||
*
|
||||
* Result: {@linkcode types.SwitchboardDecimal}
|
||||
*
|
||||
* HistoryBuffer?: Array<{@linkcode types.AggregatorHistoryRow}>
|
||||
*
|
||||
* An aggregator account belongs to a single {@linkcode QueueAccount} but can later be transferred by the aggregator's authority. In order for an {@linkcode OracleAccount} to respond to an aggregator's update request, the aggregator must initialize a {@linkcode PermissionAccount} and {@linkcode LeaseAccount}. These will need to be recreated when transferring queues.
|
||||
|
@ -140,6 +142,7 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
|
|||
params: AggregatorInitParams
|
||||
): Promise<[TransactionObject, AggregatorAccount]> {
|
||||
const keypair = params.keypair ?? Keypair.generate();
|
||||
program.verifyNewKeypair(keypair);
|
||||
|
||||
const ixns: TransactionInstruction[] = [];
|
||||
const signers: Keypair[] = [keypair];
|
||||
|
|
|
@ -164,6 +164,7 @@ export class AggregatorHistoryBuffer extends Account<
|
|||
params: AggregatorHistoryInit
|
||||
): Promise<[TransactionObject, AggregatorHistoryBuffer]> {
|
||||
const buffer = params.keypair ?? Keypair.generate();
|
||||
program.verifyNewKeypair(buffer);
|
||||
|
||||
const ixns: TransactionInstruction[] = [];
|
||||
const signers: Keypair[] = params.aggregatorAuthority
|
||||
|
|
|
@ -94,6 +94,8 @@ export class BufferRelayerAccount extends Account<types.BufferRelayerAccountData
|
|||
}
|
||||
): Promise<[TransactionObject, BufferRelayerAccount]> {
|
||||
const keypair = params.keypair ?? Keypair.generate();
|
||||
program.verifyNewKeypair(keypair);
|
||||
|
||||
const size = 2048;
|
||||
|
||||
const ixns: TransactionInstruction[] = [];
|
||||
|
|
|
@ -22,7 +22,8 @@ import { QueueAccount } from './queueAccount';
|
|||
* Account holding a priority queue of aggregators and their next available update time. This is a scheduling mechanism to ensure {@linkcode AggregatorAccount}'s are updated as close as possible to their specified update interval.
|
||||
*
|
||||
* Data: {@linkcode types.CrankAccountData}
|
||||
* Buffer: Array<{@linkcode types.CrankRow}>
|
||||
*
|
||||
* Buffer: {@linkcode CrankDataBuffer}
|
||||
*/
|
||||
export class CrankAccount extends Account<types.CrankAccountData> {
|
||||
static accountName = 'CrankAccountData';
|
||||
|
@ -61,7 +62,10 @@ export class CrankAccount extends Account<types.CrankAccountData> {
|
|||
this.publicKey
|
||||
);
|
||||
if (data === null) throw new errors.AccountNotFoundError(this.publicKey);
|
||||
this.dataBuffer = CrankDataBuffer.fromCrank(this.program, data);
|
||||
if (!this.dataBuffer) {
|
||||
this.dataBuffer = CrankDataBuffer.fromCrank(this.program, data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -71,7 +75,11 @@ export class CrankAccount extends Account<types.CrankAccountData> {
|
|||
params: CrankInitParams
|
||||
): Promise<[TransactionObject, CrankAccount]> {
|
||||
const crankAccount = params.keypair ?? Keypair.generate();
|
||||
program.verifyNewKeypair(crankAccount);
|
||||
|
||||
const buffer = anchor.web3.Keypair.generate();
|
||||
program.verifyNewKeypair(buffer);
|
||||
|
||||
const maxRows = params.maxRows ?? 500;
|
||||
const crankSize = maxRows * 40 + 8;
|
||||
|
||||
|
@ -312,19 +320,29 @@ export class CrankAccount extends Account<types.CrankAccountData> {
|
|||
return crankRows.map(row => row.pubkey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a cranks {@linkcode CrankDataBuffer}.
|
||||
* @return the list of aggregtors and their next available update time.
|
||||
*/
|
||||
async loadCrank(): Promise<Array<types.CrankRow>> {
|
||||
if (!this.dataBuffer) {
|
||||
this.dataBuffer = new CrankDataBuffer(
|
||||
this.program,
|
||||
(await this.loadData()).dataBuffer
|
||||
);
|
||||
}
|
||||
|
||||
const crankRows = await this.dataBuffer.loadData();
|
||||
|
||||
return crankRows;
|
||||
}
|
||||
|
||||
/** Whether an aggregator pubkey is active on a Crank */
|
||||
async isOnCrank(
|
||||
pubkey: PublicKey,
|
||||
crankRows?: Array<types.CrankRow>
|
||||
): Promise<boolean> {
|
||||
let crank: CrankDataBuffer;
|
||||
if (this.dataBuffer) {
|
||||
crank = this.dataBuffer;
|
||||
} else {
|
||||
const crankData = await this.loadData();
|
||||
crank = new CrankDataBuffer(this.program, crankData.dataBuffer);
|
||||
}
|
||||
const rows = await crank.loadData();
|
||||
const rows = crankRows ?? (await this.loadCrank());
|
||||
|
||||
const idx = rows.findIndex(r => r.pubkey.equals(pubkey));
|
||||
if (idx === -1) {
|
||||
|
|
|
@ -10,4 +10,5 @@ export * from './oracleAccount';
|
|||
export * from './permissionAccount';
|
||||
export * from './programStateAccount';
|
||||
export * from './queueAccount';
|
||||
export * from './queueDataBuffer';
|
||||
export * from './vrfAccount';
|
||||
|
|
|
@ -57,6 +57,8 @@ export class JobAccount extends Account<types.JobAccountData> {
|
|||
}
|
||||
|
||||
const jobKeypair = params.keypair ?? Keypair.generate();
|
||||
program.verifyNewKeypair(jobKeypair);
|
||||
|
||||
const authority = params.authority ?? payer;
|
||||
|
||||
const CHUNK_SIZE = 800;
|
||||
|
|
|
@ -213,7 +213,7 @@ export class OracleAccount extends Account<types.OracleAccountData> {
|
|||
params.tokenWallet ?? (await this.loadData()).tokenAccount;
|
||||
|
||||
const queue = params.queue ?? (await params.queueAccount.loadData());
|
||||
const oracles = await params.queueAccount.loadOracles(queue);
|
||||
const oracles = await params.queueAccount.loadOracles();
|
||||
|
||||
let lastPubkey = this.publicKey;
|
||||
if (queue.size !== 0) {
|
||||
|
|
|
@ -15,19 +15,14 @@ export interface PermissionAccountInitParams {
|
|||
authority: PublicKey;
|
||||
}
|
||||
|
||||
export type PermissionSetParams =
|
||||
| {
|
||||
/** The {@linkcode types.SwitchboardPermission} to set for the grantee. */
|
||||
permission: boolean;
|
||||
/** Keypair used to enable heartbeat permissions if payer is not the queue authority. */
|
||||
queueAuthority?: Keypair;
|
||||
}
|
||||
| {
|
||||
/** Whether to enable PERMIT_ORACLE_HEARTBEAT permissions. **Note:** Requires a provided queueAuthority keypair or payer to be the assigned queue authority. */
|
||||
enable?: boolean;
|
||||
/** Keypair used to enable heartbeat permissions if payer is not the queue authority. */
|
||||
queueAuthority?: Keypair;
|
||||
};
|
||||
export interface PermissionSetParams {
|
||||
/** The {@linkcode types.SwitchboardPermission} to set for the grantee. */
|
||||
permission: types.SwitchboardPermissionKind;
|
||||
/** Whether to enable PERMIT_ORACLE_HEARTBEAT permissions. **Note:** Requires a provided queueAuthority keypair or payer to be the assigned queue authority. */
|
||||
enable: boolean;
|
||||
/** Keypair used to enable heartbeat permissions if payer is not the queue authority. */
|
||||
queueAuthority?: Keypair;
|
||||
}
|
||||
|
||||
/**
|
||||
* Account type dictating the level of permissions between a granter and a grantee.
|
||||
|
@ -132,11 +127,7 @@ export class PermissionAccount extends Account<types.PermissionAccountData> {
|
|||
/**
|
||||
* Sets the permission in the PermissionAccount
|
||||
*/
|
||||
public async set(params: {
|
||||
permission: types.SwitchboardPermissionKind;
|
||||
enable: boolean;
|
||||
authority?: Keypair;
|
||||
}): Promise<string> {
|
||||
public async set(params: PermissionSetParams): Promise<string> {
|
||||
const setTxn = this.setInstruction(this.program.walletPubkey, params);
|
||||
const txnSignature = await this.program.signAndSend(setTxn);
|
||||
return txnSignature;
|
||||
|
@ -147,25 +138,28 @@ export class PermissionAccount extends Account<types.PermissionAccountData> {
|
|||
*/
|
||||
public setInstruction(
|
||||
payer: PublicKey,
|
||||
params: {
|
||||
permission: types.SwitchboardPermissionKind;
|
||||
enable: boolean;
|
||||
authority?: Keypair;
|
||||
}
|
||||
params: PermissionSetParams
|
||||
): TransactionObject {
|
||||
return new TransactionObject(
|
||||
payer,
|
||||
[
|
||||
types.permissionSet(
|
||||
this.program,
|
||||
{ params },
|
||||
{
|
||||
params: {
|
||||
permission: params.permission,
|
||||
enable: params.enable,
|
||||
},
|
||||
},
|
||||
{
|
||||
permission: this.publicKey,
|
||||
authority: params.authority ? params.authority.publicKey : payer,
|
||||
authority: params.queueAuthority
|
||||
? params.queueAuthority.publicKey
|
||||
: payer,
|
||||
}
|
||||
),
|
||||
],
|
||||
params.authority ? [params.authority] : []
|
||||
params.queueAuthority ? [params.queueAuthority] : []
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import { TransactionObject } from '../transaction';
|
|||
*/
|
||||
export class ProgramStateAccount extends Account<types.SbState> {
|
||||
static accountName = 'SbState';
|
||||
|
||||
/**
|
||||
* Retrieves the {@linkcode ProgramStateAccount}, creates it if it doesn't exist;
|
||||
*/
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import * as anchor from '@project-serum/anchor';
|
||||
import * as spl from '@solana/spl-token';
|
||||
import {
|
||||
AccountInfo,
|
||||
Commitment,
|
||||
Keypair,
|
||||
PublicKey,
|
||||
|
@ -26,7 +25,8 @@ import { CrankAccount, CrankInitParams } from './crankAccount';
|
|||
import { JobAccount, JobInitParams } from './jobAccount';
|
||||
import { LeaseAccount } from './leaseAccount';
|
||||
import { OracleAccount, OracleInitParams } from './oracleAccount';
|
||||
import { PermissionAccount } from './permissionAccount';
|
||||
import { PermissionAccount, PermissionSetParams } from './permissionAccount';
|
||||
import { QueueDataBuffer } from './queueDataBuffer';
|
||||
import { VrfAccount, VrfInitParams } from './vrfAccount';
|
||||
|
||||
/**
|
||||
|
@ -35,13 +35,14 @@ import { VrfAccount, VrfInitParams } from './vrfAccount';
|
|||
* A QueueAccount is responsible for allocating update requests to it's round robin queue of {@linkcode OracleAccount}'s.
|
||||
*
|
||||
* Data: {@linkcode types.OracleQueueAccountData}
|
||||
* Buffer: Array<PublicKey>
|
||||
*
|
||||
* Buffer: {@linkcode QueueDataBuffer}
|
||||
*/
|
||||
export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
||||
static accountName = 'OracleQueueAccountData';
|
||||
|
||||
/** The public key of the queue's data buffer storing a list of oracle's that are actively heartbeating */
|
||||
dataBuffer?: PublicKey;
|
||||
/** The {@linkcode QueueDataBuffer} storing a list of oracle's that are actively heartbeating */
|
||||
dataBuffer?: QueueDataBuffer;
|
||||
|
||||
/**
|
||||
* Get the size of an {@linkcode QueueAccount} on-chain.
|
||||
|
@ -78,30 +79,6 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a callback each time a QueueAccount's oracle queue buffer has changed on-chain. The buffer stores a list of oracle's and their last heartbeat timestamp.
|
||||
* @param callback - the callback invoked when the queues buffer changes
|
||||
* @param commitment - optional, the desired transaction finality. defaults to 'confirmed'
|
||||
* @returns the websocket subscription id
|
||||
*/
|
||||
onBufferChange(
|
||||
callback: OnAccountChangeCallback<Array<PublicKey>>,
|
||||
_dataBuffer?: PublicKey,
|
||||
commitment: Commitment = 'confirmed'
|
||||
): number {
|
||||
const buffer = this.dataBuffer ?? _dataBuffer;
|
||||
if (!buffer) {
|
||||
throw new Error(
|
||||
`No queue dataBuffer provided. Call queueAccount.loadData() or pass it to this function in order to watch the account for changes`
|
||||
);
|
||||
}
|
||||
return this.program.connection.onAccountChange(
|
||||
buffer,
|
||||
accountInfo => callback(QueueAccount.decodeBuffer(accountInfo)),
|
||||
commitment
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve and decode the {@linkcode types.OracleQueueAccountData} stored in this account.
|
||||
*/
|
||||
|
@ -111,7 +88,7 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
|||
this.publicKey
|
||||
);
|
||||
if (data === null) throw new errors.AccountNotFoundError(this.publicKey);
|
||||
this.dataBuffer = data.dataBuffer;
|
||||
this.dataBuffer = new QueueDataBuffer(this.program, data.dataBuffer);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -131,7 +108,10 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
|||
params: QueueInitParams
|
||||
): Promise<[TransactionObject, QueueAccount]> {
|
||||
const queueKeypair = params.keypair ?? Keypair.generate();
|
||||
program.verifyNewKeypair(queueKeypair);
|
||||
|
||||
const dataBuffer = params.dataBufferKeypair ?? Keypair.generate();
|
||||
program.verifyNewKeypair(dataBuffer);
|
||||
|
||||
const account = new QueueAccount(program, queueKeypair.publicKey);
|
||||
const queueSize = params.queueSize ?? 500;
|
||||
|
@ -224,12 +204,7 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
|||
public async createOracleInstructions(
|
||||
/** The publicKey of the account that will pay for the new accounts. Will also be used as the account authority if no other authority is provided. */
|
||||
payer: PublicKey,
|
||||
params: OracleInitParams & {
|
||||
/** Whether to enable PERMIT_ORACLE_HEARTBEAT permissions. **Note:** Requires a provided queueAuthority keypair or payer to be the assigned queue authority. */
|
||||
enable?: boolean;
|
||||
/** Keypair used to enable heartbeat permissions if payer is not the queue authority. */
|
||||
queueAuthority?: Keypair;
|
||||
}
|
||||
params: OracleInitParams & Partial<Omit<PermissionSetParams, 'permission'>>
|
||||
): Promise<[TransactionObject, OracleAccount]> {
|
||||
const queue = await this.loadData();
|
||||
|
||||
|
@ -250,7 +225,7 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
|||
const permissionSetTxn = permissionAccount.setInstruction(payer, {
|
||||
permission: new PermitOracleHeartbeat(),
|
||||
enable: true,
|
||||
authority: params.queueAuthority,
|
||||
queueAuthority: params.queueAuthority,
|
||||
});
|
||||
createPermissionTxnObject.combine(permissionSetTxn);
|
||||
}
|
||||
|
@ -265,12 +240,7 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
|||
* Create a new {@linkcode OracleAccount} for the queue.
|
||||
*/
|
||||
public async createOracle(
|
||||
params: OracleInitParams & {
|
||||
/** Whether to enable PERMIT_ORACLE_HEARTBEAT permissions. **Note:** Requires a provided queueAuthority keypair or payer to be the assigned queue authority. */
|
||||
enable?: boolean;
|
||||
/** Keypair used to enable heartbeat permissions if payer is not the queue authority. */
|
||||
queueAuthority?: Keypair;
|
||||
}
|
||||
params: OracleInitParams & Partial<Omit<PermissionSetParams, 'permission'>>
|
||||
): Promise<[TransactionSignature, OracleAccount]> {
|
||||
const signers: Keypair[] = [];
|
||||
|
||||
|
@ -293,88 +263,6 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
|||
return [signature, oracleAccount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@linkcode AggregatorAccount} for the queue, along with its {@linkcode PermissionAccount} and {@linkcode LeaseAccount}.
|
||||
*
|
||||
* Optionally, specify a crankPubkey in order to push it onto an existing {@linkcode CrankAccount}.
|
||||
*
|
||||
* Optionally, enable the permissions by setting a queueAuthority keypair along with the enable boolean set to true.
|
||||
*
|
||||
* ```ts
|
||||
* import {QueueAccount} from '@switchboard-xyz/solana.js';
|
||||
* const queueAccount = new QueueAccount(program, queuePubkey);
|
||||
* const [aggregatorInitSignatures, aggregatorAccount] =
|
||||
await queueAccount.createFeed({
|
||||
enable: true, // not needed if queue has unpermissionedFeedsEnabled
|
||||
queueAuthority: queueAuthority, // not needed if queue has unpermissionedFeedsEnabled
|
||||
batchSize: 1,
|
||||
minRequiredOracleResults: 1,
|
||||
minRequiredJobResults: 1,
|
||||
minUpdateDelaySeconds: 60,
|
||||
fundAmount: 2.5, // deposit 2.5 wSOL into the leaseAccount escrow
|
||||
jobs: [
|
||||
{ pubkey: jobAccount.publicKey },
|
||||
{
|
||||
weight: 2,
|
||||
data: OracleJob.encodeDelimited(
|
||||
OracleJob.fromObject({
|
||||
tasks: [
|
||||
{
|
||||
valueTask: {
|
||||
value: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).finish(),
|
||||
},
|
||||
],
|
||||
});
|
||||
* ```
|
||||
*/
|
||||
public async createFeed(
|
||||
params: Omit<
|
||||
Omit<Omit<AggregatorInitParams, 'queueAccount'>, 'queueAuthority'>,
|
||||
'authority'
|
||||
> & {
|
||||
authority?: Keypair;
|
||||
crankPubkey?: PublicKey;
|
||||
historyLimit?: number;
|
||||
} & {
|
||||
// lease params
|
||||
fundAmount?: number;
|
||||
funderAuthority?: Keypair;
|
||||
funderTokenAccount?: PublicKey;
|
||||
} & {
|
||||
// permission params
|
||||
enable?: boolean;
|
||||
queueAuthority?: Keypair;
|
||||
} & {
|
||||
// job params
|
||||
jobs?: Array<{ pubkey: PublicKey; weight?: number } | JobInitParams>;
|
||||
}
|
||||
): Promise<[Array<TransactionSignature>, AggregatorAccount]> {
|
||||
const signers: Keypair[] = [];
|
||||
|
||||
const queue = await this.loadData();
|
||||
|
||||
if (
|
||||
params.queueAuthority &&
|
||||
params.queueAuthority.publicKey.equals(queue.authority)
|
||||
) {
|
||||
signers.push(params.queueAuthority);
|
||||
}
|
||||
|
||||
const [txns, aggregatorAccount] = await this.createFeedInstructions(
|
||||
this.program.walletPubkey,
|
||||
params
|
||||
);
|
||||
|
||||
const signatures = await this.program.signAndSendAll(txns);
|
||||
|
||||
return [signatures, aggregatorAccount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@linkcode TransactionObject} containing the instructions and signers needed to create a new {@linkcode AggregatorAccount} for the queue along with its {@linkcode PermissionAccount} and {@linkcode LeaseAccount}.
|
||||
*
|
||||
|
@ -429,14 +317,10 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
|||
fundAmount?: number;
|
||||
funderAuthority?: Keypair;
|
||||
funderTokenAccount?: PublicKey;
|
||||
} & {
|
||||
// permission params
|
||||
enable?: boolean;
|
||||
queueAuthority?: Keypair;
|
||||
} & {
|
||||
// job params
|
||||
jobs?: Array<{ pubkey: PublicKey; weight?: number } | JobInitParams>;
|
||||
}
|
||||
} & Partial<Omit<PermissionSetParams, 'permission'>> & {
|
||||
// job params
|
||||
jobs?: Array<{ pubkey: PublicKey; weight?: number } | JobInitParams>;
|
||||
}
|
||||
): Promise<[TransactionObject[], AggregatorAccount]> {
|
||||
const queue = await this.loadData();
|
||||
|
||||
|
@ -523,7 +407,7 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
|||
const permissionSetTxn = permissionAccount.setInstruction(payer, {
|
||||
permission: new PermitOracleQueueUsage(),
|
||||
enable: true,
|
||||
authority: params.queueAuthority,
|
||||
queueAuthority: params.queueAuthority,
|
||||
});
|
||||
permissionInit.combine(permissionSetTxn);
|
||||
}
|
||||
|
@ -568,15 +452,82 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
|||
return [packed, aggregatorAccount];
|
||||
}
|
||||
|
||||
public async createCrank(
|
||||
params: Omit<CrankInitParams, 'queueAccount'>
|
||||
): Promise<[TransactionSignature, CrankAccount]> {
|
||||
const [txn, crankAccount] = await this.createCrankInstructions(
|
||||
/**
|
||||
* Create a new {@linkcode AggregatorAccount} for the queue, along with its {@linkcode PermissionAccount} and {@linkcode LeaseAccount}.
|
||||
*
|
||||
* Optionally, specify a crankPubkey in order to push it onto an existing {@linkcode CrankAccount}.
|
||||
*
|
||||
* Optionally, enable the permissions by setting a queueAuthority keypair along with the enable boolean set to true.
|
||||
*
|
||||
* ```ts
|
||||
* import {QueueAccount} from '@switchboard-xyz/solana.js';
|
||||
* const queueAccount = new QueueAccount(program, queuePubkey);
|
||||
* const [aggregatorInitSignatures, aggregatorAccount] =
|
||||
await queueAccount.createFeed({
|
||||
enable: true, // not needed if queue has unpermissionedFeedsEnabled
|
||||
queueAuthority: queueAuthority, // not needed if queue has unpermissionedFeedsEnabled
|
||||
batchSize: 1,
|
||||
minRequiredOracleResults: 1,
|
||||
minRequiredJobResults: 1,
|
||||
minUpdateDelaySeconds: 60,
|
||||
fundAmount: 2.5, // deposit 2.5 wSOL into the leaseAccount escrow
|
||||
jobs: [
|
||||
{ pubkey: jobAccount.publicKey },
|
||||
{
|
||||
weight: 2,
|
||||
data: OracleJob.encodeDelimited(
|
||||
OracleJob.fromObject({
|
||||
tasks: [
|
||||
{
|
||||
valueTask: {
|
||||
value: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).finish(),
|
||||
},
|
||||
],
|
||||
});
|
||||
* ```
|
||||
*/
|
||||
public async createFeed(
|
||||
params: Omit<
|
||||
Omit<Omit<AggregatorInitParams, 'queueAccount'>, 'queueAuthority'>,
|
||||
'authority'
|
||||
> & {
|
||||
authority?: Keypair;
|
||||
crankPubkey?: PublicKey;
|
||||
historyLimit?: number;
|
||||
} & {
|
||||
// lease params
|
||||
fundAmount?: number;
|
||||
funderAuthority?: Keypair;
|
||||
funderTokenAccount?: PublicKey;
|
||||
} & Partial<Omit<PermissionSetParams, 'permission'>> & {
|
||||
// job params
|
||||
jobs?: Array<{ pubkey: PublicKey; weight?: number } | JobInitParams>;
|
||||
}
|
||||
): Promise<[Array<TransactionSignature>, AggregatorAccount]> {
|
||||
const signers: Keypair[] = [];
|
||||
|
||||
const queue = await this.loadData();
|
||||
|
||||
if (
|
||||
params.queueAuthority &&
|
||||
params.queueAuthority.publicKey.equals(queue.authority)
|
||||
) {
|
||||
signers.push(params.queueAuthority);
|
||||
}
|
||||
|
||||
const [txns, aggregatorAccount] = await this.createFeedInstructions(
|
||||
this.program.walletPubkey,
|
||||
params
|
||||
);
|
||||
const txnSignature = await this.program.signAndSend(txn);
|
||||
return [txnSignature, crankAccount];
|
||||
|
||||
const signatures = await this.program.signAndSendAll(txns);
|
||||
|
||||
return [signatures, aggregatorAccount];
|
||||
}
|
||||
|
||||
public async createCrankInstructions(
|
||||
|
@ -589,16 +540,76 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
|||
});
|
||||
}
|
||||
|
||||
public async createCrank(
|
||||
params: Omit<CrankInitParams, 'queueAccount'>
|
||||
): Promise<[TransactionSignature, CrankAccount]> {
|
||||
const [txn, crankAccount] = await this.createCrankInstructions(
|
||||
this.program.walletPubkey,
|
||||
params
|
||||
);
|
||||
const txnSignature = await this.program.signAndSend(txn);
|
||||
return [txnSignature, crankAccount];
|
||||
}
|
||||
|
||||
public async createVrfInstructions(
|
||||
payer: PublicKey,
|
||||
params: Omit<VrfInitParams, 'queueAccount'> &
|
||||
Partial<Omit<PermissionSetParams, 'permission'>>
|
||||
): Promise<[TransactionObject, VrfAccount]> {
|
||||
const queue = await this.loadData();
|
||||
|
||||
const [vrfInit, vrfAccount] = await VrfAccount.createInstructions(
|
||||
this.program,
|
||||
payer,
|
||||
{
|
||||
vrfKeypair: params.vrfKeypair,
|
||||
queueAccount: this,
|
||||
callback: params.callback,
|
||||
authority: params.authority,
|
||||
}
|
||||
);
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [permissionInit, permissionAccount] =
|
||||
PermissionAccount.createInstruction(this.program, payer, {
|
||||
granter: this.publicKey,
|
||||
grantee: vrfAccount.publicKey,
|
||||
authority: queue.authority,
|
||||
});
|
||||
|
||||
if (params.enable) {
|
||||
if (params.queueAuthority || queue.authority.equals(payer)) {
|
||||
const permissionSet = permissionAccount.setInstruction(payer, {
|
||||
permission: new PermitOracleQueueUsage(),
|
||||
enable: true,
|
||||
queueAuthority: params.queueAuthority,
|
||||
});
|
||||
permissionInit = permissionInit.combine(permissionSet);
|
||||
}
|
||||
}
|
||||
|
||||
return [vrfInit.combine(permissionInit), vrfAccount];
|
||||
}
|
||||
|
||||
public async createVrf(
|
||||
params: Omit<VrfInitParams, 'queueAccount'> &
|
||||
Partial<Omit<PermissionSetParams, 'permission'>>
|
||||
): Promise<[TransactionSignature, VrfAccount]> {
|
||||
const [txn, vrfAccount] = await this.createVrfInstructions(
|
||||
this.program.walletPubkey,
|
||||
params
|
||||
);
|
||||
const txnSignature = await this.program.signAndSend(txn);
|
||||
return [txnSignature, vrfAccount];
|
||||
}
|
||||
|
||||
public async createBufferRelayerInstructions(
|
||||
payer: PublicKey,
|
||||
params: Omit<Omit<BufferRelayerInit, 'jobAccount'>, 'queueAccount'> & {
|
||||
// permission params
|
||||
enable?: boolean;
|
||||
queueAuthority?: Keypair;
|
||||
} & {
|
||||
// job params
|
||||
job: JobAccount | PublicKey | Omit<JobInitParams, 'weight'>;
|
||||
}
|
||||
params: Omit<Omit<BufferRelayerInit, 'jobAccount'>, 'queueAccount'> &
|
||||
Partial<Omit<PermissionSetParams, 'permission'>> & {
|
||||
// job params
|
||||
job: JobAccount | PublicKey | Omit<JobInitParams, 'weight'>;
|
||||
}
|
||||
): Promise<[TransactionObject, BufferRelayerAccount]> {
|
||||
const queue = await this.loadData();
|
||||
|
||||
|
@ -657,7 +668,7 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
|||
const permissionSet = permissionAccount.setInstruction(payer, {
|
||||
permission: new PermitOracleQueueUsage(),
|
||||
enable: true,
|
||||
authority: params.queueAuthority,
|
||||
queueAuthority: params.queueAuthority,
|
||||
});
|
||||
permissionInit = permissionInit.combine(permissionSet);
|
||||
}
|
||||
|
@ -676,14 +687,11 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
|||
}
|
||||
|
||||
public async createBufferRelayer(
|
||||
params: Omit<Omit<BufferRelayerInit, 'jobAccount'>, 'queueAccount'> & {
|
||||
// permission params
|
||||
enable?: boolean;
|
||||
queueAuthority?: Keypair;
|
||||
} & {
|
||||
// job params
|
||||
job: JobAccount | PublicKey | Omit<JobInitParams, 'weight'>;
|
||||
}
|
||||
params: Omit<Omit<BufferRelayerInit, 'jobAccount'>, 'queueAccount'> &
|
||||
Partial<Omit<PermissionSetParams, 'permission'>> & {
|
||||
// job params
|
||||
job: JobAccount | PublicKey | Omit<JobInitParams, 'weight'>;
|
||||
}
|
||||
): Promise<[TransactionSignature, BufferRelayerAccount]> {
|
||||
const [txn, bufferRelayerAccount] =
|
||||
await this.createBufferRelayerInstructions(
|
||||
|
@ -694,111 +702,21 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
|||
return [txnSignature, bufferRelayerAccount];
|
||||
}
|
||||
|
||||
public async createVrfInstructions(
|
||||
payer: PublicKey,
|
||||
params: Omit<VrfInitParams, 'queueAccount'> & {
|
||||
// permission params
|
||||
enable?: boolean;
|
||||
queueAuthority?: Keypair;
|
||||
}
|
||||
): Promise<[TransactionObject, VrfAccount]> {
|
||||
const queue = await this.loadData();
|
||||
|
||||
const [vrfInit, vrfAccount] = await VrfAccount.createInstructions(
|
||||
this.program,
|
||||
payer,
|
||||
{
|
||||
vrfKeypair: params.vrfKeypair,
|
||||
queueAccount: this,
|
||||
callback: params.callback,
|
||||
authority: params.authority,
|
||||
}
|
||||
);
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [permissionInit, permissionAccount] =
|
||||
PermissionAccount.createInstruction(this.program, payer, {
|
||||
granter: this.publicKey,
|
||||
grantee: vrfAccount.publicKey,
|
||||
authority: queue.authority,
|
||||
});
|
||||
|
||||
if (params.enable) {
|
||||
if (params.queueAuthority || queue.authority.equals(payer)) {
|
||||
const permissionSet = permissionAccount.setInstruction(payer, {
|
||||
permission: new PermitOracleQueueUsage(),
|
||||
enable: true,
|
||||
authority: params.queueAuthority,
|
||||
});
|
||||
permissionInit = permissionInit.combine(permissionSet);
|
||||
}
|
||||
}
|
||||
|
||||
return [vrfInit.combine(permissionInit), vrfAccount];
|
||||
}
|
||||
|
||||
public async createVrf(
|
||||
params: Omit<VrfInitParams, 'queueAccount'> & {
|
||||
// permission params
|
||||
enable?: boolean;
|
||||
queueAuthority?: Keypair;
|
||||
}
|
||||
): Promise<[TransactionSignature, VrfAccount]> {
|
||||
const [txn, vrfAccount] = await this.createVrfInstructions(
|
||||
this.program.walletPubkey,
|
||||
params
|
||||
);
|
||||
const txnSignature = await this.program.signAndSend(txn);
|
||||
return [txnSignature, vrfAccount];
|
||||
}
|
||||
|
||||
public static decodeBuffer(
|
||||
bufferAccountInfo: AccountInfo<Buffer>
|
||||
): Array<PublicKey> {
|
||||
const buffer = bufferAccountInfo.data.slice(8) ?? Buffer.from('');
|
||||
|
||||
const oracles: PublicKey[] = [];
|
||||
|
||||
for (let i = 0; i < buffer.byteLength * 32; i += 32) {
|
||||
if (buffer.byteLength - i < 32) {
|
||||
break;
|
||||
}
|
||||
|
||||
const pubkeyBuf = buffer.slice(i, i + 32);
|
||||
const pubkey = new PublicKey(pubkeyBuf);
|
||||
if (PublicKey.default.equals(pubkey)) {
|
||||
break;
|
||||
}
|
||||
oracles.push(pubkey);
|
||||
}
|
||||
|
||||
return oracles;
|
||||
}
|
||||
|
||||
/** Load the list of oracles that are currently stored in the buffer */
|
||||
public async loadOracles(
|
||||
queue?: types.OracleQueueAccountData,
|
||||
commitment: Commitment = 'confirmed'
|
||||
): Promise<Array<PublicKey>> {
|
||||
const dataBuffer =
|
||||
this.dataBuffer ??
|
||||
queue?.dataBuffer ??
|
||||
(await this.loadData()).dataBuffer;
|
||||
const accountInfo = await this.program.connection.getAccountInfo(
|
||||
dataBuffer,
|
||||
{ commitment }
|
||||
);
|
||||
if (!accountInfo || accountInfo.data === null) {
|
||||
throw new errors.AccountNotFoundError(dataBuffer);
|
||||
public async loadOracles(): Promise<Array<PublicKey>> {
|
||||
let queue: QueueDataBuffer;
|
||||
if (this.dataBuffer) {
|
||||
queue = this.dataBuffer;
|
||||
} else {
|
||||
const queueData = await this.loadData();
|
||||
queue = new QueueDataBuffer(this.program, queueData.dataBuffer);
|
||||
}
|
||||
|
||||
return QueueAccount.decodeBuffer(accountInfo);
|
||||
return queue.loadData();
|
||||
}
|
||||
|
||||
/** Loads the oracle states for the oracles currently on the queue's dataBuffer */
|
||||
public async loadOracleAccounts(
|
||||
queue?: types.OracleQueueAccountData
|
||||
): Promise<
|
||||
public async loadOracleAccounts(): Promise<
|
||||
Array<{
|
||||
publicKey: PublicKey;
|
||||
data: types.OracleAccountData;
|
||||
|
@ -806,7 +724,7 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
|||
> {
|
||||
const coder = this.program.coder;
|
||||
|
||||
const oraclePubkeys = await this.loadOracles(queue);
|
||||
const oraclePubkeys = await this.loadOracles();
|
||||
const accountInfos = await anchor.utils.rpc.getMultipleAccounts(
|
||||
this.program.connection,
|
||||
oraclePubkeys
|
||||
|
@ -846,7 +764,8 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
|||
}>
|
||||
> {
|
||||
const queue = _queue ?? (await this.loadData());
|
||||
const oracles = await this.loadOracleAccounts(queue);
|
||||
|
||||
const oracles = await this.loadOracleAccounts();
|
||||
|
||||
const timeout = queue.oracleTimeout;
|
||||
// TODO: Use SolanaClock
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
import { AccountInfo, Commitment, PublicKey } from '@solana/web3.js';
|
||||
import * as errors from '../errors';
|
||||
import * as types from '../generated';
|
||||
import { SwitchboardProgram } from '../program';
|
||||
import { Account, OnAccountChangeCallback } from './account';
|
||||
|
||||
/**
|
||||
* Account holding a list of oracles actively heartbeating on the queue
|
||||
*
|
||||
* Data: Array<{@linkcode PublicKey}>
|
||||
*/
|
||||
export class QueueDataBuffer extends Account<Array<PublicKey>> {
|
||||
static accountName = 'QueueDataBuffer';
|
||||
|
||||
public size = 0;
|
||||
|
||||
/**
|
||||
* Invoke a callback each time a QueueAccount's oracle queue buffer has changed on-chain. The buffer stores a list of oracle's and their last heartbeat timestamp.
|
||||
* @param callback - the callback invoked when the queues buffer changes
|
||||
* @param commitment - optional, the desired transaction finality. defaults to 'confirmed'
|
||||
* @returns the websocket subscription id
|
||||
*/
|
||||
onChange(
|
||||
callback: OnAccountChangeCallback<Array<PublicKey>>,
|
||||
commitment: Commitment = 'confirmed'
|
||||
): number {
|
||||
if (this.publicKey.equals(PublicKey.default)) {
|
||||
throw new Error(
|
||||
`No queue dataBuffer provided. Call crankAccount.loadData() or pass it to this function in order to watch the account for changes`
|
||||
);
|
||||
}
|
||||
return this.program.connection.onAccountChange(
|
||||
this.publicKey,
|
||||
accountInfo => callback(QueueDataBuffer.decode(accountInfo)),
|
||||
commitment
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve and decode the {@linkcode types.CrankAccountData} stored in this account.
|
||||
*/
|
||||
public async loadData(): Promise<Array<PublicKey>> {
|
||||
if (this.publicKey.equals(PublicKey.default)) {
|
||||
return [];
|
||||
}
|
||||
const accountInfo = await this.program.connection.getAccountInfo(
|
||||
this.publicKey
|
||||
);
|
||||
if (accountInfo === null)
|
||||
throw new errors.AccountNotFoundError(this.publicKey);
|
||||
const data = QueueDataBuffer.decode(accountInfo);
|
||||
return data;
|
||||
}
|
||||
|
||||
public static decode(
|
||||
bufferAccountInfo: AccountInfo<Buffer>
|
||||
): Array<PublicKey> {
|
||||
const buffer = bufferAccountInfo.data.slice(8) ?? Buffer.from('');
|
||||
|
||||
const oracles: PublicKey[] = [];
|
||||
|
||||
for (let i = 0; i < buffer.byteLength * 32; i += 32) {
|
||||
if (buffer.byteLength - i < 32) {
|
||||
break;
|
||||
}
|
||||
|
||||
const pubkeyBuf = buffer.slice(i, i + 32);
|
||||
const pubkey = new PublicKey(pubkeyBuf);
|
||||
if (PublicKey.default.equals(pubkey)) {
|
||||
break;
|
||||
}
|
||||
oracles.push(pubkey);
|
||||
}
|
||||
|
||||
return oracles;
|
||||
}
|
||||
|
||||
static getDataBufferSize(size: number): number {
|
||||
return 8 + size * 40;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an aggregator's assigned history buffer or undefined if it doesn't exist.
|
||||
*/
|
||||
static fromCrank(
|
||||
program: SwitchboardProgram,
|
||||
crank: types.CrankAccountData
|
||||
): QueueDataBuffer {
|
||||
if (crank.dataBuffer.equals(PublicKey.default)) {
|
||||
throw new Error(`Failed to find crank data buffer`);
|
||||
}
|
||||
|
||||
return new QueueDataBuffer(program, crank.dataBuffer);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ import * as anchor from '@project-serum/anchor';
|
|||
import * as spl from '@solana/spl-token';
|
||||
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
||||
import {
|
||||
AccountInfo,
|
||||
Commitment,
|
||||
Keypair,
|
||||
PublicKey,
|
||||
|
@ -11,6 +12,7 @@ import {
|
|||
TransactionInstruction,
|
||||
TransactionSignature,
|
||||
} from '@solana/web3.js';
|
||||
import { promiseWithTimeout } from '@switchboard-xyz/common';
|
||||
import * as errors from '../errors';
|
||||
import * as types from '../generated';
|
||||
import { SwitchboardProgram } from '../program';
|
||||
|
@ -21,8 +23,10 @@ import { PermissionAccount } from './permissionAccount';
|
|||
import { QueueAccount } from './queueAccount';
|
||||
|
||||
/**
|
||||
* @class VrfAccount
|
||||
* Account holding a Verifiable Random Function result with a callback instruction for consuming on-chain pseudo-randomness.
|
||||
*
|
||||
* Data: {@linkcode types.VrfAccountData}
|
||||
* Result: [u8;32]
|
||||
*/
|
||||
export class VrfAccount extends Account<types.VrfAccountData> {
|
||||
static accountName = 'VrfAccountData';
|
||||
|
@ -69,6 +73,7 @@ export class VrfAccount extends Account<types.VrfAccountData> {
|
|||
payer: PublicKey,
|
||||
params: VrfInitParams
|
||||
): Promise<[TransactionObject, VrfAccount]> {
|
||||
program.verifyNewKeypair(params.vrfKeypair);
|
||||
const vrfAccount = new VrfAccount(program, params.vrfKeypair.publicKey);
|
||||
const size = program.account.vrfAccountData.size;
|
||||
|
||||
|
@ -353,6 +358,50 @@ export class VrfAccount extends Account<types.VrfAccountData> {
|
|||
const txnSignature = await this.program.signAndSend(setCallbackTxn);
|
||||
return txnSignature;
|
||||
}
|
||||
|
||||
/** Await the next vrf round */
|
||||
public async nextRound(
|
||||
roundId: anchor.BN,
|
||||
/** Number of milliseconds to await the next VRF round. */
|
||||
timeout: number
|
||||
): Promise<VrfResult> {
|
||||
let ws: number | undefined = undefined;
|
||||
return await promiseWithTimeout(
|
||||
timeout,
|
||||
new Promise((resolve: (result: VrfResult) => void) => {
|
||||
ws = this.program.connection.onAccountChange(
|
||||
this.publicKey,
|
||||
(accountInfo: AccountInfo<Buffer>) => {
|
||||
const vrf = types.VrfAccountData.decode(accountInfo.data);
|
||||
if (!vrf.counter.eq(roundId)) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
vrf.status.kind === 'StatusCallbackSuccess' ||
|
||||
vrf.status.kind === 'StatusVerifyFailure'
|
||||
) {
|
||||
resolve({
|
||||
success: vrf.status.kind === 'StatusCallbackSuccess',
|
||||
result: new Uint8Array(vrf.currentRound.result),
|
||||
status: vrf.status,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
})
|
||||
).finally(async () => {
|
||||
if (ws) {
|
||||
await this.program.connection.removeAccountChangeListener(ws);
|
||||
}
|
||||
ws = undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface VrfResult {
|
||||
success: boolean;
|
||||
result: Uint8Array;
|
||||
status: types.VrfStatusKind;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,12 @@ export class SwitchboardProgramReadOnlyError extends Error {
|
|||
Object.setPrototypeOf(this, SwitchboardProgramReadOnlyError.prototype);
|
||||
}
|
||||
}
|
||||
export class ExistingKeypair extends Error {
|
||||
constructor() {
|
||||
super('Provided keypair corresponds to an existing account.');
|
||||
Object.setPrototypeOf(this, ExistingKeypair.prototype);
|
||||
}
|
||||
}
|
||||
export class AccountNotFoundError extends Error {
|
||||
constructor(publicKey: anchor.web3.PublicKey) {
|
||||
super(`No account was found at the address: ${publicKey.toBase58()}`);
|
||||
|
|
|
@ -183,6 +183,13 @@ export class SwitchboardProgram {
|
|||
}
|
||||
}
|
||||
|
||||
public async verifyNewKeypair(keypair: Keypair): Promise<void> {
|
||||
const accountInfo = await this.connection.getAccountInfo(keypair.publicKey);
|
||||
if (accountInfo) {
|
||||
throw new errors.ExistingKeypair();
|
||||
}
|
||||
}
|
||||
|
||||
public get account(): anchor.AccountNamespace {
|
||||
return this._program.account;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue