Skip to main content
Address Lookup Tables (ALTs) are a Solana feature that allows you to reference frequently used addresses in a compact format, reducing transaction size and costs. This guide shows two examples of working with ALTs.

Overview

Address Lookup Tables enable:
  • Smaller Transactions: Reference up to 256 addresses with a single byte index
  • Lower Fees: Smaller transactions cost less
  • More Instructions: Fit more operations in a single transaction
The Sava SDK provides two main ways to work with lookup tables:
  1. Real-time Monitoring: Subscribe to lookup table changes via WebSocket
  2. Batch Fetching: Query lookup tables by authority

Example 1: Subscribe to Lookup Table Changes

This example monitors all lookup table updates in real-time:
package software.sava.examples;

import software.sava.core.accounts.SolanaAccounts;
import software.sava.core.accounts.lookup.AddressLookupTable;
import software.sava.rpc.json.http.SolanaNetwork;
import software.sava.rpc.json.http.request.Commitment;
import software.sava.rpc.json.http.ws.SolanaRpcWebsocket;

import java.net.http.HttpClient;

public final class SubscribeToLookupTables {

  public static void main(final String[] args) throws InterruptedException {
    try (final var httpClient = HttpClient.newHttpClient()) {

      final var webSocket = SolanaRpcWebsocket.build()
          .uri(SolanaNetwork.MAIN_NET.getWebSocketEndpoint())
          .webSocketBuilder(httpClient)
          .commitment(Commitment.CONFIRMED)
          .onOpen(ws -> System.out.println("Websocket connected"))
          .onClose((ws, statusCode, reason) -> System.out.format("%d: %s%n", statusCode, reason))
          .onError((ws, throwable) -> throwable.printStackTrace())
          .create();

      webSocket.programSubscribe(
          SolanaAccounts.MAIN_NET.addressLookupTableProgram(),
          System.out::println,
          accountInfo -> {
            final var table = AddressLookupTable.read(accountInfo.pubKey(), accountInfo.data());
            System.out.println(table);
          }
      );

      webSocket.connect();

      Thread.sleep(Integer.MAX_VALUE);
    }
  }
}

Code Breakdown

WebSocket Setup

final var webSocket = SolanaRpcWebsocket.build()
    .uri(SolanaNetwork.MAIN_NET.getWebSocketEndpoint())
    .webSocketBuilder(httpClient)
    .commitment(Commitment.CONFIRMED)
    .onOpen(ws -> System.out.println("Websocket connected"))
    .onClose((ws, statusCode, reason) -> System.out.format("%d: %s%n", statusCode, reason))
    .onError((ws, throwable) -> throwable.printStackTrace())
    .create();
Configures a WebSocket connection to mainnet with confirmed commitment level.

Program Subscription

webSocket.programSubscribe(
    SolanaAccounts.MAIN_NET.addressLookupTableProgram(),
    System.out::println,  // Logs subscription confirmation
    accountInfo -> {
      final var table = AddressLookupTable.read(accountInfo.pubKey(), accountInfo.data());
      System.out.println(table);
    }
);
Subscribes to all accounts owned by the Address Lookup Table program. Each time a lookup table is created or modified, the callback:
  1. Deserializes the account data into an AddressLookupTable object
  2. Prints the table information

Example 2: Fetch Lookup Tables by Authority

This example retrieves all lookup tables controlled by a specific authority:
package software.sava.examples;

import software.sava.core.accounts.PublicKey;
import software.sava.core.accounts.SolanaAccounts;
import software.sava.core.accounts.lookup.AddressLookupTable;
import software.sava.core.rpc.Filter;
import software.sava.rpc.json.http.client.SolanaRpcClient;

import java.net.URI;
import java.net.http.HttpClient;
import java.util.List;

public final class FetchTablesByAuthority {

  public static void main(final String[] args) {
    final var rpcEndpoint = "https://mainnet.helius-rpc.com/?api-key="; // Add your API key
    final var authority = PublicKey.fromBase58Encoded(""); // Add authority address

    try (final var httpClient = HttpClient.newHttpClient()) {
      final var rpcClient = SolanaRpcClient.createClient(URI.create(rpcEndpoint), httpClient);

      final var tableAccountInfoList = rpcClient.getProgramAccounts(
          SolanaAccounts.MAIN_NET.addressLookupTableProgram(),
          List.of(
              Filter.createMemCompFilter(AddressLookupTable.AUTHORITY_OFFSET, authority)
          )
      ).join();

      for (final var accountInfo : tableAccountInfoList) {
        final var table = AddressLookupTable.read(accountInfo.pubKey(), accountInfo.data());
        System.out.println(table);
      }
    }
  }
}

Code Breakdown

RPC Client Setup

final var rpcEndpoint = "https://mainnet.helius-rpc.com/?api-key=YOUR_KEY";
final var authority = PublicKey.fromBase58Encoded("AUTHORITY_ADDRESS");

final var rpcClient = SolanaRpcClient.createClient(URI.create(rpcEndpoint), httpClient);
Creates an HTTP RPC client. Use a custom RPC endpoint with your API key for better rate limits.

Querying Program Accounts

