Create and discover pool ALTs
Prepare shared lookup tables with the SDK, extend them in confirmed batches, and discover candidates by pool through RPC.
The SDK's pool ALT increment adds creation, extension planning, and RPC discovery around the convention first stored address = pool address. The helpers are implemented in the source SDK; they do not imply a deployed protocol catalog. An authenticated protocol registry, automatic selection during swaps, and popularity-based maintenance remain separate proposals.
A pool can have multiple tables or generations. The lookup starts with the selected pool; a mint can help discover pools, but does not combine all those pools' vaults and ticks into one shared table.
The operation sequence
Existing pool
→ prepare empty ALT creation
→ application signs, submits, and confirms
→ prepare and confirm each extension batch
→ wait for new addresses to become active
→ discover and validate pool ALT candidates through RPC
→ choose useful tables for the operation
→ prepare the operation with transaction.lookupTablesCreation and extension are separate transactions. A failure after creation can leave an empty or partly populated table and its SOL deposit. Read actual state before resuming; a prepared transaction has not changed the chain.
1. Prepare creation
prepareCreatePoolLookupTable requires an existing pool, a table authority, and a rent payer. The client's fee payer continues to fund transaction network fees. These roles may use different addresses, and requiredSigners describes the prepared transaction's signature requirements.
import type { GraplClient, PreparedPoolLookupTableCreation } from "@grapl/sdk";
import type { Address } from "@solana/kit";
/** Prepare an empty shared table; the application confirms it before extension. Example: preparePoolTable(client, pool, authority, payer). */
export async function preparePoolTable(
client: GraplClient,
pool: Address,
authority: Address,
payer: Address,
): Promise<PreparedPoolLookupTableCreation> {
return client.prepareCreatePoolLookupTable({ pool, authority, payer });
}The result adds tableAddress, recentSlot, and extensionPlan to the ordinary prepared-transaction fields. It creates an empty ALT; it does not populate the table. Creation does not require the designated table authority to sign, but extension does. Follow the transaction lifecycle before continuing.
The ALT address derives from its authority and recent slot, not directly from the pool. Reusing that authority/slot pair cannot create a second table. Use a fresh recent slot for another table or generation; the SDK rejects an address that already exists.
The planned entries start with the pool, then canonical dependencies including mints, vaults, the tick directory, and hook-related addresses. Use additionalAddresses for reviewed operation-specific dependencies, such as initialized tick pages or extra hook accounts. Duplicate entries are removed. Inclusion in a table does not grant account access or guarantee that an address is compressible in every transaction.
2. Extend in batches
extensionPlan.batches contains address lists, not signed transactions. Each list is input for a separate prepareExtendPoolLookupTable call after its prerequisites confirm. The default batch size is 20 addresses; maxAddressesPerBatch accepts 1–20 when planning.
For later additions or recovery, planPoolLookupTableExtension re-reads the table, checks its authority and pool marker, removes existing entries, and returns the remaining batches. It rejects a resulting table above 256 addresses. Create a new table explicitly when another generation is needed.
import type {
ExtendPoolLookupTableIntent,
GraplClient,
PreparedTransaction,
} from "@grapl/sdk";
/** Re-read and simulate one batch after the previous transaction confirms. Example: preparePoolTableBatch(client, reviewedBatch). */
export async function preparePoolTableBatch(
client: GraplClient,
reviewedBatch: ExtendPoolLookupTableIntent,
): Promise<PreparedTransaction> {
return client.prepareExtendPoolLookupTable(reviewedBatch);
}Supply pool, table, authority, payer, and that batch's addresses. Extension validates an active mutable table and its authority. A first extension anchors index zero to the pool; subsequent extensions preserve it. A batch with no new entries rejects instead of pretending to make progress. An empty caller address list can still seed the pool marker on the first extension.
The full signed transaction size is checked. Batching does not bypass the transaction envelope, and extension does not silently split an oversized request. Re-prepare every batch against current state rather than collecting signatures for a stale sequence.
3. Discover tables by pool
findPoolLookupTables queries the ALT program over RPC, filtering the first stored address at offset 56. No grapl API key or new core registry instruction is required.
import type { GraplClient, PoolLookupTableDiscovery } from "@grapl/sdk";
import type { Address } from "@solana/kit";
/** Find candidates from an authority the application has independently reviewed. Example: discoverPoolTables(client, pool, authority). */
export async function discoverPoolTables(
client: GraplClient,
pool: Address,
trustedAuthority: Address,
): Promise<PoolLookupTableDiscovery> {
return client.findPoolLookupTables({
pool,
trustedAuthorities: [trustedAuthority],
});
}Discovery returns candidates, rejected entries with reasons, scannedCount, and the RPC context slot. A candidate exposes its authority, stored and active addresses, fullyWarmedUp, containsPoolAddresses, and the caller's applied trust filter.
Anyone can create an ALT whose first entry is a public pool address. This marker identifies candidates; it does not prove endorsement by the pool creator or grapl. Use independently trusted authorities or exact table addresses. When both trustedAuthorities and trustedTables are supplied, a table must satisfy both. A frozen table has no current authority and cannot prove historical authority provenance; it can match a trusted address filter.
containsPoolAddresses reports coverage of the SDK's canonical pool dependencies. It does not guarantee coverage of every tick page, hook proof, or wallet account required by your operation. Inspect the actual needed accounts before selecting tables.
4. Confirm warmup and use explicitly
New entries require warmup before they can be used. The SDK exposes only the active prefix for compression and conservatively rejects deactivating tables. Re-fetch discovery results until the entries you need are active; fullyWarmedUp reports whether the whole table is ready.
Pass selected addresses in transaction.lookupTables when preparing the dependent operation. The SDK does not automatically create, discover, select, or extend tables during a swap. Compare actual serialized size for the operation; a table has message overhead and can provide no benefit when it covers too few eligible accounts.
See Address Lookup Tables for an operation example. Signers remain inline. Wallet-specific accounts and positions need not accumulate in a shared pool table, and hook permissions remain enforced independently of compression.
RPC and lifecycle boundaries
A custom RPC port needs optional getSlot support for creation and getProgramAccounts for discovery. The standard Kit port implements both. Existing operations remain compatible with custom ports that omit those capabilities.
Discovery accepts maxCandidates from 1 to 1,000, defaulting to 100. An over-limit or explicitly incomplete response fails rather than returning a silently truncated list. The standard adapter relies on the provider's complete-response contract; it cannot cryptographically detect an RPC that silently omits matching accounts.
ALTs are append-only. Full tables require a new generation using a fresh recent slot; entries cannot be evicted or reordered. Freeze, deactivate, and close helpers are outside this increment. Table ownership belongs to Solana's ALT program; the configured authority controls supported updates, and rent funding remains explicit. See Solana's ALT guide and v0 lifecycle.
Proposed: managed protocol tables
The longer-term design is a grapl-maintained catalog keyed by deployment and pool, with explicit provenance, rotation, and table-retirement rules. Usage measurements could help create new generations containing frequently used account combinations. An RPC-readable authenticated registry could support that policy, with an optional API cache.
Those services are not provided by the first-address convention or the SDK helpers. Protocol-funded setup requires a designated payer and signing workflow. Automatic selection should optimize the current transaction's actual bytes, preserve less-used pools, and never change the operation or bypass a hook when a table is unavailable.