App management | Algorand Developer Portal

App management

App management is a higher-order use case capability provided by AlgoKit Utils that builds on top of the core capabilities. It allows you to create, update, delete, call (ABI and otherwise) smart contract apps and the metadata associated with them (including state and boxes).

AppManager

The AppManager is a class that is used to manage app information.

To get an instance of AppManager you can use either AlgorandClient via algorand.app or instantiate it directly (passing in an algod client instance):

import { AppManager } from '@algorandfoundation/algokit-utils/types/app-manager';

const appManager = new AppManager(algod);

Calling apps

App Clients

The recommended way of interacting with apps is via Typed app clients or if you can’t use a typed app client then an untyped app client. The methods shown on this page are the underlying mechanisms that app clients use and are for advanced use cases when you want more control.

Calling an app

When calling an app there are two types of transactions:

Calling an app involves providing some common parameters and some parameters that will depend on the type of app call (create vs update vs other) per below sections.

When sending transactions directly via AlgorandClient the SingleSendTransactionResult return value is expanded with extra fields depending on the type of app call:

There is a static method on AppManager that allows you to parse an ABI return value from an algod transaction confirmation:

const confirmation = modelsv2.PendingTransactionResponse.from_obj_for_encoding(

await algod.pendingTransactionInformation(transactionId).do(),
);

const abiReturn = AppManager.getABIReturn(confirmation, abiMethod);

Creation

To create an app via a raw app transaction you can use algorand.send.appCreate(params) (immediately send a single app creation transaction), algorand.createTransaction.appCreate(params) (construct an app creation transaction), or algorand.newGroup().addAppCreate(params) (add app creation to a group of transactions) per AlgorandClient transaction semantics.

To create an app via an ABI method call you can use algorand.send.appCreateMethodCall(params) (immediately send a single app creation transaction), algorand.createTransaction.appCreateMethodCall(params) (construct an app creation transaction), or algorand.newGroup().addAppCreateMethodCall(params) (add app creation to a group of transactions).

The base type for specifying an app creation transaction is AppCreateParams (extended as AppCreateMethodCall for ABI method call version), which has the following parameters in addition to the common parameters:

If you pass in approvalProgram or clearStateProgram as a string then it will automatically be compiled using Algod and the compilation result will be available via algorand.app.getCompilationResult (including the source map). To skip this behaviour you can pass in the compiled TEAL as Uint8Array.

// Basic raw example

const result = await algorand.send.appCreate({ sender: 'CREATORADDRESS', approvalProgram: 'TEALCODE', clearStateProgram: 'TEALCODE' })

const createdAppId = result.appId

// Advanced raw example

await algorand.send.appCreate({

sender: 'CREATORADDRESS',

approvalProgram: "TEALCODE",

clearStateProgram: "TEALCODE",

schema: {

globalInts: 1,

globalByteSlices: 2,

localInts: 3,

localByteSlices: 4

},

extraProgramPages: 1,

onComplete: algosdk.OnApplicationComplete.OptInOC,

args: [new Uint8Array(1, 2, 3, 4)]

accountReferences: ["ACCOUNT_1"]

appReferences: [123n, 1234n]

assetReferences: [12345n]

boxReferences: ["box1", {appId: 1234n, name: "box2"}]

lease: 'lease',

note: 'note',

// You wouldn't normally set this field

firstValidRound: 1000n,

validityWindow: 10,

extraFee: (1000).microAlgo(),

staticFee: (1000).microAlgo(),

// Max fee doesn't make sense with extraFee AND staticFee

//  already specified, but here for completeness

maxFee: (3000).microAlgo(),

// Signer only needed if you want to provide one,

//  generally you'd register it with AlgorandClient

//  against the sender and not need to pass it in

signer: transactionSigner,

maxRoundsToWaitForConfirmation: 5,

suppressLog: true,

})

// Basic ABI call example

const method = new ABIMethod({

name: 'method',

args: [{ name: 'arg1', type: 'string' }],

returns: { type: 'string' },

})

const result = await algorand.send.appCreateMethodCall({

sender: 'CREATORADDRESS',

approvalProgram: 'TEALCODE',

clearStateProgram: 'TEALCODE',

method: method,

args: ["arg1_value"]

})

const createdAppId = result.appId

Updating

To update an app via a raw app transaction you can use algorand.send.appUpdate(params) (immediately send a single app update transaction), algorand.createTransaction.appUpdate(params) (construct an app update transaction), or algorand.newGroup().addAppUpdate(params) (add app update to a group of transactions) per AlgorandClient transaction semantics.

