As the title says, I'm unable to create an Address Lookup Table in localnet. I'm trying to create it in an anchor test and using the following code (which works in devnet/mainnet):

const slot = await connection.getSlot();
const [lookupTableInst, lookupTableAddress] = AddressLookupTableProgram.createLookupTable({
    authority: provider.publicKey,
    payer: provider.publicKey,
    recentSlot: slot,

const blockhash = await connection.getLatestBlockhash();
const tx = new VersionedTransaction(
    new TransactionMessage({
        payerKey: provider.publicKey,
        recentBlockhash: blockhash.blockhash,
        instructions: [lookupTableInst],

await provider.sendAndConfirm(tx);

I get an "invalid instruction data" error, which looks like it has to do with the slot not being valid (https://github.com/solana-labs/solana/blob/057ad2ab477ee8a411c858809ff5c470175f44e9/programs/address-lookup-table/src/processor.rs#L105)

I've also tried with a legacy Transaction, which also doesn't work.

2 Answers 2



The error you're getting is being thrown by this check. The instruction to create a lookup table requires the client to pass in a recent slot as an argument, and checks that that slot is valid(contained in the slot-hashes cache). The definition of "valid" here is a slot during which a block was actually produced.

Hence you should get your recentSlot parameter with:

const currentSlot = await connection.getSlot();
console.log('currentSlot:', currentSlot);
const slots = await connection.getBlocks(currentSlot - 200);
if (slots.length < 100) {
  throw new Error(`Could find only ${slots.length} ${slots} on the main fork`);
const recentSlot = slots[0];

OR just specify a finalized commitment for your getSlot() rpc call.

const recentSlot = await provider.connection.getSlot("finalized"); // note the commitment


Address Lookup Tables usually need a cooldown period after being created, before which they are considered unactivated and cannot be used. You can either try waiting an arbitrary amount of time, for example:

const sleep = async (ms = 0): Promise<void> =>
  new Promise((resolve) => setTimeout(resolve, ms));
await sleep(10000);
const lutAccount = await provider.connection
  .then((res) => res.value);
if (lutAccount === null) {
  throw new Error(
    "Something went wrong. We don't expect our lookup table to be un-initialized, but it is."

or to be precise, wait for a new block, which is the criteria for a lookup table's activation:

const waitForNewBlock = async(connection: web3.Connection, targetHeight: number) => {
  console.log(`Waiting for ${targetHeight} new blocks`);
  return new Promise(async (resolve: any) => {
    // Get the last valid block height
    const { lastValidBlockHeight } = await connection.getLatestBlockhash();

    // Check if at least targetHeight amount of new blocks are generated every 1 second
    const intervalId = setInterval(async () => {
      const { lastValidBlockHeight: newValidBlockHeight } =
        await connection.getLatestBlockhash();

      if (newValidBlockHeight > lastValidBlockHeight + targetHeight) {
    }, 1000);
// Lifted from https://medium.com/@brknarsy/guide-to-optimizing-transactions-with-address-lookup-tables-alts-on-solana-85e654aa6789
  • This might be helpful after getting the ALT created, however my problem is getting it created in the first place. I tried using the wait code before creating the ALT, but still get the same error Commented Dec 28, 2023 at 23:39
  • 1
    My apologies. I misread the question and will be updating my answer now
    – Ademola
    Commented Dec 29, 2023 at 6:34
  • that was it! All I had to do was add "finalized" to the getSlot call and it works. Thank you! Commented Dec 30, 2023 at 13:05
  • Sweet. You’re very welcome
    – Ademola
    Commented Dec 31, 2023 at 8:40

You can use this code:

const user = new solana.PublicKey("<user-account>");

const slot = await connection.getSlot();
const [altCreateIx, altPubkey] =
    authority: wallet.publicKey,
    payer: wallet.publicKey,
    recentSlot: slot,
const extendLutIx = solana.AddressLookupTableProgram.extendLookupTable({
  payer: wallet.publicKey,
  authority: wallet.publicKey,
  lookupTable: altPubkey,
  addresses: [
console.log(`\nAddress look up table pubkey - ${altPubkey}\n`);

let block = await connection.getLatestBlockhash();
const tx = new solana.Transaction().add(altCreateIx).add(extendLutIx);
tx.feePayer = wallet.publicKey;
tx.recentBlockhash = block.blockhash;
const txSig = await connection.sendRawTransaction(tx.serialize());

await connection.confirmTransaction(
    signature: txSig,
    blockhash: block.blockhash,
    lastValidBlockHeight: block.lastValidBlockHeight,

const lutAccount = (await connection.getAddressLookupTable(altPubkey)).value;
console.log("Table address from cluster:", lutAccount.key.toBase58());
for (let i = 0; i < lutAccount.state.addresses.length; ++i) {
  const address = lutAccount.state.addresses[i];
  console.log(i, address.toBase58());

const transferIx = solana.SystemProgram.transfer({
  fromPubkey: wallet.publicKey,
  toPubkey: user,
  lamports: 0.01 * 10 ** 9,

block = await connection.getLatestBlockhash();
const txMsg = new solana.TransactionMessage({
  payerKey: wallet.publicKey,
  recentBlockhash: block.blockhash,
  instructions: [transferIx, transferIx, transferIx],
const Tx = new solana.VersionedTransaction(txMsg);
const txSig2 = await connection.sendTransaction(Tx);

console.log(`\nTransaction signature - ${txSig2}\n`);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.