Skip to content

Transaction Signing with KMD

← Back to KMD Client

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
  • LocalNet running (via algokit localnet start)

From the repository’s examples directory:

Terminal window
cd examples
uv run python kmd_client/10_transaction_signing.py

View source on GitHub

10_transaction_signing.py
# ruff: noqa: N999, C901, PLR0912, PLR0915
"""
Example: Transaction Signing with KMD
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:
- 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, SignTxnRequest
from algokit_transact import PaymentTransactionFields, Transaction, TransactionType, assign_fee, encode_transaction_raw
from algokit_utils import AlgoAmount
from 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()