16 min read
Aperçu
On October 10, 2022 (Epoch 358), Solana added support for new transaction version types through a concept known as "Versioned Transactions". After this change, the Solana runtime now supports two types of transactions: "legacy" (older transactions) and "0" (transactions that include Address Lookup Tables). Address lookup tables bring a new way for developers to efficiently load many addresses into a transaction, so if you have had issues with storage size due to accounts, this could help you out!
The Address Lookup Table Program (Program ID: AddressLookupTab1e1111111111111111111111111) allows you to store public keys in on-chain lookup tables and call the Lookup Table in your Versioned Transaction. Because serialized transactions transmitted to Solana validators must not exceed 1,232 bytes (Source Code, Reference), leveraging a Lookup Table can reduce transaction size and enable more complex transaction instructions (e.g., more accounts, more integrated Cross-Program Invocations, etc.). Solana lookup tables "effectively 'compress' a 32-byte address into a 1-byte index value" (Source). This means by using lookup tables, our transactions sizes will be smaller (or that we can pack more into our transactions)!
Ce que vous ferez
Dans ce guide, vous allez :
- create and execute a Version 0 (V0) Transaction,
- create and populate an Address Lookup Table, and
- compare the transaction size of two nearly identical transactions (one using a lookup table and one without).
If you need help with ensuring your existing client-side apps can support Versioned Transactions, check out our *Guide: How to Update Your Solana Client to Handle Versioned Transactions.
*
Ce dont vous aurez besoin
- Node.js (version 16.15 ou supérieure) installé
- Expérience avec TypeScript et ts-node installé
- Expérience dans l'exécution de transactions Solana de base (Guide : Comment envoyer des transactions sur Solana à l'aide de JavaScript)
If you're coming over from our previous Guide: How to Use Versioned Transactions on Solana, you can reuse your app.ts and can Skip to the Assemble a Version 0 Transaction section. Otherwise, set up your project:
Configurer votre projet
Créez un nouveau répertoire de projet dans votre terminal à l'aide de la commande suivante :
mkdir solana-versioned-tx
cd solana-versioned-tx
Créez un fichier pour votre application, app.ts:
echo > app.ts
Initialisez votre projet avec l'option « yes » pour utiliser les valeurs par défaut de votre nouveau paquet :
yarn init --yes
#ou
npm init --yes
Créez un fichier tsconfig.json en activant l'importation .json :
tsc -init --resolveJsonModule true
Installer la dépendance Solana Web3
Pour cet exercice, nous devrons ajouter la bibliothèque Solana Web3. Dans votre terminal, tapez :
yarn add @solana/web3.js@1
#ou
npm install @solana/web3.js@1
C'est parti.
Configurer votre application
Importer les dépendances nécessaires
Open app.ts, and paste the following imports on line 1:
import { AddressLookupTableProgram, Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, TransactionInstruction, TransactionMessage, VersionedTransaction, TransactionSignature, TransactionConfirmationStatus, SignatureStatus } from '@solana/web3.js';
We are importing a few essential methods and classes from the Solana Web3 library. There are probably a couple of imports you have not seen before (e.g., AddressLookupTableProgram and VersionedTransaction)--we will cover those later in this guide.
Créer un portefeuille et recevoir des SOL par airdrop
La première étape consiste à créer un compte avec un portefeuille et à l'approvisionner. Nous allons utiliser l'outil pratique ci-dessous pour générer automatiquement un nouveau portefeuille et y recevoir 1 SOL par airdrop. (Vous pouvez également le faire avec le Keypair.generate() et requestAirdrop() fonctions si vous préférez une approche plus manuelle).
Remarque : l'envoi trop fréquent de demandes d'airdrop peut déclencher une erreur 429 (Too Many Requests).
Une fois que vous aurez généré votre paire de clés, vous remarquerez deux nouvelles constantes : secret et PORTEFEUILLE_DE_SIGNATURE, une paire de clés. La secret est un tableau de 32 octets utilisé pour générer les clés publique et privée. Le PORTEFEUILLE_DE_SIGNATURE Il s'agit d'une instance de Keypair utilisée pour signer les transactions (nous avons distribué gratuitement des SOL du réseau de développement pour couvrir les frais de gaz). N'oubliez pas de l'ajouter à votre code sous vos autres constantes si ce n'est pas déjà fait.
Sous vos importations, collez votre nouveau secret, puis ajoutez :
const secret = [0,...,0]; // 👈 Replace with your secret
const SIGNER_WALLET = Keypair.fromSecretKey(new Uint8Array(secret));
const DESTINATION_WALLET = Keypair.generate();
//const LOOKUP_TABLE_ADDRESS = new PublicKey(""); // We will add this later
Nous avons défini deux portefeuilles : PORTEFEUILLE_DE_SIGNATURE enverra SOL à notre DESTINATION_PORTEFEUILLE. Nous avons également ajouté une constante, ADRESSE_DE_TABLE_DE_RECHERCHE, que nous mettrons à jour ultérieurement pour y faire figurer l'identifiant de compte sur la blockchain de notre table de correspondance.
Configurer votre point de terminaison Quicknode
Pour développer sur Solana, vous aurez besoin d'un point de terminaison API pour vous connecter au réseau. Vous pouvez bien sûr utiliser des nœuds publics ou déployer et gérer votre propre infrastructure ; toutefois, si vous souhaitez bénéficier de temps de réponse 8 fois plus rapides, vous pouvez nous confier cette tâche fastidieuse.
Découvrez pourquoi plus de 50 % des projets sur Solana choisissent Quicknode et créez un compte ici. Nous allons utiliser un nœud du Devnet de Solana.
Copiez le lien du fournisseur HTTP :

