solana.js: oracle stake ixns
This commit is contained in:
parent
37af41d11e
commit
5a49b6269c
|
@ -698,7 +698,7 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
|
||||||
|
|
||||||
public async openRoundInstruction(
|
public async openRoundInstruction(
|
||||||
payer: PublicKey,
|
payer: PublicKey,
|
||||||
params: Partial<{ payoutWallet: PublicKey }>
|
params?: { payoutWallet?: PublicKey }
|
||||||
): Promise<TransactionObject> {
|
): Promise<TransactionObject> {
|
||||||
const aggregatorData = await this.loadData();
|
const aggregatorData = await this.loadData();
|
||||||
const queueAccount = new QueueAccount(
|
const queueAccount = new QueueAccount(
|
||||||
|
@ -760,9 +760,9 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
|
||||||
return new TransactionObject(payer, ixns, []);
|
return new TransactionObject(payer, ixns, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async openRound(
|
public async openRound(params?: {
|
||||||
params: Partial<{ payoutWallet: PublicKey }>
|
payoutWallet?: PublicKey;
|
||||||
): Promise<TransactionSignature> {
|
}): Promise<TransactionSignature> {
|
||||||
const openRoundTxn = await this.openRoundInstruction(
|
const openRoundTxn = await this.openRoundInstruction(
|
||||||
this.program.walletPubkey,
|
this.program.walletPubkey,
|
||||||
params
|
params
|
||||||
|
|
|
@ -192,7 +192,7 @@ export class BufferRelayerAccount extends Account<types.BufferRelayerAccountData
|
||||||
);
|
);
|
||||||
const tokenAmountBN = new BN(tokenAccount.amount.toString());
|
const tokenAmountBN = new BN(tokenAccount.amount.toString());
|
||||||
if (tokenAmountBN.lt(queue.reward)) {
|
if (tokenAmountBN.lt(queue.reward)) {
|
||||||
const wrapTxn = await this.program.mint.wrapInstruction(payer, {
|
const wrapTxn = await this.program.mint.wrapInstructions(payer, {
|
||||||
fundUpTo: new Big(this.program.mint.fromTokenAmountBN(queue.reward)),
|
fundUpTo: new Big(this.program.mint.fromTokenAmountBN(queue.reward)),
|
||||||
});
|
});
|
||||||
ixns.push(...wrapTxn.ixns);
|
ixns.push(...wrapTxn.ixns);
|
||||||
|
|
|
@ -120,7 +120,7 @@ export class LeaseAccount extends Account<types.LeaseAccountData> {
|
||||||
(await program.mint.getBalance(funderAuthority)) ?? 0;
|
(await program.mint.getBalance(funderAuthority)) ?? 0;
|
||||||
|
|
||||||
if (loadAmount && funderTokenBalance < loadAmount) {
|
if (loadAmount && funderTokenBalance < loadAmount) {
|
||||||
const wrapIxns = await program.mint.wrapInstruction(
|
const wrapIxns = await program.mint.wrapInstructions(
|
||||||
payer,
|
payer,
|
||||||
{ amount: loadAmount },
|
{ amount: loadAmount },
|
||||||
params.funderAuthority
|
params.funderAuthority
|
||||||
|
@ -354,7 +354,7 @@ export class LeaseAccount extends Account<types.LeaseAccountData> {
|
||||||
const funderBalance =
|
const funderBalance =
|
||||||
(await this.program.mint.getBalance(funderAuthority)) ?? 0;
|
(await this.program.mint.getBalance(funderAuthority)) ?? 0;
|
||||||
if (funderBalance < params.loadAmount) {
|
if (funderBalance < params.loadAmount) {
|
||||||
const wrapIxns = await this.program.mint.unwrapInstruction(
|
const wrapIxns = await this.program.mint.unwrapInstructions(
|
||||||
payer,
|
payer,
|
||||||
params.loadAmount,
|
params.loadAmount,
|
||||||
params.funderAuthority
|
params.funderAuthority
|
||||||
|
@ -395,20 +395,6 @@ export class LeaseAccount extends Account<types.LeaseAccountData> {
|
||||||
return new TransactionObject(payer, ixns, signers);
|
return new TransactionObject(payer, ixns, signers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async withdraw(params: {
|
|
||||||
amount: number;
|
|
||||||
unwrap?: boolean;
|
|
||||||
withdrawWallet?: PublicKey;
|
|
||||||
withdrawAuthority?: Keypair;
|
|
||||||
}): Promise<TransactionSignature> {
|
|
||||||
const withdrawTxn = await this.withdrawInstruction(
|
|
||||||
this.program.walletPubkey,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
const txnSignature = await this.program.signAndSend(withdrawTxn);
|
|
||||||
return txnSignature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async withdrawInstruction(
|
public async withdrawInstruction(
|
||||||
payer: PublicKey,
|
payer: PublicKey,
|
||||||
params: {
|
params: {
|
||||||
|
@ -499,7 +485,7 @@ export class LeaseAccount extends Account<types.LeaseAccountData> {
|
||||||
|
|
||||||
if (params.unwrap) {
|
if (params.unwrap) {
|
||||||
txns.push(
|
txns.push(
|
||||||
await this.program.mint.unwrapInstruction(
|
await this.program.mint.unwrapInstructions(
|
||||||
payer,
|
payer,
|
||||||
params.amount,
|
params.amount,
|
||||||
params.withdrawAuthority
|
params.withdrawAuthority
|
||||||
|
@ -515,6 +501,20 @@ export class LeaseAccount extends Account<types.LeaseAccountData> {
|
||||||
return packed[0];
|
return packed[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async withdraw(params: {
|
||||||
|
amount: number;
|
||||||
|
unwrap?: boolean;
|
||||||
|
withdrawWallet?: PublicKey;
|
||||||
|
withdrawAuthority?: Keypair;
|
||||||
|
}): Promise<TransactionSignature> {
|
||||||
|
const withdrawTxn = await this.withdrawInstruction(
|
||||||
|
this.program.walletPubkey,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
const txnSignature = await this.program.signAndSend(withdrawTxn);
|
||||||
|
return txnSignature;
|
||||||
|
}
|
||||||
|
|
||||||
public async setAuthority(params: {
|
public async setAuthority(params: {
|
||||||
newAuthority: PublicKey;
|
newAuthority: PublicKey;
|
||||||
withdrawAuthority: Keypair;
|
withdrawAuthority: Keypair;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { Account, OnAccountChangeCallback } from './account';
|
||||||
import * as anchor from '@project-serum/anchor';
|
import * as anchor from '@project-serum/anchor';
|
||||||
import { SwitchboardProgram } from '../program';
|
import { SwitchboardProgram } from '../program';
|
||||||
import {
|
import {
|
||||||
|
Commitment,
|
||||||
Keypair,
|
Keypair,
|
||||||
PublicKey,
|
PublicKey,
|
||||||
SystemProgram,
|
SystemProgram,
|
||||||
|
@ -30,6 +31,35 @@ export class OracleAccount extends Account<types.OracleAccountData> {
|
||||||
*/
|
*/
|
||||||
public size = this.program.account.oracleAccountData.size;
|
public size = this.program.account.oracleAccountData.size;
|
||||||
|
|
||||||
|
decode(data: Buffer): types.OracleAccountData {
|
||||||
|
try {
|
||||||
|
return types.OracleAccountData.decode(data);
|
||||||
|
} catch {
|
||||||
|
return this.program.coder.decode<types.OracleAccountData>(
|
||||||
|
OracleAccount.accountName,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke a callback each time an OracleAccount's data has changed on-chain.
|
||||||
|
* @param callback - the callback invoked when the oracle state changes
|
||||||
|
* @param commitment - optional, the desired transaction finality. defaults to 'confirmed'
|
||||||
|
* @returns the websocket subscription id
|
||||||
|
*/
|
||||||
|
onChange(
|
||||||
|
callback: OnAccountChangeCallback<types.OracleAccountData>,
|
||||||
|
commitment: Commitment = 'confirmed'
|
||||||
|
): number {
|
||||||
|
return this.program.connection.onAccountChange(
|
||||||
|
this.publicKey,
|
||||||
|
accountInfo => {
|
||||||
|
callback(this.decode(accountInfo.data));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve and decode the {@linkcode types.OracleAccountData} stored in this account.
|
* Retrieve and decode the {@linkcode types.OracleAccountData} stored in this account.
|
||||||
*/
|
*/
|
||||||
|
@ -42,98 +72,6 @@ export class OracleAccount extends Account<types.OracleAccountData> {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async createInstructions(
|
|
||||||
program: SwitchboardProgram,
|
|
||||||
payer: PublicKey,
|
|
||||||
params: {
|
|
||||||
queueAccount: QueueAccount;
|
|
||||||
} & OracleInitParams
|
|
||||||
): Promise<[OracleAccount, TransactionObject]> {
|
|
||||||
const tokenWallet = Keypair.generate();
|
|
||||||
// console.log(`tokenWallet`, tokenWallet.publicKey.toBase58());
|
|
||||||
|
|
||||||
const authority = params.authority?.publicKey ?? payer;
|
|
||||||
|
|
||||||
const [oracleAccount, oracleBump] = OracleAccount.fromSeed(
|
|
||||||
program,
|
|
||||||
params.queueAccount.publicKey,
|
|
||||||
tokenWallet.publicKey
|
|
||||||
);
|
|
||||||
|
|
||||||
const ixns = [
|
|
||||||
SystemProgram.createAccount({
|
|
||||||
fromPubkey: payer,
|
|
||||||
newAccountPubkey: tokenWallet.publicKey,
|
|
||||||
space: spl.ACCOUNT_SIZE,
|
|
||||||
lamports: await program.connection.getMinimumBalanceForRentExemption(
|
|
||||||
spl.ACCOUNT_SIZE
|
|
||||||
),
|
|
||||||
programId: spl.TOKEN_PROGRAM_ID,
|
|
||||||
}),
|
|
||||||
spl.createInitializeAccountInstruction(
|
|
||||||
tokenWallet.publicKey,
|
|
||||||
program.mint.address,
|
|
||||||
authority
|
|
||||||
),
|
|
||||||
spl.createSetAuthorityInstruction(
|
|
||||||
tokenWallet.publicKey,
|
|
||||||
authority,
|
|
||||||
spl.AuthorityType.AccountOwner,
|
|
||||||
program.programState.publicKey
|
|
||||||
),
|
|
||||||
types.oracleInit(
|
|
||||||
program,
|
|
||||||
{
|
|
||||||
params: {
|
|
||||||
name: new Uint8Array(
|
|
||||||
Buffer.from(params.name ?? '', 'utf8').slice(0, 32)
|
|
||||||
),
|
|
||||||
metadata: new Uint8Array(
|
|
||||||
Buffer.from(params.metadata ?? '', 'utf8').slice(0, 128)
|
|
||||||
),
|
|
||||||
oracleBump,
|
|
||||||
stateBump: program.programState.bump,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
oracle: oracleAccount.publicKey,
|
|
||||||
oracleAuthority: authority,
|
|
||||||
wallet: tokenWallet.publicKey,
|
|
||||||
programState: program.programState.publicKey,
|
|
||||||
queue: params.queueAccount.publicKey,
|
|
||||||
payer,
|
|
||||||
systemProgram: SystemProgram.programId,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
return [
|
|
||||||
new OracleAccount(program, oracleAccount.publicKey),
|
|
||||||
new TransactionObject(
|
|
||||||
payer,
|
|
||||||
ixns,
|
|
||||||
params.authority ? [tokenWallet, params.authority] : [tokenWallet]
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async create(
|
|
||||||
program: SwitchboardProgram,
|
|
||||||
params: {
|
|
||||||
queueAccount: QueueAccount;
|
|
||||||
} & OracleInitParams
|
|
||||||
): Promise<[OracleAccount, TransactionSignature]> {
|
|
||||||
const [oracleAccount, txnObject] = await OracleAccount.createInstructions(
|
|
||||||
program,
|
|
||||||
program.walletPubkey,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
|
|
||||||
const txnSignature = await program.signAndSend(txnObject);
|
|
||||||
|
|
||||||
return [oracleAccount, txnSignature];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads an OracleAccount from the expected PDA seed format.
|
* Loads an OracleAccount from the expected PDA seed format.
|
||||||
* @param program The Switchboard program for the current connection.
|
* @param program The Switchboard program for the current connection.
|
||||||
|
@ -153,24 +91,172 @@ export class OracleAccount extends Account<types.OracleAccountData> {
|
||||||
return [new OracleAccount(program, publicKey), bump];
|
return [new OracleAccount(program, publicKey), bump];
|
||||||
}
|
}
|
||||||
|
|
||||||
decode(data: Buffer): types.OracleAccountData {
|
public static async createInstructions(
|
||||||
try {
|
program: SwitchboardProgram,
|
||||||
return types.OracleAccountData.decode(data);
|
payer: PublicKey,
|
||||||
} catch {
|
params: {
|
||||||
return this.program.coder.decode<types.OracleAccountData>(
|
queueAccount: QueueAccount;
|
||||||
OracleAccount.accountName,
|
} & OracleInitParams &
|
||||||
data
|
OracleStakeParams
|
||||||
);
|
): Promise<[OracleAccount, TransactionObject]> {
|
||||||
|
const tokenWallet = Keypair.generate();
|
||||||
|
|
||||||
|
const authority = params.authority?.publicKey ?? payer;
|
||||||
|
|
||||||
|
const txns: TransactionObject[] = [];
|
||||||
|
|
||||||
|
const [oracleAccount, oracleBump] = OracleAccount.fromSeed(
|
||||||
|
program,
|
||||||
|
params.queueAccount.publicKey,
|
||||||
|
tokenWallet.publicKey
|
||||||
|
);
|
||||||
|
|
||||||
|
const oracleInit = new TransactionObject(
|
||||||
|
payer,
|
||||||
|
[
|
||||||
|
SystemProgram.createAccount({
|
||||||
|
fromPubkey: payer,
|
||||||
|
newAccountPubkey: tokenWallet.publicKey,
|
||||||
|
space: spl.ACCOUNT_SIZE,
|
||||||
|
lamports: await program.connection.getMinimumBalanceForRentExemption(
|
||||||
|
spl.ACCOUNT_SIZE
|
||||||
|
),
|
||||||
|
programId: spl.TOKEN_PROGRAM_ID,
|
||||||
|
}),
|
||||||
|
spl.createInitializeAccountInstruction(
|
||||||
|
tokenWallet.publicKey,
|
||||||
|
program.mint.address,
|
||||||
|
authority
|
||||||
|
),
|
||||||
|
spl.createSetAuthorityInstruction(
|
||||||
|
tokenWallet.publicKey,
|
||||||
|
authority,
|
||||||
|
spl.AuthorityType.AccountOwner,
|
||||||
|
program.programState.publicKey
|
||||||
|
),
|
||||||
|
types.oracleInit(
|
||||||
|
program,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
name: new Uint8Array(
|
||||||
|
Buffer.from(params.name ?? '', 'utf8').slice(0, 32)
|
||||||
|
),
|
||||||
|
metadata: new Uint8Array(
|
||||||
|
Buffer.from(params.metadata ?? '', 'utf8').slice(0, 128)
|
||||||
|
),
|
||||||
|
oracleBump,
|
||||||
|
stateBump: program.programState.bump,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
oracle: oracleAccount.publicKey,
|
||||||
|
oracleAuthority: authority,
|
||||||
|
wallet: tokenWallet.publicKey,
|
||||||
|
programState: program.programState.publicKey,
|
||||||
|
queue: params.queueAccount.publicKey,
|
||||||
|
payer,
|
||||||
|
systemProgram: SystemProgram.programId,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
],
|
||||||
|
params.authority ? [params.authority, tokenWallet] : [tokenWallet]
|
||||||
|
);
|
||||||
|
|
||||||
|
txns.push(oracleInit);
|
||||||
|
|
||||||
|
if (params.stakeAmount && params.stakeAmount > 0) {
|
||||||
|
const depositTxn = await oracleAccount.stakeInstructions(payer, {
|
||||||
|
stakeAmount: params.stakeAmount,
|
||||||
|
funderAuthority: params.funderAuthority,
|
||||||
|
funderTokenAccount: params.funderTokenAccount,
|
||||||
|
tokenAccount: tokenWallet.publicKey,
|
||||||
|
});
|
||||||
|
txns.push(depositTxn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const packed = TransactionObject.pack(txns);
|
||||||
|
if (packed.length > 1) {
|
||||||
|
throw new Error(`Expected a single TransactionObject`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [oracleAccount, packed[0]];
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange(callback: OnAccountChangeCallback<types.OracleAccountData>): number {
|
public static async create(
|
||||||
return this.program.connection.onAccountChange(
|
program: SwitchboardProgram,
|
||||||
this.publicKey,
|
params: {
|
||||||
accountInfo => {
|
queueAccount: QueueAccount;
|
||||||
callback(this.decode(accountInfo.data));
|
} & OracleInitParams &
|
||||||
}
|
OracleStakeParams
|
||||||
|
): Promise<[OracleAccount, TransactionSignature]> {
|
||||||
|
const [oracleAccount, txnObject] = await OracleAccount.createInstructions(
|
||||||
|
program,
|
||||||
|
program.walletPubkey,
|
||||||
|
params
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const txnSignature = await program.signAndSend(txnObject);
|
||||||
|
|
||||||
|
return [oracleAccount, txnSignature];
|
||||||
|
}
|
||||||
|
|
||||||
|
async stakeInstructions(
|
||||||
|
payer: PublicKey,
|
||||||
|
params: OracleStakeParams & { tokenAccount?: PublicKey }
|
||||||
|
): Promise<TransactionObject> {
|
||||||
|
if (!params.stakeAmount || params.stakeAmount <= 0) {
|
||||||
|
throw new Error(`stake amount should be greater than 0`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenWallet =
|
||||||
|
params.tokenAccount ?? (await this.loadData()).tokenAccount;
|
||||||
|
|
||||||
|
const funderAuthority = params.funderAuthority?.publicKey ?? payer;
|
||||||
|
const funderTokenAccount =
|
||||||
|
this.program.mint.getAssociatedAddress(funderAuthority);
|
||||||
|
const funderTokenAccountInfo = await this.program.connection.getAccountInfo(
|
||||||
|
funderTokenAccount
|
||||||
|
);
|
||||||
|
|
||||||
|
let wrapFundsTxn: TransactionObject;
|
||||||
|
|
||||||
|
if (!funderTokenAccountInfo) {
|
||||||
|
let userTokenAccount: PublicKey;
|
||||||
|
[userTokenAccount, wrapFundsTxn] =
|
||||||
|
await this.program.mint.createWrappedUserInstructions(
|
||||||
|
payer,
|
||||||
|
params.stakeAmount,
|
||||||
|
params.funderAuthority
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
wrapFundsTxn = await this.program.mint.wrapInstructions(
|
||||||
|
payer,
|
||||||
|
{ amount: params.stakeAmount },
|
||||||
|
params.funderAuthority
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapFundsTxn.add(
|
||||||
|
spl.createTransferInstruction(
|
||||||
|
funderTokenAccount,
|
||||||
|
tokenWallet,
|
||||||
|
funderAuthority,
|
||||||
|
this.program.mint.toTokenAmount(params.stakeAmount)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return wrapFundsTxn;
|
||||||
|
}
|
||||||
|
|
||||||
|
async stake(
|
||||||
|
params: OracleStakeParams & { tokenAccount?: PublicKey }
|
||||||
|
): Promise<TransactionSignature> {
|
||||||
|
const stakeTxn = await this.stakeInstructions(
|
||||||
|
this.program.walletPubkey,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
const txnSignature = await this.program.signAndSend(stakeTxn);
|
||||||
|
return txnSignature;
|
||||||
}
|
}
|
||||||
|
|
||||||
heartbeatInstruction(
|
heartbeatInstruction(
|
||||||
|
@ -201,7 +287,7 @@ export class OracleAccount extends Account<types.OracleAccountData> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async heartbeat(params: {
|
async heartbeat(params?: {
|
||||||
queueAccount: QueueAccount;
|
queueAccount: QueueAccount;
|
||||||
tokenWallet?: PublicKey;
|
tokenWallet?: PublicKey;
|
||||||
queueAuthority?: PublicKey;
|
queueAuthority?: PublicKey;
|
||||||
|
@ -209,23 +295,27 @@ export class OracleAccount extends Account<types.OracleAccountData> {
|
||||||
permission?: [PermissionAccount, number];
|
permission?: [PermissionAccount, number];
|
||||||
authority?: Keypair;
|
authority?: Keypair;
|
||||||
}): Promise<TransactionSignature> {
|
}): Promise<TransactionSignature> {
|
||||||
const tokenWallet =
|
const oracle = await this.loadData();
|
||||||
params.tokenWallet ?? (await this.loadData()).tokenAccount;
|
const tokenWallet = params?.tokenWallet ?? oracle.tokenAccount;
|
||||||
|
|
||||||
const queue = params.queue ?? (await params.queueAccount.loadData());
|
const queueAccount =
|
||||||
const oracles = await params.queueAccount.loadOracles();
|
params?.queueAccount ??
|
||||||
|
new QueueAccount(this.program, oracle.queuePubkey);
|
||||||
|
|
||||||
|
const queue = params?.queue ?? (await queueAccount.loadData());
|
||||||
|
const oracles = await queueAccount.loadOracles();
|
||||||
|
|
||||||
let lastPubkey = this.publicKey;
|
let lastPubkey = this.publicKey;
|
||||||
if (queue.size !== 0) {
|
if (oracles.length !== 0) {
|
||||||
lastPubkey = oracles[queue.gcIdx];
|
lastPubkey = oracles[queue.gcIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
const [permissionAccount, permissionBump] =
|
const [permissionAccount, permissionBump] =
|
||||||
params.permission ??
|
params?.permission ??
|
||||||
PermissionAccount.fromSeed(
|
PermissionAccount.fromSeed(
|
||||||
this.program,
|
this.program,
|
||||||
params.queueAuthority ?? queue.authority,
|
queue.authority,
|
||||||
params.queueAccount.publicKey,
|
queueAccount.publicKey,
|
||||||
this.publicKey
|
this.publicKey
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
|
@ -236,19 +326,29 @@ export class OracleAccount extends Account<types.OracleAccountData> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
params?.authority &&
|
||||||
|
!oracle.oracleAuthority.equals(params.authority.publicKey)
|
||||||
|
) {
|
||||||
|
throw new errors.IncorrectAuthority(
|
||||||
|
oracle.oracleAuthority,
|
||||||
|
params.authority.publicKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const heartbeatTxn = new TransactionObject(
|
const heartbeatTxn = new TransactionObject(
|
||||||
this.program.walletPubkey,
|
this.program.walletPubkey,
|
||||||
[
|
[
|
||||||
this.heartbeatInstruction(this.program.walletPubkey, {
|
this.heartbeatInstruction(this.program.walletPubkey, {
|
||||||
tokenWallet: tokenWallet,
|
tokenWallet: tokenWallet,
|
||||||
gcOracle: lastPubkey,
|
gcOracle: lastPubkey,
|
||||||
oracleQueue: params.queueAccount.publicKey,
|
oracleQueue: queueAccount.publicKey,
|
||||||
dataBuffer: queue.dataBuffer,
|
dataBuffer: queue.dataBuffer,
|
||||||
permission: [permissionAccount, permissionBump],
|
permission: [permissionAccount, permissionBump],
|
||||||
authority: params.authority ? params.authority.publicKey : undefined,
|
authority: oracle.oracleAuthority,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
params.authority ? [params.authority] : []
|
params?.authority ? [params.authority] : []
|
||||||
);
|
);
|
||||||
|
|
||||||
const txnSignature = await this.program.signAndSend(heartbeatTxn);
|
const txnSignature = await this.program.signAndSend(heartbeatTxn);
|
||||||
|
@ -357,3 +457,12 @@ export interface OracleInitParams {
|
||||||
/** Alternative keypair that will be the authority for the oracle. If not set the payer will be used. */
|
/** Alternative keypair that will be the authority for the oracle. If not set the payer will be used. */
|
||||||
authority?: Keypair;
|
authority?: Keypair;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OracleStakeParams {
|
||||||
|
/** The amount of funds to deposit into the oracle's staking wallet. The oracle must have the {@linkcode QueueAccount} minStake before being permitted to heartbeat and join the queue. */
|
||||||
|
stakeAmount?: number;
|
||||||
|
/** The tokenAccount for the account funding the staking wallet. Will default to the payer's associatedTokenAccount if not provided. */
|
||||||
|
funderTokenAccount?: PublicKey;
|
||||||
|
/** The funderTokenAccount authority for approving the transfer of funds from the funderTokenAccount into the oracle staking wallet. Will default to the payer if not provided. */
|
||||||
|
funderAuthority?: Keypair;
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,11 @@ import {
|
||||||
} from '@solana/web3.js';
|
} from '@solana/web3.js';
|
||||||
import * as errors from '../errors';
|
import * as errors from '../errors';
|
||||||
import * as types from '../generated';
|
import * as types from '../generated';
|
||||||
|
import {
|
||||||
|
PermitOracleHeartbeat,
|
||||||
|
PermitOracleQueueUsage,
|
||||||
|
PermitVrfRequests,
|
||||||
|
} from '../generated/types/SwitchboardPermission';
|
||||||
import { SwitchboardProgram } from '../program';
|
import { SwitchboardProgram } from '../program';
|
||||||
import { TransactionObject } from '../transaction';
|
import { TransactionObject } from '../transaction';
|
||||||
import { Account } from './account';
|
import { Account } from './account';
|
||||||
|
@ -20,6 +25,29 @@ export interface PermissionAccountInitParams {
|
||||||
authority: PublicKey;
|
authority: PublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PermitNoneJSON {
|
||||||
|
kind: 'PermitNone';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PermitNone {
|
||||||
|
static readonly discriminator = 0;
|
||||||
|
static readonly kind = 'NONE';
|
||||||
|
readonly discriminator = 0;
|
||||||
|
readonly kind = 'PermitNone';
|
||||||
|
|
||||||
|
toJSON(): PermitNoneJSON {
|
||||||
|
return {
|
||||||
|
kind: 'PermitNone',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toEncodable() {
|
||||||
|
return {
|
||||||
|
PermitOracleHeartbeat: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface PermissionSetParams {
|
export interface PermissionSetParams {
|
||||||
/** The {@linkcode types.SwitchboardPermission} to set for the grantee. */
|
/** The {@linkcode types.SwitchboardPermission} to set for the grantee. */
|
||||||
permission: types.SwitchboardPermissionKind;
|
permission: types.SwitchboardPermissionKind;
|
||||||
|
@ -39,6 +67,25 @@ export interface PermissionSetParams {
|
||||||
export class PermissionAccount extends Account<types.PermissionAccountData> {
|
export class PermissionAccount extends Account<types.PermissionAccountData> {
|
||||||
static accountName = 'PermissionAccountData';
|
static accountName = 'PermissionAccountData';
|
||||||
|
|
||||||
|
static getPermissions(
|
||||||
|
permission: types.PermissionAccountData
|
||||||
|
): types.SwitchboardPermissionKind | PermitNone {
|
||||||
|
switch (permission.permissions) {
|
||||||
|
case 0:
|
||||||
|
return new PermitNone();
|
||||||
|
case 1:
|
||||||
|
return new PermitOracleHeartbeat();
|
||||||
|
case 2:
|
||||||
|
return new PermitOracleQueueUsage();
|
||||||
|
case 3:
|
||||||
|
return new PermitVrfRequests();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`Failed to find the assigned permissions, expected a value from 0 - 3, received ${permission.permissions}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a PermissionAccount from the expected PDA seed format.
|
* Loads a PermissionAccount from the expected PDA seed format.
|
||||||
* @param program The Switchboard program for the current connection.
|
* @param program The Switchboard program for the current connection.
|
||||||
|
|
|
@ -24,7 +24,11 @@ import { BufferRelayerAccount, BufferRelayerInit } from './bufferRelayAccount';
|
||||||
import { CrankAccount, CrankInitParams } from './crankAccount';
|
import { CrankAccount, CrankInitParams } from './crankAccount';
|
||||||
import { JobAccount, JobInitParams } from './jobAccount';
|
import { JobAccount, JobInitParams } from './jobAccount';
|
||||||
import { LeaseAccount } from './leaseAccount';
|
import { LeaseAccount } from './leaseAccount';
|
||||||
import { OracleAccount, OracleInitParams } from './oracleAccount';
|
import {
|
||||||
|
OracleAccount,
|
||||||
|
OracleInitParams,
|
||||||
|
OracleStakeParams,
|
||||||
|
} from './oracleAccount';
|
||||||
import { PermissionAccount, PermissionSetParams } from './permissionAccount';
|
import { PermissionAccount, PermissionSetParams } from './permissionAccount';
|
||||||
import { QueueDataBuffer } from './queueDataBuffer';
|
import { QueueDataBuffer } from './queueDataBuffer';
|
||||||
import { VrfAccount, VrfInitParams } from './vrfAccount';
|
import { VrfAccount, VrfInitParams } from './vrfAccount';
|
||||||
|
@ -274,8 +278,10 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
||||||
public async createOracleInstructions(
|
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. */
|
/** 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,
|
payer: PublicKey,
|
||||||
params: OracleInitParams & Partial<Omit<PermissionSetParams, 'permission'>>
|
params: OracleInitParams &
|
||||||
): Promise<[OracleAccount, TransactionObject]> {
|
OracleStakeParams &
|
||||||
|
Partial<Omit<PermissionSetParams, 'permission'>>
|
||||||
|
): Promise<[OracleAccount, Array<TransactionObject>]> {
|
||||||
const queue = await this.loadData();
|
const queue = await this.loadData();
|
||||||
|
|
||||||
const [oracleAccount, createOracleTxnObject] =
|
const [oracleAccount, createOracleTxnObject] =
|
||||||
|
@ -302,7 +308,10 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
||||||
|
|
||||||
return [
|
return [
|
||||||
oracleAccount,
|
oracleAccount,
|
||||||
createOracleTxnObject.combine(createPermissionTxnObject),
|
TransactionObject.pack([
|
||||||
|
createOracleTxnObject,
|
||||||
|
createPermissionTxnObject,
|
||||||
|
]),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,8 +335,10 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
public async createOracle(
|
public async createOracle(
|
||||||
params: OracleInitParams & Partial<Omit<PermissionSetParams, 'permission'>>
|
params: OracleInitParams &
|
||||||
): Promise<[OracleAccount, TransactionSignature]> {
|
OracleStakeParams &
|
||||||
|
Partial<Omit<PermissionSetParams, 'permission'>>
|
||||||
|
): Promise<[OracleAccount, Array<TransactionSignature>]> {
|
||||||
const signers: Keypair[] = [];
|
const signers: Keypair[] = [];
|
||||||
|
|
||||||
const queue = await this.loadData();
|
const queue = await this.loadData();
|
||||||
|
@ -344,9 +355,9 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
|
|
||||||
const signature = await this.program.signAndSend(txn);
|
const signatures = await this.program.signAndSendAll(txn);
|
||||||
|
|
||||||
return [oracleAccount, signature];
|
return [oracleAccount, signatures];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as anchor from '@project-serum/anchor';
|
import * as anchor from '@project-serum/anchor';
|
||||||
|
import { PublicKey } from '@solana/web3.js';
|
||||||
|
|
||||||
export class SwitchboardProgramIsBrowserError extends Error {
|
export class SwitchboardProgramIsBrowserError extends Error {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -69,3 +70,11 @@ export class TransactionMissingSignerError extends Error {
|
||||||
Object.setPrototypeOf(this, TransactionMissingSignerError.prototype);
|
Object.setPrototypeOf(this, TransactionMissingSignerError.prototype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export class IncorrectAuthority extends Error {
|
||||||
|
constructor(expectedAuthority: PublicKey, receivedAuthority: PublicKey) {
|
||||||
|
super(
|
||||||
|
`incorrect authority, expected ${expectedAuthority}, received ${receivedAuthority}`
|
||||||
|
);
|
||||||
|
Object.setPrototypeOf(this, IncorrectAuthority.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -31,8 +31,11 @@ export class Mint {
|
||||||
return this.provider.connection;
|
return this.provider.connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async load(provider: anchor.AnchorProvider): Promise<Mint> {
|
public static async load(
|
||||||
const splMint = await spl.getMint(provider.connection, Mint.native);
|
provider: anchor.AnchorProvider,
|
||||||
|
mint = Mint.native
|
||||||
|
): Promise<Mint> {
|
||||||
|
const splMint = await spl.getMint(provider.connection, mint);
|
||||||
return new Mint(provider, splMint);
|
return new Mint(provider, splMint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,15 +87,11 @@ export class Mint {
|
||||||
return this.fromTokenAmount(userAccount.amount);
|
return this.fromTokenAmount(userAccount.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAssociatedAddress(
|
public getAssociatedAddress(user: PublicKey): PublicKey {
|
||||||
user: anchor.web3.PublicKey
|
|
||||||
): anchor.web3.PublicKey {
|
|
||||||
return Mint.getAssociatedAddress(user);
|
return Mint.getAssociatedAddress(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getAssociatedAddress(
|
public static getAssociatedAddress(user: PublicKey): PublicKey {
|
||||||
user: anchor.web3.PublicKey
|
|
||||||
): anchor.web3.PublicKey {
|
|
||||||
const [associatedToken] = anchor.utils.publicKey.findProgramAddressSync(
|
const [associatedToken] = anchor.utils.publicKey.findProgramAddressSync(
|
||||||
[
|
[
|
||||||
user.toBuffer(),
|
user.toBuffer(),
|
||||||
|
@ -107,7 +106,7 @@ export class Mint {
|
||||||
public async getOrCreateAssociatedUser(
|
public async getOrCreateAssociatedUser(
|
||||||
payer: PublicKey,
|
payer: PublicKey,
|
||||||
user?: Keypair
|
user?: Keypair
|
||||||
): Promise<anchor.web3.PublicKey> {
|
): Promise<PublicKey> {
|
||||||
const owner = user ? user.publicKey : payer;
|
const owner = user ? user.publicKey : payer;
|
||||||
const associatedToken = Mint.getAssociatedAddress(owner);
|
const associatedToken = Mint.getAssociatedAddress(owner);
|
||||||
const accountInfo = await this.connection.getAccountInfo(associatedToken);
|
const accountInfo = await this.connection.getAccountInfo(associatedToken);
|
||||||
|
@ -120,9 +119,9 @@ export class Mint {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createAssocatedUser(
|
public async createAssocatedUser(
|
||||||
payer: anchor.web3.PublicKey,
|
payer: PublicKey,
|
||||||
user?: Keypair
|
user?: Keypair
|
||||||
): Promise<[anchor.web3.PublicKey, string]> {
|
): Promise<[PublicKey, string]> {
|
||||||
const [txn, associatedToken] = this.createAssocatedUserInstruction(
|
const [txn, associatedToken] = this.createAssocatedUserInstruction(
|
||||||
payer,
|
payer,
|
||||||
user
|
user
|
||||||
|
@ -133,7 +132,7 @@ export class Mint {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static createAssocatedUserInstruction(
|
public static createAssocatedUserInstruction(
|
||||||
payer: anchor.web3.PublicKey,
|
payer: PublicKey,
|
||||||
user?: Keypair
|
user?: Keypair
|
||||||
): [TransactionObject, PublicKey] {
|
): [TransactionObject, PublicKey] {
|
||||||
const owner = user ? user.publicKey : payer;
|
const owner = user ? user.publicKey : payer;
|
||||||
|
@ -184,8 +183,107 @@ export class Mint {
|
||||||
return [account, sig];
|
return [account, sig];
|
||||||
}
|
}
|
||||||
|
|
||||||
public async wrapInstruction(
|
public async signAndSend(
|
||||||
payer: anchor.web3.PublicKey,
|
txn: TransactionObject,
|
||||||
|
opts: anchor.web3.ConfirmOptions = {
|
||||||
|
skipPreflight: false,
|
||||||
|
maxRetries: 10,
|
||||||
|
}
|
||||||
|
): Promise<TransactionSignature> {
|
||||||
|
const blockhash = await this.connection.getLatestBlockhash();
|
||||||
|
const txnSignature = await this.provider.sendAndConfirm(
|
||||||
|
await this.provider.wallet.signTransaction(txn.toTxn(blockhash)),
|
||||||
|
txn.signers,
|
||||||
|
opts
|
||||||
|
);
|
||||||
|
return txnSignature;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NativeMint extends Mint {
|
||||||
|
public static async load(
|
||||||
|
provider: anchor.AnchorProvider
|
||||||
|
): Promise<NativeMint> {
|
||||||
|
const splMint = await spl.getMint(provider.connection, Mint.native);
|
||||||
|
return new NativeMint(provider, splMint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createWrappedUserInstructions(
|
||||||
|
payer: PublicKey,
|
||||||
|
amount: number,
|
||||||
|
user?: Keypair
|
||||||
|
): Promise<[PublicKey, TransactionObject]> {
|
||||||
|
const owner = user ? user.publicKey : payer;
|
||||||
|
const associatedAddress = this.getAssociatedAddress(owner);
|
||||||
|
const associatedAccountInfo =
|
||||||
|
this.connection.getAccountInfo(associatedAddress);
|
||||||
|
if (!associatedAccountInfo) {
|
||||||
|
throw new Error(
|
||||||
|
`Associated token address already exists for this user ${owner}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ephemeralAccount = Keypair.generate();
|
||||||
|
const ephemeralWallet = this.getAssociatedAddress(
|
||||||
|
ephemeralAccount.publicKey
|
||||||
|
);
|
||||||
|
|
||||||
|
const wrapAmountLamports = this.toTokenAmount(amount);
|
||||||
|
|
||||||
|
return [
|
||||||
|
associatedAddress,
|
||||||
|
new TransactionObject(
|
||||||
|
payer,
|
||||||
|
[
|
||||||
|
spl.createAssociatedTokenAccountInstruction(
|
||||||
|
payer,
|
||||||
|
associatedAddress,
|
||||||
|
owner,
|
||||||
|
Mint.native
|
||||||
|
),
|
||||||
|
spl.createAssociatedTokenAccountInstruction(
|
||||||
|
payer,
|
||||||
|
ephemeralWallet,
|
||||||
|
ephemeralAccount.publicKey,
|
||||||
|
spl.NATIVE_MINT
|
||||||
|
),
|
||||||
|
SystemProgram.transfer({
|
||||||
|
fromPubkey: owner,
|
||||||
|
toPubkey: ephemeralWallet,
|
||||||
|
lamports: wrapAmountLamports,
|
||||||
|
}),
|
||||||
|
spl.createSyncNativeInstruction(ephemeralWallet),
|
||||||
|
spl.createTransferInstruction(
|
||||||
|
ephemeralWallet,
|
||||||
|
associatedAddress,
|
||||||
|
ephemeralAccount.publicKey,
|
||||||
|
wrapAmountLamports
|
||||||
|
),
|
||||||
|
spl.createCloseAccountInstruction(
|
||||||
|
ephemeralWallet,
|
||||||
|
owner,
|
||||||
|
ephemeralAccount.publicKey
|
||||||
|
),
|
||||||
|
],
|
||||||
|
user ? [user, ephemeralAccount] : [ephemeralAccount]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createWrappedUser(
|
||||||
|
payer: PublicKey,
|
||||||
|
amount: number,
|
||||||
|
user?: Keypair
|
||||||
|
): Promise<[PublicKey, TransactionSignature]> {
|
||||||
|
const [tokenAccount, createWrappedUserTxn] =
|
||||||
|
await this.createWrappedUserInstructions(payer, amount, user);
|
||||||
|
const txSignature = await this.signAndSend(createWrappedUserTxn);
|
||||||
|
|
||||||
|
return [tokenAccount, txSignature];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async wrapInstructions(
|
||||||
|
payer: PublicKey,
|
||||||
params:
|
params:
|
||||||
| {
|
| {
|
||||||
amount: number;
|
amount: number;
|
||||||
|
@ -193,10 +291,6 @@ export class Mint {
|
||||||
| { fundUpTo: Big },
|
| { fundUpTo: Big },
|
||||||
user?: Keypair
|
user?: Keypair
|
||||||
): Promise<TransactionObject> {
|
): Promise<TransactionObject> {
|
||||||
if (!this.address.equals(Mint.native)) {
|
|
||||||
throw new NativeMintOnlyError();
|
|
||||||
}
|
|
||||||
|
|
||||||
const ixns: TransactionInstruction[] = [];
|
const ixns: TransactionInstruction[] = [];
|
||||||
|
|
||||||
const owner = user ? user.publicKey : payer;
|
const owner = user ? user.publicKey : payer;
|
||||||
|
@ -209,16 +303,6 @@ export class Mint {
|
||||||
userAccountInfo === null
|
userAccountInfo === null
|
||||||
? null
|
? null
|
||||||
: spl.unpackAccount(userAddress, userAccountInfo);
|
: spl.unpackAccount(userAddress, userAccountInfo);
|
||||||
// if (userAccount === null) {
|
|
||||||
// ixns.push(
|
|
||||||
// spl.createAssociatedTokenAccountInstruction(
|
|
||||||
// payer,
|
|
||||||
// userAddress,
|
|
||||||
// owner,
|
|
||||||
// Mint.native
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
const tokenBalance = userAccount
|
const tokenBalance = userAccount
|
||||||
? new Big(this.fromTokenAmount(userAccount.amount))
|
? new Big(this.fromTokenAmount(userAccount.amount))
|
||||||
|
@ -283,7 +367,7 @@ export class Mint {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async wrap(
|
public async wrap(
|
||||||
payer: anchor.web3.PublicKey,
|
payer: PublicKey,
|
||||||
params:
|
params:
|
||||||
| {
|
| {
|
||||||
amount: number;
|
amount: number;
|
||||||
|
@ -291,25 +375,17 @@ export class Mint {
|
||||||
| { fundUpTo: Big },
|
| { fundUpTo: Big },
|
||||||
user?: Keypair
|
user?: Keypair
|
||||||
) {
|
) {
|
||||||
if (!this.address.equals(Mint.native)) {
|
const wrapIxns = await this.wrapInstructions(payer, params, user);
|
||||||
throw new NativeMintOnlyError();
|
|
||||||
}
|
|
||||||
|
|
||||||
const wrapIxns = await this.wrapInstruction(payer, params, user);
|
|
||||||
const txSignature = await this.signAndSend(wrapIxns);
|
const txSignature = await this.signAndSend(wrapIxns);
|
||||||
|
|
||||||
return txSignature;
|
return txSignature;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async unwrapInstruction(
|
public async unwrapInstructions(
|
||||||
payer: anchor.web3.PublicKey,
|
payer: PublicKey,
|
||||||
amount?: number,
|
amount?: number,
|
||||||
user?: Keypair
|
user?: Keypair
|
||||||
): Promise<TransactionObject> {
|
): Promise<TransactionObject> {
|
||||||
if (!this.address.equals(Mint.native)) {
|
|
||||||
throw new NativeMintOnlyError();
|
|
||||||
}
|
|
||||||
|
|
||||||
const owner = user ? user.publicKey : payer;
|
const owner = user ? user.publicKey : payer;
|
||||||
|
|
||||||
const ixns: TransactionInstruction[] = [];
|
const ixns: TransactionInstruction[] = [];
|
||||||
|
@ -359,7 +435,7 @@ export class Mint {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async unwrap(
|
public async unwrap(
|
||||||
payer: anchor.web3.PublicKey,
|
payer: PublicKey,
|
||||||
amount?: number,
|
amount?: number,
|
||||||
user?: Keypair
|
user?: Keypair
|
||||||
): Promise<TransactionSignature> {
|
): Promise<TransactionSignature> {
|
||||||
|
@ -367,24 +443,8 @@ export class Mint {
|
||||||
throw new NativeMintOnlyError();
|
throw new NativeMintOnlyError();
|
||||||
}
|
}
|
||||||
|
|
||||||
const unwrapTxn = await this.unwrapInstruction(payer, amount, user);
|
const unwrapTxn = await this.unwrapInstructions(payer, amount, user);
|
||||||
const txSignature = await this.signAndSend(unwrapTxn);
|
const txSignature = await this.signAndSend(unwrapTxn);
|
||||||
return txSignature;
|
return txSignature;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async signAndSend(
|
|
||||||
txn: TransactionObject,
|
|
||||||
opts: anchor.web3.ConfirmOptions = {
|
|
||||||
skipPreflight: false,
|
|
||||||
maxRetries: 10,
|
|
||||||
}
|
|
||||||
): Promise<TransactionSignature> {
|
|
||||||
const blockhash = await this.connection.getLatestBlockhash();
|
|
||||||
const txnSignature = await this.provider.sendAndConfirm(
|
|
||||||
await this.provider.wallet.signTransaction(txn.toTxn(blockhash)),
|
|
||||||
txn.signers,
|
|
||||||
opts
|
|
||||||
);
|
|
||||||
return txnSignature;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,12 @@ import {
|
||||||
Transaction,
|
Transaction,
|
||||||
TransactionSignature,
|
TransactionSignature,
|
||||||
} from '@solana/web3.js';
|
} from '@solana/web3.js';
|
||||||
import { Mint } from './mint';
|
import { types } from './';
|
||||||
|
import { Mint, NativeMint } from './mint';
|
||||||
import { TransactionObject } from './transaction';
|
import { TransactionObject } from './transaction';
|
||||||
import { SwitchboardEvents } from './switchboardEvents';
|
import { SwitchboardEvents } from './switchboardEvents';
|
||||||
|
import { fromCode as fromSwitchboardCode } from './generated/errors/custom';
|
||||||
|
import { fromCode as fromAnchorCode } from './generated/errors/anchor';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switchboard Devnet Program ID
|
* Switchboard Devnet Program ID
|
||||||
|
@ -86,7 +89,7 @@ export class SwitchboardProgram {
|
||||||
bump: number;
|
bump: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
readonly mint: Mint;
|
readonly mint: NativeMint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
|
@ -94,7 +97,7 @@ export class SwitchboardProgram {
|
||||||
constructor(
|
constructor(
|
||||||
program: anchor.Program,
|
program: anchor.Program,
|
||||||
cluster: Cluster | 'localnet',
|
cluster: Cluster | 'localnet',
|
||||||
mint: Mint
|
mint: NativeMint
|
||||||
) {
|
) {
|
||||||
this._program = program;
|
this._program = program;
|
||||||
this.cluster = cluster;
|
this.cluster = cluster;
|
||||||
|
@ -169,7 +172,9 @@ export class SwitchboardProgram {
|
||||||
payerKeypair,
|
payerKeypair,
|
||||||
programId
|
programId
|
||||||
);
|
);
|
||||||
const mint = await Mint.load(program.provider as anchor.AnchorProvider);
|
const mint = await NativeMint.load(
|
||||||
|
program.provider as anchor.AnchorProvider
|
||||||
|
);
|
||||||
return new SwitchboardProgram(program, cluster, mint);
|
return new SwitchboardProgram(program, cluster, mint);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -308,17 +313,37 @@ export class SwitchboardProgram {
|
||||||
s.publicKey.equals(txn.payer) || reqSigners.has(s.publicKey.toBase58())
|
s.publicKey.equals(txn.payer) || reqSigners.has(s.publicKey.toBase58())
|
||||||
);
|
);
|
||||||
|
|
||||||
const txnSignature = await this.provider.sendAndConfirm(
|
const transaction = txn.toTxn(
|
||||||
txn.toTxn(blockhash ?? (await this.connection.getLatestBlockhash())),
|
blockhash ?? (await this.connection.getLatestBlockhash())
|
||||||
filteredSigners,
|
|
||||||
{
|
|
||||||
skipPreflight: false,
|
|
||||||
maxRetries: 10,
|
|
||||||
...opts,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return txnSignature;
|
try {
|
||||||
|
const txnSignature = await this.provider.sendAndConfirm(
|
||||||
|
transaction,
|
||||||
|
filteredSigners,
|
||||||
|
{
|
||||||
|
skipPreflight: false,
|
||||||
|
maxRetries: 10,
|
||||||
|
...opts,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return txnSignature;
|
||||||
|
} catch (error) {
|
||||||
|
if ('code' in (error as any) && typeof (error as any).code === 'number') {
|
||||||
|
const switchboardError = fromSwitchboardCode((error as any).code);
|
||||||
|
if (switchboardError) {
|
||||||
|
throw switchboardError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const anchorError = fromAnchorCode((error as any).code);
|
||||||
|
if (anchorError) {
|
||||||
|
throw anchorError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import chai, { expect } from 'chai';
|
|
||||||
import assert from 'assert';
|
|
||||||
|
|
||||||
import * as sbv2 from '../src';
|
import * as sbv2 from '../src';
|
||||||
import { setupTest, TestContext } from './utilts';
|
import { setupTest, TestContext } from './utilts';
|
||||||
|
@ -189,9 +187,8 @@ describe('Aggregator Tests', () => {
|
||||||
}
|
}
|
||||||
const aggregatorAccount = fundedAggregator;
|
const aggregatorAccount = fundedAggregator;
|
||||||
|
|
||||||
const initialUserTokenBalance = await ctx.program.mint.getBalance(
|
const initialUserTokenBalance =
|
||||||
ctx.payer.publicKey
|
(await ctx.program.mint.getBalance(ctx.payer.publicKey)) ?? 0;
|
||||||
);
|
|
||||||
|
|
||||||
const [leaseAccount] = LeaseAccount.fromSeed(
|
const [leaseAccount] = LeaseAccount.fromSeed(
|
||||||
ctx.program,
|
ctx.program,
|
||||||
|
@ -214,6 +211,13 @@ describe('Aggregator Tests', () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const finalUserBalance = await ctx.program.mint.getBalance(
|
||||||
|
ctx.payer.publicKey
|
||||||
|
);
|
||||||
|
if (!finalUserBalance) {
|
||||||
|
throw new Error(`Users wrapped account was closed`);
|
||||||
|
}
|
||||||
|
|
||||||
const finalUserTokenBalance =
|
const finalUserTokenBalance =
|
||||||
(await ctx.program.mint.getBalance(ctx.payer.publicKey)) ?? 0;
|
(await ctx.program.mint.getBalance(ctx.payer.publicKey)) ?? 0;
|
||||||
if (initialUserTokenBalance !== finalUserTokenBalance) {
|
if (initialUserTokenBalance !== finalUserTokenBalance) {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import chai, { expect } from 'chai';
|
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
|
||||||
import { setupTest, TestContext } from './utilts';
|
import { setupTest, TestContext } from './utilts';
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import chai, { expect } from 'chai';
|
|
||||||
import assert from 'assert';
|
|
||||||
|
|
||||||
import * as anchor from '@project-serum/anchor';
|
import * as anchor from '@project-serum/anchor';
|
||||||
import { setupTest, TestContext } from './utilts';
|
import { setupTest, TestContext } from './utilts';
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
import * as sbv2 from '../src';
|
import * as sbv2 from '../src';
|
||||||
import { setupTest, TestContext } from './utilts';
|
import { setupTest, TestContext } from './utilts';
|
||||||
import { Keypair } from '@solana/web3.js';
|
import { Keypair } from '@solana/web3.js';
|
||||||
import { AggregatorAccount, OracleAccount, QueueAccount, types } from '../src';
|
import {
|
||||||
|
AggregatorAccount,
|
||||||
|
OracleAccount,
|
||||||
|
PermissionAccount,
|
||||||
|
QueueAccount,
|
||||||
|
types,
|
||||||
|
} from '../src';
|
||||||
import { OracleJob } from '@switchboard-xyz/common';
|
import { OracleJob } from '@switchboard-xyz/common';
|
||||||
|
import { PermitOracleQueueUsage } from '../src/generated/types/SwitchboardPermission';
|
||||||
|
|
||||||
describe('Open Round Tests', () => {
|
describe('Open Round Tests', () => {
|
||||||
let ctx: TestContext;
|
let ctx: TestContext;
|
||||||
|
@ -15,17 +23,18 @@ describe('Open Round Tests', () => {
|
||||||
let queueAccount: QueueAccount;
|
let queueAccount: QueueAccount;
|
||||||
let queue: types.OracleQueueAccountData;
|
let queue: types.OracleQueueAccountData;
|
||||||
|
|
||||||
let createOracleSignature1: string;
|
let createOracleSignature1: string[];
|
||||||
let oracleAccount1: OracleAccount;
|
let oracleAccount1: OracleAccount;
|
||||||
let oracle1: types.OracleAccountData;
|
let oracle1: types.OracleAccountData;
|
||||||
|
|
||||||
let createOracleSignature2: string;
|
let createOracleSignature2: string[];
|
||||||
let oracleAccount2: OracleAccount;
|
let oracleAccount2: OracleAccount;
|
||||||
let oracle2: types.OracleAccountData;
|
let oracle2: types.OracleAccountData;
|
||||||
|
|
||||||
let createAggregatorSignatures: string[];
|
let createAggregatorSignatures: string[];
|
||||||
let aggregatorAccount: AggregatorAccount;
|
let aggregatorAccount: AggregatorAccount;
|
||||||
let aggregator: types.AggregatorAccountData;
|
let aggregator: types.AggregatorAccountData;
|
||||||
|
let aggregatorPermissionAccount: PermissionAccount;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
ctx = await setupTest();
|
ctx = await setupTest();
|
||||||
|
@ -52,6 +61,7 @@ describe('Open Round Tests', () => {
|
||||||
name: 'oracle-1',
|
name: 'oracle-1',
|
||||||
metadata: 'oracle-1',
|
metadata: 'oracle-1',
|
||||||
queueAuthority,
|
queueAuthority,
|
||||||
|
enable: true,
|
||||||
});
|
});
|
||||||
oracle1 = await oracleAccount1.loadData();
|
oracle1 = await oracleAccount1.loadData();
|
||||||
|
|
||||||
|
@ -59,18 +69,18 @@ describe('Open Round Tests', () => {
|
||||||
name: 'oracle-2',
|
name: 'oracle-2',
|
||||||
metadata: 'oracle-2',
|
metadata: 'oracle-2',
|
||||||
queueAuthority,
|
queueAuthority,
|
||||||
|
enable: true,
|
||||||
});
|
});
|
||||||
oracle2 = await oracleAccount2.loadData();
|
oracle2 = await oracleAccount2.loadData();
|
||||||
|
|
||||||
[aggregatorAccount, createAggregatorSignatures] =
|
[aggregatorAccount, createAggregatorSignatures] =
|
||||||
await queueAccount.createFeed({
|
await queueAccount.createFeed({
|
||||||
queueAuthority: queueAuthority,
|
batchSize: 2,
|
||||||
batchSize: 1,
|
minRequiredOracleResults: 2,
|
||||||
minRequiredOracleResults: 1,
|
|
||||||
minRequiredJobResults: 1,
|
minRequiredJobResults: 1,
|
||||||
minUpdateDelaySeconds: 60,
|
minUpdateDelaySeconds: 5,
|
||||||
fundAmount: 1,
|
fundAmount: 1,
|
||||||
enable: true,
|
enable: false,
|
||||||
jobs: [
|
jobs: [
|
||||||
{
|
{
|
||||||
weight: 2,
|
weight: 2,
|
||||||
|
@ -88,5 +98,64 @@ describe('Open Round Tests', () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[aggregatorPermissionAccount] = PermissionAccount.fromSeed(
|
||||||
|
ctx.program,
|
||||||
|
queueAuthority.publicKey,
|
||||||
|
queueAccount.publicKey,
|
||||||
|
aggregatorAccount.publicKey
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails to call open round when aggregator lacks permissions', async () => {
|
||||||
|
assert.rejects(
|
||||||
|
async () => {
|
||||||
|
await aggregatorAccount.openRound();
|
||||||
|
},
|
||||||
|
new RegExp(/custom program error: 0x1793/g)
|
||||||
|
// { code: 6035 } // PermissionDenied
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets aggregator permissions', async () => {
|
||||||
|
await aggregatorPermissionAccount.set({
|
||||||
|
permission: new PermitOracleQueueUsage(),
|
||||||
|
enable: true,
|
||||||
|
queueAuthority,
|
||||||
|
});
|
||||||
|
const permissions = await aggregatorPermissionAccount.loadData();
|
||||||
|
|
||||||
|
assert(
|
||||||
|
permissions.permissions === PermitOracleQueueUsage.discriminator + 1,
|
||||||
|
`Aggregator has incorrect permissions, expected ${
|
||||||
|
PermitOracleQueueUsage.kind
|
||||||
|
}, received ${PermissionAccount.getPermissions(permissions).kind}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails to call open round when not enough oracles are heartbeating', async () => {
|
||||||
|
assert.rejects(
|
||||||
|
async () => {
|
||||||
|
await aggregatorAccount.openRound();
|
||||||
|
},
|
||||||
|
new RegExp(/custom program error: 0x17a4/g)
|
||||||
|
// { code: 6052 } // InsufficientOracleQueueError
|
||||||
|
);
|
||||||
|
|
||||||
|
// still fails when queueSize < batchSize
|
||||||
|
await oracleAccount1.heartbeat();
|
||||||
|
assert.rejects(
|
||||||
|
async () => {
|
||||||
|
await aggregatorAccount.openRound();
|
||||||
|
},
|
||||||
|
new RegExp(/custom program error: 0x17a4/g)
|
||||||
|
// { code: 6052 } // InsufficientOracleQueueError
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('successfully calls open round', async () => {
|
||||||
|
await oracleAccount2.heartbeat();
|
||||||
|
// start heartbeating
|
||||||
|
await aggregatorAccount.openRound();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import chai, { expect } from 'chai';
|
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
|
||||||
import * as sbv2 from '../src';
|
import * as sbv2 from '../src';
|
||||||
|
@ -46,6 +45,7 @@ describe('Queue Tests', () => {
|
||||||
queueAuthority,
|
queueAuthority,
|
||||||
enable: true,
|
enable: true,
|
||||||
authority: oracleAuthority,
|
authority: oracleAuthority,
|
||||||
|
stakeAmount: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
const oracle = await oracleAccount.loadData();
|
const oracle = await oracleAccount.loadData();
|
||||||
|
|
|
@ -58,8 +58,8 @@ export async function setupTest(): Promise<TestContext> {
|
||||||
payer.publicKey,
|
payer.publicKey,
|
||||||
1 * LAMPORTS_PER_SOL
|
1 * LAMPORTS_PER_SOL
|
||||||
);
|
);
|
||||||
await program.connection.confirmTransaction(airdropTxn);
|
|
||||||
console.log(`Airdrop requested: ${airdropTxn}`);
|
console.log(`Airdrop requested: ${airdropTxn}`);
|
||||||
|
await program.connection.confirmTransaction(airdropTxn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if programStateAccount exists
|
// Check if programStateAccount exists
|
||||||
|
|
Loading…
Reference in New Issue