Transaction Signing with KMD
Description
Section titled “Description”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
Section titled “Prerequisites”- LocalNet running (via
algokit localnet start) - Covered operations:
- signTransaction() - Sign a transaction using a key from the wallet
Run This Example
Section titled “Run This Example”From the repository root:
cd examplesnpm run example kmd_client/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);});Other examples in KMD Client
Section titled “Other examples in KMD Client”- KMD Version Information
- Wallet Creation and Listing
- Wallet Session Management
- Key Generation
- Key Import and Export
- Key Listing and Deletion
- Master Key Export
- Multisig Account Setup
- Multisig Account Management
- Transaction Signing with KMD
- Multisig Transaction Signing with KMD
- Program Signing (Delegated Logic Signatures) with KMD
- Multisig Program Signing (Delegated Multisig Logic Signatures) with KMD