Dans le fichier app.ts, sous vos instructions d'importation, déclarez votre RPC et établissez votre connexion à Solana :
const QUICKNODE_RPC = 'https://example.solana-devnet.quiknode.pro/0123456/';
const SOLANA_CONNECTION = new Connection(QUICKNODE_RPC);
Votre environnement devrait ressembler à ceci :

Bon, c'est parti, on se met au travail !
Assemble a Version 0 Transaction
To use Lookup Tables, you must use Version 0 transactions. If you're unfamiliar with constructing and executing Versioned Transactions, check out our guide, here.
Let's start by creating a new function, createAndSendV0Tx, that accepts an array of TransactionInstructions, txInstructions:
async function createAndSendV0Tx(txInstructions: TransactionInstruction[]) {
// Step 1 - Fetch Latest Blockhash
let latestBlockhash = await SOLANA_CONNECTION.getLatestBlockhash('finalized');
console.log(" ✅ - Fetched latest blockhash. Last valid height:", latestBlockhash.lastValidBlockHeight);
// Step 2 - Generate Transaction Message
const messageV0 = new TransactionMessage({
payerKey: SIGNER_WALLET.publicKey,
recentBlockhash: latestBlockhash.blockhash,
instructions: txInstructions
}).compileToV0Message();
console.log(" ✅ - Compiled transaction message");
const transaction = new VersionedTransaction(messageV0);
// Step 3 - Sign your transaction with the required `Signers`
transaction.sign([SIGNER_WALLET]);
console.log(" ✅ - Transaction Signed");
// Step 4 - Send our v0 transaction to the cluster
const txid = await SOLANA_CONNECTION.sendTransaction(transaction, { maxRetries: 5 });
console.log(" ✅ - Transaction sent to network");
// Step 5 - Confirm Transaction
const confirmation = await confirmTransaction(SOLANA_CONNECTION, txid);
if (confirmation.value.err) { throw new Error(" ❌ - Transaction not confirmed.") }
console.log('🎉 Transaction succesfully confirmed!', '\n', `https://explorer.solana.com/tx/${txid}?cluster=devnet`);
}
Let's walk through our code:
- Step 1: We fetch the latest blockhash from the network. Note: We pass the parameter, 'finalized', to make sure the block does not belong to a dropped fork.
- Step 2: Using our txInstructions parameter and the latestBlockhash, we create a new MessageV0 by building a Message and executing the .compileToV0Message() method.
- Step 3: We sign the transaction with an array of signers. In this case, it is just our SIGNER_WALLET.
- Step 4: We send the transaction to the cluster using sendTransaction, which will return a transaction signature/id.
- Step 5: We wait for the cluster to confirm the transaction has succeeded. If it succeeds, we log our explorer URL; otherwise, we throw an error.
Let's define the confirmer la transaction function we called in our createAndSendV0Tx fonction. Ajoutez le code suivant à votre app.ts fichier :
async function confirmTransaction(
connection: Connection,
signature: TransactionSignature,
desiredConfirmationStatus: TransactionConfirmationStatus = 'confirmed',
timeout: number = 30000,
pollInterval: number = 1000,
searchTransactionHistory: boolean = false
): Promise<SignatureStatus> {
const start = Date.now();
while (Date.now() - start < timeout) {
const { value: statuses } = await connection.getSignatureStatuses([signature], { searchTransactionHistory });
if (!statuses || statuses.length === 0) {
throw new Error('Failed to get signature status');
}
const status = statuses[0];
if (status === null) {
// If status is null, the transaction is not yet known
await new Promise(resolve => setTimeout(resolve, pollInterval));
continue;
}
if (status.err) {
throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
}
if (status.confirmationStatus && status.confirmationStatus === desiredConfirmationStatus) {
return status;
}
if (status.confirmationStatus === 'finalized') {
return status;
}
await new Promise(resolve => setTimeout(resolve, pollInterval));
}
throw new Error(`Transaction confirmation timeout after ${timeout}ms`);
}
Cette fonction se contentera d'interroger le réseau Solana pour connaître l'état de la transaction jusqu'à ce que celle-ci soit confirmée ou que le délai d'expiration soit atteint. Nous avons défini des valeurs par défaut pour le délai d'expiration et l'intervalle d'interrogation, mais vous pouvez les modifier selon vos besoins.
Alright! We're all set. Let's add some instructions.
Create an Address Lookup Table
Create a new async function, createLookupTable, that will build our transaction instruction and invoke createAndSendV0Tx:
async function createLookupTable() {
// Step 1 - Get a lookup table address and create lookup table instruction
const [lookupTableInst, lookupTableAddress] =
AddressLookupTableProgram.createLookupTable({
authority: SIGNER_WALLET.publicKey,
payer: SIGNER_WALLET.publicKey,
recentSlot: await SOLANA_CONNECTION.getSlot(),
});
// Step 2 - Log Lookup Table Address
console.log("Lookup Table Address:", lookupTableAddress.toBase58());
// Step 3 - Generate a transaction and send it to the network
createAndSendV0Tx([lookupTableInst]);
}
Breaking down our code:
- Step 1: We create two variables, lookupTableInst and lookupTableAddress, by destructuring the results of the createLookupTable method. This method returns the public key for the table once created and a TransactionInstruction that can be passed into our createAndSendV0Tx function.
- Step 2: We log the table's address (which we will need later in this exercise).
- Step 3: Finaly, we call createAndSendV0Tx by passing lookupTableInst inside of an array to match our type requirements.
Awesome! At this point, you should be able to run your code and create an empty lookup table. After your function, call it by adding:
createLookupTable();
Then in your terminal enter:
ts-node app.ts
You should see your transaction progressing in your terminal and ultimately receive a URL to your transaction page on Solana Explorer:

Our lookup table account address is: 3uBhgRWPTPLfvfqxi4M9eVZC8nS1kDG9XPkdHKgG69nw:

Great Job! You have made your first lookup table.
Before we move on, let's do some cleanup:
- Remove your call to createLookupTable(). We won't need it again.
- Remember the LOOKUP_TABLE_ADDRESS constant we created a while ago? Remove the comment backslashes, and add your table lookup address from your console to your PublicKey declaration like so (this is line 6 for us):
const LOOKUP_TABLE_ADDRESS = new PublicKey("YOUR_TABLE_ADDRESS_HERE");
// e.g., const LOOKUP_TABLE_ADDRESS = new PublicKey("3uBhgRWPTPLfvfqxi4M9eVZC8nS1kDG9XPkdHKgG69nw");
Add Addresses to Your Lookup Table
Since we have already created createAndSendV0Tx, adding an address to a lookup table is easy! All we need to do is create a TransactionInstruction. Create a new async function, addAddressesToTable that uses the AddressLookupTableProgram.extendLookupTable() method:
async function addAddressesToTable() {
// Step 1 - Create Transaction Instruction
const addAddressesInstruction = AddressLookupTableProgram.extendLookupTable({
payer: SIGNER_WALLET.publicKey,
authority: SIGNER_WALLET.publicKey,
lookupTable: LOOKUP_TABLE_ADDRESS,
addresses: [
Keypair.generate().publicKey,
Keypair.generate().publicKey,
Keypair.generate().publicKey,
Keypair.generate().publicKey,
Keypair.generate().publicKey
],
});
// Step 2 - Generate a transaction and send it to the network
await createAndSendV0Tx([addAddressesInstruction]);
console.log(`Lookup Table Entries: `,`https://explorer.solana.com/address/${LOOKUP_TABLE_ADDRESS.toString()}/entries?cluster=devnet`)
}
Let's break down what's going on with the extendLookupTable method:
- We pass our SIGNER_WALLET to pay the transaction fees and any additional rent incurred.
- We define our update authority - in our case, we set that as the SIGNER_WALLET in our table creation step above.
- We pass in the lookup table account address (which we defined as LOOKUP_TABLE_ADDRESS).
- We pass an array of addresses into our lookup table. We will pass in a few random public keys, but you can pass in any public key that you like! The Program's "compression" supports storing up to 256 addresses in a single lookup table!
- Let's also log a link to our lookup table entries for easy access after the transaction is complete. Finally, we pass our TransactionInstruction into createAndSendV0Tx to generate a transaction and send it to the network!
After your function, call your new function by adding:
addAddressesToTable();
Run your code--in your terminal, use the up arrow on your keyboard to retrieve the previous command, ts-node app.ts, and hit Enter to run your code.
You should see a similar transaction flow in your terminal and success with a link to your transaction and your lookup table entries on Solana Explorer. Go to the lookup table entries. You should see a list of all of your table's stored public keys:

Nice work! You can modify your addresses array and rerun it to add additional public keys to your lookup table.
Before moving on, delete your call to addAddressesToTable();. We won't need it again.
Lookup Addresses in Your Lookup Table
Okay, so if you're following along, you should have an Address Lookup Table populated with a few addresses. Now, we are going to fetch all of our addresses and log them to your terminal. Create a new function, findAddressesInTable:
async function findAddressesInTable() {
// Step 1 - Fetch our address lookup table
const lookupTableAccount = await SOLANA_CONNECTION.getAddressLookupTable(LOOKUP_TABLE_ADDRESS)
console.log(`Successfully found lookup table: `, lookupTableAccount.value?.key.toString());
// Step 2 - Make sure our search returns a valid table
if (!lookupTableAccount.value) return;
// Step 3 - Log each table address to console
for (let i = 0; i < lookupTableAccount.value.state.addresses.length; i++) {
const address = lookupTableAccount.value.state.addresses[i];
console.log(` Address ${(i + 1)}: ${address.toBase58()}`);
}
}
Here's a summary of what we are doing here:
- Fetch our lookup table using a method called getAddressLookupTable() and pass our lookup table's account address. If successful, the query should return an AddressLookupTableAccount object.
- Stop the function if we don't find a valid lookup table.
- Iterate through each address found in our lookup table and log it to the console.
Not bad, right?
Call your new function and then run it:
findAddressesInTable();
In your terminal, use the up arrow on your keyboard to retrieve the previous command, ts-node app.ts, and hit Enter to run your code.
You should see your list of addresses like this:

