Skip to content

创建文章

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

App client

App client

Application client that works with ARC-0032 application spec defined smart contracts (e.g. via Beaker).

App client is a higher-order use case capability provided by AlgoKit Utils that builds on top of the core capabilities, particularly App deployment and App management. It allows you to access a high productivity application client that works with ARC-0032 application spec defined smart contracts, which you can use to create, update, delete, deploy and call a smart contract and access state data for it.

To see some usage examples check out the automated tests.

Design

The design for the app client is based on a wrapper for parsing an ARC-0032 application spec and wrapping the App deployment functionality and corresponding design. It's also heavily inspired by beaker-ts, which this library aims to eventually replace.

Creating an application client

To create an application you can either use algokit.getAppClient(appDetails, algod) or import { ApplicationClient } from '@algorandfoundation/algokit-utils/types/app-client' and new ApplicationClient(appDetails, algod)

The appDetails parameter is of type AppSpecAppDetails, which contains some core properties and then one of two key mechanisms to specify the app to target.

  • Core parameters - Core parameters that can always be applied
  • app: AppSpec | string - Either the parsed ARC-0032 AppSpec, or a raw JSON string which will get parsed as an AppSpec
  • sender?: SendTransactionFrom - Optional sender to send/sign all transactions with (if left out then individual methods must have a sender passed to them)
  • params?: SuggestedParams - Optional sending parameters if you want to avoid an extra call to algod
  • App target - How to resolve an existing app (if one exists), which can either be:
  • ResolveAppById - When you want to resolve an existing app by app ID, which consists of the following parameters:
    • id: number - The app ID, which should be set as 0 if you have yet to deploy the contract
    • name? string - The optional name to mark the contract with if you are deploying it, otherwise contract.name is used from the app spec
  • ResolveAppByCreatorAndName - When you want to resolve an existing app by name for a given creator account, which consists of the following parameters:
    • creatorAddress: string - The address of the creator account of the app for which to search for the deployed app under
    • name?: string - An overridden name to identify the contract with, otherwise contract.name is used from the app spec
    • And either:
    • indexer: Indexer - An indexer instance so the existing app deployments can be queried
    • existingDeployments: AppLookup - The result of an existing indexer lookup to generate an app lookup, which avoids extra indexer calls from being made

Creating, updating, deploying and deleting the app

Once you have an application client you can perform the following actions related to creating and managing the lifecycle of an app:

  • compile(compilationParams?) - Allows you to compile the application (approval and clear program)), including deploy-time parameter replacements and deploy-time immutability and permanence control; it returns the compiled AVM code and source maps
  • deploy(deploymentParams?) - Allows you to perform an idempotent (safely retryable) deployment of the smart contract app per the design of deployApp
  • create(createParams?) - Allows you to perform a creation of the smart contract app
  • update(updateParams?) - Allows you to perform an update of the (existing) smart contract app
  • delete(deleteParams?) - Allows you to delete the (existing) smart contract app

The input payload for create and update are the same and are a union of AppClientCallParams and AppClientCompilationParams. The input payload for delete is AppClientCallParams. The input payload for deploy is AppClientDeployParams.

The return payload for these methods directly matches the equivalent underlying App management / App deployment methods (since these methods are wrappers):

Calling the app

To make a call to a smart contract you can use the following methods (which determine the on complete action that the call will use):

  • call(call?) - A normal (noop on completion action) call
  • optIn(call?) - An opt-in call
  • closeOut(call?) - A close-out call
  • clearState(call?) - A clear state call (note: calls the clear program)
  • callOfType(call, callType) - Make a call with a specified call type

These calls will only work if the Application Client knows the ID of the app, which will occur if:

  • The app ID is passed into the constructor;
  • You have passed creatorAccount and the smart contract name to the constructor and the contract already exists; or
  • You have called create or deploy using that Application Client.

The input payload for all of these calls is the same as delete; AppClientCallParams.

The return payload for all of these is the same as callApp.

Getting a reference to the app

To get reference information for the app from outside the Application Client you can call getAppReference(). If you passed the creatorAddress and app name to the constructor then this method will return the full AppMetadata per getCreatorAppsByName. If you just passed in the app ID or used create rather than deploy then you will just receive an AppReference (which is also a sub-type of the AppMetadata):

  • appId: number - The id of the app
  • appAddress: string - The Algorand address of the account associated with the app

Funding the app account

Often there is a need to fund an app account to cover minimum balance requirements for boxes and other scenarios. There is a helper method that will do this for you fundAppAccount(fundParams).

The input parameters can either be:

  • An AlgoAmount value (note: requires sender to be passed into the constructor)
  • A FundAppAccountParams, which has the following properties:
  • amount: AlgoAmount - The amount to fund
  • sender?: SendTransactionFrom - The sender/signer to use; if unspecified then the sender that was passed into the constructor of the Application Client is used
  • note?: TransactionNote - The transaction note to use when issuing the transaction
  • sendParams?: SendTransactionParams - The transaction sending configuration

This call will only work if the Application Client knows the ID of the app, which will occur if:

  • The app ID is passed into the constructor;
  • You have passed creatorAccount and the smart contract name to the constructor and the contract already exists; or
  • You have called create or deploy using that Application Client.

If you are passing the funding payment in as an ABI argument so it can be validated by the ABI method then you'll want to issue the skipSending configuration. That might look something like this as an example:

const result = await appClient.call({
  method: 'bootstrap',
  methodArgs: {
    args: [
      appClient.fundAppAccount({
        amount: algokit.microAlgos(200_000),
        sendParams: { skipSending: true },
      }),
    ],
    boxes: ['Box1'],
  },
})

Reading state

There are various methods defined that let you read state from the smart contract app:

