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