The Solana address lookup table RPC methods on Solana Tracker find existing lookup tables (LUTs) that already contain your transaction's accounts, so a versioned transaction can reference them by one-byte index instead of 32-byte keys. getLookupTablesByAccounts with bestSet: true goes further and picks a small covering set for the whole account list.
Solana address lookup table RPC methods
Each method costs 1 credit per call and pages with limit (default 100, max 1000) and cursor.
| Method | Key params | Use it to |
|---|---|---|
getLookupTablesByAuthority | authority, mintsOnly, includeDeactivated | Inspect the tables a wallet controls |
getLookupTablesByAccount | account, owner | Find tables containing one address |
getLookupTablesByMint | mint, owner | Same lookup, keyed by token mint |
getLookupTablesByAccounts | accounts, owner, bestSet | Tables containing all accounts, or a covering set |
Without these methods you would create and extend your own tables (paying rent and waiting for activation) or scan lookup table accounts yourself. Stock RPC nodes return -32601 Method not found for them. If you only need swaps, Raptor Swap API returns addressLookupTableAddresses alongside its swap instructions.
Intersection mode versus bestSet
With bestSet false (the default), getLookupTablesByAccounts returns tables that contain every account you pass. That is strict: one missing account excludes a table. With bestSet: true it runs a greedy selection across tables and returns the set that covers the most accounts, bounded by:
| Option | Default | Effect |
|---|---|---|
maxLookupTables | 8 | Cap on tables in the returned set |
candidateLimit | 5000 | Cap on candidate tables considered |
perAccountLimit | 500 | Cap on candidates gathered per account |
Greedy means good, not proven minimal. Start with the defaults and tune only after measuring coverage and request time.
Pick tables for a transaction
Pass the accounts your instructions reference. The script needs Node 20+ and tsx; set SOLANA_RPC_URL (full URL including ?api_key=) and optionally ACCOUNTS, then run node --env-file=.env --import tsx lut.ts.
// lut.ts: pick lookup tables that cover a transaction's accounts with bestSet
export {}; // ES module, so top-level await type-checks
const RPC_URL = process.env.SOLANA_RPC_URL?.trim() ?? "";
if (!RPC_URL || !URL.canParse(RPC_URL)) {
// never print the URL: it contains the key
console.error("Set SOLANA_RPC_URL to the full URL from https://www.solanatracker.io/account/shared-rpc");
process.exit(1);
}
const BASE58 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
const DEFAULT_ACCOUNTS = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v,So11111111111111111111111111111111111111112";
const accounts = [...new Set((process.env.ACCOUNTS ?? DEFAULT_ACCOUNTS).split(",").map((a) => a.trim()).filter(Boolean))];
if (accounts.length === 0 || accounts.some((a) => !BASE58.test(a))) {
console.error("ACCOUNTS must be comma-separated base58 addresses");
process.exit(1);
}
type Lut = { pubkey: string; matchedAccounts: string[]; matchedAddressIndices: number[]; estimatedBytesSaved: number };
type BestSet = { lookupTables: Lut[]; coveredAccounts: string[]; uncoveredAccounts: string[]; estimatedBytesSaved: number };
type RpcBody = { result?: BestSet; error?: { code: number; message: string } };
try {
const res = await fetch(RPC_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "getLookupTablesByAccounts",
params: [{ accounts, bestSet: true, maxLookupTables: 4 }],
}),
signal: AbortSignal.timeout(10_000),
});
if (res.status === 429) throw new Error("Rate limited (HTTP 429)");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const body = (await res.json()) as RpcBody;
if (body.error) throw new Error(`RPC error ${body.error.code}: ${body.error.message}`);
if (!body.result) throw new Error("Response had no result");
const { lookupTables, coveredAccounts, uncoveredAccounts, estimatedBytesSaved } = body.result;
console.table(lookupTables.map((t) => ({ table: t.pubkey, covers: t.matchedAccounts.length, indices: t.matchedAddressIndices.join(" "), bytesSaved: t.estimatedBytesSaved })));
console.log(`covered ${coveredAccounts.length}/${accounts.length}, estimated bytes saved ${estimatedBytesSaved}`);
if (uncoveredAccounts.length > 0) console.log("keep as static keys:", uncoveredAccounts.join(", "));
} catch (error) {
console.error(error instanceof Error && error.name === "TimeoutError" ? "Request timed out after 10s" : String(error));
process.exit(1);
}
Response shape
The result sits directly under result, with no context wrapper:
{
"lookupTables": [
{
"pubkey": "LUT1...",
"matchedAccounts": ["AccountA...", "AccountB..."],
"matchedAddressIndices": [3, 17],
"estimatedBytesSaved": 62
}
],
"coveredAccounts": ["AccountA...", "AccountB..."],
"uncoveredAccounts": ["AccountC..."],
"estimatedBytesSaved": 62,
"count": 1
}
Full table entries also carry authority, deactivationSlot, addressCount, addresses, mints, slot and addressIndex. A deactivationSlot of 18446744073709551615 means the table is active. List responses add nextCursor and hasMore.
Pitfalls
- Trusting the estimate.
estimatedBytesSavedis a planning signal. Compile the message, check its size and simulate. - Dropping uncovered accounts. Keep
uncoveredAccountsas static keys. Signers and invoked program IDs are always static. - Unknown authorities. An authority can extend or deactivate its table. Use
ownerto limit results to authorities you trust. - Stale caches. Fetch the table accounts before compiling; the compiler needs current table state, not only a pubkey.
- Stopping at page one. Follow
nextCursorwhilehasMoreis true.
Lookup tables matter most on latency-sensitive paths; see reduce Solana RPC latency and the Solana RPC providers guide. The methods run on Solana RPC shared plans.
FAQ
Does bestSet return the smallest possible set?
No. It is a greedy selection. Check coveredAccounts and uncoveredAccounts.
What is the difference between ByAccount and ByMint?
None in behavior. getLookupTablesByMint is the same lookup with the parameter named mint.
Are deactivated tables returned?
getLookupTablesByAuthority excludes them unless includeDeactivated is true. Check deactivationSlot on any result before use.
What does a call cost?
One credit per call for all four methods.