Application Call
Description
Section titled “Description”This example demonstrates how to deploy and interact with a smart contract on Algorand using the transact package:
- Compile simple approval and clear TEAL programs using algod.tealCompile()
- Create an app with TransactionType.AppCall and OnApplicationComplete.NoOp
- Use AppCallTransactionFields with approvalProgram, clearStateProgram, globalStateSchema, and localStateSchema
- Retrieve the created app ID from pending transaction info
- Call the app with application arguments
- Demonstrate OnApplicationComplete.OptIn for local state
- Delete the app at the end
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 transact/14-app-call.ts/** * Example: Application Call * * This example demonstrates how to deploy and interact with a smart contract * on Algorand using the transact package: * - Compile simple approval and clear TEAL programs using algod.tealCompile() * - Create an app with TransactionType.AppCall and OnApplicationComplete.NoOp * - Use AppCallTransactionFields with approvalProgram, clearStateProgram, * globalStateSchema, and localStateSchema * - Retrieve the created app ID from pending transaction info * - Call the app with application arguments * - Demonstrate OnApplicationComplete.OptIn for local state * - Delete the app at the end * * Prerequisites: * - LocalNet running (via `algokit localnet start`) */
import { AlgorandClient } from '@algorandfoundation/algokit-utils'import { assignFee, OnApplicationComplete, Transaction, TransactionType, type AppCallTransactionFields,} from '@algorandfoundation/algokit-utils/transact'import { createAlgodClient, formatAlgo, loadTealSource, printHeader, printInfo, printStep, printSuccess, shortenAddress, waitForConfirmation,} from '../shared/utils.js'
/** * Gets a funded account from LocalNet's KMD wallet */async function getLocalNetFundedAccount(algorand: AlgorandClient) { return await algorand.account.kmd.getLocalNetDispenserAccount()}
async function main() { printHeader('Application Call Example')
// Step 1: Initialize clients printStep(1, 'Initialize Algod Client') const algod = createAlgodClient() const algorand = AlgorandClient.defaultLocalNet() printInfo('Connected to LocalNet Algod')
// Step 2: Get funded account printStep(2, 'Get Funded Account') const creator = await getLocalNetFundedAccount(algorand) printInfo(`Creator address: ${shortenAddress(creator.addr.toString())}`)
// Step 3: Compile approval and clear TEAL programs printStep(3, 'Compile TEAL Programs')
// Load approval and clear state programs from shared artifacts // The approval program handles app creation, calls, opt-in, and deletion // with global and local state counters const approvalSource = loadTealSource('approval-counter.teal')
// Simple clear state program that always approves const clearSource = loadTealSource('clear-state-approve.teal')
printInfo('Compiling approval program...') const approvalResult = await algod.tealCompile(approvalSource) const approvalProgram = new Uint8Array(Buffer.from(approvalResult.result, 'base64')) printInfo(`Approval program size: ${approvalProgram.length} bytes`) printInfo(`Approval program hash: ${approvalResult.hash}`)
printInfo('') printInfo('Compiling clear state program...') const clearResult = await algod.tealCompile(clearSource) const clearStateProgram = new Uint8Array(Buffer.from(clearResult.result, 'base64')) printInfo(`Clear state program size: ${clearStateProgram.length} bytes`) printInfo(`Clear state program hash: ${clearResult.hash}`)
// Step 4: Create app with TransactionType.AppCall and OnApplicationComplete.NoOp printStep(4, 'Create Application')
const suggestedParams = await algod.suggestedParams()
// Define state schemas const globalStateSchema = { numUints: 1, // For the counter numByteSlices: 0, // No byte slices in global state }
const localStateSchema = { numUints: 1, // For user counter numByteSlices: 0, // No byte slices in local state }
printInfo('App configuration:') printInfo(` Global state: ${globalStateSchema.numUints} uints, ${globalStateSchema.numByteSlices} byte slices`) printInfo(` Local state: ${localStateSchema.numUints} uints, ${localStateSchema.numByteSlices} byte slices`) printInfo('')
const appCallFields: AppCallTransactionFields = { appId: 0n, // 0 means app creation onComplete: OnApplicationComplete.NoOp, approvalProgram, clearStateProgram, globalStateSchema, localStateSchema, }
const createAppTx = new Transaction({ type: TransactionType.AppCall, sender: creator.addr, firstValid: suggestedParams.firstValid, lastValid: suggestedParams.lastValid, genesisHash: suggestedParams.genesisHash, genesisId: suggestedParams.genesisId, appCall: appCallFields, })
const createAppTxWithFee = assignFee(createAppTx, { feePerByte: suggestedParams.fee, minFee: suggestedParams.minFee, })
printInfo('Creating app transaction...') printInfo(` Transaction type: ${createAppTxWithFee.type}`) printInfo(` OnComplete: NoOp (for creation)`) printInfo(` Fee: ${createAppTxWithFee.fee} microALGO`)
const signedCreateTx = await creator.signer([createAppTxWithFee], [0]) await algod.sendRawTransaction(signedCreateTx)
// Step 5: Retrieve created app ID from pending transaction info printStep(5, 'Retrieve Created App ID')
const createPendingInfo = await waitForConfirmation(algod, createAppTxWithFee.txId()) const appId = createPendingInfo.appId as bigint
printInfo(`Transaction confirmed in round: ${createPendingInfo.confirmedRound}`) printInfo(`Created app ID: ${appId}`) printSuccess('Application created successfully!')
// Step 6: Call the app with application arguments printStep(6, 'Call the App with Arguments')
const callParams = await algod.suggestedParams()
// First call with an argument const arg1 = new TextEncoder().encode('Hello, Algorand!') const callAppFields1: AppCallTransactionFields = { appId, onComplete: OnApplicationComplete.NoOp, args: [arg1], }
const callAppTx1 = new Transaction({ type: TransactionType.AppCall, sender: creator.addr, firstValid: callParams.firstValid, lastValid: callParams.lastValid, genesisHash: callParams.genesisHash, genesisId: callParams.genesisId, appCall: callAppFields1, })
const callAppTxWithFee1 = assignFee(callAppTx1, { feePerByte: callParams.fee, minFee: callParams.minFee, })
printInfo(`Calling app ${appId} with argument: "Hello, Algorand!"`)
const signedCallTx1 = await creator.signer([callAppTxWithFee1], [0]) await algod.sendRawTransaction(signedCallTx1)
const callPendingInfo1 = await waitForConfirmation(algod, callAppTxWithFee1.txId()) printInfo(`Transaction confirmed in round: ${callPendingInfo1.confirmedRound}`)
// Check logs (the app logs the first argument) if (callPendingInfo1.logs && (callPendingInfo1.logs as Uint8Array[]).length > 0) { const logBytes = (callPendingInfo1.logs as Uint8Array[])[0] const logMessage = new TextDecoder().decode(logBytes) printInfo(`App logged: "${logMessage}"`) }
// Second call to increment counter const callParams2 = await algod.suggestedParams() const arg2 = new TextEncoder().encode('Second call!')
const callAppTx2 = new Transaction({ type: TransactionType.AppCall, sender: creator.addr, firstValid: callParams2.firstValid, lastValid: callParams2.lastValid, genesisHash: callParams2.genesisHash, genesisId: callParams2.genesisId, appCall: { appId, onComplete: OnApplicationComplete.NoOp, args: [arg2], }, })
const callAppTxWithFee2 = assignFee(callAppTx2, { feePerByte: callParams2.fee, minFee: callParams2.minFee, })
printInfo(`Calling app again with argument: "Second call!"`)
const signedCallTx2 = await creator.signer([callAppTxWithFee2], [0]) await algod.sendRawTransaction(signedCallTx2)
const callPendingInfo2 = await waitForConfirmation(algod, callAppTxWithFee2.txId()) printInfo(`Transaction confirmed in round: ${callPendingInfo2.confirmedRound}`)
if (callPendingInfo2.logs && (callPendingInfo2.logs as Uint8Array[]).length > 0) { const logBytes = (callPendingInfo2.logs as Uint8Array[])[0] const logMessage = new TextDecoder().decode(logBytes) printInfo(`App logged: "${logMessage}"`) }
printSuccess('App calls completed successfully!')
// Step 7: Demonstrate OnApplicationComplete.OptIn for local state printStep(7, 'Demonstrate OptIn for Local State')
// Create a new account that will opt into the app using AlgorandClient helper const optInUser = algorand.account.random() printInfo(`OptIn user address: ${shortenAddress(optInUser.addr.toString())}`)
// Fund the new account const fundParams = await algod.suggestedParams() const fundTx = new Transaction({ type: TransactionType.Payment, sender: creator.addr, firstValid: fundParams.firstValid, lastValid: fundParams.lastValid, genesisHash: fundParams.genesisHash, genesisId: fundParams.genesisId, payment: { receiver: optInUser.addr, amount: 1_000_000n, // 1 ALGO }, })
const fundTxWithFee = assignFee(fundTx, { feePerByte: fundParams.fee, minFee: fundParams.minFee, })
const signedFundTx = await creator.signer([fundTxWithFee], [0]) await algod.sendRawTransaction(signedFundTx) await waitForConfirmation(algod, fundTxWithFee.txId()) printInfo(`Funded OptIn user with ${formatAlgo(1_000_000n)}`)
// OptIn to the app const optInParams = await algod.suggestedParams()
const optInFields: AppCallTransactionFields = { appId, onComplete: OnApplicationComplete.OptIn, }
const optInTx = new Transaction({ type: TransactionType.AppCall, sender: optInUser.addr, firstValid: optInParams.firstValid, lastValid: optInParams.lastValid, genesisHash: optInParams.genesisHash, genesisId: optInParams.genesisId, appCall: optInFields, })
const optInTxWithFee = assignFee(optInTx, { feePerByte: optInParams.fee, minFee: optInParams.minFee, })
printInfo(`User opting into app ${appId}...`) printInfo(` OnComplete: OptIn`)
const signedOptInTx = await optInUser.signer([optInTxWithFee], [0]) await algod.sendRawTransaction(signedOptInTx)
const optInPendingInfo = await waitForConfirmation(algod, optInTxWithFee.txId()) printInfo(`Transaction confirmed in round: ${optInPendingInfo.confirmedRound}`) printSuccess('User successfully opted into the app!')
printInfo('') printInfo('OptIn explanation:') printInfo(' - OptIn allocates local storage for the user in this app') printInfo(' - The app can now read/write user-specific state') printInfo(' - The user pays for the minimum balance increase') printInfo(' - Our app initializes user_counter to 0 on OptIn')
// Step 8: Delete the app printStep(8, 'Delete the Application')
const deleteParams = await algod.suggestedParams()
const deleteFields: AppCallTransactionFields = { appId, onComplete: OnApplicationComplete.DeleteApplication, }
const deleteTx = new Transaction({ type: TransactionType.AppCall, sender: creator.addr, firstValid: deleteParams.firstValid, lastValid: deleteParams.lastValid, genesisHash: deleteParams.genesisHash, genesisId: deleteParams.genesisId, appCall: deleteFields, })
const deleteTxWithFee = assignFee(deleteTx, { feePerByte: deleteParams.fee, minFee: deleteParams.minFee, })
printInfo(`Deleting app ${appId}...`) printInfo(` OnComplete: DeleteApplication`)
const signedDeleteTx = await creator.signer([deleteTxWithFee], [0]) await algod.sendRawTransaction(signedDeleteTx)
const deletePendingInfo = await waitForConfirmation(algod, deleteTxWithFee.txId()) printInfo(`Transaction confirmed in round: ${deletePendingInfo.confirmedRound}`) printSuccess('Application deleted successfully!')
// Summary printStep(9, 'Summary') printInfo('') printInfo('App lifecycle demonstrated:') printInfo(' 1. Create - Deploy with approvalProgram, clearStateProgram, and schemas') printInfo(' 2. Call - Invoke app logic with OnApplicationComplete.NoOp') printInfo(' 3. OptIn - User opts in to allocate local state') printInfo(' 4. Delete - Remove app from the blockchain') printInfo('') printInfo('OnApplicationComplete values:') printInfo(' - NoOp: Standard app call or creation') printInfo(' - OptIn: Allocate local storage for the sender') printInfo(' - CloseOut: Deallocate local storage (graceful exit)') printInfo(' - ClearState: Deallocate local storage (forced, always succeeds)') printInfo(' - UpdateApplication: Update the programs') printInfo(' - DeleteApplication: Remove the app') printInfo('') printInfo('Key fields for app creation:') printInfo(' - appId: 0n for creation, actual ID for existing apps') printInfo(' - approvalProgram: Logic for most operations') printInfo(' - clearStateProgram: Logic for ClearState (cannot reject)') printInfo(' - globalStateSchema: {numUints, numByteSlices} for global storage') printInfo(' - localStateSchema: {numUints, numByteSlices} for per-user storage') printInfo('') printInfo('Retrieving app ID after creation:') printInfo(' const pendingInfo = await waitForConfirmation(algod, txId)') printInfo(' const appId = pendingInfo.appId // bigint')
printSuccess('Application call example completed!')}
main().catch((error) => { console.error('Error:', error) process.exit(1)})