Using Address Lookup Tables in Solana: A Comprehensive Guide
Introduction
Address Lookup Tables (ALTs), also known as “lookup tables,” empower developers to efficiently load multiple addresses within a single transaction on the Solana blockchain. This guide aims to provide a step-by-step tutorial for developers with an intermediate to advanced understanding of Solana development. The tutorial includes code examples and snippets, allowing developers to grasp the concepts and implement them in their projects. Additionally, a GitHub repository with an example codebase is provided for a hands-on experience.
Benefits of Address Lookup Tables
Increasing Transaction Efficiency
In Solana, each transaction requires listing every address involved, with a typical limit of 32 addresses per transaction. Address Lookup Tables elevate this limit to 256 addresses per transaction, enabling developers to handle more data within a single transaction.
Compressing On-chain Addresses
After storing addresses on-chain within a lookup table, each address can be referenced by its 1-byte index within the table instead of the full 32-byte address. This compression allows storing up to 256 addresses in a single lookup table, optimizing on-chain storage.
Versioned Transactions
To utilize an Address Lookup Table within a transaction, developers must use v0 transactions, introduced with the new Versioned Transaction format.
Creating an Address Lookup Table
Creating a new lookup table with the @solana/web3.js
library involves a few steps. Below is an example using TypeScript:
const web3 = require("@solana/web3.js");
// Connect to a Solana cluster and get the current slot
const connection = new web3.Connection(web3.clusterApiUrl("devnet"));
const slot = await connection.getSlot();
// Assumption: 'payer' is a valid 'Keypair' with enough SOL to pay for the execution
const [lookupTableInst, lookupTableAddress] =
web3.AddressLookupTableProgram.createLookupTable({
authority: payer.publicKey,
payer: payer.publicKey,
recentSlot: slot,
});
console.log("Lookup table address:", lookupTableAddress.toBase58());
// To create the Address Lookup Table on-chain:
// Send the 'lookupTableInst' instruction in a transaction
Note: Address lookup tables can be created with either a v0 transaction or a legacy transaction, but the Solana runtime can only retrieve additional addresses within a lookup table when using v0 Versioned Transactions.
Adding Addresses to a Lookup Table
Adding addresses to a lookup table, known as “extending,” is done using the @solana/web3.js
library. Here's an example:
// Add addresses to the 'lookupTableAddress' table via an 'extend' instruction
const extendInstruction = web3.AddressLookupTableProgram.extendLookupTable({
payer: payer.publicKey,
authority: payer.publicKey,
lookupTable: lookupTableAddress,
addresses: [
payer.publicKey,
web3.SystemProgram.programId,
// Add more 'publicKey' addresses here
],
});
// Send this 'extendInstruction' in a transaction to the cluster
// to insert the listing of 'addresses' into your lookup table with address 'lookupTableAddress'
Due to memory limits, any transaction used to extend an Address Lookup Table is also limited in how many addresses can be added at a time. Multiple transactions may be required to extend a table with more addresses.
Fetching an Address Lookup Table
To fetch a complete Address Lookup Table from the cluster, use the getAddressLookupTable
method:
// Define the 'PublicKey' of the lookup table to fetch
const lookupTableAddress = new web3.PublicKey("...");
// Get the table from the cluster
const lookupTableAccount = (await connection.getAddressLookupTable(lookupTableAddress)).value;
// 'lookupTableAccount' will now be an 'AddressLookupTableAccount' object
console.log("Table address from cluster:", lookupTableAccount.key.toBase58());
// Loop through and parse all the addresses stored in the table
for (let i = 0; i < lookupTableAccount.state.addresses.length; i++) {
const address = lookupTableAccount.state.addresses[i];
console.log(i, address.toBase58());
}
Using an Address Lookup Table in a Transaction
After creating and storing addresses on-chain in the lookup table, you can utilize it in future transactions. Here’s an example of creating a v0 transaction:
// Assumptions:
// - 'arrayOfInstructions' has been created as an array of 'TransactionInstruction'
// - We are using the 'lookupTableAccount' obtained above
// Construct a v0 compatible transaction 'Message'
const messageV0 = new web3.TransactionMessage({
payerKey: payer.publicKey,
recentBlockhash: blockhash,
instructions: arrayOfInstructions, // Note: this is an array of instructions
}).compileToV0Message([lookupTableAccount]);
// Create a v0 transaction from the v0 message
const transactionV0 = new web3.VersionedTransaction(messageV0);
// Sign the v0 transaction using the file system wallet created named 'payer'
transactionV0.sign([payer]);
// Send and confirm the transaction
// (Note: There is NOT an array of Signers here; see the note below...)
const txid = await web3.sendAndConfirmTransaction(connection, transactionV0);
console.log(`Transaction: https://explorer.solana.com/tx/${txid}?cluster=devnet`);
Note: When sending a
VersionedTransaction
to the cluster, it must be signed BEFORE calling thesendAndConfirmTransaction
method. If you pass an array of Signer (like with legacy transactions), the method will trigger an error.
Conclusion
Address Lookup Tables offer a powerful solution for optimizing Solana transactions, allowing developers to efficiently handle multiple addresses within a single transaction. By compressing on-chain addresses and leveraging versioned transactions, developers can achieve significant efficiency gains. The provided examples and code snippets offer a practical guide for incorporating Address Lookup Tables into Solana projects, empowering developers to navigate and leverage this powerful feature.