Skip to content

Transaction Signing with KMD

← Back to KMD Client

This example demonstrates how to sign transactions using the KMD signTransaction() method. It shows the complete workflow of creating a wallet, generating a key, funding it, signing a transaction, and submitting it to the network.

  • LocalNet running (via algokit localnet start)
  • Covered operations:
  • signTransaction() - Sign a transaction using a key from the wallet

From the repository root:

Terminal window
cd examples
npm run example kmd_client/10-transaction-signing.ts

View source on GitHub

10-transaction-signing.ts
/**
* Example: Transaction Signing with KMD
*
* This example demonstrates how to sign transactions using the KMD
* `signTransaction()` method. It shows the complete workflow of creating
* a wallet, generating a key, funding it, signing a transaction, and
* submitting it to the network.
*
* Prerequisites:
* - LocalNet running (via `algokit localnet start`)
*
* Covered operations:
* - signTransaction() - Sign a transaction using a key from the wallet
*/
import { algo } from '@algorandfoundation/algokit-utils'
import { Transaction, TransactionType, assignFee } from '@algorandfoundation/algokit-utils/transact'
import {
cleanupTestWallet,
createAlgodClient,
createAlgorandClient,
createKmdClient,
createTestWallet,
printError,
printHeader,
printInfo,
printStep,
printSuccess,
shortenAddress,
} from '../shared/utils.js'
/**
* Format a byte array for display, showing first and last few bytes
*/
function formatBytesForDisplay(bytes: Uint8Array, showFirst = 8, showLast = 8): string {
const hex = Buffer.from(bytes).toString('hex')
if (bytes.length <= showFirst + showLast) {
return hex
}
const firstBytes = hex.slice(0, showFirst * 2)
const lastBytes = hex.slice(-(showLast * 2))
return `${firstBytes}...${lastBytes}`
}
/**
* Format microAlgos to a human-readable string
*/
function formatMicroAlgo(microAlgos: bigint): string {
const algoValue = Number(microAlgos) / 1_000_000
return `${microAlgos.toLocaleString('en-US')} µALGO (${algoValue.toFixed(6)} ALGO)`
}
async function main() {
printHeader('KMD Transaction Signing Example')
const kmd = createKmdClient()
const algod = createAlgodClient()
const algorand = createAlgorandClient()
let walletHandleToken = ''
const walletPassword = 'test-password'
try {
// =========================================================================
// Step 1: Create a Test Wallet
// =========================================================================
printStep(1, 'Creating a test wallet for transaction signing')
const testWallet = await createTestWallet(kmd, walletPassword)
walletHandleToken = testWallet.walletHandleToken
printSuccess(`Test wallet created: ${testWallet.walletName}`)
printInfo(`Wallet ID: ${testWallet.walletId}`)
// =========================================================================
// Step 2: Generate a Key in the Wallet
// =========================================================================
printStep(2, 'Generating a key in the wallet')
const keyResult = await kmd.generateKey({
walletHandleToken,
})
const senderAddress = keyResult.address
printSuccess(`Key generated: ${senderAddress}`)
// =========================================================================
// Step 3: Fund the Generated Key Using the Dispenser
// =========================================================================
printStep(3, 'Funding the generated key using the dispenser')
const dispenser = await algorand.account.dispenserFromEnvironment()
printInfo(`Dispenser address: ${shortenAddress(dispenser.addr.toString())}`)
// Fund the generated key with 1 ALGO
const fundAmount = algo(1) // 1 ALGO
await algorand.send.payment({
sender: dispenser.addr,
receiver: senderAddress,
amount: fundAmount,
})
// Verify funding
const accountInfo = await algod.accountInformation(senderAddress.toString())
printSuccess(`Account funded: ${formatMicroAlgo(accountInfo.amount)}`)
const fundAmountMicroAlgos = fundAmount.microAlgo
// =========================================================================
// Step 4: Create a Payment Transaction
// =========================================================================
printStep(4, 'Creating a payment transaction using AlgorandClient suggestedParams')
// Get suggested transaction parameters from algod
const suggestedParams = await algod.suggestedParams()
printInfo('Suggested Parameters:')
printInfo(` First Valid Round: ${suggestedParams.firstValid.toLocaleString('en-US')}`)
printInfo(` Last Valid Round: ${suggestedParams.lastValid.toLocaleString('en-US')}`)
printInfo(` Genesis ID: ${suggestedParams.genesisId}`)
printInfo(` Min Fee: ${formatMicroAlgo(suggestedParams.minFee)}`)
printInfo('')
// Create a receiver (we'll send a small amount back to the dispenser)
const receiverAddress = dispenser.addr
const paymentAmount = 100_000n // 0.1 ALGO
// Create the transaction using the Transaction class from algokit-transact
const transactionWithoutFee = new Transaction({
type: TransactionType.Payment,
sender: senderAddress,
firstValid: suggestedParams.firstValid,
lastValid: suggestedParams.lastValid,
genesisHash: suggestedParams.genesisHash,
genesisId: suggestedParams.genesisId,
payment: {
receiver: receiverAddress,
amount: paymentAmount,
},
})
// Assign the fee using suggested params
const transaction = assignFee(transactionWithoutFee, {
feePerByte: suggestedParams.fee,
minFee: suggestedParams.minFee,
})
const txId = transaction.txId()
printSuccess('Transaction created!')
printInfo('')
printInfo('Transaction Details:')
printInfo(` Transaction ID: ${txId}`)
printInfo(` Sender: ${shortenAddress(senderAddress.toString())}`)
printInfo(` Receiver: ${shortenAddress(receiverAddress.toString())}`)
printInfo(` Amount: ${formatMicroAlgo(paymentAmount)}`)
printInfo(` Fee: ${formatMicroAlgo(transaction.fee ?? 0n)}`)
// =========================================================================
// Step 5: Sign the Transaction Using signTransaction()
// =========================================================================
printStep(5, 'Signing the transaction with signTransaction()')
const signResult = await kmd.signTransaction({
walletHandleToken,
transaction,
walletPassword,
})
printSuccess('Transaction signed successfully!')
printInfo('')
printInfo('SignTransactionResponse fields:')
printInfo(` signedTransaction: Uint8Array (${signResult.signedTransaction.length} bytes)`)
printInfo('')
printInfo('Signed transaction bytes (abbreviated):')
printInfo(` ${formatBytesForDisplay(signResult.signedTransaction)}`)
printInfo('')
printInfo('The signTransaction() method:')
printInfo(' - Takes walletHandleToken, transaction, and walletPassword')
printInfo(' - Finds the private key matching the sender in the wallet')
printInfo(' - Signs the transaction and returns the signed bytes')
// =========================================================================
// Step 6: Submit the Signed Transaction to the Network
// =========================================================================
printStep(6, 'Submitting the signed transaction to the network using algod')
const submitResponse = await algod.sendRawTransaction(signResult.signedTransaction)
printSuccess('Transaction submitted!')
printInfo(`Transaction ID: ${submitResponse.txId}`)
// =========================================================================
// Step 7: Wait for Confirmation
// =========================================================================
printStep(7, 'Waiting for confirmation')
// On LocalNet in dev mode, transactions confirm immediately
// Poll for confirmation
let confirmedRound: bigint | undefined
const maxWaitRounds = 10
let currentRound = (await algod.status()).lastRound
for (let i = 0; i < maxWaitRounds; i++) {
const pendingInfo = await algod.pendingTransactionInformation(txId)
if (pendingInfo.confirmedRound && pendingInfo.confirmedRound > 0n) {
confirmedRound = pendingInfo.confirmedRound
break
}
if (pendingInfo.poolError && pendingInfo.poolError.length > 0) {
throw new Error(`Transaction rejected: ${pendingInfo.poolError}`)
}
// Wait for next block
await algod.statusAfterBlock(currentRound)
currentRound++
}
if (confirmedRound) {
printSuccess(`Transaction confirmed in round ${confirmedRound.toLocaleString('en-US')}`)
} else {
printError('Transaction not confirmed within expected rounds')
}
// =========================================================================
// Step 8: Verify the Transaction
// =========================================================================
printStep(8, 'Verifying the transaction was successful')
// Check sender's balance (should be reduced by payment + fee)
const senderInfo = await algod.accountInformation(senderAddress.toString())
printInfo(`Sender balance after: ${formatMicroAlgo(senderInfo.amount)}`)
const expectedBalance = fundAmountMicroAlgos - paymentAmount - (transaction.fee ?? suggestedParams.minFee)
printInfo(`Expected balance: ~${formatMicroAlgo(expectedBalance)}`)
printInfo('')
if (senderInfo.amount <= fundAmountMicroAlgos - paymentAmount) {
printSuccess('Transaction verified! Balance reduced as expected.')
}
// =========================================================================
// Cleanup
// =========================================================================
printStep(9, 'Cleaning up test wallet')
await cleanupTestWallet(kmd, walletHandleToken)
walletHandleToken = '' // Mark as cleaned up
printSuccess('Test wallet handle released')
// =========================================================================
// Summary
// =========================================================================
printHeader('Summary')
printInfo('This example demonstrated transaction signing with KMD:')
printInfo('')
printInfo(' signTransaction() - Sign a transaction using a wallet key')
printInfo(' Parameters:')
printInfo(' - walletHandleToken: The wallet session token')
printInfo(' - transaction: The Transaction object to sign')
printInfo(' - walletPassword: The wallet password for security')
printInfo(' Returns:')
printInfo(' - signedTransaction: Uint8Array of signed transaction bytes')
printInfo('')
printInfo('Complete workflow:')
printInfo(' 1. Create/unlock a wallet and get walletHandleToken')
printInfo(' 2. Generate a key in the wallet (or import one)')
printInfo(' 3. Fund the key using the dispenser or another source')
printInfo(' 4. Create a Transaction using suggested params from algod')
printInfo(' 5. Sign with kmd.signTransaction()')
printInfo(' 6. Submit with algod.sendRawTransaction()')
printInfo(' 7. Wait for confirmation with pendingTransactionInformation()')
printInfo('')
printInfo('Key points:')
printInfo(' - The wallet password is required to sign transactions')
printInfo(' - The sender address in the transaction must match a key in the wallet')
printInfo(' - The signed transaction can be submitted to any algod node')
printInfo(' - KMD keeps private keys secure; only signed bytes are returned')
printInfo('')
printInfo('Note: The test wallet remains in KMD (wallets cannot be deleted via API).')
} catch (error) {
printError(`Error: ${error instanceof Error ? error.message : String(error)}`)
printInfo('')
printInfo('Troubleshooting:')
printInfo(' - Ensure LocalNet is running: algokit localnet start')
printInfo(' - If LocalNet issues occur: algokit localnet reset')
printInfo(' - Check that KMD is accessible on port 4002')
printInfo(' - Check that Algod is accessible on port 4001')
// Cleanup on error
if (walletHandleToken) {
await cleanupTestWallet(kmd, walletHandleToken)
}
process.exit(1)
}
}
main().catch((error) => {
console.error('Fatal error:', error)
process.exit(1)
})