Send Application Operations
Description
Section titled “Description”This example demonstrates how to perform smart contract (application) operations:
- algorand.send.appCreate() to deploy a new application with global/local schema
- algorand.send.appUpdate() to update application code
- algorand.send.appCall() for NoOp application calls with args
- algorand.send.appCall() with OnApplicationComplete.OptIn for account opt-in
- algorand.send.appCall() with OnApplicationComplete.CloseOut for account close-out
- algorand.send.appCall() with OnApplicationComplete.ClearState to clear local state
- algorand.send.appDelete() to delete the application
- Passing application arguments, accounts, assets, apps references
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 algorand_client/08-send-app-ops.ts/** * Example: Send Application Operations * * This example demonstrates how to perform smart contract (application) operations: * - algorand.send.appCreate() to deploy a new application with global/local schema * - algorand.send.appUpdate() to update application code * - algorand.send.appCall() for NoOp application calls with args * - algorand.send.appCall() with OnApplicationComplete.OptIn for account opt-in * - algorand.send.appCall() with OnApplicationComplete.CloseOut for account close-out * - algorand.send.appCall() with OnApplicationComplete.ClearState to clear local state * - algorand.send.appDelete() to delete the application * - Passing application arguments, accounts, assets, apps references * * Prerequisites: * - LocalNet running (via `algokit localnet start`) */
import { OnApplicationComplete } from '@algorandfoundation/algokit-utils/transact';import { AlgorandClient, algo } from '@algorandfoundation/algokit-utils';import { loadTealSource, printError, printHeader, printInfo, printStep, printSuccess, shortenAddress,} from '../shared/utils.js';
// ============================================================================// TEAL Programs - loaded from shared artifacts// ============================================================================
// A counter app that supports all lifecycle operationsconst APPROVAL_PROGRAM = loadTealSource('approval-lifecycle-full.teal');
// Updated version of the approval program (increments by 2 instead of 1)const APPROVAL_PROGRAM_V2 = loadTealSource('approval-lifecycle-full-v2.teal');
// Clear state program (must always approve)const CLEAR_STATE_PROGRAM = loadTealSource('clear-state-approve.teal');
async function main() { printHeader('Send Application Operations Example');
// Initialize client and verify LocalNet is running const algorand = AlgorandClient.defaultLocalNet();
try { await algorand.client.algod.status(); printSuccess('Connected to LocalNet'); } catch (error) { printError( `Failed to connect to LocalNet: ${error instanceof Error ? error.message : String(error)}`, ); printInfo('Make sure LocalNet is running (e.g., algokit localnet start)'); return; }
// Step 1: Create and fund test accounts printStep(1, 'Create and fund test accounts'); printInfo('Creating creator and user accounts for app operations');
const creator = algorand.account.random(); const user = algorand.account.random();
printInfo(`\nCreated accounts:`); printInfo(` Creator: ${shortenAddress(creator.addr.toString())}`); printInfo(` User: ${shortenAddress(user.addr.toString())}`);
// Fund all accounts await algorand.account.ensureFundedFromEnvironment(creator.addr, algo(10)); await algorand.account.ensureFundedFromEnvironment(user.addr, algo(5));
printSuccess('Created and funded test accounts');
// Step 2: Create a new application with algorand.send.appCreate() printStep(2, 'Create a new application with algorand.send.appCreate()'); printInfo('Deploying a counter app with global and local state schema'); printInfo(''); printInfo('Schema specification:'); printInfo(' globalInts: 1 (for the counter)'); printInfo(' globalByteSlices: 1 (for the message)'); printInfo(' localInts: 1 (for user_visits)'); printInfo(' localByteSlices: 0');
const createResult = await algorand.send.appCreate({ sender: creator.addr, approvalProgram: APPROVAL_PROGRAM, clearStateProgram: CLEAR_STATE_PROGRAM, schema: { globalInts: 1, globalByteSlices: 1, localInts: 1, localByteSlices: 0, }, });
const appId = createResult.appId; const appAddress = createResult.appAddress;
printInfo(`\nApplication created:`); printInfo(` App ID: ${appId}`); printInfo(` App Address: ${shortenAddress(appAddress.toString())}`); printInfo(` Transaction ID: ${createResult.txIds[0]}`); printInfo(` Confirmed round: ${createResult.confirmation.confirmedRound}`);
// Check global state after creation const globalState = await algorand.app.getGlobalState(appId); printInfo(`\nInitial global state:`); printInfo(` counter: ${globalState['counter']?.value ?? 0}`); printInfo(` message: "${globalState['message']?.value ?? ''}"`);
printSuccess('Application created successfully');
// Step 3: Call the application with algorand.send.appCall() - NoOp printStep(3, 'Call the application with algorand.send.appCall() - NoOp'); printInfo('Calling the app to increment the counter');
const callResult = await algorand.send.appCall({ sender: creator.addr, appId: appId, note: 'First NoOp call', // onComplete defaults to NoOp });
printInfo(`\nNoOp call completed:`); printInfo(` Transaction ID: ${callResult.txIds[0]}`); printInfo(` Confirmed round: ${callResult.confirmation.confirmedRound}`);
// Check global state after call const stateAfterCall = await algorand.app.getGlobalState(appId); printInfo(` Counter after call: ${stateAfterCall['counter']?.value ?? 0}`);
printSuccess('NoOp call completed successfully');
// Step 4: Call with application arguments printStep(4, 'Call with application arguments'); printInfo('Passing arguments to the app call (will be logged)');
// Arguments must be Uint8Array const arg1 = new TextEncoder().encode('hello'); const arg2 = new TextEncoder().encode('world');
const callWithArgsResult = await algorand.send.appCall({ sender: creator.addr, appId: appId, args: [arg1, arg2], note: 'Call with arguments', });
printInfo(`\nCall with arguments:`); printInfo(` Transaction ID: ${callWithArgsResult.txIds[0]}`); printInfo(` Args passed: ["hello", "world"]`);
// Check logs from the transaction const logs = callWithArgsResult.confirmation.logs ?? []; printInfo(` Logs from app: ${logs.length} entries`); if (logs.length > 0) { const firstLog = new TextDecoder().decode(logs[0]); printInfo(` First log: "${firstLog}"`); }
const stateAfterArgs = await algorand.app.getGlobalState(appId); printInfo(` Counter after call: ${stateAfterArgs['counter']?.value ?? 0}`);
printSuccess('Call with arguments completed');
// Step 5: Opt-in to the application with OnApplicationComplete.OptIn printStep(5, 'Opt-in to the application with appCall and OptIn'); printInfo('User opting in to the app to enable local state');
const optInResult = await algorand.send.appCall({ sender: user.addr, appId: appId, onComplete: OnApplicationComplete.OptIn, note: 'Initial opt-in', });
printInfo(`\nOpt-in completed:`); printInfo(` Transaction ID: ${optInResult.txIds[0]}`); printInfo(` Confirmed round: ${optInResult.confirmation.confirmedRound}`);
// Check local state after opt-in const localState = await algorand.app.getLocalState(appId, user.addr); printInfo(` User local state:`); printInfo(` user_visits: ${localState['user_visits']?.value ?? 0}`);
printSuccess('User opted in successfully');
// Step 6: Update the application with algorand.send.appUpdate() printStep(6, 'Update the application with algorand.send.appUpdate()'); printInfo('Updating the app to increment by 2 instead of 1');
const updateResult = await algorand.send.appUpdate({ sender: creator.addr, appId: appId, approvalProgram: APPROVAL_PROGRAM_V2, clearStateProgram: CLEAR_STATE_PROGRAM, });
printInfo(`\nApplication updated:`); printInfo(` Transaction ID: ${updateResult.txIds[0]}`); printInfo(` Confirmed round: ${updateResult.confirmation.confirmedRound}`);
// Test the updated logic const preUpdateCounter = stateAfterArgs['counter']?.value ?? 0; await algorand.send.appCall({ sender: creator.addr, appId: appId, note: 'Testing updated logic', });
const stateAfterUpdate = await algorand.app.getGlobalState(appId); const postUpdateCounter = stateAfterUpdate['counter']?.value ?? 0;
printInfo(`\nVerifying updated logic:`); printInfo(` Counter before update call: ${preUpdateCounter}`); printInfo(` Counter after update call: ${postUpdateCounter}`); printInfo( ` Increment amount: ${Number(postUpdateCounter) - Number(preUpdateCounter)} (was 1, now 2)`, );
printSuccess('Application updated successfully');
// Step 7: Demonstrate passing references (accounts, apps, assets) printStep(7, 'Demonstrate passing references to app calls'); printInfo('App calls can include references to accounts, assets, and other apps');
// Create a dummy asset to reference const assetResult = await algorand.send.assetCreate({ sender: creator.addr, total: 1000n, decimals: 0, assetName: 'Reference Test', unitName: 'REF', });
const assetId = assetResult.assetId;
// Create another app to reference const otherAppResult = await algorand.send.appCreate({ sender: creator.addr, approvalProgram: loadTealSource('simple-approve.teal'), clearStateProgram: loadTealSource('clear-state-approve.teal'), });
const otherAppId = otherAppResult.appId;
// Make a call with all reference types const refCallResult = await algorand.send.appCall({ sender: creator.addr, appId: appId, accountReferences: [user.addr], // Reference another account appReferences: [otherAppId], // Reference another app assetReferences: [assetId], // Reference an asset note: 'Call with references', });
printInfo(`\nCall with references:`); printInfo(` Transaction ID: ${refCallResult.txIds[0]}`); printInfo(` Account references: [${shortenAddress(user.addr.toString())}]`); printInfo(` App references: [${otherAppId}]`); printInfo(` Asset references: [${assetId}]`); printInfo(` Note: These references allow the app to read data from these resources`);
printSuccess('References passed successfully');
// Step 8: Close out of the application printStep(8, 'Close out with appCall and CloseOut'); printInfo('User closing out of the app (removes local state)');
const closeOutResult = await algorand.send.appCall({ sender: user.addr, appId: appId, onComplete: OnApplicationComplete.CloseOut, note: 'Close out from app', });
printInfo(`\nClose out completed:`); printInfo(` Transaction ID: ${closeOutResult.txIds[0]}`); printInfo(` Confirmed round: ${closeOutResult.confirmation.confirmedRound}`);
// Verify user is no longer opted in try { await algorand.app.getLocalState(appId, user.addr); printError('User should not have local state after close out!'); } catch { printInfo(` User no longer has local state (as expected)`); }
printSuccess('User closed out successfully');
// Step 9: Demonstrate ClearState printStep(9, 'Demonstrate ClearState operation'); printInfo('ClearState forcefully removes local state (cannot be rejected by the app)');
// First, opt the user back in await algorand.send.appCall({ sender: user.addr, appId: appId, onComplete: OnApplicationComplete.OptIn, note: 'Re-opt-in for ClearState demo', }); printInfo('User re-opted in to demonstrate ClearState');
// Now use ClearState const clearStateResult = await algorand.send.appCall({ sender: user.addr, appId: appId, onComplete: OnApplicationComplete.ClearState, note: 'Clear state operation', });
printInfo(`\nClearState completed:`); printInfo(` Transaction ID: ${clearStateResult.txIds[0]}`); printInfo(` Confirmed round: ${clearStateResult.confirmation.confirmedRound}`); printInfo(` Note: ClearState always succeeds, even if the clear program rejects`);
// Verify user is no longer opted in try { await algorand.app.getLocalState(appId, user.addr); printError('User should not have local state after clear state!'); } catch { printInfo(` User local state cleared (as expected)`); }
printSuccess('ClearState completed successfully');
// Step 10: Delete the application with algorand.send.appDelete() printStep(10, 'Delete the application with algorand.send.appDelete()'); printInfo('Deleting the app (only creator can delete in this example)');
// Get final state before deletion const finalState = await algorand.app.getGlobalState(appId); printInfo(`\nFinal global state before deletion:`); printInfo(` counter: ${finalState['counter']?.value ?? 0}`); printInfo(` message: "${finalState['message']?.value ?? ''}"`);
const deleteResult = await algorand.send.appDelete({ sender: creator.addr, appId: appId, });
printInfo(`\nApplication deleted:`); printInfo(` Transaction ID: ${deleteResult.txIds[0]}`); printInfo(` Confirmed round: ${deleteResult.confirmation.confirmedRound}`);
// Verify app no longer exists try { await algorand.app.getById(appId); printError('App should not exist after deletion!'); } catch { printInfo(` App ${appId} no longer exists (as expected)`); }
printSuccess('Application deleted successfully');
// Clean up the other test app await algorand.send.appDelete({ sender: creator.addr, appId: otherAppId, });
// Step 11: Summary of application operations printStep(11, 'Summary - Application Operations API'); printInfo('Application operations available through algorand.send:'); printInfo(''); printInfo('appCreate(params):'); printInfo(' sender: Address - Creator of the application'); printInfo(' approvalProgram: string | Uint8Array - TEAL code or compiled bytes'); printInfo(' clearStateProgram: string | Uint8Array - TEAL code or compiled bytes'); printInfo(' schema?: { globalInts, globalByteSlices, localInts, localByteSlices }'); printInfo(' extraProgramPages?: number - For large programs (auto-calculated)'); printInfo(' Returns: { appId, appAddress, ...SendSingleTransactionResult }'); printInfo(''); printInfo('appUpdate(params):'); printInfo(' sender: Address - Must be authorized to update'); printInfo(' appId: bigint - Application to update'); printInfo(' approvalProgram: string | Uint8Array - New TEAL code'); printInfo(' clearStateProgram: string | Uint8Array - New TEAL code'); printInfo(''); printInfo('appCall(params):'); printInfo(' sender: Address - Caller'); printInfo(' appId: bigint - Application to call'); printInfo(' onComplete?: OnApplicationComplete - NoOp, OptIn, CloseOut, ClearState'); printInfo(' args?: Uint8Array[] - Application arguments'); printInfo(' accountReferences?: Address[] - Accounts the app can access'); printInfo(' appReferences?: bigint[] - Apps the app can call'); printInfo(' assetReferences?: bigint[] - Assets the app can read'); printInfo(' boxReferences?: BoxReference[] - Boxes the app can access'); printInfo(''); printInfo('appDelete(params):'); printInfo(' sender: Address - Must be authorized to delete'); printInfo(' appId: bigint - Application to delete'); printInfo(''); printInfo('OnApplicationComplete enum values:'); printInfo(' NoOp (0) - Call without state changes'); printInfo(' OptIn (1) - Opt into app (creates local state)'); printInfo(' CloseOut (2) - Close out of app (removes local state)'); printInfo(' ClearState (3) - Force clear local state (always succeeds)'); printInfo(' UpdateApplication (4) - Update app code'); printInfo(' DeleteApplication (5) - Delete the app'); printInfo(''); printInfo('Reading app state:'); printInfo(' algorand.app.getGlobalState(appId) - Get global state'); printInfo(' algorand.app.getLocalState(appId, address) - Get local state'); printInfo(' algorand.app.getById(appId) - Get app info including state');
printSuccess('Send Application Operations example completed!');}
main().catch(error => { printError(`Unhandled error: ${error instanceof Error ? error.message : String(error)}`); process.exit(1);});Other examples in Algorand Client
Section titled “Other examples in Algorand Client”- Client Instantiation
- AlgoAmount Utility
- Signer Configuration
- Suggested Params Configuration
- Account Manager
- Send Payment
- Send Asset Operations
- Send Application Operations
- Create Transaction (Unsigned Transactions)
- Transaction Composer (Atomic Transaction Groups)
- Asset Manager
- App Manager
- App Deployer
- Client Manager
- Error Transformers
- Transaction Leases