AlgoKit Clients
When building on Algorand, you need reliable ways to communicate with the blockchain—sending transactions, interacting with smart contracts, and accessing blockchain data. AlgoKit Utils clients provide straightforward, developer-friendly interfaces for these interactions, reducing the complexity typically associated with blockchain development. This guide explains how to use these clients to simplify common Algorand development tasks, whether you’re sending a basic transaction or deploying complex smart contracts.
AlgoKit offers two main types of clients to interact with the Algorand blockchain:
-
Algorand Client - A general-purpose client for all Algorand interactions, including:
- Crafting, grouping, and sending transactions through a fluent interface of chained methods
- Accessing network services through REST API clients for algod, indexer, and kmd
- Configuring connection and transaction parameters with sensible defaults and optional overrides
-
Typed Application Client - A specialized, auto-generated client for interacting with specific smart contracts:
Let’s explore each client type in detail.
Algorand Client: Gateway to the Blockchain
The AlgorandClient
serves as your primary entry point for all Algorand operations. Think of it as your Swiss Army knife for blockchain interactions.
Getting Started with AlgorandClient
You can create an AlgorandClient instance in several ways, depending on your needs:
// Point to the network configured through environment variables or // if no environment variables it will point to the default LocalNet // configuration const client1 = AlgorandClient.fromEnvironment() // Point to default LocalNet configuration const client2 = AlgorandClient.defaultLocalNet() // Point to TestNet using AlgoNode free tier const client3 = AlgorandClient.testNet() // Point to MainNet using AlgoNode free tier const client4 = AlgorandClient.mainNet() // Point to a pre-created algod client const client5 = AlgorandClient.fromClients({ algod }) // Point to pre-created algod, indexer and kmd clients const client6 = AlgorandClient.fromClients({ algod, indexer, kmd }) // Point to custom configuration for algod const client7 = AlgorandClient.fromConfig({ algodConfig: { server: 'http://localhost', port: '4001', token: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', }, }) // Point to custom configuration for algod, indexer and kmd const client8 = AlgorandClient.fromConfig({ algodConfig: algodConfig, indexerConfig: indexerConfig, kmdConfig: kmdConfig, })
# Point to the network configured through environment variables or # if no environment variables it will point to the default LocalNet # configuration algorand_client = AlgorandClient.from_environment() # Point to default LocalNet configuration algorand_client = AlgorandClient.default_localnet() # Point to TestNet using AlgoNode free tier algorand_client = AlgorandClient.testnet() # Point to MainNet using AlgoNode free tier algorand_client = AlgorandClient.mainnet() # Point to a pre-created algod client algorand_client = AlgorandClient.from_clients(algod) # Point to pre-created algod, indexer and kmd clients algorand_client = AlgorandClient.from_clients(algod, indexer, kmd) # Point to custom configuration for algod algorand_client = AlgorandClient.from_config( AlgoClientNetworkConfig( server="http://localhost", token="4001", ) ) # Point to custom configuration for algod, indexer and kmd algorand_client = AlgorandClient.from_config( algod_config, indexer_config, kmd_config )
These factory methods make it easy to connect to different Algorand networks without manually configuring connection details.
Once you have an AlgorandClient
instance, you can access the REST API clients for the various Algorand APIs via the AlgorandClient.client
property:
const algorandClient = AlgorandClient.fromEnvironment()
const algodClient = algorandClient.client.algod const indexerClient = algorandClient.client.indexer const kmdClient = algorandClient.client.kmd
algod = algorand_client.client.algod indexer = algorand_client.client.indexer kmd = algorand_client.client.kmd
For more information about the functionalities of the REST API clients, refer to the following pages:
Understanding AlgorandClient’s Stateful Design
The AlgorandClient
is “stateful”, meaning that it caches various information that are reused multiple times. This allows the AlgorandClient
to avoid redundant requests to the blockchain and to provide a more efficient interface for interacting with the blockchain. This is an important concept to understand before using the AlgorandClient
.
Account Signer Caching
When sending transactions, you need to sign them with a private key. AlgorandClient
can cache these signing capabilities, eliminating the need to provide signing information for every transaction, as you can see in the following example:
/* * If you don't want the Algorand client to cache the signer, * you can manually provide the signer. */ await algorand.send.payment({ sender: randomAccountA, receiver: randomAccountB, amount: AlgoAmount.Algo(1), signer: randomAccountA.signer, // The signer must be manually provided })
""" If you don't want the Algorand client to cache the signer, you can manually provide the signer. """ algorand_client.send.payment( PaymentParams( sender=account_a.address, receiver=account_b.address, amount=AlgoAmount(algo=1), signer=account_a.signer, # The signer must be manually provided ) )
The same example, but with different approaches to signer caching demonstrated:
/* * By setting signers of accounts to the algorand client, the client will cache the signers * and use them to sign transactions when the sender is one of the accounts. */
// If no signer is provided, the client will use the default signer algorand.setDefaultSigner(randomAccountA.signer)
// If you have an address and a signer, use this method to set the signer algorand.setSigner(randomAccountA.addr, randomAccountA.signer)
// If you have a `SigningAccount` object, use this method to set the signer algorand.setSignerFromAccount(randomAccountA)
/* * The Algorand client can directly send this payment transaction without * needing a signer because it is tracking the signer for account_a. */ await algorand.send.payment({ sender: randomAccountA, receiver: randomAccountB, amount: AlgoAmount.Algo(1), })
""" By setting signers of accounts to the algorand client, the client will cache the signers and use them to sign transactions when the sender is one of the accounts. """
# If no signer is provided, the client will use the default signer algorand_client.set_default_signer(account_a.signer)
# If you have an address and a signer, use this method to set the signer algorand_client.set_signer(account_a.address, account_a.signer)
# If you have a `SigningAccount` object, use this method to set the signer algorand_client.set_signer_from_account(account_a)
""" The Algorand client can directly send this payment transaction without needing a signer because it is tracking the signer for account_a. """ algorand_client.send.payment( PaymentParams( sender=account_a.address, receiver=account_b.address, amount=AlgoAmount(algo=1), ) )
This caching mechanism simplifies your code, especially when sending multiple transactions from the same account.
Suggested Parameter Caching
AlgorandClient
caches network provided transaction values (suggested parameters) for you automatically to reduce network traffic. It has a set of default configurations that control this behavior, but you have the ability to override and change the configuration of this behavior.
What Are Suggested Parameters?
In Algorand, every transaction requires a set of network-specific parameters that define how the transaction should be processed. These “suggested parameters” include:
- Fee: The transaction fee (in microAlgos)
- First Valid Round: The first blockchain round where the transaction can be processed
- Last Valid Round: The last blockchain round where the transaction can be processed (after this, the transaction expires)
- Genesis ID: The identifier for the Algorand network (e.g., “mainnet-v1.0”)
- Genesis Hash: The hash of the genesis block for the network
- Min Fee: The minimum fee required by the network
These parameters are called “suggested” because the network provides recommended values, but developers can modify them (for example, to increase the fee during network congestion).
Why Cache These Parameters?
Without caching, your application would need to request these parameters from the network before every transaction, which:
- Increases latency: Each transaction would require an additional network request
- Increases network load: Both for your application and the Algorand node
- Slows down user experience: Especially when creating multi-transaction groups
Since these parameters only change every few seconds (when new blocks are created), repeatedly requesting them wastes resources.
How Parameter Caching Works
The AlgorandClient
automatically:
- Requests suggested parameters when needed
- Caches them for a configurable time period (default: 3 seconds)
- Reuses the cached values for subsequent transactions
- Refreshes the cache when it expires
Customized Parameter Caching
AlgorandClient
has a set of default configurations that control this behavior, but you have the ability to override and change the configuration of this behavior:
algorand.setDefaultValidityWindow(validityWindow)
- Set the default validity window (number of rounds from the current known round that the transaction will be valid to be accepted for), having a smallish value for this is usually ideal to avoid transactions that are valid for a long future period and may be submitted even after you think it failed to submit if waiting for a particular number of rounds for the transaction to be successfully submitted. The validity window defaults to 10, except in automated testing where it’s set to 1000 when targeting LocalNet.algorand.setSuggestedParams(suggestedParams, until?)
- Set the suggested network parameters to use (optionally until the given time)algorand.setSuggestedParamsTimeout(timeout)
- Set the timeout that is used to cache the suggested network parameters (by default 3 seconds)algorand.getSuggestedParams()
- Get the current suggested network parameters object, either the cached value, or if the cache has expired a fresh value
/* * Sets the default validity window for transactions. * @param validityWindow The number of rounds between the first and last valid rounds * @returns The `algorand` so method calls can be chained */ algorand.setDefaultValidityWindow(1000)
/* * Get suggested params for a transaction (either cached or from algod if the cache is stale or empty) */ const sp = await algorand.getSuggestedParams()
// The suggested params can be modified like below sp.flatFee = true sp.fee = 2000
/* * Sets a cache value to use for suggested params. Use this method to use modified suggested params for * the next transaction. * @param suggestedParams The suggested params to use * @param until A timestamp until which to cache, or if not specified then the timeout is used * @returns The `algorand` so method calls can be chained */ algorand.setSuggestedParamsCache(sp)
/* * Sets the timeout for caching suggested params. If set to 0, the Algorand client * will request suggested params from the algod client every time. * @param timeout The timeout in milliseconds * @returns The `algorand` so method calls can be chained */ algorand.setSuggestedParamsCacheTimeout(0)
""" Sets the default validity window for transactions.
:param validity_window: The number of rounds between the first and last valid rounds :return: The `AlgorandClient` so method calls can be chained """ algorand_client.set_default_validity_window(1000)
""" Get suggested params for a transaction (either cached or from algod if the cache is stale or empty) """ sp = algorand_client.get_suggested_params()
# The suggested params can be modified like below sp.flat_fee = True sp.fee = 2000
""" Sets a cache value to use for suggested params. Use this method to use modified suggested params for the next transaction.
:param suggested_params: The suggested params to use :param until: A timestamp until which to cache, or if not specified then the timeout is used :return: The `AlgorandClient` so method calls can be chained """ algorand_client.set_suggested_params_cache(sp)
""" Sets the timeout for caching suggested params. If set to 0, the Algorand client will request suggested params from the algod client every time.
:param timeout: The timeout in milliseconds :return: The `AlgorandClient` so method calls can be chained """ algorand_client.set_suggested_params_cache_timeout(0)
By understanding and properly configuring suggested parameter caching, you can optimize your application’s performance while ensuring transactions are processed correctly by the Algorand network.
Typed App Clients: Smart Contract Interaction Simplified
While the AlgorandClient
handles general blockchain interactions, typed app clients provide specialized interfaces for deployed applications. These clients are generated from contract specifications (ARC-56/ARC-32) and offer:
- Type-safe method calls
- Automatic parameter validation
- IntelliSense code completion support
Generating App Clients
The relevant smart contract’s app client is generated using the ARC56/ARC32 ABI file. There are two different ways to generate an application client for a smart contract:
1. Using the AlgoKit Build CLI Command
When you are using the AlgoKit smart contract template for your project, compiling your ARC4 smart contract written in either TypeScript or Python will automatically generate the TypeScript or Python application client for you depending on what language you chose for contract interaction. Simply run the following command to generate the artifacts including the typed application client:
algokit project run build
After running the command, you should see the following artifacts generated in the artifacts
directory under the smart_contracts
directory:
Directoryhello_world
- hello_world_client.py
- HelloWorld.approval.puya.map
- HelloWorld.approval.teal
- HelloWorld.arc56.json
- HelloWorld.clear.puya.map
- HelloWorld.clear.puya.teal
2. Using the AlgoKit Generate CLI Command
There is also an AlgoKit CLI command to generate the app client for a smart contract. You can also use it to define custom commands inside of the .algokit.toml
file in your project directory.
Note that you can specify what language you want for the application clients with the file extensions .ts
for TypeScript and .py
for Python.
# To output a single arc32.json to a TypeScript typed app client:algokit generate client path/to/arc32.json --output client.ts
# To process multiple arc32.json in a directory structure and output to a TypeScript app client for each in the current directory:algokit generate client smart_contracts/artifacts --output {contract_name}.ts
# To process multiple arc32.json in a directory structure and output to a Python client alongside each arc32.json:algokit generate client smart_contracts/artifacts --output {app_spec_path}/client.py
When compiled, all ARC-4 smart contracts generate an arc56.json
or arc32.json
file depending on what app spec was used. This file contains the smart contract’s extended ABI, which follows the ARC-32 standard.
Working with a Typed App Client Object
To get an instance of a typed client you can use an AlgorandClient
instance or a typed app Factory
instance.
The approach to obtaining a client instance depends on how many app clients you require for a given app spec and if the app has already been deployed, which is summarised below:
App is Already Deployed
/* Get typed app client by id */
//For single app client instance let appClient = await algorand.client.getTypedAppClientById(HelloWorldClient, { appId: 1234n, }) // or appClient = new HelloWorldClient({ algorand, appId: 1234n, })
// For multiple app client instances use the factory const factory = algorand.client.getTypedAppFactory(HelloWorldFactory) // or const factory2 = new HelloWorldFactory({ algorand })
const appClient1 = await factory.getAppClientById({ appId: 1234n }) const appClient2 = await factory.getAppClientById({ appId: 4321n })
/* Get typed app client by creator and name */
// For single app client instance let appClientByCreator = await algorand.client.getTypedAppClientByCreatorAndName(HelloWorldClient, { creatorAddress: randomAccountA.addr, appName: 'contract-name', // ... }) // or appClientByCreator = await HelloWorldClient.fromCreatorAndName({ algorand, creatorAddress: randomAccountA.addr, appName: 'contract-name', // ... })
// For multiple app client instances use the factory let appClientFactory = algorand.client.getTypedAppFactory(HelloWorldFactory) // or appClientFactory = new HelloWorldFactory({ algorand })
const appClientByCreator1 = await appClientFactory.getAppClientByCreatorAndName({ creatorAddress: randomAccountA.addr, appName: 'contract-name', // ... }) const appClientByCreator2 = await appClientFactory.getAppClientByCreatorAndName({ creatorAddress: randomAccountA.addr, appName: 'contract-name-2', // ... })
from smart_contracts.artifacts.hello_world.hello_world_client import ( HelloArgs, HelloWorldClient, HelloWorldFactory, )
""" Get a single typed app client by id """ app_client = algorand_client.client.get_typed_app_client_by_id( HelloWorldClient, app_id=1234, ) # or app_client = HelloWorldClient( algorand=algorand_client, app_id=1234, )
""" For multiple app client instances use the factory """ factory = algorand_client.client.get_typed_app_factory(HelloWorldFactory) # or factory = HelloWorldFactory(algorand_client)
app_client1 = factory.get_app_client_by_id( app_id=1234, ) app_client2 = factory.get_app_client_by_id( app_id=4321, )
""" Get typed app client by creator and name """ app_client = algorand_client.client.get_typed_app_client_by_creator_and_name( HelloWorldClient, creator_address=account_a.address, app_name="contract-name", # ... ) # or app_client = HelloWorldClient.from_creator_and_name( algorand=algorand_client, creator_address=account_a.address, app_name="contract-name", # ... )
""" For multiple app client instances use the factory """ factory = algorand_client.client.get_typed_app_factory(HelloWorldFactory) # or factory = HelloWorldFactory(algorand_client)
app_client1 = factory.get_app_client_by_creator_and_name( creator_address="CREATORADDRESS", app_name="contract-name", # ... ) app_client2 = factory.get_app_client_by_creator_and_name( creator_address="CREATORADDRESS", app_name="contract-name-2", # ... )
App is not Deployed
For applications that need to work with multiple instances of the same smart contract spec, factories provide a convenient way to manage multiple clients:
/* * Deploy a New App */ let createFactory = algorand.client.getTypedAppFactory(HelloWorldFactory) // or createFactory = new HelloWorldFactory({ algorand })
const { result, appClient: newAppClient } = await createFactory.send.create.bare()
// or if the contract has a custom create method: const customFactory = algorand.client.getTypedAppFactory(CustomCreateFactory)
const { result: customCreateResult, appClient: customCreateAppClient } = await customFactory.send.create.customCreate( { args: { age: 28 } }, )
// Deploy or Resolve App Idempotently by Creator and Name const { result: deployResult, appClient: deployedClient } = await createFactory.deploy({ appName: 'contract-name', })
from smart_contracts.artifacts.custom_create.custom_create_client import ( CustomCreateArgs, CustomCreateFactory, )
""" Deploy a New App """ factory = algorand_client.client.get_typed_app_factory(HelloWorldFactory) # or factory = HelloWorldFactory(algorand_client)
app_client, create_response = factory.send.create.bare()
# or if the contract has a custom create method: factory2 = algorand_client.client.get_typed_app_factory(CustomCreateFactory)
custom_create_app_client, factory_create_response = ( factory2.send.create.custom_create(CustomCreateArgs(age=28)) )
""" Deploy or Resolve App Idempotently by Creator and Name """ app_client, deploy_response = factory.deploy( app_name="contract-name", )
Calling a Smart Contract Method
To call a smart contract method using the application client instance, follow these steps:
const methodResponse = await appClient.send.sayHello({ args: { firstName: 'there', lastName: 'world' } }) console.log(methodResponse.return)
response = app_client.send.hello(args=HelloArgs(name="world")) print(response.abi_return)
The typed app client ensures you provide the correct parameters and handles all the underlying transaction construction and submission.
Example: Deploying and Interacting with a Smart Contract
For a simple example that deploys a contract and calls a hello
method, see below:
// A similar working example can be seen in the AlgoKit init production smart contract templates // In this case the generated factory is called `HelloWorldAppFactory` and is accessible via AppClients
// These require environment variables to be present, or it will retrieve from default LocalNet const algorand = AlgorandClient.fromEnvironment() const deployer = await algorand.account.fromEnvironment('DEPLOYER', (1).algo())
// Create the typed app factory const factory = algorand.client.getTypedAppFactory(HelloWorldFactory, { defaultSender: deployer.addr, })
// Create the app and get a typed app client for the created app (note: this creates a new instance of the app every time, // you can use .deploy() to deploy idempotently if the app wasn't previously // deployed or needs to be updated if that's allowed) const { appClient } = await factory.send.create.bare()
// Make a call to an ABI method and print the result const response = await appClient.send.sayHello({ args: { firstName: 'there', lastName: 'world' } }) console.log(response.return)
# A similar working example can be seen in the AlgoKit init production smart contract templates, when using Python deployment # In this case the generated factory is called `HelloWorldAppFactory` and is in `./artifacts/HelloWorldApp/client.py` from algokit_utils import AlgorandClient
from smart_contracts.artifacts.hello_world.hello_world_client import ( HelloArgs, HelloWorldClient, HelloWorldFactory, )
# These require environment variables to be present, or it will retrieve from default LocalNet algorand = AlgorandClient.from_environment() deployer = algorand.account.from_environment("DEPLOYER", AlgoAmount.from_algo(1))
# Create the typed app factory factory = algorand.client.get_typed_app_factory( HelloWorldFactory, default_sender=deployer.address, )
# Create the app and get a typed app client for the created app (note: this creates a new instance of the app every time, # you can use .deploy() to deploy idempotently if the app wasn't previously # deployed or needs to be updated if that's allowed) app_client, create_response = factory.send.create.bare()
# Make a call to an ABI method and print the result response = app_client.send.hello(args=HelloArgs(name="world")) print(response.abi_return)
When to Use Each Client Type
-
Use the
AlgorandClient
when you need to:- Send basic transactions (payments, asset transfers)
- Work with blockchain data in a general way
- Interact with contracts you don’t have specifications for
-
Use Typed App Clients when you need to:
- Deploy and interact with specific smart contracts
- Benefit from type safety and IntelliSense
- Build applications that leverage contract-specific functionality
For most Algorand applications, you’ll likely use both: AlgorandClient
for general blockchain operations and Typed App Clients for smart contract interactions.
Next Steps
Now that you understand AlgoKit Utils Clients, you’re ready to start building on Algorand with confidence. Remember:
- Start with the AlgorandClient for general blockchain interactions
- Generate Typed Application Clients for your smart contracts
- Leverage the stateful design of these clients to simplify your code