Account Transactions
Description
Section titled “Description”This example demonstrates how to query an account’s transaction history using the IndexerClient lookupAccountTransactions() 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/05-account-transactions.ts/** * Example: Account Transactions * * This example demonstrates how to query an account's transaction history using * the IndexerClient lookupAccountTransactions() method. * * Prerequisites: * - LocalNet running (via `algokit localnet start`) */
import { algo } from '@algorandfoundation/algokit-utils'import { createAlgorandClient, createIndexerClient, formatMicroAlgo, printError, printHeader, printInfo, printStep, printSuccess, shortenAddress,} from '../shared/utils.js'
async function main() { printHeader('Account Transactions Example')
// Create clients const indexer = createIndexerClient() const algorand = createAlgorandClient()
// ========================================================================= // Step 1: Get a funded account from LocalNet // ========================================================================= printStep(1, 'Getting a funded account from LocalNet')
let senderAddress: string let senderAccount: Awaited<ReturnType<typeof algorand.account.kmd.getLocalNetDispenserAccount>>
try { senderAccount = await algorand.account.kmd.getLocalNetDispenserAccount() senderAddress = senderAccount.addr.toString() algorand.setSignerFromAccount(senderAccount) printSuccess(`Using dispenser account: ${shortenAddress(senderAddress)}`) } 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: Create test transactions for demonstration // ========================================================================= printStep(2, 'Creating test transactions for demonstration')
let receiverAddress: string let assetId: bigint
try { // Create a random receiver account const receiverAccount = algorand.account.random() receiverAddress = receiverAccount.addr.toString() algorand.setSignerFromAccount(receiverAccount) printInfo(`Created receiver account: ${shortenAddress(receiverAddress)}`)
// Fund the receiver with some initial ALGO printInfo('Sending initial payment to receiver...') await algorand.send.payment({ sender: senderAccount.addr, receiver: receiverAccount.addr, amount: algo(10), }) printSuccess('Initial payment sent: 10 ALGO')
// Send a few more payments with different amounts printInfo('Sending additional payments...') await algorand.send.payment({ sender: senderAccount.addr, receiver: receiverAccount.addr, amount: algo(5), }) printSuccess('Payment sent: 5 ALGO')
await algorand.send.payment({ sender: senderAccount.addr, receiver: receiverAccount.addr, amount: algo(0.5), }) printSuccess('Payment sent: 0.5 ALGO')
// Create an asset printInfo('Creating a test asset...') const assetCreateResult = await algorand.send.assetCreate({ sender: senderAccount.addr, total: 1_000_000n, decimals: 6, assetName: 'TestToken', unitName: 'TEST', }) assetId = assetCreateResult.assetId printSuccess(`Created asset: TestToken (ID: ${assetId})`)
// Opt-in receiver to the asset printInfo('Opting receiver into asset...') await algorand.send.assetOptIn({ sender: receiverAccount.addr, assetId: assetId, }) printSuccess('Receiver opted into asset')
// Transfer some assets printInfo('Sending asset transfer...') await algorand.send.assetTransfer({ sender: senderAccount.addr, receiver: receiverAccount.addr, assetId: assetId, amount: 100_000n, }) printSuccess('Asset transfer sent: 100,000 TestToken (0.1 TEST)')
// Small delay to allow indexer to catch up printInfo('Waiting for indexer to index transactions...') await new Promise((resolve) => setTimeout(resolve, 3000)) printInfo('') } catch (error) { printError(`Failed to create test transactions: ${error instanceof Error ? error.message : String(error)}`) printInfo('') printInfo('If LocalNet errors occur, try: algokit localnet reset') return }
// ========================================================================= // Step 3: Lookup account transactions with lookupAccountTransactions() // ========================================================================= printStep(3, 'Looking up account transactions with lookupAccountTransactions()')
try { // Note: Results are returned newest to oldest for account transactions const txnsResult = await indexer.lookupAccountTransactions(senderAddress)
printSuccess(`Found ${txnsResult.transactions.length} transaction(s) for account`) printInfo('Note: Results are returned newest to oldest') printInfo('')
if (txnsResult.transactions.length > 0) { printInfo('Recent transactions:') for (const tx of txnsResult.transactions.slice(0, 5)) { printInfo(` Transaction ID: ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}`) printInfo(` - txType: ${tx.txType}`) printInfo(` - sender: ${shortenAddress(tx.sender)}`) printInfo(` - fee: ${formatMicroAlgo(tx.fee)}`) if (tx.confirmedRound !== undefined) { printInfo(` - confirmedRound: ${tx.confirmedRound}`) } if (tx.roundTime !== undefined) { const date = new Date(tx.roundTime * 1000) printInfo(` - roundTime: ${date.toISOString()}`) } // Show payment details if present if (tx.paymentTransaction) { printInfo(` - receiver: ${shortenAddress(tx.paymentTransaction.receiver)}`) printInfo(` - amount: ${formatMicroAlgo(tx.paymentTransaction.amount)}`) } // Show asset transfer details if present if (tx.assetTransferTransaction) { printInfo(` - assetId: ${tx.assetTransferTransaction.assetId}`) printInfo(` - amount: ${tx.assetTransferTransaction.amount}`) printInfo(` - receiver: ${shortenAddress(tx.assetTransferTransaction.receiver)}`) } // Show asset config details if present if (tx.assetConfigTransaction) { printInfo(` - assetId: ${tx.assetConfigTransaction.assetId ?? 'new asset'}`) if (tx.createdAssetId) { printInfo(` - createdAssetId: ${tx.createdAssetId}`) } } printInfo('') } }
printInfo(`Query performed at round: ${txnsResult.currentRound}`) } catch (error) { printError(`lookupAccountTransactions failed: ${error instanceof Error ? error.message : String(error)}`) }
// ========================================================================= // Step 4: Filter by transaction type (pay, axfer, appl) // ========================================================================= printStep(4, 'Filtering by transaction type (txType)')
try { // Filter for payment transactions only printInfo('Querying payment transactions (txType=pay)...') const payTxns = await indexer.lookupAccountTransactions(senderAddress, { txType: 'pay' }) printSuccess(`Found ${payTxns.transactions.length} payment transaction(s)`)
if (payTxns.transactions.length > 0) { for (const tx of payTxns.transactions.slice(0, 3)) { printInfo(` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: ${formatMicroAlgo(tx.paymentTransaction?.amount ?? 0n)}`) } } printInfo('')
// Filter for asset transfer transactions printInfo('Querying asset transfer transactions (txType=axfer)...') const axferTxns = await indexer.lookupAccountTransactions(senderAddress, { txType: 'axfer' }) printSuccess(`Found ${axferTxns.transactions.length} asset transfer transaction(s)`)
if (axferTxns.transactions.length > 0) { for (const tx of axferTxns.transactions.slice(0, 3)) { printInfo(` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: assetId=${tx.assetTransferTransaction?.assetId}, amount=${tx.assetTransferTransaction?.amount}`) } } printInfo('')
// Filter for asset config transactions printInfo('Querying asset config transactions (txType=acfg)...') const acfgTxns = await indexer.lookupAccountTransactions(senderAddress, { txType: 'acfg' }) printSuccess(`Found ${acfgTxns.transactions.length} asset config transaction(s)`)
if (acfgTxns.transactions.length > 0) { for (const tx of acfgTxns.transactions.slice(0, 3)) { const assetIdDisplay = tx.createdAssetId ?? tx.assetConfigTransaction?.assetId ?? 'N/A' printInfo(` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: assetId=${assetIdDisplay}`) } } } catch (error) { printError(`Transaction type filter failed: ${error instanceof Error ? error.message : String(error)}`) }
// ========================================================================= // Step 5: Filter by round range (minRound, maxRound) // ========================================================================= printStep(5, 'Filtering by round range (minRound, maxRound)')
try { // Get current round const allTxns = await indexer.lookupAccountTransactions(senderAddress, { limit: 1 }) const currentRound = allTxns.currentRound
// Filter to recent rounds only (last 100 rounds, but not negative) const minRound = currentRound > 100n ? currentRound - 100n : 0n printInfo(`Querying transactions from round ${minRound} to ${currentRound}...`)
const roundFilteredTxns = await indexer.lookupAccountTransactions(senderAddress, { minRound: minRound, maxRound: currentRound, })
printSuccess(`Found ${roundFilteredTxns.transactions.length} transaction(s) in round range ${minRound}-${currentRound}`)
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 6: Filter by time (beforeTime, afterTime) // ========================================================================= printStep(6, 'Filtering by time (beforeTime, afterTime)')
try { // Get current time and a time window const now = new Date() const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000) // 1 hour ago
// Format as RFC 3339 (ISO 8601 format that indexer expects) const afterTimeStr = oneHourAgo.toISOString() const beforeTimeStr = now.toISOString()
printInfo(`Querying transactions from ${afterTimeStr} to ${beforeTimeStr}...`)
const timeFilteredTxns = await indexer.lookupAccountTransactions(senderAddress, { afterTime: afterTimeStr, beforeTime: beforeTimeStr, })
printSuccess(`Found ${timeFilteredTxns.transactions.length} transaction(s) in the last hour`)
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 7: Filter by amount (currencyGreaterThan, currencyLessThan) // ========================================================================= printStep(7, 'Filtering by amount (currencyGreaterThan, currencyLessThan)')
try { // Filter for transactions with amount greater than 1 ALGO (1,000,000 microAlgo) const minAmount = 1_000_000n printInfo(`Querying transactions with amount > ${formatMicroAlgo(minAmount)}...`)
const largeAmountTxns = await indexer.lookupAccountTransactions(senderAddress, { currencyGreaterThan: minAmount, })
printSuccess(`Found ${largeAmountTxns.transactions.length} transaction(s) with amount > 1 ALGO`)
if (largeAmountTxns.transactions.length > 0) { for (const tx of largeAmountTxns.transactions.slice(0, 3)) { const amount = tx.paymentTransaction?.amount ?? tx.assetTransferTransaction?.amount ?? 0n printInfo(` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: ${tx.txType}, amount=${amount}`) } } printInfo('')
// Filter for transactions with amount less than 5 ALGO (5,000,000 microAlgo) const maxAmount = 5_000_000n printInfo(`Querying transactions with amount < ${formatMicroAlgo(maxAmount)}...`)
const smallAmountTxns = await indexer.lookupAccountTransactions(senderAddress, { currencyLessThan: maxAmount, })
printSuccess(`Found ${smallAmountTxns.transactions.length} transaction(s) with amount < 5 ALGO`)
if (smallAmountTxns.transactions.length > 0) { for (const tx of smallAmountTxns.transactions.slice(0, 3)) { const amount = tx.paymentTransaction?.amount ?? tx.assetTransferTransaction?.amount ?? 0n printInfo(` - ${tx.id ? shortenAddress(tx.id, 8, 6) : 'N/A'}: ${tx.txType}, amount=${amount}`) } } printInfo('')
// Combine both filters to find transactions in a specific range printInfo(`Querying transactions with amount between ${formatMicroAlgo(minAmount)} and ${formatMicroAlgo(maxAmount)}...`)
const rangeAmountTxns = await indexer.lookupAccountTransactions(senderAddress, { currencyGreaterThan: minAmount, currencyLessThan: maxAmount, })
printSuccess(`Found ${rangeAmountTxns.transactions.length} transaction(s) with amount in range 1-5 ALGO`) } catch (error) { printError(`Amount filter failed: ${error instanceof Error ? error.message : String(error)}`) }
// ========================================================================= // Step 8: Pagination with limit and next // ========================================================================= printStep(8, 'Demonstrating pagination with limit and next')
try { printInfo('Querying transactions with limit=2...') const page1 = await indexer.lookupAccountTransactions(senderAddress, { limit: 2 })
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('')
// Get next page printInfo('Querying next page...') const page2 = await indexer.lookupAccountTransactions(senderAddress, { limit: 2, 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 demo failed: ${error instanceof Error ? error.message : String(error)}`) }
// ========================================================================= // Summary // ========================================================================= printHeader('Summary') printInfo('This example demonstrated:') printInfo(' 1. lookupAccountTransactions(address) - Get transaction history for an account') printInfo(' 2. Results are returned newest to oldest') printInfo(' 3. Filtering by txType (pay, axfer, acfg, appl, etc.)') printInfo(' 4. Filtering by round range (minRound, maxRound)') printInfo(' 5. Filtering by time (beforeTime, afterTime) using RFC 3339 format') printInfo(' 6. Filtering by amount (currencyGreaterThan, currencyLessThan)') printInfo(' 7. Pagination using limit and next parameters') printInfo('') printInfo('Key Transaction fields:') printInfo(' - id: Transaction ID (string)') printInfo(' - txType: Transaction type (pay, keyreg, acfg, axfer, afrz, appl, stpf, hb)') printInfo(' - sender: Sender address (string)') printInfo(' - fee: Transaction fee in microAlgos (bigint)') printInfo(' - confirmedRound: Round when confirmed (bigint)') printInfo(' - roundTime: Unix timestamp when confirmed (number)') printInfo(' - paymentTransaction: Payment details (receiver, amount, closeRemainderTo)') printInfo(' - assetTransferTransaction: Asset transfer details (assetId, amount, receiver)') printInfo(' - assetConfigTransaction: Asset config details (assetId, params)') printInfo(' - applicationTransaction: App call details (applicationId, onComplete, etc.)') printInfo('') printInfo('Filter parameters:') printInfo(' - txType: Filter by transaction type') printInfo(' - minRound/maxRound: Filter by round range') printInfo(' - beforeTime/afterTime: Filter by time (RFC 3339 format)') printInfo(' - currencyGreaterThan/currencyLessThan: Filter by amount') printInfo(' - assetId: Filter by specific asset') printInfo(' - limit/next: Pagination controls')}
main().catch((error) => { console.error('Fatal error:', error) process.exit(1)})