Skip to content
Algorand Developer Portal

Application Call

← Back to Transactions

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
  • LocalNet running (via algokit localnet start)

From the repository root:

Terminal window
cd examples
npm run example transact/14-app-call.ts

View source on GitHub

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);
});