Versioned Transactions and Lookup Tables
Summary
- Versioned Transactions refers to a way to support both legacy versions and newer versions of transaction formats. The original transaction format is "legacy" and new transaction versions start at version 0. Versioned transactions were implemented to support the use of Address Lookup Tables (also called lookup tables or LUTs).
- Address Lookup Tables are accounts used to store addresses of other accounts, which can then be referenced in versioned transactions using a 1 byte index instead of the full 32 bytes per address. This enables the creation of more complex transactions than what was possible prior to the introduction of LUTs.
Lesson
By design, Solana transactions are limited to 1232 bytes. Transactions exceeding this size will fail. While this enables a number of network optimizations, it can also limit the types of atomic operations that can be performed on the network.
To help get around the transaction size limitation, Solana released a new transaction format that allows support for multiple versions of transaction formats. At the time of writing, Solana supports two transaction versions:
legacy
- the original transaction format0
- the newest transaction format that includes support for Address Lookup Tables
Versioned transactions don't require any modifications to existing Solana programs, but any client-side code created prior to the release of versioned transactions should be updated. In this lesson, we'll cover the basics of versioned transactions and how to use them, including:
- Creating versioned transactions
- Creating and managing lookup tables
- Using lookup tables in versioned transactions
Versioned Transactions
One of the items taking up the most space in Solana transactions is the inclusion of full account addresses. At 32 bytes each, 39 accounts will render a transaction too large. That's not even accounting for instruction data. In practice, most transactions will be too large with around 20 accounts.
Solana released versioned transactions to support multiple transaction formats. Alongside the release of versioned transactions, Solana released version 0 of transactions to support Address Lookup Tables. Lookup tables are separate accounts that store account addresses and then allow them to be referenced in a transaction using a 1 byte index. This significantly decreases the size of a transaction since each included account now only needs to use 1 byte instead of 32 bytes.
Even if you don't need to use lookup tables, you'll need to know how to support
versioned transactions in your client-side code. Fortunately, everything you
need to work with versioned transactions and lookup tables is included in the
@solana/web3.js
library.
Create versioned transactions
To create a versioned transaction, you simply create a TransactionMessage
with
the following parameters:
payerKey
- the public key of the account that will pay for the transactionrecentBlockhash
- a recent blockhash from the networkinstructions
- the instructions to include in the transaction
You then transform this message object into a version 0
transaction using the
compileToV0Message()
method.
import * as web3 from "@solana/web3.js";
// Example transfer instruction
const transferInstruction = [
web3.SystemProgram.transfer({
fromPubkey: payer.publicKey, // Public key of account that will send the funds
toPubkey: toAccount.publicKey, // Public key of the account that will receive the funds
lamports: 1 * LAMPORTS_PER_SOL, // Amount of lamports to be transferred
}),
];
// Get the latest blockhash
let { blockhash } = await connection.getLatestBlockhash();
// Create the transaction message
const message = new web3.TransactionMessage({
payerKey: payer.publicKey, // Public key of the account that will pay for the transaction
recentBlockhash: blockhash, // Latest blockhash
instructions: transferInstruction, // Instructions included in transaction
}).compileToV0Message();
Finally, you pass the compiled message into the VersionedTransaction
constructor to create a new versioned transaction. Your code can then sign and
send the transaction to the network, similar to a legacy transaction.
// Create the versioned transaction using the message
const transaction = new web3.VersionedTransaction(message);
// Sign the transaction
transaction.sign([payer]);
// Send the signed transaction to the network
const transactionSignature = await connection.sendTransaction(transaction);
Address Lookup Table
Address Lookup Tables (also called lookup tables or LUTs) are accounts that store a lookup table of other account addresses. These LUT accounts are owned by the Address Lookup Table Program and are used to increase the number of accounts that can be included in a single transaction.
Versioned transactions can include the address of an LUT account and then reference additional accounts with a 1-byte index instead of including the full address of those accounts. This significantly reduces the amount of space used for referencing accounts in a transaction.
To simplify the process of working with LUTs, the @solana/web3.js
library
includes an AddressLookupTableProgram
class which provides a set of methods to
create instructions for managing LUTs. These methods include:
createLookupTable
- creates a new LUT accountfreezeLookupTable
- makes an existing LUT immutableextendLookupTable
- adds addresses to an existing LUTdeactivateLookupTable
- puts an LUT in a “deactivation” period before it can be closedcloseLookupTable
- permanently closes an LUT account
Create a lookup table
You use the createLookupTable
method to construct the instruction that creates
a lookup table. The function requires the following parameters:
authority
- the account that will have permission to modify the lookup tablepayer
- the account that will pay for the account creationrecentSlot
- a recent slot to derive the lookup table's address
The function returns both the instruction to create the lookup table and the address of the lookup table.
// Get the current slot
const slot = await connection.getSlot();
// Create an instruction for creating a lookup table
// and retrieve the address of the new lookup table
const [lookupTableInst, lookupTableAddress] =
web3.AddressLookupTableProgram.createLookupTable({
authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table)
payer: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees)
recentSlot: slot - 1, // The recent slot to derive lookup table's address
});
Under the hood, the lookup table address is simply a PDA derived using the
authority
and recentSlot
as seeds.
const [lookupTableAddress, bumpSeed] = PublicKey.findProgramAddressSync(
[params.authority.toBuffer(), toBufferLE(BigInt(params.recentSlot), 8)],
this.programId,
);
Note that using the most recent slot sometimes results in an error after sending
the transaction. To avoid this, you can use a slot that is one slot prior the
most recent one (e.g. recentSlot: slot - 1
). However, if you still encounter
an error when sending the transaction, you can try resending the transaction.
"Program AddressLookupTab1e1111111111111111111111111 invoke [1]",
"188115589 is not a recent slot",
"Program AddressLookupTab1e1111111111111111111111111 failed: invalid instruction data";
Extend a lookup table
You use the extendLookupTable
method to create an instruction that adds
addresses to an existing lookup table. It takes the following parameters:
payer
- the account that will pay for the transaction fees and any increased rentauthority
- the account that has permission to change the lookup tablelookupTable
- the address of the lookup table to extendaddresses
- the addresses to add to the lookup table
The function returns an instruction to extend the lookup table.
const addresses = [
new web3.PublicKey("31Jy3nFeb5hKVdB4GS4Y7MhU7zhNMFxwF7RGVhPc1TzR"),
new web3.PublicKey("HKSeapcvwJ7ri6mf3HwBtspLFTDKqaJrMsozdfXfg5y2"),
// add more addresses
];
// Create an instruction to extend a lookup table with the provided addresses
const extendInstruction = web3.AddressLookupTableProgram.extendLookupTable({
payer: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees)
authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table)
lookupTable: lookupTableAddress, // The address of the lookup table to extend
addresses: addresses, // The addresses to add to the lookup table
});
Note that when extending a lookup table, the number of addresses that can be added in one instruction is limited by the transaction size limit, which is 1232 bytes. This means you can add 30 addresses to a lookup table at a time. If you need to add more than that, you'll need to send multiple transactions. Each lookup table can store a maximum of 256 addresses.
Send Transaction
After creating the instructions, you can add them to a transaction and sent it to the network.
// Get the latest blockhash
let { blockhash } = await connection.getLatestBlockhash();
// Create the transaction message
const message = new web3.TransactionMessage({
payerKey: payer.publicKey, // Public key of the account that will pay for the transaction
recentBlockhash: blockhash, // Latest blockhash
instructions: [lookupTableInst, extendInstruction], // Instructions included in transaction
}).compileToV0Message();
// Create the versioned transaction using the message
const transaction = new web3.VersionedTransaction(message);
// Sign the transaction
transaction.sign([payer]);
// Send the signed transaction to the network
const transactionSignature = await connection.sendTransaction(transaction);
Note that when you first create or extend a lookup table, it needs to "warm up" for one slot before the LUT or new addresses can be used in transactions. In other words, you can only use lookup tables and access addresses that were added prior to the current slot.
SendTransactionError: failed to send transaction: invalid transaction: Transaction address table lookup uses an invalid index
If you encounter the error above or are unable to access addresses in a lookup table immediately after extending it, it's likely because you're attempting to access the lookup table or a specific address prior to the end of the warm up period. To avoid this issue, add a delay after extending the lookup table before sending a transaction that references the table.
Deactivate a lookup table
When a lookup table is no longer needed, you can deactivate and close it to reclaim its rent balance. Address lookup tables can be deactivated at any time, but they can continue to be used by transactions until a specified "deactivation" slot is no longer "recent". This "cool-down" period ensures that in-flight transactions can't be censored by LUTs being closed and recreated in the same slot. The deactivation period is approximately 513 slots.
To deactivate an LUT, use the deactivateLookupTable
method and pass in the
following parameters:
lookupTable
- the address of the LUT to be deactivatedauthority
- the account with permission to deactivate the LUT
const deactivateInstruction =
web3.AddressLookupTableProgram.deactivateLookupTable({
lookupTable: lookupTableAddress, // The address of the lookup table to deactivate
authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table)
});
Close a lookup table
To close a lookup table after its deactivation period, use the
closeLookupTable
method. This method creates an instruction to close a
deactivated lookup table and reclaim its rent balance. It takes the following
parameters:
lookupTable
- the address of the LUT to be closedauthority
- the account with permission to close the LUTrecipient
- the account that will receive the reclaimed rent balance
const closeInstruction = web3.AddressLookupTableProgram.closeLookupTable({
lookupTable: lookupTableAddress, // The address of the lookup table to close
authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table)
recipient: user.publicKey, // The recipient of closed account lamports
});
Attempting to close a lookup table before it's been fully deactivated will result in an error.
"Program AddressLookupTab1e1111111111111111111111111 invoke [1]",
"Table cannot be closed until it's fully deactivated in 513 blocks",
"Program AddressLookupTab1e1111111111111111111111111 failed: invalid program argument";
Freeze a lookup table
In addition to standard CRUD operations, you can "freeze" a lookup table. This makes it immutable so that it can no longer be extended, deactivated, or closed.
You freeze a lookup table with the freezeLookupTable
method. It takes the
following parameters:
lookupTable
- the address of the LUT to be frozenauthority
- the account with permission to freeze the LUT
const freezeInstruction = web3.AddressLookupTableProgram.freezeLookupTable({
lookupTable: lookupTableAddress, // The address of the lookup table to freeze
authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table)
});
Once an LUT is frozen, any further attempts to modify it will result in an error.
"Program AddressLookupTab1e1111111111111111111111111 invoke [1]",
"Lookup table is frozen",
"Program AddressLookupTab1e1111111111111111111111111 failed: Account is immutable";
Using lookup tables in versioned transactions
To use a lookup table in a versioned transaction, you need to retrieve the lookup table account using its address.
const lookupTableAccount = (
await connection.getAddressLookupTable(lookupTableAddress)
).value;
You can then create a list of instructions to include in a transaction as usual.
When creating the TransactionMessage
, you can include any lookup table
accounts by passing them as an array to the compileToV0Message()
method. You
can also provide multiple lookup table accounts.
const message = new web3.TransactionMessage({
payerKey: payer.publicKey, // The payer (i.e., the account that will pay for the transaction fees)
recentBlockhash: blockhash, // The blockhash of the most recent block
instructions: instructions, // The instructions to include in the transaction
}).compileToV0Message([lookupTableAccount]); // Include lookup table accounts
// Create the versioned transaction using the message
const transaction = new web3.VersionedTransaction(message);
// Sign the transaction
transaction.sign([payer]);
// Send the signed transaction to the network
const transactionSignature = await connection.sendTransaction(transaction);
Lab
Let's go ahead and practice using lookup tables!
this lab will guide you through the steps of creating, extending, and then using a lookup table in a versioned transaction.
1. Get the starter code
To begin, download the starter code from the starter branch of this
repository.
Once you have the starter code, run npm install
in the terminal to install the
required dependencies.
The starter code includes an example of creating a legacy transaction that intends to atomically transfer SOL to 22 recipients. The transaction contains 22 instructions where each instruction transfers SOL from the signer to a different recipient.
The purpose of the starter code is to illustrate the limitation on the number of addresses that can be included in a legacy transaction. The transaction built in the starter code is expected to fail when sent.
The following starter code can be found in the index.ts
file.
import { initializeKeypair } from "./initializeKeypair";
import * as web3 from "@solana/web3.js";
async function main() {
// Connect to the devnet cluster
const connection = new web3.Connection(web3.clusterApiUrl("devnet"));
// Initialize the user's keypair
const user = await initializeKeypair(connection);
console.log("PublicKey:", user.publicKey.toBase58());
// Generate 22 addresses
const recipients = [];
for (let i = 0; i < 22; i++) {
recipients.push(web3.Keypair.generate().publicKey);
}
// Create an array of transfer instructions
const transferInstructions = [];
// Add a transfer instruction for each address
for (const address of recipients) {
transferInstructions.push(
web3.SystemProgram.transfer({
fromPubkey: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees)
toPubkey: address, // The destination account for the transfer
lamports: web3.LAMPORTS_PER_SOL * 0.01, // The amount of lamports to transfer
}),
);
}
// Create a transaction and add the transfer instructions
const transaction = new web3.Transaction().add(...transferInstructions);
// Send the transaction to the cluster (this will fail in this example if addresses > 21)
const txid = await connection.sendTransaction(transaction, [user]);
// Get the latest blockhash and last valid block height
const { lastValidBlockHeight, blockhash } =
await connection.getLatestBlockhash();
// Confirm the transaction
await connection.confirmTransaction({
blockhash: blockhash,
lastValidBlockHeight: lastValidBlockHeight,
signature: txid,
});
// Log the transaction URL on the Solana Explorer
console.log(`https://explorer.solana.com/tx/${txid}?cluster=devnet`);
}
To execute the code, run npm start
. This will create a new keypair, write it
to the .env
file, airdrop devnet SOL to the keypair, and send the transaction
built in the starter code. The transaction is expected to fail with the error
message Transaction too large
.
Creating .env file
Current balance is 0
Airdropping 1 SOL...
New balance is 1
PublicKey: 5ZZzcDbabFHmoZU8vm3VzRzN5sSQhkf91VJzHAJGNM7B
Error: Transaction too large: 1244 > 1232
In the next steps, we'll go over how to use lookup tables with versioned transactions to increase the number of addresses that can be included in a single transaction.
Before we start, go ahead and delete the content of the main
function to leave
only the following:
async function main() {
// Connect to the devnet cluster
const connection = new web3.Connection(web3.clusterApiUrl("devnet"));
// Initialize the user's keypair
const user = await initializeKeypair(connection);
console.log("PublicKey:", user.publicKey.toBase58());
// Generate 22 addresses
const addresses = [];
for (let i = 0; i < 22; i++) {
addresses.push(web3.Keypair.generate().publicKey);
}
}
2. Create a sendV0Transaction
helper function
We'll be sending multiple "version 0" transactions, so let's create a helper function to facilitate this.
This function should take parameters for a connection, a user's keypair, an array of transaction instructions, and an optional array of lookup table accounts.
The function then performs the following tasks:
- Retrieves the latest blockhash and last valid block height from the Solana network
- Creates a new transaction message using the provided instructions
- Signs the transaction using the user's keypair
- Sends the transaction to the Solana network
- Confirms the transaction
- Logs the transaction URL on the Solana Explorer
async function sendV0Transaction(
connection: web3.Connection,
user: web3.Keypair,
instructions: web3.TransactionInstruction[],
lookupTableAccounts?: web3.AddressLookupTableAccount[],
) {
// Get the latest blockhash and last valid block height
const { lastValidBlockHeight, blockhash } =
await connection.getLatestBlockhash();
// Create a new transaction message with the provided instructions
const messageV0 = new web3.TransactionMessage({
payerKey: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees)
recentBlockhash: blockhash, // The blockhash of the most recent block
instructions, // The instructions to include in the transaction
}).compileToV0Message(lookupTableAccounts ? lookupTableAccounts : undefined);
// Create a new transaction object with the message
const transaction = new web3.VersionedTransaction(messageV0);
// Sign the transaction with the user's keypair
transaction.sign([user]);
// Send the transaction to the cluster
const txid = await connection.sendTransaction(transaction);
// Confirm the transaction
await connection.confirmTransaction(
{
blockhash: blockhash,
lastValidBlockHeight: lastValidBlockHeight,
signature: txid,
},
"finalized",
);
// Log the transaction URL on the Solana Explorer
console.log(`https://explorer.solana.com/tx/${txid}?cluster=devnet`);
}
3. Create a waitForNewBlock
helper function
Recall that lookup tables and the addresses contained in them can't be
referenced immediately after creation or extension. This means we'll end up
needing to wait for a new block before submitting transactions that reference
the newly created or extended lookup table. To make this simpler down the road,
let's create a waitForNewBlock
helper function that we'll use to wait for
lookup tables to activate between sending transactions.
This function will have parameters for a connection and a target block height. It then starts an interval that checks the current block height of the network every 1000ms. Once the new block height exceeds the target height, the interval is cleared and the promise is resolved.
function waitForNewBlock(connection: web3.Connection, targetHeight: number) {
console.log(`Waiting for ${targetHeight} new blocks`);
return new Promise(async (resolve: any) => {
// Get the last valid block height of the blockchain
const { lastValidBlockHeight } = await connection.getLatestBlockhash();
// Set an interval to check for new blocks every 1000ms
const intervalId = setInterval(async () => {
// Get the new valid block height
const { lastValidBlockHeight: newValidBlockHeight } =
await connection.getLatestBlockhash();
// console.log(newValidBlockHeight)
// Check if the new valid block height is greater than the target block height
if (newValidBlockHeight > lastValidBlockHeight + targetHeight) {
// If the target block height is reached, clear the interval and resolve the promise
clearInterval(intervalId);
resolve();
}
}, 1000);
});
}
4. Create an initializeLookupTable
function
Now that we have some helper functions ready to go, declare a function named
initializeLookupTable
. This function has parameters user
, connection
, and
addresses
. The function will:
- Retrieve the current slot
- Generate an instruction for creating a lookup table
- Generate an instruction for extending the lookup table with the provided addresses
- Send and confirm a transaction with the instructions for creating and extending the lookup table
- Return the address of the lookup table
async function initializeLookupTable(
user: web3.Keypair,
connection: web3.Connection,
addresses: web3.PublicKey[],
): Promise<web3.PublicKey> {
// Get the current slot
const slot = await connection.getSlot();
// Create an instruction for creating a lookup table
// and retrieve the address of the new lookup table
const [lookupTableInst, lookupTableAddress] =
web3.AddressLookupTableProgram.createLookupTable({
authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table)
payer: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees)
recentSlot: slot - 1, // The recent slot to derive lookup table's address
});
console.log("lookup table address:", lookupTableAddress.toBase58());
// Create an instruction to extend a lookup table with the provided addresses
const extendInstruction = web3.AddressLookupTableProgram.extendLookupTable({
payer: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees)
authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table)
lookupTable: lookupTableAddress, // The address of the lookup table to extend
addresses: addresses.slice(0, 30), // The addresses to add to the lookup table
});
await sendV0Transaction(connection, user, [
lookupTableInst,
extendInstruction,
]);
return lookupTableAddress;
}
5. Modify main
to use lookup tables
Now that we can initialize a lookup table with all of the recipients' addresses,
let's update main
to use versioned transactions and lookup tables. We'll need
to:
- Call
initializeLookupTable
- Call
waitForNewBlock
- Get the lookup table using
connection.getAddressLookupTable
- Create the transfer instruction for each recipient
- Send the v0 transaction with all of the transfer instructions
async function main() {
// Connect to the devnet cluster
const connection = new web3.Connection(web3.clusterApiUrl("devnet"));
// Initialize the user's keypair
const user = await initializeKeypair(connection);
console.log("PublicKey:", user.publicKey.toBase58());
// Generate 22 addresses
const recipients = [];
for (let i = 0; i < 22; i++) {
recipients.push(web3.Keypair.generate().publicKey);
}
const lookupTableAddress = await initializeLookupTable(
user,
connection,
recipients,
);
await waitForNewBlock(connection, 1);
const lookupTableAccount = (
await connection.getAddressLookupTable(lookupTableAddress)
).value;
if (!lookupTableAccount) {
throw new Error("Lookup table not found");
}
const transferInstructions = recipients.map(recipient => {
return web3.SystemProgram.transfer({
fromPubkey: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees)
toPubkey: recipient, // The destination account for the transfer
lamports: web3.LAMPORTS_PER_SOL * 0.01, // The amount of lamports to transfer
});
});
await sendV0Transaction(connection, user, transferInstructions, [
lookupTableAccount,
]);
}
Notice that you create the transfer instructions with the full recipient address
even though we created a lookup table. That's because by including the lookup
table in the versioned transaction, you tell the web3.js
framework to replace
any recipient addresses that match addresses in the lookup table with pointers
to the lookup table instead. By the time the transaction is sent to the network,
addresses that exist in the lookup table will be referenced by a single byte
rather than the full 32 bytes.
Use npm start
in the command line to execute the main
function. You should
see an output similar to the following:
Current balance is 1.38866636
PublicKey: 8iGVBt3dcJdp9KfyTRcKuHY6gXCMFdnSG2F1pAwsUTMX
lookup table address: Cc46Wp1mtci3Jm9EcH35JcDQS3rLKBWzy9mV1Kkjjw7M
https://explorer.solana.com/tx/4JvCo2azy2u8XK2pU8AnJiHAucKTrZ6QX7EEHVuNSED8B5A8t9GqY5CP9xB8fZpTNuR7tbUcnj2MiL41xRJnLGzV?cluster=devnet
Waiting for 1 new blocks
https://explorer.solana.com/tx/rgpmxGU4QaAXw9eyqfMUqv8Lp6LHTuTyjQqDXpeFcu1ijQMmCH2V3Sb54x2wWAbnWXnMpJNGg4eLvuy3r8izGHt?cluster=devnet
Finished successfully
The first transaction link in the console represents the transaction for creating and extending the lookup table. The second transaction represents the transfers to all recipients. Feel free to inspect these transactions in the explorer.
Remember, this same transaction was failing when you first downloaded the starter code. Now that we're using lookup tables, we can do all 22 transfers in a single transaction.
6. Add more address to the lookup table
Keep in mind that the solution we've come up with so far only supports transfers to up to 30 accounts since we only extend the lookup table once. When you factor in the transfer instruction size, it's actually possible to extend the lookup table with an additional 27 addresses and complete an atomic transfer to up to 57 recipients. Let's go ahead and add support for this now!
All we need to do is go into initializeLookupTable
and do two things:
- Modify the existing call to
extendLookupTable
to only add the first 30 addresses (any more than that and the transaction will be too large) - Add a loop that will keep extending a lookup table 30 addresses at a time until all addresses have been added
async function initializeLookupTable(
user: web3.Keypair,
connection: web3.Connection,
addresses: web3.PublicKey[],
): Promise<web3.PublicKey> {
// Get the current slot
const slot = await connection.getSlot();
// Create an instruction for creating a lookup table
// and retrieve the address of the new lookup table
const [lookupTableInst, lookupTableAddress] =
web3.AddressLookupTableProgram.createLookupTable({
authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table)
payer: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees)
recentSlot: slot - 1, // The recent slot to derive lookup table's address
});
console.log("lookup table address:", lookupTableAddress.toBase58());
// Create an instruction to extend a lookup table with the provided addresses
const extendInstruction = web3.AddressLookupTableProgram.extendLookupTable({
payer: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees)
authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table)
lookupTable: lookupTableAddress, // The address of the lookup table to extend
addresses: addresses.slice(0, 30), // The addresses to add to the lookup table
});
await sendV0Transaction(connection, user, [
lookupTableInst,
extendInstruction,
]);
var remaining = addresses.slice(30);
while (remaining.length > 0) {
const toAdd = remaining.slice(0, 30);
remaining = remaining.slice(30);
const extendInstruction = web3.AddressLookupTableProgram.extendLookupTable({
payer: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees)
authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table)
lookupTable: lookupTableAddress, // The address of the lookup table to extend
addresses: toAdd, // The addresses to add to the lookup table
});
await sendV0Transaction(connection, user, [extendInstruction]);
}
return lookupTableAddress;
}
Congratulations! If you feel good about this lab, you're probably ready to work with lookup tables and versioned transactions on your own. If you want to take a look at the final solution code you can find it on the solution branch.
Challenge
As a challenge, experiment with deactivating, closing and freezing lookup tables. Remember that you need to wait for a lookup table to finish deactivating before you can close it. Also, if a lookup table is frozen, it cannot be modified (deactivated or closed), so you will have to test separately or use separate lookup tables.
- Create a function for deactivating the lookup table.
- Create a function for closing the lookup table
- Create a function for freezing the lookup table
- Test the functions by calling them in the
main()
function
You can reuse the functions we created in the lab for sending the transaction and waiting for the lookup table to activate/deactivate. Feel free to reference this solution code.