diff --git a/CHANGELOG.md b/CHANGELOG.md index 447efa921..7a5ed5183 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,6 @@ The minor version will be incremented upon a breaking change and the patch versi - client: Add support for multithreading to the rust client: use flag `--multithreaded` ([#2321](https://github.com/coral-xyz/anchor/pull/2321)). - client: Add `async_rpc` a method which returns a nonblocking solana rpc client ([2322](https://github.com/coral-xyz/anchor/pull/2322)). - avm, cli: Use the `rustls-tls` feature of `reqwest` so that users don't need OpenSSL installed ([#2385](https://github.com/coral-xyz/anchor/pull/2385)). -- ts: Add `VersionedTransaction` support. Methods in the `Provider` class and `Wallet` interface now use the argument `tx: Transaction | VersionedTransaction` ([2407](https://github.com/coral-xyz/anchor/pull/2407)). - cli: Add `--arch sbf` option to compile programs using `cargo build-sbf` ([#2398](https://github.com/coral-xyz/anchor/pull/2398)). ### Fixes diff --git a/tests/misc/tests/misc/misc.ts b/tests/misc/tests/misc/misc.ts index f17cb1480..18f143929 100644 --- a/tests/misc/tests/misc/misc.ts +++ b/tests/misc/tests/misc/misc.ts @@ -6,8 +6,6 @@ import { SystemProgram, Message, VersionedTransaction, - AddressLookupTableProgram, - TransactionMessage, } from "@solana/web3.js"; import { TOKEN_PROGRAM_ID, @@ -63,104 +61,6 @@ const miscTest = ( assert.strictEqual(dataAccount.data, 99); }); - it("Can send VersionedTransaction", async () => { - // Create the lookup table - const recentSlot = await provider.connection.getSlot(); - const [loookupTableInstruction, lookupTableAddress] = - AddressLookupTableProgram.createLookupTable({ - authority: provider.publicKey, - payer: provider.publicKey, - recentSlot, - }); - const extendInstruction = AddressLookupTableProgram.extendLookupTable({ - payer: provider.publicKey, - authority: provider.publicKey, - lookupTable: lookupTableAddress, - addresses: [provider.publicKey, SystemProgram.programId], - }); - let createLookupTableTx = new VersionedTransaction( - new TransactionMessage({ - instructions: [loookupTableInstruction, extendInstruction], - payerKey: program.provider.publicKey, - recentBlockhash: (await provider.connection.getLatestBlockhash()) - .blockhash, - }).compileToV0Message() - ); - await provider.sendAndConfirm(createLookupTableTx, [], { - skipPreflight: true, - }); - - // Use the lookup table in a transaction - const transferAmount = 1_000_000; - const lookupTableAccount = await provider.connection - .getAddressLookupTable(lookupTableAddress) - .then((res) => res.value); - const target = anchor.web3.Keypair.generate(); - let transferInstruction = SystemProgram.transfer({ - fromPubkey: provider.publicKey, - lamports: transferAmount, - toPubkey: target.publicKey, - }); - let transferUsingLookupTx = new VersionedTransaction( - new TransactionMessage({ - instructions: [transferInstruction], - payerKey: program.provider.publicKey, - recentBlockhash: (await provider.connection.getLatestBlockhash()) - .blockhash, - }).compileToV0Message([lookupTableAccount]) - ); - await provider.simulate(transferUsingLookupTx, [], "processed"); - await provider.sendAndConfirm(transferUsingLookupTx, [], { - skipPreflight: true, - commitment: "confirmed", - }); - let newBalance = await provider.connection.getBalance( - target.publicKey, - "confirmed" - ); - assert.strictEqual(newBalance, transferAmount); - - // Test sendAll with versioned transaction - let oneTransferUsingLookupTx = new VersionedTransaction( - new TransactionMessage({ - instructions: [ - SystemProgram.transfer({ - fromPubkey: provider.publicKey, - // Needed to make the transactions distinct - lamports: transferAmount + 1, - toPubkey: target.publicKey, - }), - ], - payerKey: program.provider.publicKey, - recentBlockhash: (await provider.connection.getLatestBlockhash()) - .blockhash, - }).compileToV0Message([lookupTableAccount]) - ); - let twoTransferUsingLookupTx = new VersionedTransaction( - new TransactionMessage({ - instructions: [ - SystemProgram.transfer({ - fromPubkey: provider.publicKey, - lamports: transferAmount, - toPubkey: target.publicKey, - }), - ], - payerKey: program.provider.publicKey, - recentBlockhash: (await provider.connection.getLatestBlockhash()) - .blockhash, - }).compileToV0Message([lookupTableAccount]) - ); - await provider.sendAll( - [{ tx: oneTransferUsingLookupTx }, { tx: twoTransferUsingLookupTx }], - { skipPreflight: true, commitment: "confirmed" } - ); - newBalance = await provider.connection.getBalance( - target.publicKey, - "confirmed" - ); - assert.strictEqual(newBalance, transferAmount * 3 + 1); - }); - it("Can embed programs into genesis from the Anchor.toml", async () => { const pid = new anchor.web3.PublicKey( "FtMNMKp9DZHKWUyVAsj3Q5QV8ow4P3fUPP7ZrWEQJzKr" diff --git a/ts/packages/anchor/src/nodewallet.ts b/ts/packages/anchor/src/nodewallet.ts index f0ec7a6e4..6d7924aff 100644 --- a/ts/packages/anchor/src/nodewallet.ts +++ b/ts/packages/anchor/src/nodewallet.ts @@ -1,10 +1,5 @@ import { Buffer } from "buffer"; -import { - Keypair, - PublicKey, - Transaction, - VersionedTransaction, -} from "@solana/web3.js"; +import { Keypair, PublicKey, Transaction } from "@solana/web3.js"; import { Wallet } from "./provider"; /** @@ -35,26 +30,14 @@ export default class NodeWallet implements Wallet { return new NodeWallet(payer); } - async signTransaction( - tx: T - ): Promise { - if (tx instanceof VersionedTransaction) { - tx.sign([this.payer]); - } else { - tx.partialSign(this.payer); - } + async signTransaction(tx: Transaction): Promise { + tx.partialSign(this.payer); return tx; } - async signAllTransactions( - txs: T[] - ): Promise { + async signAllTransactions(txs: Transaction[]): Promise { return txs.map((t) => { - if (t instanceof VersionedTransaction) { - t.sign([this.payer]); - } else { - t.partialSign(this.payer); - } + t.partialSign(this.payer); return t; }); } diff --git a/ts/packages/anchor/src/provider.ts b/ts/packages/anchor/src/provider.ts index b61d9397a..e0fd503d7 100644 --- a/ts/packages/anchor/src/provider.ts +++ b/ts/packages/anchor/src/provider.ts @@ -9,8 +9,6 @@ import { Commitment, SendTransactionError, SendOptions, - VersionedTransaction, - RpcResponseAndContext, } from "@solana/web3.js"; import { bs58 } from "./utils/bytes/index.js"; import { isBrowser } from "./utils/common.js"; @@ -24,24 +22,21 @@ export default interface Provider { readonly publicKey?: PublicKey; send?( - tx: Transaction | VersionedTransaction, + tx: Transaction, signers?: Signer[], opts?: SendOptions ): Promise; sendAndConfirm?( - tx: Transaction | VersionedTransaction, + tx: Transaction, signers?: Signer[], opts?: ConfirmOptions ): Promise; - sendAll?( - txWithSigners: { - tx: T; - signers?: Signer[]; - }[], + sendAll?( + txWithSigners: { tx: Transaction; signers?: Signer[] }[], opts?: ConfirmOptions ): Promise>; simulate?( - tx: Transaction | VersionedTransaction, + tx: Transaction, signers?: Signer[], commitment?: Commitment, includeAccounts?: boolean | PublicKey[] @@ -129,7 +124,7 @@ export class AnchorProvider implements Provider { * @param opts Transaction confirmation options. */ async sendAndConfirm( - tx: Transaction | VersionedTransaction, + tx: Transaction, signers?: Signer[], opts?: ConfirmOptions ): Promise { @@ -137,23 +132,17 @@ export class AnchorProvider implements Provider { opts = this.opts; } - if (tx instanceof VersionedTransaction) { - if (signers) { - tx.sign(signers); - } - } else { - tx.feePayer = tx.feePayer ?? this.wallet.publicKey; - tx.recentBlockhash = ( - await this.connection.getLatestBlockhash(opts.preflightCommitment) - ).blockhash; + tx.feePayer = tx.feePayer || this.wallet.publicKey; + + tx.recentBlockhash = ( + await this.connection.getLatestBlockhash(opts.preflightCommitment) + ).blockhash; - if (signers) { - for (const signer of signers) { - tx.partialSign(signer); - } - } - } tx = await this.wallet.signTransaction(tx); + (signers ?? []).forEach((kp) => { + tx.partialSign(kp); + }); + const rawTx = tx.serialize(); try { @@ -166,14 +155,10 @@ export class AnchorProvider implements Provider { // (the json RPC does not support any shorter than "confirmed" for 'getTransaction') // because that will see the tx sent with `sendAndConfirmRawTransaction` no matter which // commitment `sendAndConfirmRawTransaction` used - const txSig = bs58.encode( - tx instanceof VersionedTransaction - ? tx.signatures?.[0] || new Uint8Array() - : tx.signature ?? new Uint8Array() + const failedTx = await this.connection.getTransaction( + bs58.encode(tx.signature!), + { commitment: "confirmed" } ); - const failedTx = await this.connection.getTransaction(txSig, { - commitment: "confirmed", - }); if (!failedTx) { throw err; } else { @@ -188,44 +173,34 @@ export class AnchorProvider implements Provider { /** * Similar to `send`, but for an array of transactions and signers. - * All transactions need to be of the same type, it doesn't support a mix of `VersionedTransaction`s and `Transaction`s. * * @param txWithSigners Array of transactions and signers. * @param opts Transaction confirmation options. */ - async sendAll( - txWithSigners: { - tx: T; - signers?: Signer[]; - }[], + async sendAll( + txWithSigners: { tx: Transaction; signers?: Signer[] }[], opts?: ConfirmOptions ): Promise> { if (opts === undefined) { opts = this.opts; } - const recentBlockhash = ( - await this.connection.getLatestBlockhash(opts.preflightCommitment) - ).blockhash; + const blockhash = await this.connection.getLatestBlockhash( + opts.preflightCommitment + ); let txs = txWithSigners.map((r) => { - if (r.tx instanceof VersionedTransaction) { - let tx: VersionedTransaction = r.tx; - if (r.signers) { - tx.sign(r.signers); - } - return tx; - } else { - let tx: Transaction = r.tx; - let signers = r.signers ?? []; + let tx = r.tx; + let signers = r.signers ?? []; - tx.feePayer = tx.feePayer ?? this.wallet.publicKey; - tx.recentBlockhash = recentBlockhash; + tx.feePayer = tx.feePayer || this.wallet.publicKey; - signers.forEach((kp) => { - tx.partialSign(kp); - }); - return tx; - } + tx.recentBlockhash = blockhash.blockhash; + + signers.forEach((kp) => { + tx.partialSign(kp); + }); + + return tx; }); const signedTxs = await this.wallet.signAllTransactions(txs); @@ -248,14 +223,10 @@ export class AnchorProvider implements Provider { // (the json RPC does not support any shorter than "confirmed" for 'getTransaction') // because that will see the tx sent with `sendAndConfirmRawTransaction` no matter which // commitment `sendAndConfirmRawTransaction` used - const txSig = bs58.encode( - tx instanceof VersionedTransaction - ? tx.signatures?.[0] || new Uint8Array() - : tx.signature ?? new Uint8Array() + const failedTx = await this.connection.getTransaction( + bs58.encode(tx.signature!), + { commitment: "confirmed" } ); - const failedTx = await this.connection.getTransaction(txSig, { - commitment: "confirmed", - }); if (!failedTx) { throw err; } else { @@ -282,42 +253,29 @@ export class AnchorProvider implements Provider { * @param opts Transaction confirmation options. */ async simulate( - tx: Transaction | VersionedTransaction, + tx: Transaction, signers?: Signer[], commitment?: Commitment, includeAccounts?: boolean | PublicKey[] ): Promise { - let recentBlockhash = ( + tx.feePayer = tx.feePayer || this.wallet.publicKey; + + tx.recentBlockhash = ( await this.connection.getLatestBlockhash( commitment ?? this.connection.commitment ) ).blockhash; - let result: RpcResponseAndContext; - if (tx instanceof VersionedTransaction) { - if (signers) { - tx.sign(signers); - tx = await this.wallet.signTransaction(tx); - } - - // Doesn't support includeAccounts which has been changed to something - // else in later versions of this function. - result = await this.connection.simulateTransaction(tx, { commitment }); - } else { - tx.feePayer = tx.feePayer || this.wallet.publicKey; - tx.recentBlockhash = recentBlockhash; - - if (signers) { - tx = await this.wallet.signTransaction(tx); - } - result = await simulateTransaction( - this.connection, - tx, - signers, - commitment, - includeAccounts - ); + if (signers) { + tx = await this.wallet.signTransaction(tx); } + const result = await simulateTransaction( + this.connection, + tx, + signers, + commitment, + includeAccounts + ); if (result.value.err) { throw new SimulateError(result.value); @@ -345,12 +303,8 @@ export type SendTxRequest = { * Wallet interface for objects that can be used to sign provider transactions. */ export interface Wallet { - signTransaction( - tx: T - ): Promise; - signAllTransactions( - txs: T[] - ): Promise; + signTransaction(tx: Transaction): Promise; + signAllTransactions(txs: Transaction[]): Promise; publicKey: PublicKey; } @@ -358,7 +312,7 @@ export interface Wallet { // a better error if 'confirmTransaction` returns an error status async function sendAndConfirmRawTransaction( connection: Connection, - rawTransaction: Buffer | Uint8Array, + rawTransaction: Buffer, options?: ConfirmOptions ): Promise { const sendOptions = options && {