Payment Filters
Description
Section titled “Description”This example demonstrates payment transaction filters.
- Filter by sender, receiver, amount range, and note prefix
- Verify each filter matches the expected transactions
Prerequisites
Section titled “Prerequisites”- LocalNet running (via
algokit localnet start)
Run This Example
Section titled “Run This Example”From the repository’s examples/subscriber directory:
cd examples/subscribernpx tsx 03-payment-filters.ts/** * Example: Payment Filters * * This example demonstrates payment transaction filters. * - Filter by sender, receiver, amount range, and note prefix * - Verify each filter matches the expected transactions * * Prerequisites: * - LocalNet running (via `algokit localnet start`) */import { algo, AlgorandClient, microAlgo } from '@algorandfoundation/algokit-utils';import { printHeader, printStep, printInfo, printSuccess, printError, shortenAddress, formatMicroAlgo, createFilterTester, type SubscribedTransaction,} from './shared/utils.js';
async function main() { printHeader('03 — Payment Filters');
// Step 1: Connect to LocalNet printStep(1, 'Connect to LocalNet'); const algorand = AlgorandClient.defaultLocalNet(); const status = await algorand.client.algod.status(); printInfo(`Current round: ${status.lastRound.toString()}`); printSuccess('Connected to LocalNet');
// Step 2: Create 3 accounts (sender, receiver, thirdParty) printStep(2, 'Create and fund 3 accounts (sender, receiver, thirdParty)'); const sender = await algorand.account.fromEnvironment('FILTER_SENDER', algo(100)); const receiver = await algorand.account.fromEnvironment('FILTER_RECEIVER', algo(100)); const thirdParty = await algorand.account.fromEnvironment('FILTER_THIRD_PARTY', algo(100)); const senderAddr = sender.addr.toString(); const receiverAddr = receiver.addr.toString(); const thirdPartyAddr = thirdParty.addr.toString(); printInfo(`Sender: ${shortenAddress(senderAddr)}`); printInfo(`Receiver: ${shortenAddress(receiverAddr)}`); printInfo(`Third party: ${shortenAddress(thirdPartyAddr)}`); printSuccess('3 accounts created and funded');
// Step 3: Send 5 payments with varying senders, receivers, amounts, and notes printStep(3, 'Send 5 payments with varying parameters');
const payments = [ { sender: sender.addr, receiver: receiver.addr, amount: algo(1), note: 'invoice-001' }, { sender: sender.addr, receiver: thirdParty.addr, amount: algo(5), note: 'invoice-002' }, { sender: receiver.addr, receiver: sender.addr, amount: algo(2), note: 'receipt-001' }, { sender: thirdParty.addr, receiver: receiver.addr, amount: algo(3), note: 'invoice-003' }, { sender: sender.addr, receiver: receiver.addr, amount: microAlgo(500_000), note: 'receipt-002', }, ];
const txnResults = []; for (const [i, p] of payments.entries()) { const result = await algorand.send.payment({ sender: p.sender, receiver: p.receiver, amount: p.amount, note: p.note, }); txnResults.push(result); printInfo( `Txn ${i + 1}: ${shortenAddress(p.sender.toString())} -> ${shortenAddress(p.receiver.toString())} | ${formatMicroAlgo(p.amount.microAlgo)} | note: "${p.note}"`, ); } printSuccess(`Sent ${payments.length} payments`);
// Record watermark before first txn const watermarkBefore = txnResults[0].confirmation!.confirmedRound! - 1n;
const testFilter = createFilterTester(algorand.client.algod, watermarkBefore); const formatPayment = (txn: SubscribedTransaction) => { const amount = txn.paymentTransaction?.amount ?? 0n; const note = txn.note ? Buffer.from(txn.note).toString('utf-8') : ''; printInfo(` Matched: ${txn.id} | amount: ${formatMicroAlgo(amount)} | note: "${note}"`); };
// Step 4: Filter by sender (sender account only) printStep(4, 'Filter: sender = sender account'); const senderTxns = await testFilter( 'sender-filter', { sender: senderAddr }, 3, 'Sender filter matched 3 payments from sender', formatPayment, );
// Step 5: Filter by receiver (receiver account only) printStep(5, 'Filter: receiver = receiver account'); const receiverTxns = await testFilter( 'receiver-filter', { receiver: receiverAddr }, 3, 'Receiver filter matched 3 payments to receiver', formatPayment, );
// Step 6: Filter by minAmount/maxAmount range (1_000_000 to 3_000_000 microAlgo) printStep(6, 'Filter: minAmount=1000000, maxAmount=3000000'); // Payments in range [1M, 3M]: txn1 (1M), txn3 (2M), txn4 (3M) = 3 const rangeTxns = await testFilter( 'amount-range', { minAmount: 1_000_000, maxAmount: 3_000_000 }, 3, 'Amount range filter matched 3 payments in [1M, 3M] microAlgo', formatPayment, );
// Step 7: Filter by notePrefix ("invoice") printStep(7, 'Filter: notePrefix = "invoice"'); // Txns with "invoice" prefix: txn1, txn2, txn4 = 3 const invoiceTxns = await testFilter( 'note-prefix', { notePrefix: 'invoice' }, 3, 'Note prefix filter matched 3 payments with "invoice" prefix', formatPayment, );
// Step 8: Summary printStep(8, 'Summary'); printInfo(`Sender filter: ${senderTxns.length} matched`); printInfo(`Receiver filter: ${receiverTxns.length} matched`); printInfo(`Amount [1M,3M] filter: ${rangeTxns.length} matched`); printInfo(`notePrefix="invoice" filter: ${invoiceTxns.length} matched`);
printHeader('Example complete');}
main().catch(err => { printError(err.message); process.exit(1);});Other examples
Section titled “Other examples”- Basic Poll Once
- Continuous Subscriber
- Payment Filters
- Asset Transfer Subscription
- App Call Subscription
- Multiple Named Filters
- Balance Change Tracking
- ARC-28 Event Subscription
- Inner Transaction Subscription
- Batch Handling & Data Mappers
- Watermark Persistence
- Sync Behaviours
- Custom Filters
- Stateless Subscriptions
- Lifecycle Hooks & Error Handling