Skip to content
Algorand Developer Portal

Application Lookup

← Back to Indexer Client

This example demonstrates how to lookup and search for applications using the IndexerClient lookupApplicationById() and searchForApplications() methods.

  • LocalNet running (via algokit localnet start)

From the repository root:

Terminal window
cd examples
npm run example indexer_client/11-application-lookup.ts

View source on GitHub

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 demonstration
txn ApplicationID
int 0
==
bnz handle_creation
txn OnCompletion
int NoOp
==
bnz handle_noop
txn OnCompletion
int DeleteApplication
==
bnz handle_delete
int 0
return
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 10
int 1
return
`;
// 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);
});