To create an app via an ABI method call you can use algorand.send.appUpdateMethodCall(params) (immediately send a single app update transaction), algorand.createTransaction.appUpdateMethodCall(params) (construct an app update transaction), or algorand.newGroup().addAppUpdateMethodCall(params) (add app update to a group of transactions).

The base type for specifying an app update transaction is AppUpdateParams (extended as AppUpdateMethodCall for ABI method call version), which has the following parameters in addition to the common parameters:

// Basic raw example

await algorand.send.appUpdate({ sender: 'SENDERADDRESS', approvalProgram: 'TEALCODE', clearStateProgram: 'TEALCODE' })

// Advanced raw example

await algorand.send.appUpdate({

sender: 'SENDERADDRESS',

approvalProgram: "TEALCODE",

clearStateProgram: "TEALCODE",

onComplete: algosdk.OnApplicationComplete.UpdateApplicationOC,

args: [new Uint8Array(1, 2, 3, 4)]

accountReferences: ["ACCOUNT_1"]

appReferences: [123n, 1234n]

assetReferences: [12345n]

boxReferences: ["box1", {appId: 1234n, name: "box2"}]

lease: 'lease',

note: 'note',

// You wouldn't normally set this field

firstValidRound: 1000n,

validityWindow: 10,

extraFee: (1000).microAlgo(),

staticFee: (1000).microAlgo(),

// Max fee doesn't make sense with extraFee AND staticFee

//  already specified, but here for completeness

maxFee: (3000).microAlgo(),

// Signer only needed if you want to provide one,

//  generally you'd register it with AlgorandClient

//  against the sender and not need to pass it in

signer: transactionSigner,

maxRoundsToWaitForConfirmation: 5,

suppressLog: true,

})

// Basic ABI call example

const method = new ABIMethod({

name: 'method',

args: [{ name: 'arg1', type: 'string' }],

returns: { type: 'string' },

})

await algorand.send.appUpdateMethodCall({

sender: 'SENDERADDRESS',

approvalProgram: 'TEALCODE',

clearStateProgram: 'TEALCODE',

method: method,

args: ["arg1_value"]

})

Deleting

To delete an app via a raw app transaction you can use algorand.send.appDelete(params) (immediately send a single app deletion transaction), algorand.createTransaction.appDelete(params) (construct an app deletion transaction), or algorand.newGroup().addAppDelete(params) (add app deletion to a group of transactions) per AlgorandClient transaction semantics.

To delete an app via an ABI method call you can use algorand.send.appDeleteMethodCall(params) (immediately send a single app deletion transaction), algorand.createTransaction.appDeleteMethodCall(params) (construct an app deletion transaction), or algorand.newGroup().addAppDeleteMethodCall(params) (add app deletion to a group of transactions).

The base type for specifying an app deletion transaction is AppDeleteParams (extended as AppDeleteMethodCall for ABI method call version), which has the following parameters in addition to the common parameters:

// Basic raw example

await algorand.send.appDelete({ sender: 'SENDERADDRESS' })

// Advanced raw example

await algorand.send.appDelete({

sender: 'SENDERADDRESS',

onComplete: algosdk.OnApplicationComplete.DeleteApplicationOC,

args: [new Uint8Array(1, 2, 3, 4)]

accountReferences: ["ACCOUNT_1"]

appReferences: [123n, 1234n]

assetReferences: [12345n]

boxReferences: ["box1", {appId: 1234n, name: "box2"}]

lease: 'lease',

note: 'note',

// You wouldn't normally set this field

firstValidRound: 1000n,

validityWindow: 10,

extraFee: (1000).microAlgo(),

staticFee: (1000).microAlgo(),

// Max fee doesn't make sense with extraFee AND staticFee

//  already specified, but here for completeness

maxFee: (3000).microAlgo(),

// Signer only needed if you want to provide one,

//  generally you'd register it with AlgorandClient

//  against the sender and not need to pass it in

signer: transactionSigner,

maxRoundsToWaitForConfirmation: 5,

suppressLog: true,

})

// Basic ABI call example

const method = new ABIMethod({

name: 'method',

args: [{ name: 'arg1', type: 'string' }],

returns: { type: 'string' },

})

await algorand.send.appDeleteMethodCall({

sender: 'SENDERADDRESS',

method: method,

args: ["arg1_value"]

})

Calling

To call an app via a raw app transaction you can use algorand.send.appCall(params) (immediately send a single app call transaction), algorand.createTransaction.appCall(params) (construct an app call transaction), or algorand.newGroup().addAppCall(params) (add app deletion to a group of transactions) per AlgorandClient transaction semantics.

To call an app via an ABI method call you can use algorand.send.appCallMethodCall(params) (immediately send a single app call transaction), algorand.createTransaction.appCallMethodCall(params) (construct an app call transaction), or algorand.newGroup().addAppCallMethodCall(params) (add app call to a group of transactions).

