|
| 1 | +# Send a versioned transaction |
| 2 | + |
| 3 | +The Solana runtime supports two types of transactions: `legacy` (see [Send a legacy transaction](/solana/sending-a-transaction)) and `v0` (transactions that can include Address Lookup Tables or LUTs). |
| 4 | + |
| 5 | +The goal of `v0` is to increase the maximum size of a transaction, and hence the number of accounts that can fit in a single atomic transaction. With LUTs, developers can now build transactions with a maximum of 256 accounts, as compared to the limit of 35 accounts in legacy transactions that do not utilize LUTs. |
| 6 | + |
| 7 | +:::info |
| 8 | +For a dive deep on versioned transactions, LUTs, and how the above changes affect the anatomy of a transaction, see [Versioned Transactions - Anvit Mangal's Blog](https://anvit.hashnode.dev/versioned-transactions). |
| 9 | +::: |
| 10 | + |
| 11 | +On this page, we'll go over the following: |
| 12 | + |
| 13 | +1. Building a versioned tansaction. |
| 14 | +2. Signing and sending a versioned transaction. |
| 15 | +3. Building an Address LUT. |
| 16 | +4. Extending an Address LUT. |
| 17 | +5. Signing and sending a versioned transaction using a LUT. |
| 18 | + |
| 19 | +## Build a versioned transaction |
| 20 | + |
| 21 | +Versioned transactions are built in a very similar fashion to [legacy transactions](sending-a-transaction). The only difference is that developers should use the `VersionedTransaction` class rather than the `Transaction` class. |
| 22 | + |
| 23 | +The following example shows how to build a simple transfer instruction. Once the transfer instruction is made, a `MessageV0` formatted transaction message is constructed with the transfer instruction. Finally, a new `VersionedTransaction` is created, parsing in the `v0` compatible message. |
| 24 | + |
| 25 | +### createTransactionV0() |
| 26 | + |
| 27 | +```typescript theme={null} |
| 28 | +// create array of instructions |
| 29 | +const instructions = [ |
| 30 | + SystemProgram.transfer({ |
| 31 | + fromPubkey: publicKey, |
| 32 | + toPubkey: publicKey, |
| 33 | + lamports: 10, |
| 34 | + }), |
| 35 | +]; |
| 36 | + |
| 37 | +// create v0 compatible message |
| 38 | +const messageV0 = new TransactionMessage({ |
| 39 | + payerKey: publicKey, |
| 40 | + recentBlockhash: blockhash, |
| 41 | + instructions, |
| 42 | +}).compileToV0Message(); |
| 43 | + |
| 44 | +// make a versioned transaction |
| 45 | +const transactionV0 = new VersionedTransaction(messageV0); |
| 46 | +``` |
| 47 | + |
| 48 | +For a live example of creating a versioned transaction, refer to [createTransferTransactionV0](https://github.com/phantom-labs/sandbox/blob/main/src/utils/createTransferTransactionV0.ts) in our sandbox. |
| 49 | + |
| 50 | +## Sign and send a versioned transaction |
| 51 | + |
| 52 | +Once a Versioned transaction is created, it can be signed and sent via Phantom using the `signAndSendTransaction` method on the provider. The call will return a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) for an object containing the `signature`. This is the same way a legacy transaction is sent via the Phantom provider. |
| 53 | + |
| 54 | +### signAndSendTransaction() |
| 55 | + |
| 56 | +```javascript theme={null} |
| 57 | +const provider = getProvider(); // see "Detecting the Provider" |
| 58 | +const network = "<NETWORK_URL>"; |
| 59 | +const connection = new Connection(network); |
| 60 | +const versionedTransaction = new VersionedTransaction(); |
| 61 | +const { signature } = await provider.signAndSendTransaction(versionedTransaction); |
| 62 | +await connection.getSignatureStatus(signature); |
| 63 | +``` |
| 64 | + |
| 65 | +You can also specify a `SendOptions` [object](https://solana-foundation.github.io/solana-web3.js/modules.html#SendOptions) as a second argument into `signAndSendTransaction()` or as an `options` parameter when using `request`. |
| 66 | + |
| 67 | +For a live demo of signing and sending a versioned transaction, refer to [handleSignAndSendTransactionV0](https://github.com/phantom-labs/sandbox/blob/78dc35fe140140a961345a6af30a058e1e19a7aa/src/App.tsx#L191) in our sandbox. |
| 68 | + |
| 69 | +## Build an Address LUT |
| 70 | + |
| 71 | +Address LUTs can be used to load accounts into table-like data structures. These structures can then be referenced to significantly increase the number of accounts that can be loaded in a single transaction. |
| 72 | + |
| 73 | +This lookup method effectively "*compresses*" a 32-byte address into a 1-byte index value. This "*compression*" enables storing up to 256 address in a single LUT for use inside any given transaction. |
| 74 | + |
| 75 | +With the `@solana/web3.js` [createLookupTable](https://solana-foundation.github.io/solana-web3.js/classes/AddressLookupTableProgram.html#createLookupTable) method, developers can construct the instruction needed to create a new LUT, as well as determine its address. Once we have the LUT instruction, we can construct a transaction, sign it, and send it to create a LUT on-chain. Address LUTs can be created with either a `v0` transaction or a `legacy` transaction. However, the Solana runtime can only retrieve and handle the additional addresses within a LUT while using `v0` transactions. |
| 76 | + |
| 77 | +The following is a code snippet that creates a LUT. |
| 78 | + |
| 79 | +### createAddressLookupTable() |
| 80 | + |
| 81 | +```typescript theme={null} |
| 82 | +// create an Address Lookup Table |
| 83 | +const [lookupTableInst, lookupTableAddress] = AddressLookupTableProgram.createLookupTable({ |
| 84 | + authority: publicKey, |
| 85 | + payer: publicKey, |
| 86 | + recentSlot: slot, |
| 87 | +}); |
| 88 | + |
| 89 | +// To create the Address Lookup Table on chain: |
| 90 | +// send the `lookupTableInst` instruction in a transaction |
| 91 | +const lookupMessage = new TransactionMessage({ |
| 92 | + payerKey: publicKey, |
| 93 | + recentBlockhash: blockhash, |
| 94 | + instructions: [lookupTableInst], |
| 95 | +}).compileToV0Message(); |
| 96 | + |
| 97 | +const lookupTransaction = new VersionedTransaction(lookupMessage); |
| 98 | +const lookupSignature = await signAndSendTransaction(provider, lookupTransaction); |
| 99 | +``` |
| 100 | + |
| 101 | +For a live demo of creating a LUT, refer to [handleSignAndSendTransactionV0WithLookupTable](https://github.com/phantom-labs/sandbox/blob/78dc35fe140140a961345a6af30a058e1e19a7aa/src/App.tsx#L218) in our sandbox. |
| 102 | + |
| 103 | +## Extend an Address LUT |
| 104 | + |
| 105 | +Once an Address LUT is created, it can then be extended, which means that accounts can be appended to the table. Using the `@solana/web3.js` library, you can create a new `extend` instruction using the [extendLookupTable](https://solana-labs.github.io/solana-web3.js/classes/AddressLookupTableProgram.html#extendLookupTable) method. Once the extend instruction is created, it can be sent in a transaction. |
| 106 | + |
| 107 | +### extendAddressLookupTable() |
| 108 | + |
| 109 | +```typescript theme={null} |
| 110 | +// add addresses to the `lookupTableAddress` table via an `extend` instruction |
| 111 | +const extendInstruction = AddressLookupTableProgram.extendLookupTable({ |
| 112 | + payer: publicKey, |
| 113 | + authority: publicKey, |
| 114 | + lookupTable: lookupTableAddress, |
| 115 | + addresses: [ |
| 116 | + publicKey, |
| 117 | + SystemProgram.programId, |
| 118 | + // more `publicKey` addresses can be listed here |
| 119 | + ], |
| 120 | +}); |
| 121 | + |
| 122 | +// Send this `extendInstruction` in a transaction to the cluster |
| 123 | +// to insert the listing of `addresses` into your lookup table with address `lookupTableAddress` |
| 124 | +const extensionMessageV0 = new TransactionMessage({ |
| 125 | + payerKey: publicKey, |
| 126 | + recentBlockhash: blockhash, |
| 127 | + instructions: [extendInstruction], |
| 128 | +}).compileToV0Message(); |
| 129 | + |
| 130 | +const extensionTransactionV0 = new VersionedTransaction(extensionMessageV0); |
| 131 | +const extensionSignature = await signAndSendTransaction(provider, extensionTransactionV0); |
| 132 | +``` |
| 133 | + |
| 134 | +For a live demo of extending a LUT, refer to the [handleSignAndSendTransactionV0WithLookupTable](https://github.com/phantom-labs/sandbox/blob/78dc35fe140140a961345a6af30a058e1e19a7aa/src/App.tsx#L218) function in our sandbox. |
| 135 | + |
| 136 | +## Sign and send a versioned transaction using a LUT |
| 137 | + |
| 138 | +Up until now, we have: |
| 139 | + |
| 140 | +1. Learned how to create a `VersionedTransaction`. |
| 141 | +2. Created an Address LUT. |
| 142 | +3. Extended the Address LUT. |
| 143 | + |
| 144 | +At this point, we are now ready to sign and send a `VersionedTransaction` using an Address LUT. |
| 145 | + |
| 146 | +First, we need to fetch the account of the created Address LUT. |
| 147 | + |
| 148 | +### getAddressLookupTable() |
| 149 | + |
| 150 | +```typescript theme={null} |
| 151 | +// get the table from the cluster |
| 152 | +const lookupTableAccount = await connection.getAddressLookupTable(lookupTableAddress).then((res) => res.value); |
| 153 | +// `lookupTableAccount` will now be a `AddressLookupTableAccount` object |
| 154 | +console.log('Table address from cluster:', lookupTableAccount.key.toBase58()); |
| 155 | +``` |
| 156 | + |
| 157 | +We can also parse and read all the addresses currently stores in the fetched Address LUT. |
| 158 | + |
| 159 | +## Parse and read addresses |
| 160 | + |
| 161 | +```typescript theme={null} |
| 162 | +// Loop through and parse all the address stored in the table |
| 163 | +for (let i = 0; i < lookupTableAccount.state.addresses.length; i++) { |
| 164 | + const address = lookupTableAccount.state.addresses[i]; |
| 165 | + console.log(i, address.toBase58()); |
| 166 | +} |
| 167 | +``` |
| 168 | + |
| 169 | +We can now create the instructions array with an arbitrary transfer instruction, just the way we did while creating the `VersionedTransaction` earlier. This `VersionedTransaction` can then be sent using the `signAndSendTransaction()` provider function. |
| 170 | + |
| 171 | +```typescript theme={null} |
| 172 | +// create an array with your desired `instructions` |
| 173 | +// in this case, just a transfer instruction |
| 174 | +const instructions = [ |
| 175 | + SystemProgram.transfer({ |
| 176 | + fromPubkey: publicKey, |
| 177 | + toPubkey: publicKey, |
| 178 | + lamports: minRent, |
| 179 | + }), |
| 180 | +]; |
| 181 | + |
| 182 | +// create v0 compatible message |
| 183 | +const messageV0 = new TransactionMessage({ |
| 184 | + payerKey: publicKey, |
| 185 | + recentBlockhash: blockhash, |
| 186 | + instructions, |
| 187 | +}).compileToV0Message([lookupTableAccount]); |
| 188 | + |
| 189 | +// make a versioned transaction |
| 190 | +const transactionV0 = new VersionedTransaction(messageV0); |
| 191 | +const signature = await signAndSendTransaction(provider, transactionV0); |
| 192 | +``` |
| 193 | + |
| 194 | +For a live demo of of signing and sending a versioned transaction using an Address LUT, refer to the [handleSignAndSendTransactionV0WithLookupTable](https://github.com/phantom-labs/sandbox/blob/78dc35fe140140a961345a6af30a058e1e19a7aa/src/App.tsx#L218) in our sandbox. |
0 commit comments