Account Applications
Description
Section titled “Description”This example demonstrates how to query account application relationships using the IndexerClient lookupAccountCreatedApplications() and lookupAccountAppLocalStates() 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/04-account-applications.ts/** * Example: Account Applications * * This example demonstrates how to query account application relationships using * the IndexerClient lookupAccountCreatedApplications() and lookupAccountAppLocalStates() methods. * * Prerequisites: * - LocalNet running (via `algokit localnet start`) */
import { assignFee, OnApplicationComplete, Transaction, TransactionType, type AppCallTransactionFields,} from '@algorandfoundation/algokit-utils/transact';import { createAlgodClient, createAlgorandClient, createIndexerClient, loadTealSource, 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('Account Applications 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 { // Load approval program from shared artifacts that stores a counter in global state and supports local state const approvalSource = loadTealSource('approval-lifecycle-counter.teal');
// Load clear state program from shared artifacts const clearSource = loadTealSource('clear-state-approve.teal');
// 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: 0 }, localStateSchema: { numUints: 1, 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 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: 1 }, localStateSchema: { numUints: 2, 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}`); 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: Opt into an application to create local state // ========================================================================= printStep(3, 'Opting into an application to create local state');
try { const optInParams = await algod.suggestedParams();
const optInFields: AppCallTransactionFields = { appId: appId1, onComplete: OnApplicationComplete.OptIn, };
const optInTx = new Transaction({ type: TransactionType.AppCall, sender: creatorAccount.addr, firstValid: optInParams.firstValid, lastValid: optInParams.lastValid, genesisHash: optInParams.genesisHash, genesisId: optInParams.genesisId, appCall: optInFields, });
const optInTxWithFee = assignFee(optInTx, { feePerByte: optInParams.fee, minFee: optInParams.minFee, });
printInfo(`Opting into app ${appId1}...`); const signedOptInTx = await creatorAccount.signer([optInTxWithFee], [0]); await algod.sendRawTransaction(signedOptInTx); await waitForConfirmation(algod, optInTxWithFee.txId()); printSuccess(`Successfully opted into app ${appId1}`); printInfo(''); } catch (error) { printError( `Failed to opt into application: ${error instanceof Error ? error.message : String(error)}`, ); // Continue anyway to demonstrate lookups }
// ========================================================================= // Step 4: Lookup created applications with lookupAccountCreatedApplications() // ========================================================================= printStep(4, 'Looking up created applications with lookupAccountCreatedApplications()');
try { // lookupAccountCreatedApplications() returns applications created by an account const createdAppsResult = await indexer.lookupAccountCreatedApplications(creatorAddress);
printSuccess( `Found ${createdAppsResult.applications.length} application(s) created by account`, ); printInfo('');
if (createdAppsResult.applications.length > 0) { printInfo('Created applications:'); for (const app of createdAppsResult.applications) { printInfo(` Application 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.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`, ); } if (app.createdAtRound !== undefined) { printInfo(` - createdAtRound: ${app.createdAtRound}`); } if (app.params.globalState && app.params.globalState.length > 0) { printInfo(` - globalState: ${app.params.globalState.length} key-value pair(s)`); } printInfo(''); } }
printInfo(`Query performed at round: ${createdAppsResult.currentRound}`); } catch (error) { printError( `lookupAccountCreatedApplications failed: ${error instanceof Error ? error.message : String(error)}`, ); }
// ========================================================================= // Step 5: Lookup account app local states with lookupAccountAppLocalStates() // ========================================================================= printStep(5, 'Looking up app local states with lookupAccountAppLocalStates()');
try { // lookupAccountAppLocalStates() returns local state for applications the account has opted into const localStatesResult = await indexer.lookupAccountAppLocalStates(creatorAddress);
printSuccess( `Found ${localStatesResult.appsLocalStates.length} app local state(s) for account`, ); printInfo('');
if (localStatesResult.appsLocalStates.length > 0) { printInfo('App local states:'); for (const localState of localStatesResult.appsLocalStates) { printInfo(` Application ID: ${localState.id}`); printInfo( ` - schema: ${localState.schema.numUints} uints, ${localState.schema.numByteSlices} byte slices`, ); if (localState.optedInAtRound !== undefined) { printInfo(` - optedInAtRound: ${localState.optedInAtRound}`); } if (localState.keyValue && localState.keyValue.length > 0) { printInfo(` - keyValue pairs: ${localState.keyValue.length}`); for (const kv of localState.keyValue) { // 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(` - keyValue: (empty)`); } printInfo(''); } }
printInfo(`Query performed at round: ${localStatesResult.currentRound}`); } catch (error) { printError( `lookupAccountAppLocalStates failed: ${error instanceof Error ? error.message : String(error)}`, ); }
// ========================================================================= // Step 6: Demonstrate pagination with limit parameter // ========================================================================= printStep(6, 'Demonstrating pagination with limit parameter');
try { // First query: get only 1 created application printInfo('Querying created applications with limit=1...'); const page1 = await indexer.lookupAccountCreatedApplications(creatorAddress, { 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('');
// Second query: use the next token to get more results printInfo('Querying next page with next parameter...'); const page2 = await indexer.lookupAccountCreatedApplications(creatorAddress, { 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)}`); }
// ========================================================================= // Step 7: Query specific application with applicationId filter // ========================================================================= printStep(7, 'Querying specific application with applicationId filter');
try { // You can filter lookupAccountCreatedApplications by a specific applicationId printInfo(`Querying created application with ID ${appId1} only...`); const specificResult = await indexer.lookupAccountCreatedApplications(creatorAddress, { applicationId: appId1, });
if (specificResult.applications.length > 0) { const app = specificResult.applications[0]; printSuccess(`Found created application with ID ${appId1}`); 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`, ); } } else { printInfo(`No created application found with ID ${appId1}`); } } catch (error) { printError( `Specific application query failed: ${error instanceof Error ? error.message : String(error)}`, ); }
// ========================================================================= // Step 8: Query specific local state with applicationId filter // ========================================================================= printStep(8, 'Querying specific local state with applicationId filter');
try { // You can also filter lookupAccountAppLocalStates by a specific applicationId printInfo(`Querying local state for application ID ${appId1} only...`); const specificLocalState = await indexer.lookupAccountAppLocalStates(creatorAddress, { applicationId: appId1, });
if (specificLocalState.appsLocalStates.length > 0) { const localState = specificLocalState.appsLocalStates[0]; printSuccess(`Found local state for application ID ${appId1}`); printInfo( ` - schema: ${localState.schema.numUints} uints, ${localState.schema.numByteSlices} byte slices`, ); if (localState.keyValue && localState.keyValue.length > 0) { printInfo(` - keyValue pairs: ${localState.keyValue.length}`); } } else { printInfo(`No local state found for application ID ${appId1}`); } } catch (error) { printError( `Specific local state query failed: ${error instanceof Error ? error.message : String(error)}`, ); }
// ========================================================================= // Summary // ========================================================================= printHeader('Summary'); printInfo('This example demonstrated:'); printInfo(' 1. Deploying test applications using TEAL compilation and TransactionType.AppCall'); printInfo(' 2. Opting into an application to create local state'); printInfo( ' 3. lookupAccountCreatedApplications(address) - Get applications created by an account', ); printInfo( ' 4. lookupAccountAppLocalStates(address) - Get application local states for an account', ); printInfo(' 5. Pagination using limit and next parameters'); printInfo(' 6. Filtering by specific applicationId'); printInfo(''); printInfo('Key Application fields (from lookupAccountCreatedApplications):'); printInfo(' - id: The application identifier (bigint)'); printInfo(' - params.creator: Address that created the application'); printInfo(' - params.approvalProgram: TEAL bytecode for approval logic (Uint8Array)'); printInfo(' - params.clearStateProgram: TEAL bytecode for clear state logic (Uint8Array)'); printInfo(' - params.globalStateSchema: {numUints, numByteSlices} for global storage'); printInfo(' - params.localStateSchema: {numUints, numByteSlices} for per-user storage'); printInfo(' - params.globalState: Array of TealKeyValue for current global state'); printInfo(' - createdAtRound: Round when application was created (optional bigint)'); printInfo(''); printInfo('Key ApplicationLocalState fields (from lookupAccountAppLocalStates):'); printInfo(' - id: The application identifier (bigint)'); printInfo(' - schema: {numUints, numByteSlices} for allocated local storage'); printInfo(' - optedInAtRound: Round when account opted in (optional bigint)'); printInfo(' - keyValue: Array of TealKeyValue for current local state'); printInfo(''); printInfo('TealKeyValue structure:'); printInfo(' - key: The key as Uint8Array (decode with TextDecoder)'); printInfo(' - value.type: 1 for bytes, 2 for uint'); printInfo(' - value.bytes: Byte value as Uint8Array'); printInfo(' - value.uint: Integer value as bigint'); printInfo(''); printInfo('Pagination parameters:'); printInfo(' - limit: Maximum number of results per page'); printInfo(' - next: Token from previous response to get next page');}
main().catch(error => { console.error('Fatal error:', error); process.exit(1);});