Skip to content

Send Application Operations

← Back to Algorand Client

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

From the repository root:

Terminal window
cd examples
npm run example algorand_client/08-send-app-ops.ts

View source on GitHub

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 operations
const 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)
})