Skip to content
Algorand Developer Portal

Multiple Named Filters

← Back to Examples

This example demonstrates multiple named filters with deduplication.

  • Define multiple filters on a single subscriber
  • Verify transactions are deduplicated across filters
  • Inspect filtersMatched on each transaction
  • LocalNet running (via algokit localnet start)

From the repository’s examples/subscriber directory:

Terminal window
cd examples/subscriber
npx tsx 06-multiple-filters.ts

View source on GitHub

06-multiple-filters.ts
/**
* Example: Multiple Named Filters
*
* This example demonstrates multiple named filters with deduplication.
* - Define multiple filters on a single subscriber
* - Verify transactions are deduplicated across filters
* - Inspect filtersMatched on each transaction
*
* Prerequisites:
* - LocalNet running (via `algokit localnet start`)
*/
import { algo, AlgorandClient, microAlgo } from '@algorandfoundation/algokit-utils';
import { AlgorandSubscriber } from '@algorandfoundation/algokit-subscriber';
import {
printHeader,
printStep,
printInfo,
printSuccess,
printError,
shortenAddress,
formatMicroAlgo,
} from './shared/utils.js';
async function main() {
printHeader('06 — Multiple Named 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 accounts
printStep(2, 'Create and fund accounts (sender, receiver)');
const sender = await algorand.account.fromEnvironment('MULTI_SENDER', algo(100));
const receiver = await algorand.account.fromEnvironment('MULTI_RECEIVER', algo(100));
const senderAddr = sender.addr.toString();
const receiverAddr = receiver.addr.toString();
printInfo(`Sender: ${shortenAddress(senderAddr)}`);
printInfo(`Receiver: ${shortenAddress(receiverAddr)}`);
printSuccess('Accounts created and funded');
// Step 3: Send transactions designed to match different filter combinations
// Filter plan:
// 'from-sender' — sender = sender account
// 'to-receiver' — receiver = receiver account
// 'large-txns' — minAmount = 3_000_000 microAlgo
//
// Txn 1: sender -> receiver, 5 ALGO, note "multi-01" => matches ALL 3 filters
// Txn 2: sender -> sender, 1 ALGO, note "multi-02" => matches 'from-sender' only (1 filter)
// Txn 3: receiver -> sender, 4 ALGO, note "multi-03" => matches 'large-txns' only (1 filter)
// Txn 4: sender -> receiver, 1 ALGO, note "multi-04" => matches 'from-sender' + 'to-receiver' (2 filters)
// Txn 5: receiver -> sender, 0.5 ALGO, note "multi-05" => matches NO filters
printStep(3, 'Send 5 transactions with varying filter overlap');
const txnSpecs = [
{
sender: sender.addr,
receiver: receiver.addr,
amount: algo(5),
note: 'multi-01',
desc: 'sender->receiver 5A (all 3)',
},
{
sender: sender.addr,
receiver: sender.addr,
amount: algo(1),
note: 'multi-02',
desc: 'sender->sender 1A (from-sender)',
},
{
sender: receiver.addr,
receiver: sender.addr,
amount: algo(4),
note: 'multi-03',
desc: 'receiver->sender 4A (large-txns)',
},
{
sender: sender.addr,
receiver: receiver.addr,
amount: algo(1),
note: 'multi-04',
desc: 'sender->receiver 1A (from-sender + to-receiver)',
},
{
sender: receiver.addr,
receiver: sender.addr,
amount: microAlgo(500_000),
note: 'multi-05',
desc: 'receiver->sender 0.5A (none)',
},
];
const txnResults = [];
for (const [i, spec] of txnSpecs.entries()) {
const result = await algorand.send.payment({
sender: spec.sender,
receiver: spec.receiver,
amount: spec.amount,
note: spec.note,
});
txnResults.push(result);
printInfo(`Txn ${i + 1}: ${spec.desc}`);
}
printSuccess(`Sent ${txnSpecs.length} transactions`);
// Step 4: Create subscriber with 3 named filters on a single instance
printStep(4, 'Create subscriber with 3 named filters');
const watermarkBefore = txnResults[0].confirmation!.confirmedRound! - 1n;
let watermark = watermarkBefore;
const subscriber = new AlgorandSubscriber(
{
filters: [
{
name: 'from-sender',
filter: { sender: senderAddr },
},
{
name: 'to-receiver',
filter: { receiver: receiverAddr },
},
{
name: 'large-txns',
filter: { minAmount: 3_000_000 },
},
],
syncBehaviour: 'sync-oldest',
maxRoundsToSync: 100,
watermarkPersistence: {
get: async () => watermark,
set: async (w: bigint) => {
watermark = w;
},
},
},
algorand.client.algod,
);
printInfo(`Filter 1: 'from-sender' — sender = sender account`);
printInfo(`Filter 2: 'to-receiver' — receiver = receiver account`);
printInfo(`Filter 3: 'large-txns' — minAmount = 3,000,000 microAlgo`);
printSuccess('Subscriber created with 3 named filters');
// Step 5: Poll once and inspect results
printStep(5, 'Poll once and inspect deduplicated results');
const result = await subscriber.pollOnce();
const txns = result.subscribedTransactions;
printInfo(`Total subscribed transactions: ${txns.length.toString()}`);
// Step 6: Show filtersMatched on each transaction
printStep(6, 'Inspect filtersMatched per transaction');
for (const txn of txns) {
const note = txn.note ? Buffer.from(txn.note).toString('utf-8') : '';
const amount = txn.paymentTransaction?.amount ?? 0n;
printInfo(
`${note}: ${formatMicroAlgo(amount)} | filtersMatched: [${(txn.filtersMatched ?? []).join(', ')}]`,
);
}
// Step 7: Verify deduplication — txn 1 matches all 3 filters but appears once
printStep(7, 'Verify deduplication');
const txn1Note = 'multi-01';
const txn1Matches = txns.filter(t => {
const note = t.note ? Buffer.from(t.note).toString('utf-8') : '';
return note === txn1Note;
});
if (txn1Matches.length !== 1) {
throw new Error(
`Dedup failed: txn "${txn1Note}" appears ${txn1Matches.length} times, expected 1`,
);
}
printInfo(
`Txn "multi-01": appears ${txn1Matches.length} time (matched ${txn1Matches[0].filtersMatched?.length} filters)`,
);
printSuccess('Transaction appears once even though it matched all 3 filters');
// Step 8: Verify filtersMatched contents
printStep(8, 'Verify filtersMatched accuracy');
// Expected: multi-01 matches all 3 filters
const txn1Filters = txn1Matches[0].filtersMatched ?? [];
if (
!txn1Filters.includes('from-sender') ||
!txn1Filters.includes('to-receiver') ||
!txn1Filters.includes('large-txns')
) {
throw new Error(`multi-01 expected all 3 filters, got: [${txn1Filters.join(', ')}]`);
}
printSuccess('multi-01 matched: from-sender, to-receiver, large-txns');
// Expected: multi-02 matches from-sender only
const txn2Match = txns.find(t => t.note && Buffer.from(t.note).toString('utf-8') === 'multi-02')!;
const txn2Filters = txn2Match.filtersMatched ?? [];
if (txn2Filters.length !== 1 || !txn2Filters.includes('from-sender')) {
throw new Error(`multi-02 expected [from-sender], got: [${txn2Filters.join(', ')}]`);
}
printSuccess('multi-02 matched: from-sender');
// Expected: multi-03 matches large-txns only
const txn3Match = txns.find(t => t.note && Buffer.from(t.note).toString('utf-8') === 'multi-03')!;
const txn3Filters = txn3Match.filtersMatched ?? [];
if (txn3Filters.length !== 1 || !txn3Filters.includes('large-txns')) {
throw new Error(`multi-03 expected [large-txns], got: [${txn3Filters.join(', ')}]`);
}
printSuccess('multi-03 matched: large-txns');
// Expected: multi-04 matches from-sender + to-receiver (2 filters)
const txn4Match = txns.find(t => t.note && Buffer.from(t.note).toString('utf-8') === 'multi-04')!;
const txn4Filters = txn4Match.filtersMatched ?? [];
if (
txn4Filters.length !== 2 ||
!txn4Filters.includes('from-sender') ||
!txn4Filters.includes('to-receiver')
) {
throw new Error(
`multi-04 expected [from-sender, to-receiver], got: [${txn4Filters.join(', ')}]`,
);
}
printSuccess('multi-04 matched: from-sender, to-receiver');
// Expected: multi-05 matches NO filters (should not appear)
const txn5Match = txns.find(t => t.note && Buffer.from(t.note).toString('utf-8') === 'multi-05');
if (txn5Match) {
throw new Error(
`multi-05 should not match any filter but appeared with: [${txn5Match.filtersMatched?.join(', ')}]`,
);
}
printSuccess('multi-05 correctly excluded (matched no filters)');
// Expected total: 4 deduplicated transactions (txns 1-4 match at least one filter; txn 5 matches none)
if (txns.length !== 4) {
throw new Error(`Expected 4 deduplicated transactions, got ${txns.length}`);
}
printSuccess('Exactly 4 deduplicated transactions returned');
// Step 9: Summary table
printStep(9, 'Summary table');
console.log();
console.log(' ┌────────────┬─────────────────────────┬───────────────────────────────────────┐');
console.log(' │ Note │ Amount │ Filters Matched │');
console.log(' ├────────────┼─────────────────────────┼───────────────────────────────────────┤');
for (const txn of txns) {
const note = txn.note ? Buffer.from(txn.note).toString('utf-8') : '';
const amount = txn.paymentTransaction?.amount ?? 0n;
const filters = (txn.filtersMatched ?? []).join(', ');
console.log(
` │ ${note.padEnd(10)} │ ${formatMicroAlgo(amount).padEnd(23)} │ ${filters.padEnd(37)} │`,
);
}
console.log(' ├────────────┼─────────────────────────┼───────────────────────────────────────┤');
console.log(
` │ ${'multi-05'.padEnd(10)} │ ${'500000 microALGO'.padEnd(23)} │ ${'(no match — excluded)'.padEnd(37)} │`,
);
console.log(' └────────────┴─────────────────────────┴───────────────────────────────────────┘');
console.log();
printHeader('Example complete');
}
main().catch(err => {
printError(err.message);
process.exit(1);
});