These calls will only work if the Application Client knows the ID of the app, which will occur if:

  • The app ID is passed into the constructor;
  • You have passed creatorAccount and the smart contract name to the constructor and the contract already exists; or
  • You have called create or deploy using that Application Client.

Handling logic errors and diagnosing errors

Often when calling a smart contract during development you will get logic errors that cause an exception to throw. This may be because of a failing assertion, a lack of fees, exhaustion of opcode budget, or any number of other reasons.

When this occurs, you will generally get an error that looks something like: TransactionPool.Remember: transaction {TRANSACTION_ID}: logic eval error: {ERROR_MESSAGE}. Details: pc={PROGRAM_COUNTER_VALUE}, opcodes={LIST_OF_OP_CODES}.

The information in that error message can be parsed and when combined with the source map from compilation you can expose debugging information that makes it much easier to understand what's happening.

The Application Client automatically provides this functionality for all smart contract calls. It also exposes a function that can be used for any custom calls you manually construct and need to add into your own try/catch exposeLogicError(e: Error, isClear?: boolean).

When an error is thrown then the resulting error that is re-thrown will be a LogicError object, which has the following fields:

  • message: string - The formatted error message {ERROR_MESSAGE}. at:{TEAL_LINE}. {ERROR_DESCRIPTION}
  • stack: string - A stack trace of the TEAL code showing where the error was with the 5 lines either side of it
  • led: LogicErrorDetails - The parsed logic error details from the error message, with the following properties:
  • txId: string - The transaction ID that triggered the error
  • pc: number - The program counter
  • msg: string - The raw error message
  • desc: string - The full error description
  • traces: Record<string, unknown>[] - Any traces that were included in the error
  • program: string[] - The TEAL program split by line
  • teal_line: number - The line number in the TEAL program that triggered the error

Note: This information will only show if the Application Client has a source map. This will occur if:

  • You have called create, update or deploy
  • You have called importSourceMaps(sourceMaps) and provided the source maps (which you can get by calling exportSourceMaps() after calling create, update or deploy and it returns a serialisable value)

If you want to go a step further and automatically issue a dry run transaction when there is an error when an ABI method is called you can turn on debug mode:

algokit.Config.configure({ debug: true })

Note

The "dry run" feature has been deprecated and is now replaced by the "simulation" feature. Please refer to the Simulation Documentation for more details.

If you do that then the exception will have the traces property within the underlying exception will have key information from the simulation within it and this will get populated into the led.traces property of the thrown error.

AppClientCallParams

All methods that call the smart contract apart from deploy make use of this type. It consists of the following core properties, all of which are optional:

  • sender?: SendTransactionFrom - The sender/signer to use; if unspecified then the sender that was passed into the constructor of the Application Client is used
  • note?: TransactionNote - The transaction note to use when issuing the transaction
  • sendParams?: SendTransactionParams - The transaction sending configuration

In addition to these parameters, it may specify call arguments parameters (AppClientCallArgs).

AppClientCallArgs

Whenever an app call is specified, including within deploy this type specifies the arguments. There are two forms you can use:

  • Raw call - Directly specifies the values that will be populated onto an algosdk.Transaction
  • ABI call - Specifies a ARC-0004 ABI call along with relevant values (like boxes) that will be directly populated onto an algosdk.Transaction. Consists of the ABI app call args type with the method parameter replaced with a string (since the Application Client only needs it as a string):
  • method: string - The name of the method (e.g. hello) or the ABI signature of the method (e.g. hello(string)string) for when you have multiple methods with the same name and need to differentiate between them
  • methodArgs?: ABIAppCallArg[] - An array of arguments to pass into the ABI method
  • boxes: (BoxReference | BoxIdentifier | algosdk.BoxReference)[] - Any boxes to load to the boxes array
  • lease: string | Uint8Array: A lease to assign to the transaction to enforce a mutually exclusive transaction (useful to prevent double-posting and other scenarios)

If you want to get call args for manually populating into an algosdk.Transaction you can use the getCallArgs method on Application Client.

Also, if you want to manually construct an ABI call (e.g. to use with AtomicTransactionComposer directly) then you can use getABIMethod and/or getABIMethodParams

AppClientCompilationParams

When calling create or update there are extra parameter that need to be passed to facilitate the compilation of the code in addition to the other parameters in AppClientCallParams:

AppClientDeployParams

When calling deploy the AppClientDeployParams type defines the input parameters, all of which are optional. This type closely models the semantics of deployApp.

  • version?: string - The version of the contract, uses "1.0" by default
  • sender?: SendTransactionFrom - The sender/signer to use; if unspecified then the sender that was passed into the constructor of the Application Client is used
  • sendParams?: SendTransactionParams - The transaction sending configuration
  • deployTimeParams?: TealTemplateParams - Any deploy-time parameters to replace in the TEAL code
  • allowUpdate?: boolean - Whether or not to allow updates in the contract using the deploy-time updatability control if present in your contract; if this is not specified then it will automatically be determined based on the AppSpec definition (if there is an update method)
  • allowDelete?: boolean - Whether or not to allow deletes in the contract using the deploy-time deletability control if present in your contract; if this is not specified then it will automatically be determined based on the AppSpec definition (if there is a delete method)
  • onSchemaBreak?: 'replace' | 'fail' | OnSchemaBreak determines what should happen if a breaking change to the schema is detected (e.g. if you need more global or local state that was previously requested when the contract was originally created)
  • onUpdate?: 'update' | 'replace' | 'fail' | OnUpdate determines what should happen if an update to the smart contract is detected (e.g. the TEAL code has changed since last deployment)
  • createArgs?: AppClientCallArgs - The args to use if a create is needed
  • updateArgs?: AppClientCallArgs - The args to use if an update is needed
  • deleteArgs?: AppClientCallArgs - The args to use if a delete is needed