final var tableAccountInfoList = rpcClient.getProgramAccounts(
    SolanaAccounts.MAIN_NET.addressLookupTableProgram(),
    List.of(
        Filter.createMemCompFilter(AddressLookupTable.AUTHORITY_OFFSET, authority)
    )
).join();
Fetches all accounts owned by the ALT program where:
  • The authority field matches the specified public key
  • AUTHORITY_OFFSET is the byte position where the authority is stored in the account data

Processing Results

for (final var accountInfo : tableAccountInfoList) {
  final var table = AddressLookupTable.read(accountInfo.pubKey(), accountInfo.data());
  System.out.println(table);
}
Iterates through all matching lookup tables and parses their data.

Working with Address Lookup Table Data

The AddressLookupTable class provides structured access to lookup table data:
AddressLookupTable table = AddressLookupTable.read(accountKey, accountData);

// Access table properties
PublicKey tableAddress = table.publicKey();
PublicKey authority = table.authority();
long deactivationSlot = table.deactivationSlot();
long lastExtendedSlot = table.lastExtendedSlot();
List<PublicKey> addresses = table.addresses();

System.out.format("""
  Lookup Table: %s
  Authority: %s
  Addresses: %d
  Last Extended: %d
  Deactivation Slot: %d
  """,
  tableAddress.toBase58(),
  authority.toBase58(),
  addresses.size(),
  lastExtendedSlot,
  deactivationSlot
);

// Access individual addresses
for (int i = 0; i < addresses.size(); i++) {
  System.out.println("Index " + i + ": " + addresses.get(i).toBase58());
}

Understanding Lookup Table Fields

Authority

The public key that can modify the lookup table (extend, deactivate, close).

Addresses

The list of public keys stored in the table. These can be referenced by index in transactions.

Last Extended Slot

The slot number when the table was last extended with new addresses.

Deactivation Slot

If non-zero, the slot when the table was deactivated. Deactivated tables cannot be modified but can still be used in transactions until closed.

Using Lookup Tables in Transactions

While these examples focus on reading lookup tables, here’s how you’d use them in transactions:
import software.sava.core.tx.TransactionSkeleton;
import software.sava.core.accounts.lookup.AddressLookupTable;

// Create a transaction with lookup table support
AddressLookupTable lookupTable = // ... fetched from above examples

// Reference addresses by index instead of including full 32-byte addresses
// For example, if lookupTable.addresses().get(5) is your target account:
// The transaction can reference it with just index 5 (1 byte) instead of 32 bytes

Filtering Lookup Tables

By Authority

Filter.createMemCompFilter(AddressLookupTable.AUTHORITY_OFFSET, authorityPubKey)
Finds all tables controlled by a specific authority.

By Data Size

You can also filter by account size if needed:
Filter.createDataSizeFilter(minSize)
Larger lookup tables have more addresses stored.

Performance Considerations

Subscribing to all lookup table updates can generate significant data on mainnet. Consider using filters or polling specific tables instead.

WebSocket Subscriptions

  • Pros: Real-time updates, efficient for monitoring specific tables
  • Cons: High data volume on mainnet, requires persistent connection
  • Use When: You need real-time notifications of table changes

HTTP Polling

  • Pros: Simple, no persistent connection, can fetch multiple tables at once
  • Cons: Not real-time, higher latency
  • Use When: You need to query tables periodically or fetch by authority

Common Use Cases

1. Monitor Your Own Lookup Tables

Use the authority filter to track tables you control:
final var myAuthority = PublicKey.fromBase58Encoded("YOUR_WALLET_ADDRESS");

// Fetch all your lookup tables
final var myTables = rpcClient.getProgramAccounts(
    SolanaAccounts.MAIN_NET.addressLookupTableProgram(),
    List.of(
        Filter.createMemCompFilter(AddressLookupTable.AUTHORITY_OFFSET, myAuthority)
    )
).join();

2. Track Protocol Lookup Tables

Monitor lookup tables used by specific protocols:
final var protocolAuthority = PublicKey.fromBase58Encoded("PROTOCOL_AUTHORITY");

webSocket.programSubscribe(
    SolanaAccounts.MAIN_NET.addressLookupTableProgram(),
    List.of(
        Filter.createMemCompFilter(AddressLookupTable.AUTHORITY_OFFSET, protocolAuthority)
    ),
    System.out::println,
    accountInfo -> {
      final var table = AddressLookupTable.read(accountInfo.pubKey(), accountInfo.data());
      System.out.println("Protocol table updated: " + table);
    }
);

3. Analyze Lookup Table Usage

Collect statistics about lookup table sizes:
int totalTables = tableAccountInfoList.size();
int totalAddresses = 0;

for (final var accountInfo : tableAccountInfoList) {
  final var table = AddressLookupTable.read(accountInfo.pubKey(), accountInfo.data());
  totalAddresses += table.addresses().size();
}

System.out.format("Total tables: %d, Average addresses per table: %.2f%n",
  totalTables,
  (double) totalAddresses / totalTables
);

Running the Examples

Subscribe to Lookup Tables

mvn exec:java -Dexec.mainClass="software.sava.examples.SubscribeToLookupTables"

Fetch Tables by Authority

  1. Add your RPC endpoint and authority address to the code
  2. Run:
mvn exec:java -Dexec.mainClass="software.sava.examples.FetchTablesByAuthority"

Next Steps

Creating Keypairs

Learn about keypair management

Transactions

Build and sign transactions with lookup tables

Build docs developers (and LLMs) love