Payment with Close
Description
Section titled “Description”This example demonstrates how to close an account by transferring all remaining ALGO to another account using the closeRemainderTo field in PaymentTransactionFields. Key concepts:
- closeRemainderTo: Specifies an account to receive all remaining ALGO after the transaction
- When an account is closed, its balance becomes 0
- The close-to account receives: (original balance - sent amount - fee)
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 transact/02-payment-close.ts/** * Example: Payment with Close * * This example demonstrates how to close an account by transferring all remaining * ALGO to another account using the closeRemainderTo field in PaymentTransactionFields. * * Key concepts: * - closeRemainderTo: Specifies an account to receive all remaining ALGO after the transaction * - When an account is closed, its balance becomes 0 * - The close-to account receives: (original balance - sent amount - fee) * * Prerequisites: * - LocalNet running (via `algokit localnet start`) */
import { AlgorandClient } from '@algorandfoundation/algokit-utils'import { Transaction, TransactionType, assignFee, type PaymentTransactionFields,} from '@algorandfoundation/algokit-utils/transact'import { createAlgodClient, formatAlgo, getAccountBalance, printHeader, printInfo, printStep, printSuccess, shortenAddress, waitForConfirmation,} from '../shared/utils.js'
/** * Gets a funded account from LocalNet's KMD wallet */async function getLocalNetFundedAccount(algorand: AlgorandClient) { return await algorand.account.kmd.getLocalNetDispenserAccount()}
async function main() { printHeader('Payment with Close Example')
// Step 1: Initialize clients printStep(1, 'Initialize Algod Client') const algod = createAlgodClient() const algorand = AlgorandClient.defaultLocalNet() printInfo('Connected to LocalNet Algod')
// Step 2: Get a funded account from KMD (funding source) printStep(2, 'Get Funded Account from KMD') const fundingAccount = await getLocalNetFundedAccount(algorand) printInfo(`Funding account: ${shortenAddress(fundingAccount.addr.toString())}`)
// Step 3: Generate temporary account to be closed using AlgorandClient helper printStep(3, 'Generate Temporary Account (will be closed)') const tempAccount = algorand.account.random() printInfo(`Temporary account: ${shortenAddress(tempAccount.addr.toString())}`)
// Step 4: Generate close-to account (receives remaining balance) using AlgorandClient helper printStep(4, 'Generate Close-To Account (receives remainder)') const closeToAccount = algorand.account.random() printInfo(`Close-to account: ${shortenAddress(closeToAccount.addr.toString())}`)
// Step 5: Fund the temporary account printStep(5, 'Fund Temporary Account') const fundAmount = 2_000_000n // 2 ALGO in microALGO (enough to cover min balance + tx amount + fee)
const fundParams = await algod.suggestedParams() const fundTx = new Transaction({ type: TransactionType.Payment, sender: fundingAccount.addr, firstValid: fundParams.firstValid, lastValid: fundParams.lastValid, genesisHash: fundParams.genesisHash, genesisId: fundParams.genesisId, payment: { receiver: tempAccount.addr, amount: fundAmount, }, }) const fundTxWithFee = assignFee(fundTx, { feePerByte: fundParams.fee, minFee: fundParams.minFee, }) const signedFundTxns = await fundingAccount.signer([fundTxWithFee], [0]) await algod.sendRawTransaction(signedFundTxns) await waitForConfirmation(algod, fundTxWithFee.txId())
const tempBalanceAfterFund = await getAccountBalance(algorand, tempAccount.addr.toString()) printInfo(`Funded temporary account with: ${formatAlgo(fundAmount)}`) printInfo(`Temporary account balance: ${formatAlgo(tempBalanceAfterFund.microAlgo)}`)
// Step 6: Record initial close-to account balance printStep(6, 'Check Initial Close-To Account Balance') let closeToBalanceBefore: bigint try { const info = await getAccountBalance(algorand, closeToAccount.addr.toString()) closeToBalanceBefore = info.microAlgo } catch { closeToBalanceBefore = 0n } printInfo(`Close-to account initial balance: ${formatAlgo(closeToBalanceBefore)}`)
// Step 7: Create payment transaction with closeRemainderTo printStep(7, 'Create Payment Transaction with closeRemainderTo') const suggestedParams = await algod.suggestedParams() const paymentAmount = 100_000n // 0.1 ALGO sent to funding account (can be 0)
// The key field: closeRemainderTo specifies where remaining balance goes const paymentFields: PaymentTransactionFields = { receiver: fundingAccount.addr, // Send a small amount to funding account amount: paymentAmount, closeRemainderTo: closeToAccount.addr, // All remaining balance goes here }
const closeTx = new Transaction({ type: TransactionType.Payment, sender: tempAccount.addr, firstValid: suggestedParams.firstValid, lastValid: suggestedParams.lastValid, genesisHash: suggestedParams.genesisHash, genesisId: suggestedParams.genesisId, payment: paymentFields, })
printInfo(`Payment amount: ${formatAlgo(paymentAmount)}`) printInfo(`closeRemainderTo: ${shortenAddress(closeToAccount.addr.toString())}`)
// Step 8: Assign fee and sign the transaction printStep(8, 'Assign Fee and Sign Transaction') const closeTxWithFee = assignFee(closeTx, { feePerByte: suggestedParams.fee, minFee: suggestedParams.minFee, }) const txFee = closeTxWithFee.fee ?? 0n printInfo(`Transaction fee: ${txFee} microALGO`)
// Calculate expected remainder before signing const expectedRemainder = tempBalanceAfterFund.microAlgo - paymentAmount - BigInt(txFee) printInfo(`Expected remainder to close-to account: ${formatAlgo(expectedRemainder)}`)
// Sign using the temp account's signer const signedCloseTxns = await tempAccount.signer([closeTxWithFee], [0]) const txId = closeTxWithFee.txId() printInfo(`Transaction ID: ${txId}`)
// Step 9: Submit and confirm the close transaction printStep(9, 'Submit Close Transaction') await algod.sendRawTransaction(signedCloseTxns) printInfo('Transaction submitted to network')
const pendingInfo = await waitForConfirmation(algod, txId) printInfo(`Transaction confirmed in round: ${pendingInfo.confirmedRound}`)
// Step 10: Verify closed account has 0 balance printStep(10, 'Verify Closed Account Balance') let tempBalanceAfterClose: bigint try { const info = await getAccountBalance(algorand, tempAccount.addr.toString()) tempBalanceAfterClose = info.microAlgo } catch { // Account may not exist anymore after being closed tempBalanceAfterClose = 0n } printInfo(`Temporary account balance after close: ${formatAlgo(tempBalanceAfterClose)}`)
if (tempBalanceAfterClose === 0n) { printSuccess('Temporary account successfully closed (balance is 0)') } else { throw new Error(`Expected closed account to have 0 balance, but got ${tempBalanceAfterClose}`) }
// Step 11: Verify close-to account received the remainder printStep(11, 'Verify Close-To Account Received Remainder') const closeToBalanceAfter = await getAccountBalance(algorand, closeToAccount.addr.toString()) printInfo(`Close-to account balance after: ${formatAlgo(closeToBalanceAfter.microAlgo)}`)
const actualRemainder = closeToBalanceAfter.microAlgo - closeToBalanceBefore printInfo(`Actual remainder received: ${formatAlgo(actualRemainder)}`) printInfo(`Expected remainder: ${formatAlgo(expectedRemainder)}`)
// Verify the close-to account received the expected remainder if (actualRemainder === expectedRemainder) { printSuccess(`Close-to account received correct remainder of ${formatAlgo(expectedRemainder)}`) } else { throw new Error(`Expected remainder ${expectedRemainder}, but got ${actualRemainder}`) }
printSuccess('Payment with close example completed successfully!')}
main().catch((error) => { console.error('Error:', error) process.exit(1)})