Client Manager
Description
Section titled “Description”This example demonstrates how to access the underlying raw clients through the ClientManager (algorand.client), and how to get typed app clients:
- algorand.client.algod - Access the raw Algod client
- algorand.client.indexer - Access the raw Indexer client
- algorand.client.kmd - Access the raw KMD client
- algorand.client.indexerIfPresent - Safely access Indexer (returns undefined if not configured)
- algorand.client.getAppClientById() - Get typed app client by ID
- algorand.client.getAppClientByCreatorAndName() - Get typed app client by creator/name
- algorand.client.getAppFactory() - Get app factory for creating/deploying apps
- When to use raw clients vs AlgorandClient methods
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/14-client-manager.ts/** * Example: Client Manager * * This example demonstrates how to access the underlying raw clients through * the ClientManager (algorand.client), and how to get typed app clients: * - algorand.client.algod - Access the raw Algod client * - algorand.client.indexer - Access the raw Indexer client * - algorand.client.kmd - Access the raw KMD client * - algorand.client.indexerIfPresent - Safely access Indexer (returns undefined if not configured) * - algorand.client.getAppClientById() - Get typed app client by ID * - algorand.client.getAppClientByCreatorAndName() - Get typed app client by creator/name * - algorand.client.getAppFactory() - Get app factory for creating/deploying apps * - When to use raw clients vs AlgorandClient methods * * Prerequisites: * - LocalNet running (via `algokit localnet start`) */
import { AlgorandClient, algo } from '@algorandfoundation/algokit-utils';import type { Arc56Contract } from '@algorandfoundation/algokit-utils/abi';import type { AlgoConfig } from '@algorandfoundation/algokit-utils/types/network-client';import { loadTealSource, printError, printHeader, printInfo, printStep, printSuccess, shortenAddress,} from '../shared/utils.js';
// ============================================================================// Simple TEAL Programs for App Client Demonstrations (loaded from shared artifacts)// ============================================================================
const SIMPLE_APPROVAL_PROGRAM = loadTealSource('approval-counter-simple.teal');const CLEAR_STATE_PROGRAM = loadTealSource('clear-state-approve.teal');
// A minimal ARC-56 compatible app spec for demonstrationconst SIMPLE_APP_SPEC: Arc56Contract = { name: 'SimpleCounter', desc: 'A simple counter application for demonstration', methods: [], state: { schema: { global: { ints: 1, bytes: 0, }, local: { ints: 0, bytes: 0, }, }, keys: { global: { counter: { keyType: 'AVMString', valueType: 'AVMUint64', key: 'Y291bnRlcg==', // base64 of "counter" }, }, local: {}, box: {}, }, maps: { global: {}, local: {}, box: {}, }, }, bareActions: { create: ['NoOp'], call: ['NoOp', 'DeleteApplication'], }, arcs: [56], structs: {}, source: { approval: SIMPLE_APPROVAL_PROGRAM, clear: CLEAR_STATE_PROGRAM, }, byteCode: { approval: '', clear: '', }, compilerInfo: { compiler: 'algod', compilerVersion: { major: 3, minor: 0, patch: 0, }, }, events: [], templateVariables: {}, networks: {}, sourceInfo: { approval: { sourceInfo: [], pcOffsetMethod: 'none' }, clear: { sourceInfo: [], pcOffsetMethod: 'none' }, }, scratchVariables: {},};
async function main() { printHeader('Client Manager 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: Access raw Algod client via algorand.client.algod printStep(1, 'Access raw Algod client via algorand.client.algod'); printInfo('The Algod client provides direct access to the Algorand node REST API');
const algod = algorand.client.algod;
// Get node status const status = await algod.status(); printInfo(`\nAlgod status():`); printInfo(` Last round: ${status.lastRound}`); printInfo(` Time since last round: ${status.timeSinceLastRound}ns`); printInfo(` Catchup time: ${status.catchupTime}ns`); printInfo(` Last version: ${status.lastVersion}`);
// Get suggested transaction parameters const suggestedParams = await algod.suggestedParams(); printInfo(`\nAlgod suggestedParams():`); printInfo(` Genesis ID: ${suggestedParams.genesisId}`); printInfo( ` Genesis Hash: ${Buffer.from(suggestedParams.genesisHash ?? new Uint8Array()) .toString('base64') .slice(0, 20)}...`, ); printInfo(` First valid round: ${suggestedParams.firstValid}`); printInfo(` Last valid round: ${suggestedParams.lastValid}`); printInfo(` Min fee: ${suggestedParams.minFee}`);
// Get genesis information const genesis = await algod.genesis(); printInfo(`\nAlgod genesis():`); printInfo(` Network: ${genesis.network}`); printInfo(` Protocol: ${genesis.proto}`);
// Get supply information const supply = await algod.supply(); printInfo(`\nAlgod supply():`); printInfo(` Total money: ${supply.totalMoney} microAlgo`); printInfo(` Online money: ${supply.onlineMoney} microAlgo`);
printSuccess('Raw Algod client accessed successfully');
// Step 2: Access raw Indexer client via algorand.client.indexer printStep(2, 'Access raw Indexer client via algorand.client.indexer'); printInfo('The Indexer client provides access to historical blockchain data');
const indexer = algorand.client.indexer;
// Health check const health = await indexer.healthCheck(); printInfo(`\nIndexer healthCheck():`); printInfo(` Database available: ${health.dbAvailable}`); printInfo(` Is migrating: ${health.isMigrating}`); printInfo(` Round: ${health.round}`); printInfo(` Version: ${health.version}`);
// Search for transactions const txnSearchResult = await indexer.searchForTransactions({ limit: 3 }); printInfo(`\nIndexer searchForTransactions({ limit: 3 }):`); printInfo(` Found ${txnSearchResult.transactions.length} transactions`); printInfo(` Current round: ${txnSearchResult.currentRound}`); for (const txn of txnSearchResult.transactions) { printInfo(` - ${txn.id?.slice(0, 12) ?? 'unknown'}... (type: ${txn.txType})`); }
// Lookup an account const dispenser = await algorand.account.dispenserFromEnvironment(); const accountResult = await indexer.lookupAccountById(dispenser.addr.toString()); printInfo(`\nIndexer lookupAccountById():`); printInfo(` Address: ${shortenAddress(accountResult.account.address)}`); printInfo(` Balance: ${accountResult.account.amount} microAlgo`); printInfo(` Status: ${accountResult.account.status}`);
printSuccess('Raw Indexer client accessed successfully');
// Step 3: Access raw KMD client via algorand.client.kmd printStep(3, 'Access raw KMD client via algorand.client.kmd'); printInfo('The KMD (Key Management Daemon) client manages wallets and keys');
const kmd = algorand.client.kmd;
// List wallets const walletsResult = await kmd.listWallets(); printInfo(`\nKMD listWallets():`); printInfo(` Found ${walletsResult.wallets.length} wallet(s)`); for (const wallet of walletsResult.wallets) { printInfo(` - "${wallet.name}" (ID: ${wallet.id.slice(0, 8)}...)`); }
// Get the default LocalNet wallet and list keys const defaultWallet = walletsResult.wallets.find(w => w.name === 'unencrypted-default-wallet'); if (defaultWallet) { const handleResult = await kmd.initWalletHandle({ walletId: defaultWallet.id, walletPassword: '', });
const keysResult = await kmd.listKeysInWallet({ walletHandleToken: handleResult.walletHandleToken, });
printInfo(`\nKMD listKeysInWallet() for default wallet:`); printInfo(` Found ${keysResult.addresses.length} key(s)`); for (const address of keysResult.addresses.slice(0, 3)) { printInfo(` - ${shortenAddress(address.toString())}`); } if (keysResult.addresses.length > 3) { printInfo(` ... and ${keysResult.addresses.length - 3} more`); }
// Release the wallet handle await kmd.releaseWalletHandleToken({ walletHandleToken: handleResult.walletHandleToken, }); }
printSuccess('Raw KMD client accessed successfully');
// Step 4: Demonstrate algorand.client.indexerIfPresent printStep(4, 'Demonstrate algorand.client.indexerIfPresent'); printInfo( 'indexerIfPresent returns undefined if Indexer is not configured (instead of throwing)', );
// With LocalNet, indexer is configured const indexerIfPresent = algorand.client.indexerIfPresent; if (indexerIfPresent) { printInfo(`\nIndexer is present: true`); const indexerHealth = await indexerIfPresent.healthCheck(); printInfo(` Indexer round: ${indexerHealth.round}`); }
// Create a client without indexer to demonstrate undefined behavior const algodOnlyConfig: AlgoConfig = { algodConfig: { server: 'http://localhost', port: 4001, token: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', }, // Note: No indexerConfig provided }; const algodOnlyClient = AlgorandClient.fromConfig(algodOnlyConfig);
const noIndexer = algodOnlyClient.client.indexerIfPresent; printInfo(`\nFor client without Indexer configured:`); printInfo(` indexerIfPresent: ${noIndexer === undefined ? 'undefined' : 'present'}`); printInfo(` Use this to gracefully handle missing Indexer configuration`);
printSuccess('indexerIfPresent demonstrated');
// Step 5: Create an application for app client demonstrations printStep(5, 'Create test application for app client demonstrations');
const creator = algorand.account.random(); await algorand.account.ensureFundedFromEnvironment(creator.addr, algo(10));
const createResult = await algorand.send.appCreate({ sender: creator.addr, approvalProgram: SIMPLE_APPROVAL_PROGRAM, clearStateProgram: CLEAR_STATE_PROGRAM, schema: { globalInts: 1, globalByteSlices: 0, localInts: 0, localByteSlices: 0, }, });
const appId = createResult.appId; printInfo(`\nCreated test application:`); printInfo(` App ID: ${appId}`); printInfo(` Creator: ${shortenAddress(creator.addr.toString())}`);
printSuccess('Test application created');
// Step 6: Demonstrate algorand.client.getAppClientById() printStep(6, 'Demonstrate algorand.client.getAppClientById()'); printInfo('Creates an AppClient for an existing application by its ID');
const appClientById = algorand.client.getAppClientById({ appSpec: SIMPLE_APP_SPEC, appId: appId, defaultSender: creator.addr, });
printInfo(`\nAppClient created with getAppClientById():`); printInfo(` App ID: ${appClientById.appId}`); printInfo(` App Name: ${appClientById.appName}`); printInfo(` App Address: ${shortenAddress(appClientById.appAddress.toString())}`);
// Use the app client to make a call const callResult = await appClientById.send.bare.call(); printInfo(`\nCalled app via AppClient:`); printInfo(` Transaction ID: ${callResult.txIds[0]}`);
// Read state using the app client const globalState = await appClientById.state.global.getAll(); printInfo(` Global state after call: counter = ${globalState.counter}`);
printSuccess('getAppClientById() demonstrated');
// Step 7: Demonstrate algorand.client.getAppClientByCreatorAndName() printStep(7, 'Demonstrate algorand.client.getAppClientByCreatorAndName()'); printInfo('Creates an AppClient by looking up app ID from creator and app name');
// First, deploy an app using the app deployer (which stores name metadata) // Note: We don't use deploy-time controls (updatable/deletable) since our TEAL // doesn't have TMPL_UPDATABLE/TMPL_DELETABLE placeholders const deployedApp = await algorand.appDeployer.deploy({ metadata: { name: 'NamedCounterApp', version: '1.0.0', }, createParams: { sender: creator.addr, approvalProgram: SIMPLE_APPROVAL_PROGRAM, clearStateProgram: CLEAR_STATE_PROGRAM, schema: { globalInts: 1, globalByteSlices: 0, localInts: 0, localByteSlices: 0, }, }, updateParams: { sender: creator.addr }, deleteParams: { sender: creator.addr }, });
printInfo(`\nDeployed named app:`); printInfo(` Name: NamedCounterApp`); printInfo(` App ID: ${deployedApp.appId}`);
// Now get the app client by creator and name (async - returns Promise) const appClientByName = await algorand.client.getAppClientByCreatorAndName({ appSpec: SIMPLE_APP_SPEC, creatorAddress: creator.addr, appName: 'NamedCounterApp', defaultSender: creator.addr, });
printInfo(`\nAppClient from getAppClientByCreatorAndName():`); printInfo(` Resolved App ID: ${appClientByName.appId}`); printInfo(` App Name: ${appClientByName.appName}`); printInfo(` Note: App ID was resolved by looking up the creator's apps`);
printSuccess('getAppClientByCreatorAndName() demonstrated');
// Step 8: Demonstrate algorand.client.getAppFactory() printStep(8, 'Demonstrate algorand.client.getAppFactory()'); printInfo('Creates an AppFactory for deploying and managing multiple app instances');
const appFactory = algorand.client.getAppFactory({ appSpec: SIMPLE_APP_SPEC, defaultSender: creator.addr, });
printInfo(`\nAppFactory created with getAppFactory():`); printInfo(` App Name: ${appFactory.appName}`); printInfo(''); printInfo('AppFactory provides methods for:'); printInfo(' - factory.send.bare.create() - Create app with bare call'); printInfo(' - factory.send.create() - Create app with ABI method'); printInfo(' - factory.deploy() - Idempotent deployment with version management'); printInfo(' - factory.params.* - Get transaction params for app operations'); printInfo(''); printInfo('Note: Creating apps via factory requires a properly compiled ARC-56 app spec'); printInfo('with either compiled bytecode or TEAL source that the factory can compile.');
printSuccess('getAppFactory() demonstrated');
// Step 9: Explain when to use raw clients vs AlgorandClient methods printStep(9, 'When to use raw clients vs AlgorandClient methods'); printInfo( '\nWhen to use AlgorandClient high-level methods (algorand.send.*, algorand.app.*, etc.):', ); printInfo(' - Creating and sending transactions (automatic signer management)'); printInfo(' - Account management and funding'); printInfo(' - Reading app state (getGlobalState, getLocalState)'); printInfo(' - Common operations that benefit from SDK convenience'); printInfo(' - When you want automatic transaction composition and signing');
printInfo('\nWhen to use raw Algod client (algorand.client.algod):'); printInfo(' - Direct node status queries (status(), genesis(), supply())'); printInfo(' - Low-level transaction submission (sendRawTransaction)'); printInfo(' - Block information queries'); printInfo(' - Pending transaction information'); printInfo(' - Node configuration queries'); printInfo(' - When you need fine-grained control over API calls');
printInfo('\nWhen to use raw Indexer client (algorand.client.indexer):'); printInfo(' - Historical transaction searches with complex filters'); printInfo(' - Account lookups with specific query parameters'); printInfo(' - Asset and application searches'); printInfo(' - Block lookups and searches'); printInfo(' - Paginated queries with custom limits'); printInfo(' - When AlgorandClient does not expose the specific query you need');
printInfo('\nWhen to use raw KMD client (algorand.client.kmd):'); printInfo(' - Wallet management (create, list, rename wallets)'); printInfo(' - Key generation and import/export'); printInfo(' - Signing transactions with KMD-managed keys'); printInfo(' - LocalNet development with default wallets'); printInfo(' - When you need direct control over key management');
printInfo('\nWhen to use AppClient (getAppClientById, getAppClientByCreatorAndName):'); printInfo(' - Interacting with a specific deployed application'); printInfo(' - Type-safe method calls based on ARC-56 app spec'); printInfo(' - Reading/writing app state with type information'); printInfo(' - When you have the app spec and want IDE autocompletion');
printInfo('\nWhen to use AppFactory (getAppFactory):'); printInfo(' - Deploying new application instances'); printInfo(' - Creating multiple instances of the same app'); printInfo(' - Idempotent deployment with version management'); printInfo(' - When you need to create apps programmatically');
printSuccess('Usage guidance provided');
// Step 10: Summary printStep(10, 'Summary - Client Manager API'); printInfo('The ClientManager (algorand.client) provides access to underlying clients:'); printInfo(''); printInfo('algorand.client.algod:'); printInfo(' - Raw AlgodClient for direct node API access'); printInfo(' - Methods: status(), suggestedParams(), genesis(), supply(), etc.'); printInfo(''); printInfo('algorand.client.indexer:'); printInfo(' - Raw IndexerClient for historical data queries'); printInfo(' - Methods: searchForTransactions(), lookupAccountById(), etc.'); printInfo(' - Throws error if Indexer not configured'); printInfo(''); printInfo('algorand.client.indexerIfPresent:'); printInfo(' - Same as indexer but returns undefined if not configured'); printInfo(' - Use for graceful handling of optional Indexer'); printInfo(''); printInfo('algorand.client.kmd:'); printInfo(' - Raw KmdClient for wallet/key management'); printInfo(' - Methods: listWallets(), listKeysInWallet(), etc.'); printInfo(' - Only available on LocalNet or custom KMD setups'); printInfo(''); printInfo('algorand.client.getAppClientById({ appSpec, appId }):'); printInfo(' - Creates AppClient for existing app by ID'); printInfo(' - Provides type-safe app interaction'); printInfo(''); printInfo('algorand.client.getAppClientByCreatorAndName({ appSpec, creatorAddress, appName }):'); printInfo(' - Creates AppClient by resolving app ID from creator and name'); printInfo(' - Uses AlgoKit app deployment metadata for lookup'); printInfo(' - Returns Promise<AppClient>'); printInfo(''); printInfo('algorand.client.getAppFactory({ appSpec }):'); printInfo(' - Creates AppFactory for deploying new app instances'); printInfo(' - Supports bare and ABI-based app creation'); printInfo(''); printInfo('Best practices:'); printInfo(' - Use high-level AlgorandClient methods for common operations'); printInfo(' - Drop to raw clients when you need specific API features'); printInfo(' - Use indexerIfPresent for portable code that may run without Indexer'); printInfo(' - Use AppClient/AppFactory for type-safe smart contract interaction');
// Clean up - delete the apps we created await algorand.send.appDelete({ sender: creator.addr, appId: appId }); await algorand.send.appDelete({ sender: creator.addr, appId: deployedApp.appId });
printSuccess('Client Manager 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