Ledger State Deltas
Description
Section titled “Description”This example demonstrates how to retrieve ledger state deltas using:
- ledgerStateDelta(round) - Get state changes for a specific round
- ledgerStateDeltaForTransactionGroup(txId) - Get deltas for a specific transaction group
- transactionGroupLedgerStateDeltasForRound(round) - Get all transaction group deltas in a round State deltas show what changed in the ledger (accounts, balances, apps, assets) between rounds. Note: These endpoints may require node configuration to enable (EnableDeveloperAPI=true).
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 algod_client/14-state-deltas.ts/** * Example: Ledger State Deltas * * This example demonstrates how to retrieve ledger state deltas using: * - ledgerStateDelta(round) - Get state changes for a specific round * - ledgerStateDeltaForTransactionGroup(txId) - Get deltas for a specific transaction group * - transactionGroupLedgerStateDeltasForRound(round) - Get all transaction group deltas in a round * * State deltas show what changed in the ledger (accounts, balances, apps, assets) between rounds. * * Note: These endpoints may require node configuration to enable (EnableDeveloperAPI=true). * * Prerequisites: * - LocalNet running (via `algokit localnet start`) */
import { algo } from '@algorandfoundation/algokit-utils';import type { LedgerStateDelta, TransactionGroupLedgerStateDeltasForRoundResponse,} from '@algorandfoundation/algokit-utils/algod-client';import { createAlgodClient, createAlgorandClient, formatMicroAlgo, printError, printHeader, printInfo, printStep, printSuccess, shortenAddress,} from '../shared/utils.js';
async function main() { printHeader('Ledger State Deltas Example');
// Create clients const algod = createAlgodClient(); const algorand = createAlgorandClient();
// ========================================================================= // Step 1: Set up accounts and submit a transaction to create state changes // ========================================================================= printStep(1, 'Setting up accounts and submitting a payment transaction');
// Get a funded account from LocalNet (the dispenser) const sender = await algorand.account.dispenserFromEnvironment(); printInfo(`Sender address: ${shortenAddress(sender.addr.toString())}`);
// Get sender initial balance const senderInfoBefore = await algod.accountInformation(sender.addr.toString()); printInfo(`Sender initial balance: ${formatMicroAlgo(senderInfoBefore.amount)}`);
// Create a new random account as receiver const receiver = algorand.account.random(); printInfo(`Receiver address: ${shortenAddress(receiver.addr.toString())}`);
// Submit a payment transaction - this will create state changes (balance changes) const paymentAmount = algo(5); printInfo(`Sending ${paymentAmount.algo} ALGO to receiver...`);
const result = await algorand.send.payment({ sender: sender.addr, receiver: receiver.addr, amount: paymentAmount, });
const txId = result.txIds[0]; const confirmedRound = result.confirmation.confirmedRound!;
printSuccess(`Transaction confirmed!`); printInfo(`Transaction ID: ${txId}`); printInfo(`Confirmed in round: ${confirmedRound.toLocaleString('en-US')}`); printInfo('');
// Get balances after transaction const senderInfoAfter = await algod.accountInformation(sender.addr.toString()); const receiverInfo = await algod.accountInformation(receiver.addr.toString());
printInfo(`Sender balance after: ${formatMicroAlgo(senderInfoAfter.amount)}`); printInfo(`Receiver balance after: ${formatMicroAlgo(receiverInfo.amount)}`); printInfo('');
// ========================================================================= // Step 2: Demonstrate ledgerStateDelta(round) // ========================================================================= printStep(2, 'Getting ledger state delta for a round using ledgerStateDelta(round)');
printInfo('ledgerStateDelta(round) returns all state changes that occurred in a specific round.'); printInfo('This includes account balance changes, app state changes, and more.'); printInfo('');
try { const stateDelta = await algod.ledgerStateDelta(confirmedRound); printSuccess('Successfully retrieved state delta for the round!'); printInfo('');
displayStateDelta(stateDelta, 'ledgerStateDelta'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if ( errorMessage.includes('not supported') || errorMessage.includes('not enabled') || errorMessage.includes('404') ) { printError('ledgerStateDelta endpoint may not be enabled on this node.'); printInfo( 'Node configuration may need EnableDeveloperAPI=true or specific delta tracking settings.', ); } else { printError(`Error getting state delta: ${errorMessage}`); } printInfo(''); }
// ========================================================================= // Step 3: Demonstrate ledgerStateDeltaForTransactionGroup(txId) // ========================================================================= printStep( 3, 'Getting state delta for a specific transaction group using ledgerStateDeltaForTransactionGroup(txId)', );
printInfo('ledgerStateDeltaForTransactionGroup(txId) returns the state changes'); printInfo( 'caused by a specific transaction group, identified by any transaction ID in the group.', ); printInfo('');
try { const txGroupDelta = await algod.ledgerStateDeltaForTransactionGroup(txId); printSuccess('Successfully retrieved state delta for the transaction group!'); printInfo('');
displayStateDelta(txGroupDelta, 'ledgerStateDeltaForTransactionGroup'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes('tracer') || errorMessage.includes('501')) { printError('ledgerStateDeltaForTransactionGroup requires delta tracking to be enabled.'); printInfo( 'This endpoint needs EnableDeveloperAPI=true AND EnableTxnEvalTracer=true in node config.', ); printInfo('On LocalNet, this may require custom configuration.'); } else if ( errorMessage.includes('not supported') || errorMessage.includes('not enabled') || errorMessage.includes('404') ) { printError('ledgerStateDeltaForTransactionGroup endpoint may not be enabled on this node.'); } else { printError(`Error getting transaction group delta: ${errorMessage}`); } printInfo(''); }
// ========================================================================= // Step 4: Demonstrate transactionGroupLedgerStateDeltasForRound(round) // ========================================================================= printStep( 4, 'Getting all transaction group deltas for a round using transactionGroupLedgerStateDeltasForRound(round)', );
printInfo('transactionGroupLedgerStateDeltasForRound(round) returns deltas for ALL'); printInfo('transaction groups that were included in a specific round.'); printInfo('Each entry includes the delta and the transaction IDs in that group.'); printInfo('');
try { const roundDeltas = await algod.transactionGroupLedgerStateDeltasForRound(confirmedRound); printSuccess('Successfully retrieved all transaction group deltas for the round!'); printInfo('');
displayRoundDeltas(roundDeltas); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes('tracer') || errorMessage.includes('501')) { printError( 'transactionGroupLedgerStateDeltasForRound requires delta tracking to be enabled.', ); printInfo( 'This endpoint needs EnableDeveloperAPI=true AND EnableTxnEvalTracer=true in node config.', ); printInfo('On LocalNet, this may require custom configuration.'); } else if ( errorMessage.includes('not supported') || errorMessage.includes('not enabled') || errorMessage.includes('404') ) { printError( 'transactionGroupLedgerStateDeltasForRound endpoint may not be enabled on this node.', ); } else { printError(`Error getting round deltas: ${errorMessage}`); } printInfo(''); }
// ========================================================================= // Step 5: Submit more transactions to see different state changes // ========================================================================= printStep(5, 'Submitting additional transactions to demonstrate more state changes');
// Create another account and do a multi-transaction round const receiver2 = algorand.account.random(); printInfo(`Created second receiver: ${shortenAddress(receiver2.addr.toString())}`);
// Send payments to create more activity const result2 = await algorand.send.payment({ sender: sender.addr, receiver: receiver2.addr, amount: algo(3), });
const confirmedRound2 = result2.confirmation.confirmedRound!; printSuccess(`Second transaction confirmed in round ${confirmedRound2.toLocaleString('en-US')}`); printInfo('');
// Try to get deltas for this new round try { const roundDeltas2 = await algod.transactionGroupLedgerStateDeltasForRound(confirmedRound2); printSuccess( `Found ${roundDeltas2.deltas.length} transaction group(s) in round ${confirmedRound2.toLocaleString('en-US')}`, );
for (let i = 0; i < roundDeltas2.deltas.length; i++) { const groupDelta = roundDeltas2.deltas[i]; printInfo(`\n Transaction Group ${i + 1}:`); printInfo(` Transaction IDs: ${groupDelta.ids.length}`); for (const id of groupDelta.ids) { printInfo(` - ${id}`); }
// Show account changes summary const accounts = groupDelta.delta.accounts; if (accounts.accounts && accounts.accounts.length > 0) { printInfo(` Accounts modified: ${accounts.accounts.length}`); } } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes('tracer') || errorMessage.includes('501')) { printInfo('(Skipped - requires EnableTxnEvalTracer node configuration)'); } else { printError(`Could not get deltas: ${errorMessage}`); } } printInfo('');
// ========================================================================= // Summary // ========================================================================= printHeader('Summary');
printInfo('This example demonstrated three ways to get ledger state deltas:'); printInfo(''); printInfo('1. ledgerStateDelta(round):'); printInfo(' - Returns ALL state changes for a specific round'); printInfo(' - Includes: account balances, app state, asset holdings'); printInfo(' - LedgerStateDelta contains: accounts, block, totals, kvMods, txIds, creatables'); printInfo(''); printInfo('2. ledgerStateDeltaForTransactionGroup(txId):'); printInfo(' - Returns state changes for a SPECIFIC transaction group'); printInfo(' - Accepts any transaction ID from the group'); printInfo(' - Useful for tracking changes from your transactions'); printInfo(''); printInfo('3. transactionGroupLedgerStateDeltasForRound(round):'); printInfo(' - Returns deltas for ALL transaction groups in a round'); printInfo(' - TransactionGroupLedgerStateDeltasForRoundResponse has deltas array'); printInfo(' - Each LedgerStateDeltaForTransactionGroup has: delta, ids (transaction IDs)'); printInfo(''); printInfo('State Delta Structure (LedgerStateDelta):'); printInfo(' accounts: LedgerAccountDeltas'); printInfo(' - accounts?: LedgerBalanceRecord[] (address + microAlgos)'); printInfo(' - appResources?: LedgerAppResourceRecord[]'); printInfo(' - assetResources?: LedgerAssetResourceRecord[]'); printInfo(' block: Block (block header information)'); printInfo(' totals: LedgerAccountTotals (online, offline, notParticipating algo counts)'); printInfo(' stateProofNext: bigint'); printInfo(' prevTimestamp: bigint'); printInfo(' kvMods?: Map<Uint8Array, LedgerKvValueDelta> (key-value store changes)'); printInfo(' txIds?: Map<Uint8Array, LedgerIncludedTransactions>'); printInfo(' creatables?: Map<number, LedgerModifiedCreatable>'); printInfo(''); printInfo('Note: Node configuration requirements:'); printInfo(' - ledgerStateDelta(round) - Works with default LocalNet configuration'); printInfo(' - ledgerStateDeltaForTransactionGroup(txId) - Requires EnableTxnEvalTracer=true'); printInfo( ' - transactionGroupLedgerStateDeltasForRound(round) - Requires EnableTxnEvalTracer=true', ); printInfo(' - All endpoints need EnableDeveloperAPI=true (enabled by default on LocalNet)');}
/** * Display details from a LedgerStateDelta */function displayStateDelta(delta: LedgerStateDelta, source: string): void { printInfo(`State Delta from ${source}:`); printInfo('');
// Block information printInfo(' Block Information:'); printInfo(` Round: ${delta.block.header.round.toLocaleString('en-US')}`); printInfo( ` Timestamp: ${new Date(Number(delta.block.header.timestamp) * 1000).toISOString()}`, ); printInfo(` Previous timestamp: ${delta.prevTimestamp.toLocaleString('en-US')}`); if (delta.block.header.proposer) { printInfo(` Proposer: ${shortenAddress(delta.block.header.proposer.toString())}`); } printInfo('');
// Account changes printInfo(' Account Changes (LedgerAccountDeltas):'); const accounts = delta.accounts;
if (accounts.accounts && accounts.accounts.length > 0) { printInfo(` Modified accounts: ${accounts.accounts.length}`); for (const record of accounts.accounts.slice(0, 5)) { // Show first 5 const baseData = record.accountData.accountBaseData; printInfo(` - ${shortenAddress(record.address.toString())}`); printInfo(` Balance: ${formatMicroAlgo(baseData.microAlgos)}`); printInfo(` Status: ${getAccountStatus(baseData.status)}`); } if (accounts.accounts.length > 5) { printInfo(` ... and ${accounts.accounts.length - 5} more`); } } else { printInfo(' No account balance changes'); }
if (accounts.appResources && accounts.appResources.length > 0) { printInfo(` App resources modified: ${accounts.appResources.length}`); for (const app of accounts.appResources.slice(0, 3)) { printInfo(` - App ${app.appId}: ${app.params.deleted ? 'DELETED' : 'MODIFIED'}`); } }
if (accounts.assetResources && accounts.assetResources.length > 0) { printInfo(` Asset resources modified: ${accounts.assetResources.length}`); for (const asset of accounts.assetResources.slice(0, 3)) { printInfo(` - Asset ${asset.assetId}: ${asset.params.deleted ? 'DELETED' : 'MODIFIED'}`); } } printInfo('');
// Totals printInfo(' Account Totals (LedgerAccountTotals):'); printInfo(` Online money: ${formatMicroAlgo(delta.totals.online.money)}`); printInfo(` Offline money: ${formatMicroAlgo(delta.totals.offline.money)}`); printInfo(` Not participating: ${formatMicroAlgo(delta.totals.notParticipating.money)}`); printInfo(` Rewards level: ${delta.totals.rewardsLevel.toLocaleString('en-US')}`); printInfo('');
// KV mods if (delta.kvMods && delta.kvMods.size > 0) { printInfo(` KV Store Modifications: ${delta.kvMods.size} keys changed`); }
// Transaction IDs if (delta.txIds && delta.txIds.size > 0) { printInfo(` Transactions included: ${delta.txIds.size}`); }
// Creatables if (delta.creatables && delta.creatables.size > 0) { printInfo(` Creatables modified: ${delta.creatables.size}`); delta.creatables.forEach((creatable, id) => { const type = creatable.creatableType === 0 ? 'Asset' : 'Application'; const action = creatable.created ? 'CREATED' : 'DELETED'; printInfo( ` - ${type} ${id}: ${action} by ${shortenAddress(creatable.creator.toString())}`, ); }); } printInfo('');}
/** * Display details from a TransactionGroupLedgerStateDeltasForRoundResponse */function displayRoundDeltas(response: TransactionGroupLedgerStateDeltasForRoundResponse): void { printInfo('Transaction Group Deltas for Round:'); printInfo(` Total transaction groups: ${response.deltas.length}`); printInfo('');
for (let i = 0; i < response.deltas.length; i++) { const groupDelta = response.deltas[i]; printInfo(` Group ${i + 1}:`); printInfo(` Transaction IDs in group: ${groupDelta.ids.length}`);
for (const txId of groupDelta.ids) { printInfo(` - ${txId}`); }
// Summary of delta const accounts = groupDelta.delta.accounts; const modifiedCount = accounts.accounts?.length ?? 0; const appCount = accounts.appResources?.length ?? 0; const assetCount = accounts.assetResources?.length ?? 0;
printInfo(` State changes:`); printInfo(` - Accounts modified: ${modifiedCount}`); if (appCount > 0) { printInfo(` - App resources: ${appCount}`); } if (assetCount > 0) { printInfo(` - Asset resources: ${assetCount}`); } printInfo(''); }}
/** * Convert an account status number to a string */function getAccountStatus(status: number): string { switch (status) { case 0: return 'Offline'; case 1: return 'Online'; case 2: return 'NotParticipating'; default: return `Unknown (${status})`; }}
main().catch(error => { console.error('Fatal error:', error); process.exit(1);});Other examples in Algod Client
Section titled “Other examples in Algod Client”- Node Health and Status
- Version and Genesis Information
- Ledger Supply Information
- Account Information
- Transaction Parameters
- Send and Confirm Transaction
- Pending Transactions
- Block Data
- Asset Information
- Application Information
- Application Boxes
- TEAL Compile and Disassemble
- Transaction Simulation
- Ledger State Deltas
- Transaction Proof
- Light Block Header Proof
- State Proof
- DevMode Timestamp Offset
- Sync Round Management