Skip to content
Algorand Developer Portal

Asset Transactions

← Back to Indexer Client

This example demonstrates how to lookup transactions for a specific asset using the IndexerClient lookupAssetTransactions() method.

  • LocalNet running (via algokit localnet start)

From the repository root:

Terminal window
cd examples
npm run example indexer_client/10-asset-transactions.ts

View source on GitHub

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);
});