Send and Confirm Transaction
Description
Section titled “Description”This example demonstrates how to send transactions and wait for confirmation using sendRawTransaction() and pendingTransactionInformation(). It shows the complete lifecycle of submitting a transaction to the Algorand network.
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/06-send-transaction.ts/** * Example: Send and Confirm Transaction * * This example demonstrates how to send transactions and wait for confirmation * using sendRawTransaction() and pendingTransactionInformation(). It shows * the complete lifecycle of submitting a transaction to the Algorand network. * * Prerequisites: * - LocalNet running (via `algokit localnet start`) */
import { algo } from '@algorandfoundation/algokit-utils'import type { PendingTransactionResponse } from '@algorandfoundation/algokit-utils/algod-client'import { createAlgodClient, createAlgorandClient, formatMicroAlgo, printError, printHeader, printInfo, printStep, printSuccess, shortenAddress,} from '../shared/utils.js'
/** * Format a bigint as microAlgos and Algos */function formatFee(microAlgos: bigint): string { const algoValue = Number(microAlgos) / 1_000_000 return `${microAlgos.toLocaleString('en-US')} µALGO (${algoValue.toFixed(6)} ALGO)`}
/** * Wait for a transaction to be confirmed using pendingTransactionInformation * This implements a polling loop to check transaction status. * * @param algod - The AlgodClient instance * @param txId - The transaction ID to wait for * @param maxRounds - Maximum number of rounds to wait (default: 10) * @returns The PendingTransactionResponse when confirmed */async function waitForConfirmation( algod: ReturnType<typeof createAlgodClient>, txId: string, maxRounds: number = 10): Promise<PendingTransactionResponse> { // Get the current status to know what round we're on const status = await algod.status() let currentRound = status.lastRound const endRound = currentRound + BigInt(maxRounds)
printInfo(` Starting at round: ${currentRound.toLocaleString('en-US')}`) printInfo(` Will wait until round: ${endRound.toLocaleString('en-US')}`) printInfo('')
while (currentRound < endRound) { // Check the transaction status const pendingInfo = await algod.pendingTransactionInformation(txId)
// Case 1: Transaction is confirmed (confirmedRound > 0) if (pendingInfo.confirmedRound && pendingInfo.confirmedRound > 0n) { printInfo(` Transaction confirmed in round ${pendingInfo.confirmedRound.toLocaleString('en-US')}`) return pendingInfo }
// Case 2: Transaction was rejected (poolError is not empty) if (pendingInfo.poolError && pendingInfo.poolError.length > 0) { throw new Error(`Transaction rejected: ${pendingInfo.poolError}`) }
// Case 3: Transaction is still pending (confirmedRound = 0, poolError = "") printInfo(` Round ${currentRound.toLocaleString('en-US')}: Transaction still pending...`)
// Wait for the next block await algod.statusAfterBlock(currentRound) currentRound++ }
throw new Error(`Transaction ${txId} not confirmed after ${maxRounds} rounds`)}
async function main() { printHeader('Send and Confirm Transaction Example')
// Create clients const algod = createAlgodClient() const algorand = createAlgorandClient()
// ========================================================================= // Step 1: Get a funded account and create a receiver // ========================================================================= printStep(1, 'Setting up sender and receiver accounts')
// Get a funded account from LocalNet (the dispenser) const sender = await algorand.account.dispenserFromEnvironment() printInfo(`Sender address: ${shortenAddress(sender.addr.toString())}`)
// Get sender balance const senderInfo = await algod.accountInformation(sender.addr.toString()) printInfo(`Sender balance: ${formatMicroAlgo(senderInfo.amount)}`)
// Create a new random account as receiver const receiver = algorand.account.random() printInfo(`Receiver address: ${shortenAddress(receiver.addr.toString())}`) printInfo('Receiver is a new unfunded account') printInfo('')
// ========================================================================= // Step 2: Get suggested transaction parameters // ========================================================================= printStep(2, 'Getting suggested transaction parameters')
const suggestedParams = await algod.suggestedParams() printInfo(`First valid round: ${suggestedParams.firstValid.toLocaleString('en-US')}`) printInfo(`Last valid round: ${suggestedParams.lastValid.toLocaleString('en-US')}`) printInfo(`Min fee: ${formatFee(suggestedParams.minFee)}`) printInfo(`Genesis ID: ${suggestedParams.genesisId}`) printInfo('')
// ========================================================================= // Step 3: Create a payment transaction using algosdk/algokit // ========================================================================= printStep(3, 'Creating a payment transaction')
const paymentAmount = algo(1) // 1 ALGO printInfo(`Payment amount: ${paymentAmount.algo} ALGO (${paymentAmount.microAlgo.toLocaleString('en-US')} µALGO)`) printInfo(`Sender: ${shortenAddress(sender.addr.toString())}`) printInfo(`Receiver: ${shortenAddress(receiver.addr.toString())}`) printInfo('')
// Build the transaction using AlgorandClient.createTransaction // This creates an unsigned Transaction object const paymentTxn = await algorand.createTransaction.payment({ sender: sender.addr, receiver: receiver.addr, amount: paymentAmount, })
// Get the transaction ID before sending const txId = paymentTxn.txId() printInfo(`Transaction ID: ${txId}`)
// Sign the transaction using the sender's signer const signedTxn = await sender.signer([paymentTxn], [0]) printSuccess('Transaction signed successfully!') printInfo('')
// ========================================================================= // Step 4: Submit the transaction using sendRawTransaction() // ========================================================================= printStep(4, 'Submitting transaction with sendRawTransaction()')
try { const submitResponse = await algod.sendRawTransaction(signedTxn) printSuccess(`Transaction submitted successfully!`) printInfo(`Transaction ID from response: ${submitResponse.txId}`) printInfo('')
printInfo('sendRawTransaction() accepts:') printInfo(' - A single signed transaction (Uint8Array)') printInfo(' - An array of signed transactions (Uint8Array[])') printInfo('Returns PostTransactionsResponse with txid field') printInfo('')
} catch (error) { printError(`Failed to submit transaction: ${error instanceof Error ? error.message : String(error)}`) printInfo('Common errors:') printInfo(' - "txn dead" - Transaction validity window has passed') printInfo(' - "overspend" - Sender has insufficient funds') printInfo(' - "fee too small" - Transaction fee is below minimum') throw error }
// ========================================================================= // Step 5: Check transaction status with pendingTransactionInformation() // ========================================================================= printStep(5, 'Checking transaction status with pendingTransactionInformation()')
// First, let's check the initial status (may already be confirmed on LocalNet) const initialStatus = await algod.pendingTransactionInformation(txId) printInfo('Initial transaction status:') printInfo(` confirmedRound: ${initialStatus.confirmedRound ?? 'undefined (not yet confirmed)'}`) printInfo(` poolError: "${initialStatus.poolError}" ${initialStatus.poolError ? '(ERROR!)' : '(empty = no error)'}`) printInfo('')
printInfo('pendingTransactionInformation() returns PendingTransactionResponse with:') printInfo(' - confirmedRound: The round the txn was confirmed (0 or undefined if pending)') printInfo(' - poolError: Error message if txn was rejected (empty if OK)') printInfo(' - txn: The signed transaction object') printInfo(' - And other fields like rewards, inner transactions, etc.') printInfo('')
// ========================================================================= // Step 6: Wait for confirmation using a polling loop // ========================================================================= printStep(6, 'Implementing waitForConfirmation loop')
printInfo('The waitForConfirmation pattern:') printInfo(' 1. Call pendingTransactionInformation(txId)') printInfo(' 2. If confirmedRound > 0: Transaction confirmed!') printInfo(' 3. If poolError is not empty: Transaction rejected!') printInfo(' 4. Otherwise: Wait for next block with statusAfterBlock(round)') printInfo(' 5. Repeat until confirmed, rejected, or timeout') printInfo('')
let confirmedInfo: PendingTransactionResponse
// On LocalNet in dev mode, the transaction may already be confirmed if (initialStatus.confirmedRound && initialStatus.confirmedRound > 0n) { printInfo('Transaction was already confirmed (LocalNet dev mode)') confirmedInfo = initialStatus } else { printInfo('Waiting for confirmation...') printInfo('') confirmedInfo = await waitForConfirmation(algod, txId, 10) }
printSuccess('Transaction confirmed!') printInfo('')
// ========================================================================= // Step 7: Display confirmed transaction details // ========================================================================= printStep(7, 'Displaying confirmed transaction details')
printInfo('Confirmed Transaction Details:') printInfo(` confirmedRound: ${confirmedInfo.confirmedRound?.toLocaleString('en-US')}`) printInfo('')
// Display the transaction object details printInfo('Transaction Object (txn):') const txn = confirmedInfo.txn.txn printInfo(` type: ${txn.type}`) printInfo(` sender: ${shortenAddress(txn.sender.toString())}`) printInfo(` fee: ${formatFee(txn.fee ?? 0n)}`) printInfo(` firstValid: ${txn.firstValid.toLocaleString('en-US')}`) printInfo(` lastValid: ${txn.lastValid.toLocaleString('en-US')}`) printInfo(` genesisId: ${txn.genesisId}`)
// Payment-specific fields if (txn.payment) { printInfo('') printInfo('Payment Fields:') printInfo(` receiver: ${shortenAddress(txn.payment.receiver.toString())}`) printInfo(` amount: ${formatMicroAlgo(txn.payment.amount)}`) } printInfo('')
// Display rewards (if any) if (confirmedInfo.senderRewards !== undefined) { printInfo('Rewards:') printInfo(` senderRewards: ${formatMicroAlgo(confirmedInfo.senderRewards)}`) if (confirmedInfo.receiverRewards !== undefined) { printInfo(` receiverRewards: ${formatMicroAlgo(confirmedInfo.receiverRewards)}`) } printInfo('') }
// ========================================================================= // Step 8: Handle and display transaction errors // ========================================================================= printStep(8, 'Demonstrating error handling (poolError)')
printInfo('The poolError field in PendingTransactionResponse indicates why a') printInfo('transaction was rejected from the transaction pool.') printInfo('')
printInfo('Common poolError values:') printInfo(' "" (empty string) - Transaction is valid and in pool/confirmed') printInfo(' "transaction already in ledger" - Duplicate transaction') printInfo(' "txn dead" - Transaction validity window expired') printInfo(' "overspend" - Sender has insufficient funds') printInfo(' "fee too small" - Fee is below network minimum') printInfo(' "asset frozen" - Asset is frozen for the account') printInfo(' "logic eval error" - Smart contract evaluation failed') printInfo('')
printInfo('Best practice: Always check poolError before assuming success') printInfo('')
// Example of checking poolError printInfo('Example error handling pattern:') printInfo('```') printInfo('const pendingInfo = await algod.pendingTransactionInformation(txId)') printInfo('if (pendingInfo.poolError && pendingInfo.poolError.length > 0) {') printInfo(' throw new Error(`Transaction rejected: ${pendingInfo.poolError}`)') printInfo('}') printInfo('if (pendingInfo.confirmedRound && pendingInfo.confirmedRound > 0) {') printInfo(' console.log("Transaction confirmed!")') printInfo('}') printInfo('```') printInfo('')
// ========================================================================= // Step 9: Verify the payment was received // ========================================================================= printStep(9, 'Verifying the receiver got the funds')
const receiverInfo = await algod.accountInformation(receiver.addr.toString()) printInfo(`Receiver balance: ${formatMicroAlgo(receiverInfo.amount)}`)
if (receiverInfo.amount === paymentAmount.microAlgo) { printSuccess(`Payment of ${paymentAmount.algo} ALGO received successfully!`) } else { printError(`Expected ${paymentAmount.microAlgo} µALGO but got ${receiverInfo.amount} µALGO`) }
// ========================================================================= // Summary // ========================================================================= printHeader('Summary') printInfo('This example demonstrated:') printInfo(' 1. sendRawTransaction(signedTxn) - Submit a signed transaction') printInfo(' 2. pendingTransactionInformation(txId) - Check transaction status') printInfo(' 3. waitForConfirmation loop - Poll until confirmed or rejected') printInfo(' 4. Confirmed transaction details: confirmedRound, txn, fee') printInfo(' 5. Error handling with poolError field') printInfo('') printInfo('Key PendingTransactionResponse fields:') printInfo(' - confirmedRound: Round when confirmed (bigint, undefined if pending)') printInfo(' - poolError: Error message if rejected (string, empty if OK)') printInfo(' - txn: The SignedTransaction object') printInfo(' - senderRewards: Rewards applied to sender (bigint)') printInfo(' - receiverRewards: Rewards applied to receiver (bigint)') printInfo(' - closingAmount: Amount sent to close-to address (bigint)') printInfo('') printInfo('Transaction Status Cases:') printInfo(' - confirmedRound > 0: Transaction committed to ledger') printInfo(' - confirmedRound = 0, poolError = "": Still pending in pool') printInfo(' - confirmedRound = 0, poolError != "": Rejected from pool') printInfo('') printInfo('Best practices:') printInfo(' - Always wait for confirmation before considering a transaction final') printInfo(' - Check poolError for rejection reasons') printInfo(' - Use appropriate timeout (validity window is typically 1000 rounds)') printInfo(' - On LocalNet dev mode, transactions confirm immediately')}
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