If you're not seeing that or having trouble with any of the code, feel free to drop us a line on Discord -- we're here to help!
Remove or comment out your call to findAddressesInTable(); before moving on.
Put Lookup Tables to the Test
You're well on your way to becoming a Solana lookup table master--give yourself a quick pat on the back for making it this far. But what's the point of all of this? Good question! Solana lookup tables "effectively 'compress' a 32-byte address into a 1-byte index value" (Source). This means by using lookup tables, our transactions sizes will be smaller (or that we can pack more into our transactions)--let's prove it!
We're going to create a function that generates two transactions using the same transfer instruction; in one of them, we will pass our lookup table, and in the other we will not. We will check each transaction's size and then see which is smaller!
Add this new function, compareTxSize, to your code, and then we will walk through it together:
async function compareTxSize() {
// Step 1 - Fetch the lookup table
const lookupTable = (await SOLANA_CONNECTION.getAddressLookupTable(LOOKUP_TABLE_ADDRESS)).value;
if (!lookupTable) return;
console.log(" ✅ - Fetched lookup table:", lookupTable.key.toString());
// Step 2 - Generate an array of Solana transfer instruction to each address in our lookup table
const txInstructions: TransactionInstruction[] = [];
for (let i = 0; i < lookupTable.state.addresses.length; i++) {
const address = lookupTable.state.addresses[i];
txInstructions.push(
SystemProgram.transfer({
fromPubkey: SIGNER_WALLET.publicKey,
toPubkey: address,
lamports: 0.01 * LAMPORTS_PER_SOL,
})
)
}
// Step 3 - Fetch the latest Blockhash
let latestBlockhash = await SOLANA_CONNECTION.getLatestBlockhash('finalized');
console.log(" ✅ - Fetched latest blockhash. Last valid height:", latestBlockhash.lastValidBlockHeight);
// Step 4 - Generate and sign a transaction that uses a lookup table
const messageWithLookupTable = new TransactionMessage({
payerKey: SIGNER_WALLET.publicKey,
recentBlockhash: latestBlockhash.blockhash,
instructions: txInstructions
}).compileToV0Message([lookupTable]); // 👈 NOTE: We DO include the lookup table
const transactionWithLookupTable = new VersionedTransaction(messageWithLookupTable);
transactionWithLookupTable.sign([SIGNER_WALLET]);
// Step 5 - Generate and sign a transaction that DOES NOT use a lookup table
const messageWithoutLookupTable = new TransactionMessage({
payerKey: SIGNER_WALLET.publicKey,
recentBlockhash: latestBlockhash.blockhash,
instructions: txInstructions
}).compileToV0Message(); // 👈 NOTE: We do NOT include the lookup table
const transactionWithoutLookupTable = new VersionedTransaction(messageWithoutLookupTable);
transactionWithoutLookupTable.sign([SIGNER_WALLET]);
console.log(" ✅ - Compiled transactions");
// Step 6 - Log our transaction size
console.log('Transaction size without address lookup table: ', transactionWithoutLookupTable.serialize().length, 'bytes');
console.log('Transaction size with address lookup table: ', transactionWithLookupTable.serialize().length, 'bytes');
}
This looks like a lot, but it's reusing the code we created earlier in this guide. Let's step through: Step 1. We fetch our lookup table and make sure that we return a valid result. Step 2. Using a similar loop to our previous address log exercise, we iterate through each address in our table, create a transfer instruction to that address, and we push each TransactionInstruction to an array, txInstructions. Step 3. We fetch the latest blockhash, which we need to generate our messages. Step 4. We generate a transaction that uses our lookup table. Note that we pass [lookupTable] into the .compileToV0Message() method. When we used this before, we did not pass a value here. This lets our transaction know to use our lookup table. To calculate the size of our transaction, we also need to sign it--we do so with .sign(). Step 5. We generate the same transaction WITHOUT calling our lookup table. We now have two transactions that should process the same instructions, but one uses a lookup table, and the other just uses the public keys that were passed into each instruction. Step 6. Finally, we log each transaction's size. You can calculate a transaction size by serializing it using .serialize() (which yields a Uint8Array) and calculating its length using .length.
We did a lot there, but I hope everything is starting to feel familiar! Call your function by adding compareTxSize(); to the end of your code:
compareTxSize();
In your terminal, use the up arrow on your keyboard to retrieve the previous command, ts-node app.ts, and hit Enter to run your code.
Do you see results like this?
Transaction Size without Address Lookup Table: 413 bytes
Transaction Size with Address Lookup Table: 292 bytes
Wow! That's a lot of savings. My lookup table saved me over 120 bytes (your savings may be more or less depending on the number of addresses in your table)! In an environment where transactions are limited to 1,232 bytes, that's a big deal.
Conclusion
That concludes our guide! If you'd like to check your final code against ours, you can find it on GitHub, here.
Address Lookup Tables are an exciting new addition to Solana. As you continue building out more complex transactions, you should find that they create flexibility in how you approach your programs and dApps.
**Want to keep building?
**
- Try adding a UI to your lookup table in your Solana Explorer Clone.
- See how many Solana airdrops you can include in a Bulk Transaction
If you're stuck, have questions, or just want to talk about what you're building, drop us a line on Discord or Twitter!
Nous ❤️ les commentaires !
If you have any feedback on this guide, let us know. We’d love to hear from you.