The base type for specifying an app call transaction is AppCallParams (extended as AppCallMethodCall for ABI method call version), which has the following parameters in addition to the common parameters:

// Basic raw example

await algorand.send.appCall({ sender: 'SENDERADDRESS' })

// Advanced raw example

await algorand.send.appCall({

sender: 'SENDERADDRESS',

onComplete: algosdk.OnApplicationComplete.OptInOC,

args: [new Uint8Array(1, 2, 3, 4)]

accountReferences: ["ACCOUNT_1"]

appReferences: [123n, 1234n]

assetReferences: [12345n]

boxReferences: ["box1", {appId: 1234n, name: "box2"}]

lease: 'lease',

note: 'note',

// You wouldn't normally set this field

firstValidRound: 1000n,

validityWindow: 10,

extraFee: (1000).microAlgo(),

staticFee: (1000).microAlgo(),

// Max fee doesn't make sense with extraFee AND staticFee

//  already specified, but here for completeness

maxFee: (3000).microAlgo(),

// Signer only needed if you want to provide one,

//  generally you'd register it with AlgorandClient

//  against the sender and not need to pass it in

signer: transactionSigner,

maxRoundsToWaitForConfirmation: 5,

suppressLog: true,

})

// Basic ABI call example

const method = new ABIMethod({

name: 'method',

args: [{ name: 'arg1', type: 'string' }],

returns: { type: 'string' },

})

await algorand.send.appCallMethodCall({

sender: 'SENDERADDRESS',

method: method,

args: ["arg1_value"]

})

Accessing state

Global state

To access local state you can use the following method from an AppManager instance:

const globalState = await algorand.app.getGlobalState(12345n);

Global state is parsed from the underlying algod response via the following static method from AppManager:

const globalAppState = /* value from algod */

const appState = AppManager.decodeAppState(globalAppState)

const keyAsBinary = appState['value1'].keyRaw

const keyAsBase64 = appState['value1'].keyBase64

if (typeof appState['value1'].value === 'string') {

const valueAsString = appState['value1'].value

const valueAsBinary = appState['value1'].valueRaw

const valueAsBase64 = appState['value1'].valueBase64

} else {

const valueAsNumberOrBigInt = appState['value1'].value
}

Local state

To access local state you can use the following method from an AppManager instance:

const localState = await algorand.app.getLocalState(12345n, 'ACCOUNTADDRESS');

Boxes

To access and parse box values and names for an app you can use the following methods from an AppManager instance:

const appId = 12345n;

const boxName: BoxReference = 'my-box';

const boxName2: BoxReference = 'my-box2';

const boxNames = algorand.app.getBoxNames(appId);

const boxValue = algorand.app.getBoxValue(appId, boxName);

const boxValues = algorand.app.getBoxValues(appId, [boxName, boxName2]);

const boxABIValue = algorand.app.getBoxValueFromABIType(appId, boxName, algosdk.ABIStringType);

const boxABIValues = algorand.app.getBoxValuesFromABIType(

appId,

[boxName, boxName2],

algosdk.ABIStringType,
);

Getting app information

To get reference information and metadata about an existing app you can use the following methods:

Common app parameters

When interacting with apps (creating, updating, deleting, calling), there are some CommonAppCallParams that you will be able to pass in to all calls in addition to the common transaction parameters:

When making an ABI call, the args parameter is replaced with a different type and there is also a method parameter per the AppMethodCall type:

Box references

Referencing boxes can by done by either BoxIdentifier (which identifies the name of the box and app ID 0 will be used (i.e. the current app)) or BoxReference:

/**
 * Something that identifies an app box name - either a:
 *  * `Uint8Array` (the actual binary of the box name)
 *  * `string` (that will be encoded to a `Uint8Array`)
 *  * `TransactionSignerAccount` (that will be encoded into the
 *    public key address of the corresponding account)
 */
export type BoxIdentifier = string | Uint8Array | TransactionSignerAccount;

/**
 * A grouping of the app ID and name identifier to reference an app box.
 */
export interface BoxReference {
  /**
   * A unique application id
   */
  appId: bigint;

/**
   * Identifier for a box name
   */
  name: BoxIdentifier;
}

Compilation

The AppManager class allows you to compile TEAL code with caching semantics that allows you to avoid duplicate compilation and keep track of source maps from compiled code.

If you call algorand.app.compileTeal(tealCode) then the compilation result will be stored and retrievable from algorand.app.getCompilationResult(tealCode).

const tealCode = 'return 1';

const compilationResult = await algorand.app.compileTeal(tealCode);

// ...

const previousCompilationResult = algorand.app.getCompilationResult(tealCode);