Transaction Signing with KMD
Description
Section titled “Description”This example demonstrates how to sign transactions using the KMD sign_transaction() 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: Covered operations:
- sign_transaction() - Sign a transaction using a key from the wallet
Prerequisites
Section titled “Prerequisites”- LocalNet running (via
algokit localnet start)
Run This Example
Section titled “Run This Example”From the repository’s examples directory:
cd examplesuv run python kmd_client/10_transaction_signing.py# ruff: noqa: N999, C901, PLR0912, PLR0915"""Example: Transaction Signing with KMD
This example demonstrates how to sign transactions using the KMDsign_transaction() method. It shows the complete workflow of creatinga wallet, generating a key, funding it, signing a transaction, andsubmitting it to the network.
Prerequisites:- LocalNet running (via `algokit localnet start`)
Covered operations:- sign_transaction() - Sign a transaction using a key from the wallet"""
import sys
from shared import ( cleanup_test_wallet, create_algod_client, create_algorand_client, create_kmd_client, create_test_wallet, print_error, print_header, print_info, print_step, print_success, shorten_address, wait_for_confirmation,)
from algokit_kmd_client.models import GenerateKeyRequest, SignTxnRequestfrom algokit_transact import PaymentTransactionFields, Transaction, TransactionType, assign_fee, encode_transaction_rawfrom algokit_utils import AlgoAmountfrom algokit_utils.transactions.types import PaymentParams
def format_bytes_for_display(data: bytes, show_first: int = 8, show_last: int = 8) -> str: """Format a byte array for display, showing first and last few bytes.""" hex_str = data.hex() if len(data) <= show_first + show_last: return hex_str first_bytes = hex_str[: show_first * 2] last_bytes = hex_str[-(show_last * 2) :] return f"{first_bytes}...{last_bytes}"
def format_micro_algo(micro_algo: int) -> str: """Format microAlgos to a human-readable string.""" algo_value = micro_algo / 1_000_000 return f"{micro_algo:,} microALGO ({algo_value:.6f} ALGO)"
def main() -> None: print_header("KMD Transaction Signing Example")
kmd = create_kmd_client() algod = create_algod_client() algorand = create_algorand_client() wallet_handle_token = "" wallet_password = "test-password"
try: # ========================================================================= # Step 1: Create a Test Wallet # ========================================================================= print_step(1, "Creating a test wallet for transaction signing")
test_wallet = create_test_wallet(kmd, wallet_password) wallet_handle_token = test_wallet["wallet_handle_token"]
print_success(f"Test wallet created: {test_wallet['wallet_name']}") print_info(f"Wallet ID: {test_wallet['wallet_id']}")
# ========================================================================= # Step 2: Generate a Key in the Wallet # ========================================================================= print_step(2, "Generating a key in the wallet")
generate_key_response = kmd.generate_key( GenerateKeyRequest( wallet_handle_token=wallet_handle_token, ) ) sender_address = generate_key_response.address
print_success(f"Key generated: {sender_address}")
# ========================================================================= # Step 3: Fund the Generated Key Using the Dispenser # ========================================================================= print_step(3, "Funding the generated key using the dispenser")
dispenser = algorand.account.localnet_dispenser() print_info(f"Dispenser address: {shorten_address(dispenser.addr)}")
# Fund the generated key with 1 ALGO fund_amount = AlgoAmount.from_algo(1) algorand.send.payment( PaymentParams( sender=dispenser.addr, receiver=sender_address, amount=fund_amount, ) )
# Verify funding account_info = algod.account_information(sender_address) print_success(f"Account funded: {format_micro_algo(account_info.amount)}")
fund_amount_micro_algo = fund_amount.micro_algo
# ========================================================================= # Step 4: Create a Payment Transaction # ========================================================================= print_step(4, "Creating a payment transaction using algod suggestedParams")
# Get suggested transaction parameters from algod suggested_params = algod.suggested_params()
print_info("Suggested Parameters:") print_info(f" First Valid Round: {suggested_params.first_valid:,}") print_info(f" Last Valid Round: {suggested_params.last_valid:,}") print_info(f" Genesis ID: {suggested_params.genesis_id}") print_info(f" Min Fee: {format_micro_algo(suggested_params.min_fee)}") print_info("")
# Create a receiver (we'll send a small amount back to the dispenser) receiver_address = dispenser.addr payment_amount = 100_000 # 0.1 ALGO
# Create the transaction using Transaction and PaymentTransactionFields from algokit-transact transaction_without_fee = Transaction( transaction_type=TransactionType.Payment, sender=sender_address, first_valid=suggested_params.first_valid, last_valid=suggested_params.last_valid, genesis_hash=suggested_params.genesis_hash, genesis_id=suggested_params.genesis_id, payment=PaymentTransactionFields( receiver=receiver_address, amount=payment_amount, ), )
# Assign the fee using suggested params transaction = assign_fee( transaction_without_fee, fee_per_byte=suggested_params.fee, min_fee=suggested_params.min_fee, )
tx_id = transaction.tx_id()
print_success("Transaction created!") print_info("") print_info("Transaction Details:") print_info(f" Transaction ID: {tx_id}") print_info(f" Sender: {shorten_address(sender_address)}") print_info(f" Receiver: {shorten_address(receiver_address)}") print_info(f" Amount: {format_micro_algo(payment_amount)}") print_info(f" Fee: {format_micro_algo(transaction.fee or 0)}")
# ========================================================================= # Step 5: Sign the Transaction Using sign_transaction() # ========================================================================= print_step(5, "Signing the transaction with sign_transaction()")
tx_bytes = encode_transaction_raw(transaction) signed_txn_response = kmd.sign_transaction( SignTxnRequest( transaction=tx_bytes, wallet_handle_token=wallet_handle_token, wallet_password=wallet_password, ) ) signed_txn = signed_txn_response.signed_transaction
print_success("Transaction signed successfully!") print_info("") print_info("sign_transaction() return value:") print_info(f" signed_transaction: bytes ({len(signed_txn)} bytes)") print_info("") print_info("Signed transaction bytes (abbreviated):") print_info(f" {format_bytes_for_display(signed_txn)}") print_info("") print_info("The sign_transaction() method:") print_info(" - Takes wallet_handle_token, wallet_password, and transaction") print_info(" - Finds the private key matching the sender in the wallet") print_info(" - Signs the transaction and returns the signed bytes")
# ========================================================================= # Step 6: Submit the Signed Transaction to the Network # ========================================================================= print_step(6, "Submitting the signed transaction to the network using algod")
submit_response = algod.send_raw_transaction(signed_txn)
print_success("Transaction submitted!") print_info(f"Transaction ID: {submit_response.tx_id}")
# ========================================================================= # Step 7: Wait for Confirmation # ========================================================================= print_step(7, "Waiting for confirmation")
# On LocalNet in dev mode, transactions confirm immediately pending_info = wait_for_confirmation(algod, tx_id)
confirmed_round = pending_info.confirmed_round or 0 if confirmed_round > 0: print_success(f"Transaction confirmed in round {confirmed_round:,}") else: print_error("Transaction not confirmed within expected rounds")
# ========================================================================= # Step 8: Verify the Transaction # ========================================================================= print_step(8, "Verifying the transaction was successful")
# Check sender's balance (should be reduced by payment + fee) sender_info = algod.account_information(sender_address) print_info(f"Sender balance after: {format_micro_algo(sender_info.amount)}")
expected_balance = fund_amount_micro_algo - payment_amount - (transaction.fee or suggested_params.min_fee) print_info(f"Expected balance: ~{format_micro_algo(expected_balance)}") print_info("")
if sender_info.amount <= fund_amount_micro_algo - payment_amount: print_success("Transaction verified! Balance reduced as expected.")
# ========================================================================= # Cleanup # ========================================================================= print_step(9, "Cleaning up test wallet")
cleanup_test_wallet(kmd, wallet_handle_token) wallet_handle_token = "" # Mark as cleaned up
print_success("Test wallet handle released")
# ========================================================================= # Summary # ========================================================================= print_header("Summary") print_info("This example demonstrated transaction signing with KMD:") print_info("") print_info(" sign_transaction() - Sign a transaction using a wallet key") print_info(" Parameters:") print_info(" - wallet_handle_token: The wallet session token") print_info(" - wallet_password: The wallet password for security") print_info(" - transaction: The Transaction object to sign") print_info(" Returns:") print_info(" - signed_transaction: bytes of signed transaction") print_info("") print_info("Complete workflow:") print_info(" 1. Create/unlock a wallet and get wallet_handle_token") print_info(" 2. Generate a key in the wallet (or import one)") print_info(" 3. Fund the key using the dispenser or another source") print_info(" 4. Create a Transaction using suggested params from algod") print_info(" 5. Sign with kmd.sign_transaction()") print_info(" 6. Submit with algod.send_raw_transaction()") print_info(" 7. Wait for confirmation with pending_transaction_info()") print_info("") print_info("Key points:") print_info(" - The wallet password is required to sign transactions") print_info(" - The sender address in the transaction must match a key in the wallet") print_info(" - The signed transaction can be submitted to any algod node") print_info(" - KMD keeps private keys secure; only signed bytes are returned") print_info("") print_info("Note: The test wallet remains in KMD (wallets cannot be deleted via API).") except Exception as e: print_error(f"Error: {e}") print_info("") print_info("Troubleshooting:") print_info(" - Ensure LocalNet is running: algokit localnet start") print_info(" - If LocalNet issues occur: algokit localnet reset") print_info(" - Check that KMD is accessible on port 4002") print_info(" - Check that Algod is accessible on port 4001")
# Cleanup on error if wallet_handle_token: cleanup_test_wallet(kmd, wallet_handle_token)
sys.exit(1)
if __name__ == "__main__": main()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