Skip to content

App Call Subscription

← Back to Examples

This example demonstrates application call subscription.

  • Subscribe to app creation with appCreate filter
  • Filter by methodSignature for specific ABI methods
  • Filter by appOnComplete for opt-in calls
  • Use appCallArgumentsMatch for custom argument inspection
  • LocalNet running (via algokit localnet start)

From the repository’s examples/subscriber directory:

Terminal window
cd examples/subscriber
npx tsx 05-app-call.ts

View source on GitHub

05-app-call.ts
/**
* Example: App Call Subscription
*
* This example demonstrates application call subscription.
* - Subscribe to app creation with appCreate filter
* - Filter by methodSignature for specific ABI methods
* - Filter by appOnComplete for opt-in calls
* - Use appCallArgumentsMatch for custom argument inspection
*
* Prerequisites:
* - LocalNet running (via `algokit localnet start`)
*/
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
import { algo, AlgorandClient, AppFactory } from '@algorandfoundation/algokit-utils'
import { printHeader, printStep, printInfo, printSuccess, printError, shortenAddress, createFilterTester } from './shared/utils.js'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
async function main() {
printHeader('05 — App Call Subscription')
// 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 and fund an account
printStep(2, 'Create and fund account')
const creator = await algorand.account.fromEnvironment('APP_CREATOR', algo(100))
const creatorAddr = creator.addr.toString()
printInfo(`Creator: ${shortenAddress(creatorAddr)}`)
printSuccess('Account created and funded')
// Step 3: Deploy TestingApp using AppFactory with embedded ARC-56 spec
printStep(3, 'Deploy TestingApp via AppFactory')
const appSpec = JSON.parse(
readFileSync(join(__dirname, 'shared/artifacts/testing-app.arc56.json'), 'utf-8'),
)
const factory = new AppFactory({
appSpec,
algorand,
defaultSender: creator.addr,
})
const { result: createResult, appClient } = await factory.send.bare.create({
sender: creator.addr,
})
const appId = createResult.appId
const createRound = createResult.confirmation.confirmedRound!
printInfo(`App ID: ${appId.toString()}`)
printInfo(`Create round: ${createRound.toString()}`)
printSuccess('TestingApp deployed')
// Step 4: Make ABI method calls (non-readonly methods produce on-chain transactions)
printStep(4, 'Make ABI method calls')
// set_global(uint64,uint64,string,byte[4])void — NoOp
const setGlobalResult = await appClient.send.call({
method: 'set_global',
args: [1n, 2n, 'test', new Uint8Array([0, 1, 2, 3])],
sender: creator.addr,
})
printInfo(`set_global txn: ${setGlobalResult.txIds.at(-1)}`)
// emitSwapped(uint64,uint64)void — NoOp (emits ARC-28 event)
const emitResult = await appClient.send.call({
method: 'emitSwapped',
args: [42n, 99n],
sender: creator.addr,
})
printInfo(`emitSwapped txn: ${emitResult.txIds.at(-1)}`)
// opt_in()void — OptIn on-complete
const optInResult = await appClient.send.optIn({
method: 'opt_in',
args: [],
sender: creator.addr,
})
printInfo(`opt_in txn: ${optInResult.txIds.at(-1)}`)
printSuccess('3 ABI method calls sent')
// Watermark: just before the app creation round
const watermarkBefore = createRound - 1n
const testFilter = createFilterTester(algorand.client.algod, watermarkBefore)
// Step 5: Subscribe with appCreate: true — matches app creation
printStep(5, 'Filter: appCreate = true')
const createTxns = await testFilter(
'app-create', { appCreate: true }, 1,
'appCreate filter matched 1 app creation transaction',
(txn) => {
const appTxn = txn.applicationTransaction
printInfo(` Created app: ${txn.createdAppId} | onComplete: ${appTxn?.onCompletion} | txn: ${txn.id}`)
},
)
// Step 6: Subscribe with appId + methodSignature — matches specific ABI method calls
printStep(6, 'Filter: appId + methodSignature = set_global')
const methodTxns = await testFilter(
'set-global-method', {
appId: appId,
methodSignature: 'set_global(uint64,uint64,string,byte[4])void',
}, 1,
'methodSignature filter matched 1 set_global call',
(txn) => {
const appArgs = txn.applicationTransaction?.applicationArgs
const selectorHex = appArgs && appArgs.length > 0 ? Buffer.from(appArgs[0].slice(0, 4)).toString('hex') : 'N/A'
printInfo(
` Method call: selector: 0x${selectorHex} | filters: [${txn.filtersMatched?.join(', ')}] | txn: ${txn.id}`,
)
},
)
// Step 7: Subscribe with appOnComplete filter — matches by on-complete type
printStep(7, 'Filter: appOnComplete = optin')
const optInTxns = await testFilter(
'optin-calls', { appOnComplete: 'optin' }, 1,
'appOnComplete filter matched 1 opt-in transaction',
(txn) => {
const appTxn = txn.applicationTransaction
printInfo(
` OptIn call: app: ${appTxn?.applicationId} | onComplete: ${appTxn?.onCompletion} | filters: [${txn.filtersMatched?.join(', ')}] | txn: ${txn.id}`,
)
},
)
// Step 8: Demonstrate appCallArgumentsMatch predicate — custom arg inspection
// Match emitSwapped calls by checking the method selector in the first app arg
printStep(8, 'Filter: appCallArgumentsMatch predicate')
const emitSwappedSelector = 'd43cee5d' // method selector for emitSwapped(uint64,uint64)void
const argMatchTxns = await testFilter(
'arg-match', {
appId: appId,
appCallArgumentsMatch: (args?: readonly Uint8Array[]) => {
if (!args || args.length === 0) return false
const selectorHex = Buffer.from(args[0].slice(0, 4)).toString('hex')
return selectorHex === emitSwappedSelector
},
}, 1,
'appCallArgumentsMatch predicate matched 1 emitSwapped call',
(txn) => {
const appArgs = txn.applicationTransaction?.applicationArgs
if (appArgs && appArgs.length > 0) {
const selectorHex = Buffer.from(appArgs[0].slice(0, 4)).toString('hex')
printInfo(
` Arg match: selector: 0x${selectorHex} | filters: [${txn.filtersMatched?.join(', ')}] | txn: ${txn.id}`,
)
}
},
)
// Step 9: Summary
printStep(9, 'Summary')
printInfo(`App ID: ${appId.toString()}`)
printInfo(`appCreate filter: ${createTxns.length} matched (creation)`)
printInfo(`methodSignature filter: ${methodTxns.length} matched (set_global)`)
printInfo(`appOnComplete filter: ${optInTxns.length} matched (opt-in)`)
printInfo(`appCallArgumentsMatch filter: ${argMatchTxns.length} matched (emitSwapped by selector)`)
printHeader('Example complete')
}
main().catch((err) => {
printError(err.message)
process.exit(1)
})