Asset Transactions
Description
Section titled “Description”This example demonstrates how to lookup transactions for a specific asset using the IndexerClient lookupAssetTransactions() method.
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/10-asset-transactions.ts/** * Example: Asset Transactions * * This example demonstrates how to lookup transactions for a specific asset using * the IndexerClient lookupAssetTransactions() method. * * Prerequisites: * - LocalNet running (via `algokit localnet start`) */
import { createAlgorandClient, createIndexerClient, createRandomAccount, printError, printHeader, printInfo, printStep, printSuccess, shortenAddress,} from '../shared/utils.js';
async function main() { printHeader('Asset Transactions Example');
// Create clients const indexer = createIndexerClient(); const algorand = createAlgorandClient();
// ========================================================================= // Step 1: Get a funded account and create additional accounts // ========================================================================= printStep(1, 'Setting up accounts for demonstration');
let creatorAddress: string; let holder1Address: string; let holder2Address: string;
try { // Get the dispenser account as the creator const dispenser = await algorand.account.dispenserFromEnvironment(); creatorAddress = dispenser.addr.toString(); printSuccess(`Creator account (dispenser): ${shortenAddress(creatorAddress)}`);
// Create additional accounts to hold the asset const holder1 = await createRandomAccount(algorand); holder1Address = holder1.addr.toString(); printSuccess(`Holder 1: ${shortenAddress(holder1Address)}`);
const holder2 = await createRandomAccount(algorand); holder2Address = holder2.addr.toString(); printSuccess(`Holder 2: ${shortenAddress(holder2Address)}`); } catch (error) { printError( `Failed to set up accounts: ${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: Create a test asset with freeze address // ========================================================================= printStep(2, 'Creating a test asset with freeze address');
let assetId: bigint; let startRound: bigint;
try { // Record the starting round for later filtering const status = await algorand.client.algod.status(); startRound = status.lastRound;
// Create asset with freeze address to enable freeze transactions printInfo('Creating test asset: TxnToken (TXN)...'); const result = await algorand.send.assetCreate({ sender: creatorAddress, total: 10_000_000n, // 10,000 units with 3 decimals decimals: 3, assetName: 'TxnToken', unitName: 'TXN', url: 'https://example.com/txntoken', defaultFrozen: false, manager: creatorAddress, reserve: creatorAddress, freeze: creatorAddress, // Enable freeze functionality clawback: creatorAddress, }); assetId = result.assetId; printSuccess(`Created TxnToken with Asset ID: ${assetId}`); printInfo( ` - freeze address: ${shortenAddress(creatorAddress)} (enables freeze transactions)`, ); } catch (error) { printError( `Failed to create test asset: ${error instanceof Error ? error.message : String(error)}`, ); printInfo(''); printInfo('If LocalNet errors occur, try: algokit localnet reset'); return; }
// ========================================================================= // Step 3: Perform several asset transactions (opt-in, transfer, freeze) // ========================================================================= printStep(3, 'Performing several asset transactions');
try { // 1. Holder 1: Opt-in (axfer to self with 0 amount) printInfo('Holder 1 opting into asset...'); await algorand.send.assetOptIn({ sender: holder1Address, assetId, }); printSuccess('Holder 1 opted in (axfer)');
// 2. Transfer to Holder 1 printInfo('Transferring 1000 TXN to Holder 1...'); await algorand.send.assetTransfer({ sender: creatorAddress, receiver: holder1Address, assetId, amount: 1_000_000n, // 1000 TXN (with 3 decimals) }); printSuccess('Transfer to Holder 1 complete (axfer)');
// 3. Holder 2: Opt-in printInfo('Holder 2 opting into asset...'); await algorand.send.assetOptIn({ sender: holder2Address, assetId, }); printSuccess('Holder 2 opted in (axfer)');
// 4. Transfer to Holder 2 printInfo('Transferring 500 TXN to Holder 2...'); await algorand.send.assetTransfer({ sender: creatorAddress, receiver: holder2Address, assetId, amount: 500_000n, // 500 TXN (with 3 decimals) }); printSuccess('Transfer to Holder 2 complete (axfer)');
// 5. Freeze Holder 1's account printInfo('Freezing Holder 1 account...'); await algorand.send.assetFreeze({ sender: creatorAddress, assetId, freezeTarget: holder1Address, frozen: true, }); printSuccess('Holder 1 account frozen (afrz)');
// 6. Unfreeze Holder 1's account printInfo('Unfreezing Holder 1 account...'); await algorand.send.assetFreeze({ sender: creatorAddress, assetId, freezeTarget: holder1Address, frozen: false, }); printSuccess('Holder 1 account unfrozen (afrz)');
// 7. Reconfigure asset (acfg) printInfo('Reconfiguring asset (updating manager)...'); await algorand.send.assetConfig({ sender: creatorAddress, assetId, manager: creatorAddress, reserve: creatorAddress, freeze: creatorAddress, clawback: creatorAddress, }); printSuccess('Asset reconfigured (acfg)');
printInfo(''); printInfo('Transaction summary:'); printInfo(' - 1 asset creation (acfg)'); printInfo(' - 2 opt-ins (axfer with 0 amount)'); printInfo(' - 2 transfers (axfer with positive amount)'); printInfo(' - 2 freeze operations (afrz)'); printInfo(' - 1 asset reconfiguration (acfg)');
// Small delay to allow indexer to catch up printInfo(''); printInfo('Waiting for indexer to index transactions...'); await new Promise(resolve => setTimeout(resolve, 3000)); } catch (error) { printError( `Failed to create asset transactions: ${error instanceof Error ? error.message : String(error)}`, ); printInfo(''); printInfo('If LocalNet errors occur, try: algokit localnet reset'); return; }
// ========================================================================= // Step 4: Basic lookupAssetTransactions() - Get all transactions for asset // ========================================================================= printStep(4, 'Looking up all transactions for asset with lookupAssetTransactions()');
try { // lookupAssetTransactions() returns all transactions involving an asset // Note: Results are returned oldest to newest const txnsResult = await indexer.lookupAssetTransactions(assetId);
printSuccess(`Found ${txnsResult.transactions.length} transaction(s) for Asset ID ${assetId}`); printInfo('Note: Results are returned oldest to newest'); printInfo('');
if (txnsResult.transactions.length > 0) { printInfo('Asset transactions:'); for (const tx of txnsResult.transactions) { printInfo(` Transaction: ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}`); printInfo(` - txType: ${tx.txType}`); printInfo(` - sender: ${shortenAddress(tx.sender)}`); if (tx.confirmedRound !== undefined) { printInfo(` - confirmedRound: ${tx.confirmedRound}`); }
// Show type-specific details if (tx.txType === 'axfer' && tx.assetTransferTransaction) { printInfo(` - receiver: ${shortenAddress(tx.assetTransferTransaction.receiver)}`); printInfo(` - amount: ${tx.assetTransferTransaction.amount.toLocaleString('en-US')}`); } else if (tx.txType === 'afrz' && tx.assetFreezeTransaction) { printInfo(` - frozenAddress: ${shortenAddress(tx.assetFreezeTransaction.address)}`); printInfo(` - newFreezeStatus: ${tx.assetFreezeTransaction.newFreezeStatus}`); } else if (tx.txType === 'acfg') { if (tx.createdAssetId) { printInfo(` - createdAssetId: ${tx.createdAssetId} (asset creation)`); } else { printInfo(` - assetId: ${tx.assetConfigTransaction?.assetId} (reconfiguration)`); } } printInfo(''); } }
printInfo(`Query performed at round: ${txnsResult.currentRound}`); } catch (error) { printError( `lookupAssetTransactions failed: ${error instanceof Error ? error.message : String(error)}`, ); }
// ========================================================================= // Step 5: Filter by address and addressRole - Sender // ========================================================================= printStep(5, 'Filtering by address with addressRole=sender');
try { // addressRole can be: sender, receiver, freeze-target printInfo('Available addressRole values: sender, receiver, freeze-target'); printInfo('');
printInfo(`Searching for transactions where ${shortenAddress(creatorAddress)} is sender...`); const senderTxns = await indexer.lookupAssetTransactions(assetId, { address: creatorAddress, addressRole: 'sender', });
printSuccess(`Found ${senderTxns.transactions.length} transaction(s) where creator is sender`); if (senderTxns.transactions.length > 0) { for (const tx of senderTxns.transactions.slice(0, 5)) { printInfo(` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: ${tx.txType}`); } } } catch (error) { printError( `addressRole=sender filter failed: ${error instanceof Error ? error.message : String(error)}`, ); }
// ========================================================================= // Step 6: Filter by address and addressRole - Receiver // ========================================================================= printStep(6, 'Filtering by address with addressRole=receiver');
try { printInfo(`Searching for transactions where ${shortenAddress(holder1Address)} is receiver...`); const receiverTxns = await indexer.lookupAssetTransactions(assetId, { address: holder1Address, addressRole: 'receiver', });
printSuccess( `Found ${receiverTxns.transactions.length} transaction(s) where Holder 1 is receiver`, ); if (receiverTxns.transactions.length > 0) { for (const tx of receiverTxns.transactions) { if (tx.assetTransferTransaction) { printInfo( ` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: ${tx.txType}, amount: ${tx.assetTransferTransaction.amount.toLocaleString('en-US')}`, ); } else { printInfo(` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: ${tx.txType}`); } } } } catch (error) { printError( `addressRole=receiver filter failed: ${error instanceof Error ? error.message : String(error)}`, ); }
// ========================================================================= // Step 7: Filter by address and addressRole - Freeze-target // ========================================================================= printStep(7, 'Filtering by address with addressRole=freeze-target');
try { // freeze-target filters for accounts that were the target of freeze operations printInfo(`Searching for freeze transactions targeting ${shortenAddress(holder1Address)}...`); const freezeTargetTxns = await indexer.lookupAssetTransactions(assetId, { address: holder1Address, addressRole: 'freeze-target', });
printSuccess( `Found ${freezeTargetTxns.transactions.length} freeze transaction(s) targeting Holder 1`, ); if (freezeTargetTxns.transactions.length > 0) { for (const tx of freezeTargetTxns.transactions) { if (tx.assetFreezeTransaction) { printInfo( ` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: ${tx.txType}, newFreezeStatus: ${tx.assetFreezeTransaction.newFreezeStatus}`, ); } } } printInfo(''); printInfo('Note: freeze-target is specifically for afrz transactions targeting an account'); } catch (error) { printError( `addressRole=freeze-target filter failed: ${error instanceof Error ? error.message : String(error)}`, ); }
// ========================================================================= // Step 8: Filter by txType - Asset Transfer (axfer) // ========================================================================= printStep(8, 'Filtering by txType for specific asset operations');
try { // txType values relevant to assets: acfg (config), axfer (transfer), afrz (freeze) printInfo('Asset-related txType values: acfg (config), axfer (transfer), afrz (freeze)'); printInfo('');
// Search for asset transfers only printInfo('Searching for asset transfer transactions (txType=axfer)...'); const axferTxns = await indexer.lookupAssetTransactions(assetId, { txType: 'axfer', }); printSuccess(`Found ${axferTxns.transactions.length} asset transfer transaction(s)`); if (axferTxns.transactions.length > 0) { for (const tx of axferTxns.transactions.slice(0, 4)) { const amount = tx.assetTransferTransaction?.amount ?? 0n; const amountStr = amount === 0n ? '0 (opt-in)' : amount.toLocaleString('en-US'); printInfo(` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: amount=${amountStr}`); } } printInfo('');
// Search for asset freeze transactions printInfo('Searching for asset freeze transactions (txType=afrz)...'); const afrzTxns = await indexer.lookupAssetTransactions(assetId, { txType: 'afrz', }); printSuccess(`Found ${afrzTxns.transactions.length} asset freeze transaction(s)`); if (afrzTxns.transactions.length > 0) { for (const tx of afrzTxns.transactions) { const frozen = tx.assetFreezeTransaction?.newFreezeStatus ?? false; printInfo(` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: frozen=${frozen}`); } } printInfo('');
// Search for asset config transactions printInfo('Searching for asset config transactions (txType=acfg)...'); const acfgTxns = await indexer.lookupAssetTransactions(assetId, { txType: 'acfg', }); printSuccess(`Found ${acfgTxns.transactions.length} asset config transaction(s)`); if (acfgTxns.transactions.length > 0) { for (const tx of acfgTxns.transactions) { if (tx.createdAssetId) { printInfo(` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: asset creation`); } else { printInfo(` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: asset reconfiguration`); } } } } catch (error) { printError(`txType filter failed: ${error instanceof Error ? error.message : String(error)}`); }
// ========================================================================= // Step 9: Filter by round range (minRound, maxRound) // ========================================================================= printStep(9, 'Filtering by round range (minRound, maxRound)');
try { // Get current round const latestTxns = await indexer.lookupAssetTransactions(assetId, { limit: 1 }); const currentRound = latestTxns.currentRound;
printInfo(`Transactions created starting from round: ${startRound}`); printInfo(`Current round: ${currentRound}`); printInfo('');
// Filter by round range printInfo(`Searching for transactions from round ${startRound} to ${currentRound}...`); const roundFilteredTxns = await indexer.lookupAssetTransactions(assetId, { minRound: startRound, maxRound: currentRound, });
printSuccess(`Found ${roundFilteredTxns.transactions.length} transaction(s) in round range`); if (roundFilteredTxns.transactions.length > 0) { const rounds = roundFilteredTxns.transactions .map(tx => tx.confirmedRound) .filter(r => r !== undefined); if (rounds.length > 0) { const minFoundRound = rounds.reduce((a, b) => (a! < b! ? a : b)); const maxFoundRound = rounds.reduce((a, b) => (a! > b! ? a : b)); printInfo(` Rounds of found transactions: ${minFoundRound} to ${maxFoundRound}`); } } } catch (error) { printError(`round filter failed: ${error instanceof Error ? error.message : String(error)}`); }
// ========================================================================= // Step 10: Filter by time range (beforeTime, afterTime) // ========================================================================= printStep(10, 'Filtering by time range (beforeTime, afterTime)');
try { // Time filters use RFC 3339 format (ISO 8601, e.g., "2026-01-26T10:00:00.000Z") const now = new Date(); const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
const afterTimeStr = oneHourAgo.toISOString(); const beforeTimeStr = now.toISOString();
printInfo('Time filters use RFC 3339 format (ISO 8601)'); printInfo(` afterTime: ${afterTimeStr}`); printInfo(` beforeTime: ${beforeTimeStr}`); printInfo('');
printInfo('Searching for transactions in the last hour...'); const timeFilteredTxns = await indexer.lookupAssetTransactions(assetId, { afterTime: afterTimeStr, beforeTime: beforeTimeStr, });
printSuccess(`Found ${timeFilteredTxns.transactions.length} transaction(s) in time range`); if (timeFilteredTxns.transactions.length > 0) { const times = timeFilteredTxns.transactions .map(tx => tx.roundTime) .filter(t => t !== undefined); if (times.length > 0) { const minTime = Math.min(...(times as number[])); const maxTime = Math.max(...(times as number[])); printInfo(` Time range of found transactions:`); printInfo(` - Earliest: ${new Date(minTime * 1000).toISOString()}`); printInfo(` - Latest: ${new Date(maxTime * 1000).toISOString()}`); } } } catch (error) { printError(`time filter failed: ${error instanceof Error ? error.message : String(error)}`); }
// ========================================================================= // Step 11: Filter by currency amount // ========================================================================= printStep(11, 'Filtering by currency amount (currencyGreaterThan, currencyLessThan)');
try { // currencyGreaterThan/currencyLessThan filter by transaction amount printInfo('Searching for transfers with amount > 0 (excludes opt-ins)...'); const nonZeroTxns = await indexer.lookupAssetTransactions(assetId, { txType: 'axfer', currencyGreaterThan: 0n, });
printSuccess(`Found ${nonZeroTxns.transactions.length} transfer(s) with amount > 0`); if (nonZeroTxns.transactions.length > 0) { for (const tx of nonZeroTxns.transactions) { const amount = tx.assetTransferTransaction?.amount ?? 0n; printInfo( ` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: amount=${amount.toLocaleString('en-US')}`, ); } } printInfo('');
// Filter for large transfers only printInfo('Searching for transfers with amount > 500,000 (> 500 TXN)...'); const largeTxns = await indexer.lookupAssetTransactions(assetId, { txType: 'axfer', currencyGreaterThan: 500_000n, });
printSuccess(`Found ${largeTxns.transactions.length} large transfer(s)`); if (largeTxns.transactions.length > 0) { for (const tx of largeTxns.transactions) { const amount = tx.assetTransferTransaction?.amount ?? 0n; printInfo( ` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: amount=${amount.toLocaleString('en-US')}`, ); } } } catch (error) { printError(`currency filter failed: ${error instanceof Error ? error.message : String(error)}`); }
// ========================================================================= // Step 12: Combining multiple filters // ========================================================================= printStep(12, 'Combining multiple filters');
try { printInfo('You can combine multiple filters to narrow down results.'); printInfo('');
// Combine txType and address printInfo(`Searching for asset transfers TO ${shortenAddress(holder1Address)}...`); const combinedTxns1 = await indexer.lookupAssetTransactions(assetId, { txType: 'axfer', address: holder1Address, addressRole: 'receiver', }); printSuccess(`Found ${combinedTxns1.transactions.length} transfer(s) to Holder 1`); for (const tx of combinedTxns1.transactions) { const amount = tx.assetTransferTransaction?.amount ?? 0n; printInfo( ` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: amount=${amount.toLocaleString('en-US')}`, ); } printInfo('');
// Combine round range and txType printInfo('Searching for freeze transactions in recent rounds...'); const latestResult = await indexer.lookupAssetTransactions(assetId, { limit: 1 }); const combinedTxns2 = await indexer.lookupAssetTransactions(assetId, { txType: 'afrz', minRound: startRound, maxRound: latestResult.currentRound, }); printSuccess( `Found ${combinedTxns2.transactions.length} freeze transaction(s) in recent rounds`, ); for (const tx of combinedTxns2.transactions) { const frozen = tx.assetFreezeTransaction?.newFreezeStatus ?? false; printInfo( ` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: frozen=${frozen}, round=${tx.confirmedRound}`, ); } } catch (error) { printError( `combined filters failed: ${error instanceof Error ? error.message : String(error)}`, ); }
// ========================================================================= // Step 13: Pagination with limit and next // ========================================================================= printStep(13, 'Demonstrating pagination with limit and next');
try { printInfo('Using limit=3 to demonstrate pagination...'); const page1 = await indexer.lookupAssetTransactions(assetId, { limit: 3 });
printInfo(`Page 1: Retrieved ${page1.transactions.length} transaction(s)`); for (const tx of page1.transactions) { printInfo(` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: ${tx.txType}`); }
if (page1.nextToken) { printInfo(` Next token available: ${page1.nextToken.substring(0, 20)}...`); printInfo('');
printInfo('Fetching next page...'); const page2 = await indexer.lookupAssetTransactions(assetId, { limit: 3, next: page1.nextToken, });
printInfo(`Page 2: Retrieved ${page2.transactions.length} transaction(s)`); for (const tx of page2.transactions) { printInfo(` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: ${tx.txType}`); }
if (page2.nextToken) { printInfo(` More pages available (nextToken present)`); } else { printInfo(` No more pages (no nextToken)`); } } else { printInfo(' No pagination needed (all results fit in one page)'); } } catch (error) { printError(`pagination failed: ${error instanceof Error ? error.message : String(error)}`); }
// ========================================================================= // Summary // ========================================================================= printHeader('Summary'); printInfo('This example demonstrated lookupAssetTransactions() with various filters:'); printInfo(''); printInfo('Key characteristics:'); printInfo(' - Results are returned oldest to newest'); printInfo(' - Returns all transaction types involving the asset (acfg, axfer, afrz)'); printInfo(''); printInfo('Transaction types for assets:'); printInfo(' - acfg: Asset configuration (create, reconfigure, destroy)'); printInfo(' - axfer: Asset transfer (opt-in with 0 amount, transfers, close-out)'); printInfo(' - afrz: Asset freeze (freeze/unfreeze account holdings)'); printInfo(''); printInfo('Address filtering with addressRole:'); printInfo(' - sender: Transactions where address is the sender'); printInfo(' - receiver: Transactions where address is the receiver'); printInfo(' - freeze-target: Freeze transactions targeting the address'); printInfo(''); printInfo('Other filter parameters:'); printInfo(' - txType: Filter by transaction type (acfg, axfer, afrz)'); printInfo(' - minRound/maxRound: Filter by round range'); printInfo(' - beforeTime/afterTime: Filter by time (RFC 3339 format)'); printInfo(' - currencyGreaterThan/currencyLessThan: Filter by amount'); printInfo(' - sigType: Filter by signature type (sig, msig, lsig)'); printInfo(' - notePrefix: Filter by note prefix'); printInfo(' - txId: Find specific transaction by ID'); printInfo(' - excludeCloseTo: Exclude close-to transactions'); printInfo(' - rekeyTo: Filter for rekey transactions'); printInfo(''); printInfo('Pagination:'); 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);});