Skip to content
Algorand Developer Portal

Client Manager

← Back to Algorand Client

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

From the repository root:

Terminal window
cd examples
npm run example algorand_client/14-client-manager.ts

View source on GitHub

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 demonstration
const 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);
});