diff --git a/.github/workflows/solana-js-test.yml b/.github/workflows/solana-js-test.yml index f1664d9..803efb8 100644 --- a/.github/workflows/solana-js-test.yml +++ b/.github/workflows/solana-js-test.yml @@ -64,5 +64,5 @@ jobs: run: | solana-keygen new --no-bip39-passphrase --outfile ~/.config/solana/id.json --silent mkdir -p .anchor/test-ledger - solana-test-validator -q -r --ledger .anchor/test-ledger --mint $(solana-keygen pubkey ~/.config/solana/id.json) --bind-address 0.0.0.0 --url https://api.devnet.solana.com --rpc-port 8899 --clone 2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG --clone J4CArpsbrZqu1axqQ4AnrqREs3jwoyA1M5LMiQQmAzB9 --clone CKwZcshn4XDvhaWVH9EXnk3iu19t6t5xP2Sy2pD6TRDp & sleep 15 + solana-test-validator -q -r --ledger .anchor/test-ledger --mint $(solana-keygen pubkey ~/.config/solana/id.json) --bind-address 0.0.0.0 --url https://api.devnet.solana.com --rpc-port 8899 --clone 2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG --clone J4CArpsbrZqu1axqQ4AnrqREs3jwoyA1M5LMiQQmAzB9 --clone CKwZcshn4XDvhaWVH9EXnk3iu19t6t5xP2Sy2pD6TRDp --clone BYM81n8HvTJuqZU1PmTVcwZ9G8uoji7FKM6EaPkwphPt --clone FVLfR6C2ckZhbSwBzZY4CX7YBcddUSge5BNeGQv5eKhy & sleep 15 npm run test:localnet diff --git a/javascript/solana.js/src/accounts/aggregatorAccount.ts b/javascript/solana.js/src/accounts/aggregatorAccount.ts index bbfd18d..fbc7199 100644 --- a/javascript/solana.js/src/accounts/aggregatorAccount.ts +++ b/javascript/solana.js/src/accounts/aggregatorAccount.ts @@ -5,6 +5,7 @@ import * as errors from '../errors'; import Big from 'big.js'; import { SwitchboardProgram } from '../program'; import { + AccountMeta, Keypair, PublicKey, SystemProgram, @@ -20,6 +21,7 @@ import { LeaseAccount } from './leaseAccount'; import { PermissionAccount } from './permissionAccount'; import * as spl from '@solana/spl-token'; import { TransactionObject } from '../transaction'; +import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; export class AggregatorAccount extends Account { static accountName = 'AggregatorAccountData'; @@ -716,62 +718,20 @@ export class AggregatorAccount extends Account { return txnSignature; } - public openRoundInstruction( + public async openRoundInstruction( payer: PublicKey, - params: { - queueAccount: QueueAccount; - queueAuthority: PublicKey; - queueDataBuffer: PublicKey; - mint: PublicKey; - payoutWallet: PublicKey; - lease: [LeaseAccount, number]; - leaseEscrow: PublicKey; - permission: [PermissionAccount, number]; - } - ): TransactionInstruction { - const [leaseAccount, leaseBump] = params.lease; - const [permissionAccount, permissionBump] = params.permission; - - return types.aggregatorOpenRound( - this.program, - { - params: { - stateBump: this.program.programState.bump, - leaseBump: leaseBump, - permissionBump: permissionBump, - jitter: 0, // what is this for? - }, - }, - { - aggregator: this.publicKey, - lease: leaseAccount.publicKey, - oracleQueue: params.queueAccount.publicKey, - queueAuthority: params.queueAuthority, - permission: permissionAccount.publicKey, - escrow: params.leaseEscrow, - programState: this.program.programState.publicKey, - payoutWallet: params.payoutWallet, - tokenProgram: spl.TOKEN_PROGRAM_ID, - dataBuffer: params.queueDataBuffer, - mint: params.mint, - } - ); - } - - public async openRound( params: Partial<{ - aggregator: types.AggregatorAccountData; - queueAccount: QueueAccount; - queueAuthority: PublicKey; - queueDataBuffer: PublicKey; - mint: PublicKey; - payoutWallet: PublicKey; - lease: [LeaseAccount, number]; - leaseEscrow: PublicKey; - permission: [PermissionAccount, number]; + aggregator?: types.AggregatorAccountData; + queueAccount?: QueueAccount; + queue?: types.OracleQueueAccountData; + queueAuthority?: PublicKey; + queueDataBuffer?: PublicKey; + payoutWallet?: PublicKey; + lease?: [LeaseAccount, number]; + leaseEscrow?: PublicKey; + permission?: [PermissionAccount, number]; }> - ): Promise { - const payer = this.program.walletPubkey; + ): Promise { const aggregator = params.aggregator ?? (await this.loadData()); const queueAccount = params.queueAccount ?? @@ -779,12 +739,16 @@ export class AggregatorAccount extends Account { let queueAuthority = params.queueAuthority; let queueDataBuffer = params.queueDataBuffer; - let mint = params.mint; - if (!queueAuthority || !queueDataBuffer || !mint) { - const queue = await queueAccount.loadData(); + + let queue = params.queue; + if (params.queue) { + queueAuthority = params.queue.authority; + queueDataBuffer = params.queue.dataBuffer; + } + if (!queueAuthority || !queueDataBuffer) { + queue = await queueAccount.loadData(); queueAuthority = queue.authority; queueDataBuffer = queue.dataBuffer; - mint = mint ?? queue.mint ?? spl.NATIVE_MINT; } const [leaseAccount, leaseBump] = @@ -794,18 +758,23 @@ export class AggregatorAccount extends Account { queueAccount.publicKey, this.publicKey ); + let lease: types.LeaseAccountData; try { - await leaseAccount.loadData(); + lease = await leaseAccount.loadData(); } catch (_) { throw new Error( 'A requested lease pda account has not been initialized.' ); } - const leaseEscrow = spl.getAssociatedTokenAddressSync( - mint, - leaseAccount.publicKey, - true - ); + + const leaseEscrow = + params.leaseEscrow ?? + lease.escrow ?? + spl.getAssociatedTokenAddressSync( + this.program.mint.address, + leaseAccount.publicKey, + true + ); const [permissionAccount, permissionBump] = params.permission ?? @@ -815,44 +784,76 @@ export class AggregatorAccount extends Account { queueAccount.publicKey, this.publicKey ); + let permission: types.PermissionAccountData; try { - await permissionAccount.loadData(); + permission = await permissionAccount.loadData(); } catch (_) { throw new Error( 'A requested aggregator permission pda account has not been initialized.' ); } - const preInstructions: Array = []; + const ixns: Array = []; - const payoutWallet = spl.getAssociatedTokenAddressSync(mint, payer, true); - try { - await spl.getAccount(this.program.connection, payoutWallet); - } catch (error) { - // TODO: Catch error and make sure it matches account doesnt exist - preInstructions.push( - spl.createAssociatedTokenAccountInstruction( - payer, - payoutWallet, - payer, - mint - ) - ); + const payoutWallet = + params.payoutWallet ?? this.program.mint.getAssociatedAddress(payer); + const payoutWalletAccountInfo = + await this.program.connection.getAccountInfo(payoutWallet); + if (payoutWalletAccountInfo === null) { + const [createTokenAccountTxn] = + this.program.mint.createAssocatedUserInstruction(payer); + ixns.push(...createTokenAccountTxn.ixns); } - return await this.program.signAndSendTransaction([ - ...preInstructions, - this.openRoundInstruction(this.program.walletPubkey, { - queueAccount, - queueAuthority, - queueDataBuffer, - mint, - lease: [leaseAccount, leaseBump], - permission: [permissionAccount, permissionBump], - payoutWallet: payoutWallet, - leaseEscrow: leaseEscrow, - }), - ]); + ixns.push( + types.aggregatorOpenRound( + this.program, + { + params: { + stateBump: this.program.programState.bump, + leaseBump, + permissionBump, + jitter: 0, + }, + }, + { + aggregator: this.publicKey, + lease: leaseAccount.publicKey, + oracleQueue: queueAccount.publicKey, + queueAuthority: queueAuthority, + permission: permissionAccount.publicKey, + escrow: leaseEscrow, + programState: this.program.programState.publicKey, + payoutWallet: payoutWallet, + tokenProgram: TOKEN_PROGRAM_ID, + dataBuffer: queueDataBuffer, + mint: this.program.mint.address, + } + ) + ); + + return new TransactionObject(payer, ixns, []); + } + + public async openRound( + params: Partial<{ + aggregator?: types.AggregatorAccountData; + queueAccount?: QueueAccount; + queue?: types.OracleQueueAccountData; + queueAuthority?: PublicKey; + queueDataBuffer?: PublicKey; + payoutWallet?: PublicKey; + lease?: [LeaseAccount, number]; + leaseEscrow?: PublicKey; + permission?: [PermissionAccount, number]; + }> + ): Promise { + const openRoundTxn = await this.openRoundInstruction( + this.program.walletPubkey, + params + ); + const txnSignature = await this.program.signAndSend(openRoundTxn); + return txnSignature; } public saveResultInstruction( @@ -877,6 +878,7 @@ export class AggregatorAccount extends Account { } & Partial<{ error?: boolean; historyBuffer?: PublicKey; + oracles: Array; }> ): TransactionInstruction { const [leaseAccount, leaseBump] = params.lease; @@ -944,11 +946,26 @@ export class AggregatorAccount extends Account { payoutWallet: PublicKey; lease: [LeaseAccount, number]; leaseEscrow: PublicKey; + oracles: Array; }> ): Promise { const payer = this.program.walletPubkey; const aggregator = params.aggregator ?? (await this.loadData()); + const remainingAccounts: Array = []; + for (let i = 0; i < aggregator.oracleRequestBatchSize; ++i) { + remainingAccounts.push(aggregator.currentRound.oraclePubkeysData[i]); + } + for (const oracle of params?.oracles ?? []) { + remainingAccounts.push(oracle.tokenAccount); + } + remainingAccounts.push( + anchor.utils.publicKey.findProgramAddressSync( + [Buffer.from('SlidingResultAccountData'), this.publicKey.toBytes()], + this.program.programId + )[0] + ); + const oracleIdx = params.oracleIdx ?? aggregator.currentRound.oraclePubkeysData @@ -959,6 +976,18 @@ export class AggregatorAccount extends Account { throw new Error('Failed to find oracle in current round'); } + const ixns: Array = []; + + const payoutWallet = + params.payoutWallet ?? this.program.mint.getAssociatedAddress(payer); + const payoutWalletAccountInfo = + await this.program.connection.getAccountInfo(payoutWallet); + if (payoutWalletAccountInfo === null) { + const [createTokenAccountTxn] = + this.program.mint.createAssocatedUserInstruction(payer); + ixns.push(...createTokenAccountTxn.ixns); + } + const queueAccount = params.queueAccount ?? new QueueAccount(this.program, aggregator.queuePubkey); @@ -1023,49 +1052,41 @@ export class AggregatorAccount extends Account { ); } - const preInstructions: Array = []; - - const payoutWallet = spl.getAssociatedTokenAddressSync(mint, payer, true); - try { - await spl.getAccount(this.program.connection, payoutWallet); - } catch (error) { - // TODO: Catch error and make sure it matches account doesnt exist - preInstructions.push( - spl.createAssociatedTokenAccountInstruction( - payer, - payoutWallet, - payer, - mint - ) - ); - } - const historyBuffer = params.historyBuffer ?? aggregator.historyBuffer; - // TODO: Add SlidingWindow account to remainingAccounts + const saveResultIxn = this.saveResultInstruction({ + queueAccount, + queueAuthority, + feedPermission: [feedPermissionAccount, feedPermissionBump], + jobs: params.jobs, + historyBuffer: historyBuffer.equals(PublicKey.default) + ? undefined + : historyBuffer, + oracleAccount: params.oracleAccount, + oracleIdx, + oraclePermission: [oraclePermissionAccount, oraclePermissionBump], + value: params.value, + minResponse: params.minResponse, + maxResponse: params.maxResponse, + error: params.error ?? false, + mint, + payoutWallet: payoutWallet, + lease: [leaseAccount, leaseBump], + leaseEscrow: leaseEscrow, + }); + saveResultIxn.keys.push( + ...remainingAccounts.map((pubkey): AccountMeta => { + return { isSigner: false, isWritable: true, pubkey }; + }) + ); + ixns.push(saveResultIxn); - return await this.program.signAndSendTransaction([ - ...preInstructions, - this.saveResultInstruction({ - queueAccount, - queueAuthority, - feedPermission: [feedPermissionAccount, feedPermissionBump], - jobs: params.jobs, - historyBuffer: historyBuffer.equals(PublicKey.default) - ? undefined - : historyBuffer, - oracleAccount: params.oracleAccount, - oracleIdx, - oraclePermission: [oraclePermissionAccount, oraclePermissionBump], - value: params.value, - minResponse: params.minResponse, - maxResponse: params.maxResponse, - error: params.error ?? false, - mint, - payoutWallet: payoutWallet, - lease: [leaseAccount, leaseBump], - leaseEscrow: leaseEscrow, - }), - ]); + const saveResultTxn = new TransactionObject( + this.program.walletPubkey, + ixns, + [] + ); + const txnSignature = await this.program.signAndSend(saveResultTxn); + return txnSignature; } } diff --git a/javascript/solana.js/src/accounts/leaseAccount.ts b/javascript/solana.js/src/accounts/leaseAccount.ts index 412bc30..a1bde30 100644 --- a/javascript/solana.js/src/accounts/leaseAccount.ts +++ b/javascript/solana.js/src/accounts/leaseAccount.ts @@ -90,7 +90,6 @@ export class LeaseAccount extends Account { payer: PublicKey, params: { loadAmount?: number; - mint: PublicKey; funder?: PublicKey; funderAuthority?: Keypair; queuePubkey: PublicKey; @@ -111,11 +110,11 @@ export class LeaseAccount extends Account { const funder = params.funder ? params.funder : program.mint.getAssociatedAddress(funderAuthority); - const funderBalance = await program.mint.getBalance(funderAuthority); + const funderBalance = (await program.mint.getBalance(funderAuthority)) ?? 0; if (loadAmount && funderBalance < loadAmount) { - const wrapIxns = await program.mint.unwrapInstruction( + const wrapIxns = await program.mint.wrapInstruction( payer, - params.loadAmount, + { amount: loadAmount }, params.funderAuthority ); ixns.push(...wrapIxns.ixns); @@ -132,14 +131,14 @@ export class LeaseAccount extends Account { [ leaseAccount.publicKey.toBuffer(), spl.TOKEN_PROGRAM_ID.toBuffer(), - params.mint.toBuffer(), + program.mint.address.toBuffer(), ], spl.ASSOCIATED_TOKEN_PROGRAM_ID ); const { walletBumps } = LeaseAccount.getWallets( params.jobAuthorities, - params.mint + program.mint.address ); ixns.push( @@ -147,7 +146,7 @@ export class LeaseAccount extends Account { payer, escrow, leaseAccount.publicKey, - params.mint + program.mint.address ), types.leaseInit( program, @@ -171,7 +170,7 @@ export class LeaseAccount extends Account { owner: funderAuthority, escrow: escrow, programState: program.programState.publicKey, - mint: params.mint, + mint: program.mint.address, } ) ); @@ -348,7 +347,8 @@ export class LeaseAccount extends Account { const funder = params.funder ? params.funder : this.program.mint.getAssociatedAddress(funderAuthority); - const funderBalance = await this.program.mint.getBalance(funderAuthority); + const funderBalance = + (await this.program.mint.getBalance(funderAuthority)) ?? 0; if (funderBalance < params.loadAmount) { const wrapIxns = await this.program.mint.unwrapInstruction( payer, diff --git a/javascript/solana.js/src/accounts/oracleAccount.ts b/javascript/solana.js/src/accounts/oracleAccount.ts index 8e106c5..f4f7842 100644 --- a/javascript/solana.js/src/accounts/oracleAccount.ts +++ b/javascript/solana.js/src/accounts/oracleAccount.ts @@ -253,7 +253,8 @@ export class OracleAccount extends Account { ); } - return await this.program.signAndSendTransaction( + const heartbeatTxn = new TransactionObject( + this.program.walletPubkey, [ this.heartbeatInstruction(this.program.walletPubkey, { tokenWallet: tokenWallet, @@ -266,6 +267,9 @@ export class OracleAccount extends Account { ], params.authority ? [params.authority] : [] ); + + const txnSignature = await this.program.signAndSend(heartbeatTxn); + return txnSignature; } withdrawInstruction( @@ -343,7 +347,8 @@ export class OracleAccount extends Account { permissionBump = permission[1]; } - return await this.program.signAndSendTransaction( + const withdrawTxn = new TransactionObject( + this.program.walletPubkey, [ this.withdrawInstruction(this.program.walletPubkey, { amount: params.amount, @@ -355,5 +360,8 @@ export class OracleAccount extends Account { ], params.authority ? [params.authority] : [] ); + + const txnSignature = await this.program.signAndSend(withdrawTxn); + return txnSignature; } } diff --git a/javascript/solana.js/src/accounts/programStateAccount.ts b/javascript/solana.js/src/accounts/programStateAccount.ts index 9e3a9bc..05eb0fd 100644 --- a/javascript/solana.js/src/accounts/programStateAccount.ts +++ b/javascript/solana.js/src/accounts/programStateAccount.ts @@ -1,5 +1,5 @@ import * as anchor from '@project-serum/anchor'; -import { SwitchboardProgram } from '../'; +import { SwitchboardProgram } from '../program'; import * as types from '../generated'; import { Account } from './account'; import * as spl from '@solana/spl-token'; @@ -10,6 +10,7 @@ import { SystemProgram, TransactionSignature, } from '@solana/web3.js'; +import { TransactionObject } from '../transaction'; /** * Account type representing Switchboard global program state. @@ -68,21 +69,28 @@ export class ProgramStateAccount extends Account { ]; } })(); - const instruction = types.programInit( - program, - { params: { stateBump: bump } }, - { - state: account.publicKey, - authority: program.wallet.publicKey, - payer: program.wallet.publicKey, - tokenMint: mint, - vault: vault, - systemProgram: SystemProgram.programId, - tokenProgram: spl.TOKEN_PROGRAM_ID, - daoMint: params.daoMint ?? mint, - } + + const programInit = new TransactionObject( + program.walletPubkey, + [ + types.programInit( + program, + { params: { stateBump: bump } }, + { + state: account.publicKey, + authority: program.wallet.publicKey, + payer: program.wallet.publicKey, + tokenMint: mint, + vault: vault, + systemProgram: SystemProgram.programId, + tokenProgram: spl.TOKEN_PROGRAM_ID, + daoMint: params.daoMint ?? mint, + } + ), + ], + [] ); - await program.signAndSendTransaction([instruction]); + await program.signAndSend(programInit); } catch {} // eslint-disable-line no-empty } return [account, bump]; @@ -143,18 +151,26 @@ export class ProgramStateAccount extends Account { ): Promise { const [account, bump] = ProgramStateAccount.fromSeed(program); const vault = (await account.loadData()).tokenVault; - const instruction = types.vaultTransfer( - program, - { params: { stateBump: bump, amount: params.amount } }, - { - state: account.publicKey, - to, - vault, - authority: authority.publicKey, - tokenProgram: spl.TOKEN_PROGRAM_ID, - } + + const vaultTransfer = new TransactionObject( + program.walletPubkey, + [ + types.vaultTransfer( + program, + { params: { stateBump: bump, amount: params.amount } }, + { + state: account.publicKey, + to, + vault, + authority: authority.publicKey, + tokenProgram: spl.TOKEN_PROGRAM_ID, + } + ), + ], + [] ); - return program.signAndSendTransaction([instruction]); + const txnSignature = await program.signAndSend(vaultTransfer); + return txnSignature; } /** * @return account size of the global {@linkcode ProgramStateAccount}. diff --git a/javascript/solana.js/src/accounts/queueAccount.ts b/javascript/solana.js/src/accounts/queueAccount.ts index 49abfeb..b921006 100644 --- a/javascript/solana.js/src/accounts/queueAccount.ts +++ b/javascript/solana.js/src/accounts/queueAccount.ts @@ -405,7 +405,20 @@ export class QueueAccount extends Account { ): Promise<[TransactionObject[], AggregatorAccount]> { const queue = await this.loadData(); + const pre: TransactionObject[] = []; const txns: TransactionObject[] = []; + const post: TransactionObject[] = []; + + // getOrCreate token account for + const userTokenAddress = this.program.mint.getAssociatedAddress(payer); + const userTokenAccountInfo = await this.program.connection.getAccountInfo( + userTokenAddress + ); + if (userTokenAccountInfo === null) { + const [createTokenAccount] = + this.program.mint.createAssocatedUserInstruction(payer); + pre.push(createTokenAccount); + } // create / load jobs const jobs: { job: JobAccount; weight: number }[] = []; @@ -424,7 +437,7 @@ export class QueueAccount extends Account { jobKeypair: job.jobKeypair, } ); - txns.push(...jobInit); + pre.push(...jobInit); jobs.push({ job: jobAccount, weight: job.weight ?? 1 }); } else if ('pubkey' in job) { const jobAccount = new JobAccount(this.program, job.pubkey); @@ -435,16 +448,6 @@ export class QueueAccount extends Account { } } } - // getOrCreate token account for - const userTokenAddress = this.program.mint.getAssociatedAddress(payer); - const userTokenAccountInfo = await this.program.connection.getAccountInfo( - userTokenAddress - ); - if (userTokenAccountInfo === null) { - const [createTokenAccount] = - this.program.mint.createAssocatedUserInstruction(payer); - txns.push(createTokenAccount); - } const [aggregatorInit, aggregatorAccount] = await AggregatorAccount.createInstruction(this.program, payer, { @@ -465,14 +468,14 @@ export class QueueAccount extends Account { loadAmount: params.loadAmount, funder: params.funder, funderAuthority: params.funderAuthority, - mint: this.program.mint.address, aggregatorPubkey: aggregatorAccount.publicKey, queuePubkey: this.publicKey, jobAuthorities: [], } ); txns.push(leaseInit); - // // create permission account + + // create permission account const [permissionInit, permissionAccount] = PermissionAccount.createInstruction(this.program, payer, { granter: this.publicKey, @@ -498,10 +501,14 @@ export class QueueAccount extends Account { weight: weight, authority: params.authority, }); - txns.push(addJobTxn); + post.push(addJobTxn); } - const packed = TransactionObject.pack(txns); + const packed = TransactionObject.pack([ + ...TransactionObject.pack(pre), + ...TransactionObject.pack(txns), + ...TransactionObject.pack(post), + ]); return [packed, aggregatorAccount]; } diff --git a/javascript/solana.js/src/accounts/vrfAccount.ts b/javascript/solana.js/src/accounts/vrfAccount.ts index add29e5..bf2434d 100644 --- a/javascript/solana.js/src/accounts/vrfAccount.ts +++ b/javascript/solana.js/src/accounts/vrfAccount.ts @@ -4,6 +4,7 @@ import { PublicKey, SystemProgram } from '@solana/web3.js'; import * as errors from '../errors'; import * as types from '../generated'; import { SwitchboardProgram } from '../program'; +import { TransactionObject } from '../transaction'; import { Account, OnAccountChangeCallback } from './account'; import { QueueAccount } from './queueAccount'; @@ -48,15 +49,13 @@ export class VrfAccount extends Account { program: SwitchboardProgram, params: VrfInitParams ): Promise<[string, VrfAccount]> { - return this.createInstructions(program, { - ...params, - payerKeypair: program.wallet.payer, - }).then(async ([instructions, account]) => { - const txSignature = await program.signAndSendTransaction(instructions, [ - params.vrfKeypair, - ]); - return [txSignature, account]; - }); + const [createTxn, vrfAccount] = await VrfAccount.createInstructions( + program, + program.walletPubkey, + params + ); + const txnSignature = await program.signAndSend(createTxn); + return [txnSignature, vrfAccount]; } /** @@ -67,9 +66,9 @@ export class VrfAccount extends Account { */ public static async createInstructions( program: SwitchboardProgram, - params: VrfInitParams & { payerKeypair: anchor.web3.Keypair }, - payer: PublicKey = program.walletPubkey - ): Promise<[anchor.web3.TransactionInstruction[], VrfAccount]> { + payer: PublicKey, + params: VrfInitParams + ): Promise<[TransactionObject, VrfAccount]> { const vrfAccount = new VrfAccount(program, params.vrfKeypair.publicKey); const size = program.account.vrfAccountData.size; const switchTokenMint = await params.queue.loadMint(); @@ -78,50 +77,50 @@ export class VrfAccount extends Account { params.vrfKeypair.publicKey, true ); - return [ - [ - spl.createAssociatedTokenAccountInstruction( - program.wallet.payer.publicKey, - escrow, - params.vrfKeypair.publicKey, - switchTokenMint.address + const ixns = [ + spl.createAssociatedTokenAccountInstruction( + program.wallet.payer.publicKey, + escrow, + params.vrfKeypair.publicKey, + switchTokenMint.address + ), + spl.createSetAuthorityInstruction( + escrow, + params.vrfKeypair.publicKey, + spl.AuthorityType.AccountOwner, + program.programState.publicKey, + [program.wallet.payer, params.vrfKeypair] + ), + SystemProgram.createAccount({ + fromPubkey: program.wallet.payer.publicKey, + newAccountPubkey: params.vrfKeypair.publicKey, + space: size, + lamports: await program.connection.getMinimumBalanceForRentExemption( + size ), - spl.createSetAuthorityInstruction( - escrow, - params.vrfKeypair.publicKey, - spl.AuthorityType.AccountOwner, - program.programState.publicKey, - [program.wallet.payer, params.vrfKeypair] - ), - SystemProgram.createAccount({ - fromPubkey: program.wallet.payer.publicKey, - newAccountPubkey: params.vrfKeypair.publicKey, - space: size, - lamports: await program.connection.getMinimumBalanceForRentExemption( - size - ), - programId: program.programId, - }), - types.vrfInit( - program, - { - params: { - stateBump: program.programState.bump, - callback: params.callback, - }, + programId: program.programId, + }), + types.vrfInit( + program, + { + params: { + stateBump: program.programState.bump, + callback: params.callback, }, - { - vrf: params.vrfKeypair.publicKey, - authority: params.authority ?? params.vrfKeypair.publicKey, - escrow, - oracleQueue: params.queue.publicKey, - programState: program.programState.publicKey, - tokenProgram: spl.TOKEN_PROGRAM_ID, - } - ), - ], - vrfAccount, + }, + { + vrf: params.vrfKeypair.publicKey, + authority: params.authority ?? params.vrfKeypair.publicKey, + escrow, + oracleQueue: params.queue.publicKey, + programState: program.programState.publicKey, + tokenProgram: spl.TOKEN_PROGRAM_ID, + } + ), ]; + + const txn = new TransactionObject(payer, ixns, [params.vrfKeypair]); + return [txn, vrfAccount]; } /** diff --git a/javascript/solana.js/src/mint.ts b/javascript/solana.js/src/mint.ts index c4d2bb0..eb4dc6e 100644 --- a/javascript/solana.js/src/mint.ts +++ b/javascript/solana.js/src/mint.ts @@ -55,14 +55,23 @@ export class Mint { return swbDecimal.toBig().toNumber(); } - public async getAccount(user: PublicKey): Promise { + public async getAccount(user: PublicKey): Promise { const userTokenAddress = Mint.getAssociatedAddress(user); - const account = await spl.getAccount(this.connection, userTokenAddress); + const userTokenAccountInfo = await this.provider.connection.getAccountInfo( + userTokenAddress + ); + if (userTokenAccountInfo === null) { + return null; + } + const account = spl.unpackAccount(userTokenAddress, userTokenAccountInfo); return account; } - public async getBalance(user: PublicKey): Promise { + public async getBalance(user: PublicKey): Promise { const userAccount = await this.getAccount(user); + if (userAccount === null) { + return null; + } return this.fromTokenAmount(userAccount.amount); } @@ -147,7 +156,7 @@ export class Mint { Mint.native, owner ); - return [account, new TransactionObject(payer, [ixn], user ? [user] : [])]; + return [account, new TransactionObject(payer, [ixn], [])]; } public createUserInstruction( @@ -179,6 +188,8 @@ export class Mint { throw new NativeMintOnlyError(); } + const ixns: TransactionInstruction[] = []; + const owner = user ? user.publicKey : payer; const ownerBalance = new Big(await this.connection.getBalance(owner)); @@ -189,6 +200,16 @@ export class Mint { userAccountInfo === null ? null : spl.unpackAccount(userAddress, userAccountInfo); + // if (userAccount === null) { + // ixns.push( + // spl.createAssociatedTokenAccountInstruction( + // payer, + // userAddress, + // owner, + // Mint.native + // ) + // ); + // } const balance = userAccount ? new Big(this.fromTokenAmount(userAccount.amount)) @@ -219,7 +240,7 @@ export class Mint { const wrapAmountLamports = this.toTokenAmount(wrapAmount.toNumber()); - const ixns = [ + ixns.push( spl.createAssociatedTokenAccountInstruction( payer, ephemeralWallet, @@ -242,8 +263,8 @@ export class Mint { ephemeralWallet, owner, ephemeralAccount.publicKey - ), - ]; + ) + ); return new TransactionObject( payer, @@ -286,7 +307,7 @@ export class Mint { const signers: Keypair[] = user ? [user] : []; const userAddress = this.getAssociatedAddress(owner); - const userBalance = await this.getBalance(owner); + const userBalance = (await this.getBalance(owner)) ?? 0; if (amount) { if (amount >= userBalance) { diff --git a/javascript/solana.js/src/program.ts b/javascript/solana.js/src/program.ts index b381635..da7bd5e 100644 --- a/javascript/solana.js/src/program.ts +++ b/javascript/solana.js/src/program.ts @@ -376,27 +376,6 @@ export class SwitchboardProgram { return txnSignature; } - - public async signAndSendTransaction( - instructions: TransactionInstruction[], - signers?: Signer[] - ): Promise { - if (isBrowser) throw new errors.SwitchboardProgramIsBrowserError(); - if (this.isReadOnly) throw new errors.SwitchboardProgramReadOnlyError(); - - const txnSignature = await this.provider.sendAndConfirm( - await this.provider.wallet.signTransaction( - new Transaction({ - feePayer: this.provider.wallet.publicKey, - ...(await this.connection.getLatestBlockhash()), - }).add(...instructions) - ), - signers, - { skipPreflight: false, maxRetries: 10 } - ); - - return txnSignature; - } } export class AnchorWallet implements anchor.Wallet { diff --git a/javascript/solana.js/test/aggregator.spec.ts b/javascript/solana.js/test/aggregator.spec.ts index a3beaa6..c523a43 100644 --- a/javascript/solana.js/test/aggregator.spec.ts +++ b/javascript/solana.js/test/aggregator.spec.ts @@ -50,6 +50,8 @@ describe('Aggregator Tests', () => { queueAccount = oracleQueue; + await ctx.program.mint.getOrCreateAssociatedUser(ctx.program.walletPubkey); + // add a single oracle for open round calls await queueAccount.createOracle({ name: 'oracle-1', @@ -222,9 +224,8 @@ describe('Aggregator Tests', () => { ); } - const finalUserTokenBalance = await ctx.program.mint.getBalance( - ctx.payer.publicKey - ); + const finalUserTokenBalance = + (await ctx.program.mint.getBalance(ctx.payer.publicKey)) ?? 0; if (initialUserTokenBalance !== finalUserTokenBalance) { throw new Error( `User token balance has incorrect funds, expected ${initialUserTokenBalance} wSOL, received ${finalUserTokenBalance}` diff --git a/javascript/solana.js/test/mint.spec.ts b/javascript/solana.js/test/mint.spec.ts index ddc1e59..0282562 100644 --- a/javascript/solana.js/test/mint.spec.ts +++ b/javascript/solana.js/test/mint.spec.ts @@ -27,10 +27,8 @@ describe('Mint Tests', () => { await ctx.program.mint.createAssocatedUser(ctx.payer.publicKey, user); userTokenAddress = tokenAddress; - const userTokenAccount = await ctx.program.mint.getAccount(user.publicKey); - const userTokenBalance = ctx.program.mint.fromTokenAmount( - userTokenAccount.amount - ); + const userTokenBalance = + (await ctx.program.mint.getBalance(user.publicKey)) ?? 0; if (userTokenBalance !== 0) { throw new Error( @@ -50,10 +48,8 @@ describe('Mint Tests', () => { user ); - const userTokenAccount = await ctx.program.mint.getAccount(user.publicKey); - const userTokenBalance = ctx.program.mint.fromTokenAmount( - userTokenAccount.amount - ); + const userTokenBalance = + (await ctx.program.mint.getBalance(user.publicKey)) ?? 0; if (userTokenBalance !== 0.25) { throw new Error( `Incorrect user token balance, expected 0.25, received ${userTokenBalance}` @@ -66,12 +62,8 @@ describe('Mint Tests', () => { throw new Error(`User token address does not exist`); } - const initialUserTokenAccount = await ctx.program.mint.getAccount( - user.publicKey - ); - const initialUserTokenBalance = ctx.program.mint.fromTokenAmount( - initialUserTokenAccount.amount - ); + const initialUserTokenBalance = + (await ctx.program.mint.getBalance(user.publicKey)) ?? 0; const expectedFinalBalance = initialUserTokenBalance - 0.1; if (expectedFinalBalance < 0) { throw new Error(`Final user token address would be negative`); diff --git a/javascript/solana.js/test/utilts.ts b/javascript/solana.js/test/utilts.ts index 1d0d6db..76458fa 100644 --- a/javascript/solana.js/test/utilts.ts +++ b/javascript/solana.js/test/utilts.ts @@ -74,6 +74,8 @@ export async function setupTest(): Promise { console.error(e); } + await program.mint.getOrCreateAssociatedUser(program.walletPubkey); + return { cluster, program, diff --git a/javascript/switchboard-v2/src/sbv2.ts b/javascript/switchboard-v2/src/sbv2.ts index c7cf8f3..b223337 100644 --- a/javascript/switchboard-v2/src/sbv2.ts +++ b/javascript/switchboard-v2/src/sbv2.ts @@ -1426,7 +1426,7 @@ export class AggregatorAccount { mint: params.tokenMint, }) .remainingAccounts( - remainingAccounts.map((pubkey: PublicKey) => { + remainingAccounts.map((pubkey: PublicKey): AccountMeta => { return { isSigner: false, isWritable: true, pubkey }; }) )