Application Lookup
Description
Section titled “Description”This example demonstrates how to lookup and search for applications using the IndexerClient lookupApplicationById() and searchForApplications() methods.
Prerequisites
Section titled “Prerequisites”- LocalNet running (via
algokit localnet start)
Run This Example
Section titled “Run This Example”From the repository root:
cd examplesnpm run example indexer_client/11-application-lookup.ts/** * Example: Application Lookup * * This example demonstrates how to lookup and search for applications using * the IndexerClient lookupApplicationById() and searchForApplications() methods. * * Prerequisites: * - LocalNet running (via `algokit localnet start`) */
import { assignFee, OnApplicationComplete, Transaction, TransactionType, type AppCallTransactionFields,} from '@algorandfoundation/algokit-utils/transact';import { createAlgodClient, createAlgorandClient, createIndexerClient, printError, printHeader, printInfo, printStep, printSuccess, shortenAddress,} from '../shared/utils.js';
/** * Wait for a transaction to be confirmed */async function waitForConfirmation( algod: ReturnType<typeof createAlgodClient>, txId: string, maxRounds = 10,): Promise<Record<string, unknown>> { let lastRound = (await algod.status()).lastRound; const startRound = lastRound;
while (lastRound < startRound + BigInt(maxRounds)) { const pendingInfo = await algod.pendingTransactionInformation(txId); if (pendingInfo.confirmedRound && pendingInfo.confirmedRound > 0n) { return pendingInfo as Record<string, unknown>; } lastRound = (await algod.statusAfterBlock(lastRound)).lastRound; }
throw new Error(`Transaction ${txId} not confirmed after ${maxRounds} rounds`);}
async function main() { printHeader('Application Lookup Example');
// Create clients const indexer = createIndexerClient(); const algorand = createAlgorandClient(); const algod = createAlgodClient();
// ========================================================================= // Step 1: Get a funded account from LocalNet // ========================================================================= printStep(1, 'Getting a funded account from LocalNet');
let creatorAddress: string; let creatorAccount: Awaited<ReturnType<typeof algorand.account.kmd.getLocalNetDispenserAccount>>;
try { creatorAccount = await algorand.account.kmd.getLocalNetDispenserAccount(); creatorAddress = creatorAccount.addr.toString(); printSuccess(`Using dispenser account: ${shortenAddress(creatorAddress)}`); } catch (error) { printError( `Failed to get dispenser account: ${error instanceof Error ? error.message : String(error)}`, ); printInfo(''); printInfo('Make sure LocalNet is running: algokit localnet start'); printInfo('If issues persist, try: algokit localnet reset'); return; }
// ========================================================================= // Step 2: Deploy test applications using AlgorandClient // ========================================================================= printStep(2, 'Deploying test applications for demonstration');
let appId1: bigint; let appId2: bigint;
try { // Simple approval program that stores a counter in global state const approvalSource = `#pragma version 10// Simple smart contract for demonstrationtxn ApplicationIDint 0==bnz handle_creation
txn OnCompletionint NoOp==bnz handle_noop
txn OnCompletionint DeleteApplication==bnz handle_delete
int 0return
handle_creation: byte "counter" int 0 app_global_put byte "name" byte "DemoApp" app_global_put int 1 return
handle_noop: byte "counter" app_global_get int 1 + byte "counter" swap app_global_put int 1 return
handle_delete: int 1 return`;
const clearSource = `#pragma version 10int 1return`;
// Compile TEAL programs printInfo('Compiling TEAL programs...'); const approvalResult = await algod.tealCompile(approvalSource); const approvalProgram = new Uint8Array(Buffer.from(approvalResult.result, 'base64'));
const clearResult = await algod.tealCompile(clearSource); const clearStateProgram = new Uint8Array(Buffer.from(clearResult.result, 'base64'));
printInfo(`Approval program: ${approvalProgram.length} bytes`); printInfo(`Clear state program: ${clearStateProgram.length} bytes`); printInfo('');
// Create first application printInfo('Creating first test application: DemoApp1...'); const suggestedParams = await algod.suggestedParams();
const appCallFields1: AppCallTransactionFields = { appId: 0n, onComplete: OnApplicationComplete.NoOp, approvalProgram, clearStateProgram, globalStateSchema: { numUints: 1, numByteSlices: 1 }, localStateSchema: { numUints: 0, numByteSlices: 0 }, };
const createAppTx1 = new Transaction({ type: TransactionType.AppCall, sender: creatorAccount.addr, firstValid: suggestedParams.firstValid, lastValid: suggestedParams.lastValid, genesisHash: suggestedParams.genesisHash, genesisId: suggestedParams.genesisId, appCall: appCallFields1, });
const createAppTxWithFee1 = assignFee(createAppTx1, { feePerByte: suggestedParams.fee, minFee: suggestedParams.minFee, });
const signedCreateTx1 = await creatorAccount.signer([createAppTxWithFee1], [0]); await algod.sendRawTransaction(signedCreateTx1); const createPendingInfo1 = await waitForConfirmation(algod, createAppTxWithFee1.txId()); appId1 = createPendingInfo1.appId as bigint; printSuccess(`Created DemoApp1 with Application ID: ${appId1}`);
// Create second application with different schema printInfo('Creating second test application: DemoApp2...'); const suggestedParams2 = await algod.suggestedParams();
const appCallFields2: AppCallTransactionFields = { appId: 0n, onComplete: OnApplicationComplete.NoOp, approvalProgram, clearStateProgram, globalStateSchema: { numUints: 2, numByteSlices: 2 }, localStateSchema: { numUints: 1, numByteSlices: 1 }, };
const createAppTx2 = new Transaction({ type: TransactionType.AppCall, sender: creatorAccount.addr, firstValid: suggestedParams2.firstValid, lastValid: suggestedParams2.lastValid, genesisHash: suggestedParams2.genesisHash, genesisId: suggestedParams2.genesisId, appCall: appCallFields2, });
const createAppTxWithFee2 = assignFee(createAppTx2, { feePerByte: suggestedParams2.fee, minFee: suggestedParams2.minFee, });
const signedCreateTx2 = await creatorAccount.signer([createAppTxWithFee2], [0]); await algod.sendRawTransaction(signedCreateTx2); const createPendingInfo2 = await waitForConfirmation(algod, createAppTxWithFee2.txId()); appId2 = createPendingInfo2.appId as bigint; printSuccess(`Created DemoApp2 with Application ID: ${appId2}`);
// Small delay to allow indexer to catch up printInfo('Waiting for indexer to index applications...'); await new Promise(resolve => setTimeout(resolve, 3000)); printInfo(''); } catch (error) { printError( `Failed to create test applications: ${error instanceof Error ? error.message : String(error)}`, ); printInfo(''); printInfo('If LocalNet errors occur, try: algokit localnet reset'); return; }
// ========================================================================= // Step 3: Lookup application by ID with lookupApplicationById() // ========================================================================= printStep(3, 'Looking up application by ID with lookupApplicationById()');
try { // lookupApplicationById() returns detailed information about a single application const appResult = await indexer.lookupApplicationById(appId1);
if (appResult.application) { const app = appResult.application; printSuccess(`Found application with ID: ${app.id}`); printInfo('');
// Display application params printInfo('Application params:'); printInfo(` - id: ${app.id}`); if (app.params.creator) { printInfo(` - creator: ${shortenAddress(app.params.creator.toString())}`); } if (app.params.approvalProgram) { printInfo(` - approvalProgram: ${app.params.approvalProgram.length} bytes`); } if (app.params.clearStateProgram) { printInfo(` - clearStateProgram: ${app.params.clearStateProgram.length} bytes`); } if (app.params.extraProgramPages !== undefined) { printInfo(` - extraProgramPages: ${app.params.extraProgramPages}`); } if (app.params.version !== undefined) { printInfo(` - version: ${app.params.version}`); } printInfo('');
// Display state schema printInfo('State schema:'); if (app.params.globalStateSchema) { printInfo( ` - globalStateSchema: ${app.params.globalStateSchema.numUints} uints, ${app.params.globalStateSchema.numByteSlices} byte slices`, ); } if (app.params.localStateSchema) { printInfo( ` - localStateSchema: ${app.params.localStateSchema.numUints} uints, ${app.params.localStateSchema.numByteSlices} byte slices`, ); } printInfo('');
// Display global state key-value pairs if present if (app.params.globalState && app.params.globalState.length > 0) { printInfo('Global state key-value pairs:'); for (const kv of app.params.globalState) { // Decode the key from bytes to string const keyStr = new TextDecoder().decode(kv.key); // Value type: 1 = bytes, 2 = uint if (kv.value.type === 2) { printInfo(` - "${keyStr}": ${kv.value.uint} (uint)`); } else { const valueStr = new TextDecoder().decode(kv.value.bytes); printInfo(` - "${keyStr}": "${valueStr}" (bytes)`); } } } else { printInfo('Global state: (empty or not set)'); } printInfo('');
// Display additional metadata if (app.createdAtRound !== undefined) { printInfo(`Created at round: ${app.createdAtRound}`); } if (app.deleted !== undefined) { printInfo(`Deleted: ${app.deleted}`); } if (app.deletedAtRound !== undefined) { printInfo(`Deleted at round: ${app.deletedAtRound}`); } } else { printInfo('Application not found in response'); }
printInfo(`Query performed at round: ${appResult.currentRound}`); } catch (error) { printError( `lookupApplicationById failed: ${error instanceof Error ? error.message : String(error)}`, ); }
// ========================================================================= // Step 4: Lookup second application to compare // ========================================================================= printStep(4, 'Looking up second application to compare schemas');
try { const appResult2 = await indexer.lookupApplicationById(appId2);
if (appResult2.application) { const app = appResult2.application; printSuccess(`Found application with ID: ${app.id}`); printInfo('');
printInfo('State schema (different from first app):'); if (app.params.globalStateSchema) { printInfo( ` - globalStateSchema: ${app.params.globalStateSchema.numUints} uints, ${app.params.globalStateSchema.numByteSlices} byte slices`, ); } if (app.params.localStateSchema) { printInfo( ` - localStateSchema: ${app.params.localStateSchema.numUints} uints, ${app.params.localStateSchema.numByteSlices} byte slices`, ); } } } catch (error) { printError( `lookupApplicationById failed: ${error instanceof Error ? error.message : String(error)}`, ); }
// ========================================================================= // Step 5: Search for applications with searchForApplications() // ========================================================================= printStep(5, 'Searching for applications with searchForApplications()');
try { // searchForApplications() returns a list of applications matching the criteria const searchResult = await indexer.searchForApplications();
printSuccess(`Found ${searchResult.applications.length} application(s)`); printInfo('');
if (searchResult.applications.length > 0) { printInfo('Applications found:'); // Show first 5 applications to avoid too much output const appsToShow = searchResult.applications.slice(0, 5); for (const app of appsToShow) { printInfo(` Application ID: ${app.id}`); if (app.params.creator) { printInfo(` - creator: ${shortenAddress(app.params.creator.toString())}`); } if (app.deleted) { printInfo(` - deleted: ${app.deleted}`); } printInfo(''); } if (searchResult.applications.length > 5) { printInfo(` ... and ${searchResult.applications.length - 5} more`); } }
printInfo(`Query performed at round: ${searchResult.currentRound}`); } catch (error) { printError( `searchForApplications failed: ${error instanceof Error ? error.message : String(error)}`, ); }
// ========================================================================= // Step 6: Filter applications by creator address // ========================================================================= printStep(6, 'Filtering applications by creator address');
try { // Filter applications by creator printInfo(`Searching for applications created by: ${shortenAddress(creatorAddress)}`); const filteredResult = await indexer.searchForApplications({ creator: creatorAddress, });
printSuccess(`Found ${filteredResult.applications.length} application(s) by this creator`); printInfo('');
if (filteredResult.applications.length > 0) { printInfo('Applications by this creator:'); for (const app of filteredResult.applications) { printInfo(` Application ID: ${app.id}`); if (app.params.globalStateSchema) { printInfo( ` - globalStateSchema: ${app.params.globalStateSchema.numUints} uints, ${app.params.globalStateSchema.numByteSlices} byte slices`, ); } } } } catch (error) { printError( `searchForApplications by creator failed: ${error instanceof Error ? error.message : String(error)}`, ); }
// ========================================================================= // Step 7: Delete an application and demonstrate includeAll parameter // ========================================================================= printStep(7, 'Deleting an application to demonstrate includeAll parameter');
try { // Delete the second application printInfo(`Deleting application ${appId2}...`); const deleteParams = await algod.suggestedParams();
const deleteFields: AppCallTransactionFields = { appId: appId2, onComplete: OnApplicationComplete.DeleteApplication, };
const deleteTx = new Transaction({ type: TransactionType.AppCall, sender: creatorAccount.addr, firstValid: deleteParams.firstValid, lastValid: deleteParams.lastValid, genesisHash: deleteParams.genesisHash, genesisId: deleteParams.genesisId, appCall: deleteFields, });
const deleteTxWithFee = assignFee(deleteTx, { feePerByte: deleteParams.fee, minFee: deleteParams.minFee, });
const signedDeleteTx = await creatorAccount.signer([deleteTxWithFee], [0]); await algod.sendRawTransaction(signedDeleteTx); await waitForConfirmation(algod, deleteTxWithFee.txId()); printSuccess(`Deleted application ${appId2}`); printInfo('');
// Search without includeAll (should not include deleted apps) printInfo('Searching for applications by creator (without includeAll)...'); const withoutDeleted = await indexer.searchForApplications({ creator: creatorAddress, includeAll: false, }); printInfo(`Found ${withoutDeleted.applications.length} application(s) (excludes deleted)`);
// Search with includeAll to include deleted applications printInfo('Searching for applications by creator (with includeAll: true)...'); const withDeleted = await indexer.searchForApplications({ creator: creatorAddress, includeAll: true, }); printInfo(`Found ${withDeleted.applications.length} application(s) (includes deleted)`); printInfo('');
// Show deleted application details const deletedApp = withDeleted.applications.find(app => app.id === appId2); if (deletedApp) { printInfo(`Deleted application (ID: ${deletedApp.id}):`); printInfo(` - deleted: ${deletedApp.deleted}`); if (deletedApp.deletedAtRound !== undefined) { printInfo(` - deletedAtRound: ${deletedApp.deletedAtRound}`); } } } catch (error) { printError( `Delete/includeAll demo failed: ${error instanceof Error ? error.message : String(error)}`, ); }
// ========================================================================= // Step 8: Handle case where application is not found // ========================================================================= printStep(8, 'Handling case where application is not found');
try { // Try to lookup a non-existent application const nonExistentAppId = 999999999n; printInfo(`Attempting to lookup non-existent application ID: ${nonExistentAppId}`);
const result = await indexer.lookupApplicationById(nonExistentAppId);
// The response may have application as undefined for non-existent apps if (result.application) { printInfo(`Unexpectedly found application: ${result.application.id}`); } else { printInfo('Application field is undefined in response (application not found)'); } } catch (error) { // Some indexers throw an error for non-existent applications printInfo( `Application not found (caught error): ${error instanceof Error ? error.message : String(error)}`, ); }
// ========================================================================= // Step 9: Demonstrate pagination with limit and next // ========================================================================= printStep(9, 'Demonstrating pagination with limit and next parameters');
try { // First page with limit printInfo('Fetching first page of applications (limit: 1)...'); const page1 = await indexer.searchForApplications({ limit: 1 });
printInfo(`Page 1: Retrieved ${page1.applications.length} application(s)`); if (page1.applications.length > 0) { printInfo(` - Application ID: ${page1.applications[0].id}`); }
// Check if there are more results if (page1.nextToken) { printInfo(` - Next token available: ${page1.nextToken.substring(0, 20)}...`); printInfo('');
// Fetch second page using next token printInfo('Fetching second page using next token...'); const page2 = await indexer.searchForApplications({ limit: 1, next: page1.nextToken, });
printInfo(`Page 2: Retrieved ${page2.applications.length} application(s)`); if (page2.applications.length > 0) { printInfo(` - Application ID: ${page2.applications[0].id}`); }
if (page2.nextToken) { printInfo(` - More results available (nextToken present)`); } else { printInfo(` - No more results (no nextToken)`); } } else { printInfo(' - No pagination needed (all results fit in one page)'); } } catch (error) { printError(`Pagination demo failed: ${error instanceof Error ? error.message : String(error)}`); }
// ========================================================================= // Summary // ========================================================================= printHeader('Summary'); printInfo('This example demonstrated:'); printInfo(' 1. Deploying test applications using TEAL compilation'); printInfo(' 2. lookupApplicationById(appId) - Get detailed info about a single application'); printInfo(' 3. Display application params: id, creator, approvalProgram, clearStateProgram'); printInfo(' 4. Display state schema: globalStateSchema, localStateSchema'); printInfo(' 5. Display global state key-value pairs'); printInfo(' 6. searchForApplications() - Search for applications'); printInfo(' 7. Filtering by creator address'); printInfo(' 8. Using includeAll to include deleted applications'); printInfo(' 9. Handling case where application is not found'); printInfo(' 10. Pagination with limit and next parameters'); printInfo(''); printInfo('Key lookupApplicationById response fields:'); printInfo(' - application: The Application object (may be undefined if not found)'); printInfo(' - currentRound: Round at which results were computed'); printInfo(''); printInfo('Key Application fields:'); printInfo(' - id: Application identifier (bigint)'); printInfo(' - deleted: Whether app is deleted (boolean, optional)'); printInfo(' - createdAtRound: Round when created (bigint, optional)'); printInfo(' - deletedAtRound: Round when deleted (bigint, optional)'); printInfo(' - params: ApplicationParams object'); printInfo(''); printInfo('Key ApplicationParams fields:'); printInfo(' - creator: Address that created the application'); printInfo(' - approvalProgram: TEAL bytecode for approval logic (Uint8Array)'); printInfo(' - clearStateProgram: TEAL bytecode for clear state logic (Uint8Array)'); printInfo(' - globalStateSchema: {numUints, numByteSlices} for global storage'); printInfo(' - localStateSchema: {numUints, numByteSlices} for per-user storage'); printInfo(' - globalState: Array of TealKeyValue for current global state'); printInfo(' - extraProgramPages: Extra program pages (number, optional)'); printInfo(' - version: Number of program updates (number, optional)'); printInfo(''); printInfo('searchForApplications() filter parameters:'); printInfo(' - applicationId: Filter by specific app ID'); printInfo(' - creator: Filter by creator address'); printInfo(' - includeAll: Include deleted applications (default: false)'); printInfo(' - limit: Maximum results per page'); printInfo(' - next: Pagination token from previous response');}
main().catch(error => { console.error('Fatal error:', error); process.exit(1);});