Skip to content

Transactions

This guide covers building, signing, and broadcasting transactions with the Kaspa Python SDK.

Security Warning

Handle Private Keys Securely

These examples do not use proper private key/mnemonic/seed handling. This is omitted for brevity.

Never store your private keys in plain text, or directly in source code. Store securely offline. Anyone with access to this phrase has full control over your funds.

Using the Generator

The Generator class handles UTXO selection, fee calculation, and change management:

import asyncio
from kaspa import (
    RpcClient, Resolver, Generator, PaymentOutput,
    Address, PrivateKey, NetworkId
)

async def send_payment():
    # Connect to network
    client = RpcClient(resolver=Resolver(), network_id="mainnet")
    await client.connect()

    try:
        # Your private key and address
        private_key = PrivateKey("your-private-key-hex")
        my_address = private_key.to_address("mainnet")

        # Fetch UTXOs
        utxos = await client.get_utxos_by_addresses({
            "addresses": [my_address.to_string()]
        })

        # Define payment
        recipient = Address("kaspa:recipient-address...")
        amount = 500_000_000  # 5 KAS in sompi

        # Create generator
        generator = Generator(
            network_id=NetworkId("mainnet"),
            entries=utxos["entries"],
            change_address=my_address,
            outputs=[PaymentOutput(recipient, amount)],
        )

        # Process transactions
        for pending_tx in generator:
            pending_tx.sign([private_key])
            tx_id = await pending_tx.submit(client)
            print(f"Submitted: {tx_id}")

        # Get summary
        summary = generator.summary()
        print(f"Total fees: {summary.fees}")
        print(f"Transactions: {summary.transactions}")

    finally:
        await client.disconnect()

asyncio.run(send_payment())

Generator Options

from kaspa import Generator, NetworkId, PaymentOutput

generator = Generator(
    # Required
    network_id=NetworkId("mainnet"),
    entries=utxo_entries,           # List of UTXOs
    change_address=my_address,       # Where to send change

    # Optional
    outputs=[payment1, payment2],    # Payment outputs
    payload=b"optional-data",        # OP_RETURN data
    priority_fee=1000,               # Additional fee in sompi
    priority_entries=priority_utxos, # UTXOs to use first
    sig_op_count=1,                  # Signature operations per input
    minimum_signatures=1,            # For multisig estimation
)

Estimating Transactions

Transactions can be estimated prior to submission.

from kaspa import Generator, estimate_transactions

# Using Generator.estimate()
generator = Generator(
    network_id="mainnet",
    entries=utxos,
    change_address=my_address,
    outputs=[PaymentOutput(recipient, amount)],
)

summary = generator.estimate()
print(f"Estimated fee: {summary.fees} sompi")
print(f"Number of transactions: {summary.transactions}")
print(f"UTXOs consumed: {summary.utxos}")

# Using standalone function
summary = estimate_transactions(
    network_id="mainnet",
    entries=utxos,
    change_address=my_address,
    outputs=[{"address": recipient, "amount": amount}],
)

Pending Transactions

The PendingTransaction represents a transaction ready for signing:

for pending_tx in generator:
    # Transaction properties
    print(f"ID: {pending_tx.id}")
    print(f"Payment amount: {pending_tx.payment_amount}")
    print(f"Change amount: {pending_tx.change_amount}")
    print(f"Fee: {pending_tx.fee_amount}")
    print(f"Mass: {pending_tx.mass}")
    print(f"Type: {pending_tx.transaction_type}")

    # Get UTXOs being spent
    utxo_refs = pending_tx.get_utxo_entries()

    # Get addresses involved
    addresses = pending_tx.addresses()

    # Access underlying transaction
    tx = pending_tx.transaction

Signing Transactions

Simple Signing

# Sign with one or more private keys
pending_tx.sign([private_key])

# Or sign with multiple keys for multisig
pending_tx.sign([key1, key2, key3])

Per-Input Signing

For more control, sign each input individually:

for i, utxo in enumerate(pending_tx.get_utxo_entries()):
    pending_tx.sign_input(i, private_key)

Custom Signature Scripts

For advanced use cases (like multisig):

from kaspa import SighashType

# Create signature
signature = pending_tx.create_input_signature(
    input_index=0,
    private_key=private_key,
    sighash_type=SighashType.All
)

# Set custom signature script
pending_tx.fill_input(0, signature_script_bytes)

Manual Transaction Building

Transactions can be built manually:

from kaspa import (
    Transaction, TransactionInput, TransactionOutput,
    TransactionOutpoint, ScriptPublicKey, UtxoEntryReference,
    sign_transaction
)

# Create inputs from UTXOs
inputs = []
for utxo in my_utxos:
    outpoint = TransactionOutpoint(
        transaction_id=utxo["outpoint"]["transactionId"],
        index=utxo["outpoint"]["index"]
    )
    tx_input = TransactionInput(
        previous_outpoint=outpoint,
        signature_script="",  # Will be filled when signing
        sequence=0,
        sig_op_count=1,
        utxo=UtxoEntryReference(utxo)
    )
    inputs.append(tx_input)

# Create outputs
outputs = [
    TransactionOutput(
        value=amount,
        script_public_key=pay_to_address_script(recipient)
    ),
    TransactionOutput(
        value=change_amount,
        script_public_key=pay_to_address_script(change_address)
    )
]

# Build transaction
tx = Transaction(
    version=0,
    inputs=inputs,
    outputs=outputs,
    lock_time=0,
    subnetwork_id="0000000000000000000000000000000000000000",
    gas=0,
    payload="",
    mass=0
)

# Calculate and update mass
from kaspa import update_transaction_mass
update_transaction_mass("mainnet", tx)

# Sign
signed_tx = sign_transaction(tx, [private_key], verify_sig=True)

Transaction Mass and Fees

Kaspa uses a mass-based fee model:

from kaspa import (
    calculate_transaction_mass,
    calculate_transaction_fee,
    calculate_storage_mass,
    maximum_standard_transaction_mass
)

# Get maximum allowed mass
max_mass = maximum_standard_transaction_mass()
print(f"Max mass: {max_mass}")

# Calculate transaction mass
mass = calculate_transaction_mass("mainnet", tx)
print(f"Transaction mass: {mass}")

# Calculate required fee
fee = calculate_transaction_fee("mainnet", tx)
print(f"Required fee: {fee} sompi")

# Calculate storage mass component
storage_mass = calculate_storage_mass(
    network_id="mainnet",
    input_values=[1000000, 2000000],
    output_values=[2500000, 400000]
)

Submitting Transactions

# Using PendingTransaction
tx_id = await pending_tx.submit(client)

# Manual submission
result = await client.submit_transaction({
    "transaction": tx.serialize_to_dict(),
    "allowOrphan": False
})

Helper Functions

Create Single Transaction

from kaspa import create_transaction

tx = create_transaction(
    utxo_entry_source=utxos,
    outputs=[{"address": "kaspa:...", "amount": 100000000}],
    priority_fee=1000,
    payload=None,
    sig_op_count=1
)

Create Multiple Transactions

from kaspa import create_transactions

result = create_transactions(
    network_id="mainnet",
    entries=utxos,
    change_address=my_address,
    outputs=[{"address": "kaspa:...", "amount": 100000000}],
    priority_fee=1000,
)

for pending in result["transactions"]:
    pending.sign([private_key])
    # submit...

print(f"Summary: {result['summary']}")

Multi-Signature Transactions

from kaspa import (
    Generator, create_multisig_address,
    PublicKey, NetworkType
)

# Create multisig address (2-of-3)
pubkeys = [PublicKey(k) for k in [key1_pub, key2_pub, key3_pub]]
multisig_addr = create_multisig_address(2, pubkeys, NetworkType.Mainnet)

# Build transaction spending from multisig
generator = Generator(
    network_id="mainnet",
    entries=multisig_utxos,
    change_address=multisig_addr,
    outputs=[PaymentOutput(recipient, amount)],
    minimum_signatures=2,  # For accurate mass calculation
)

for pending_tx in generator:
    # Collect signatures from 2 of 3 signers
    pending_tx.sign([signer1_key, signer2_key])
    tx_id = await pending_tx.submit(client)

Unit Conversions

from kaspa import kaspa_to_sompi, sompi_to_kaspa, sompi_to_kaspa_string_with_suffix

# KAS to sompi
sompi = kaspa_to_sompi(1.5)  # 150,000,000 sompi

# Sompi to KAS
kas = sompi_to_kaspa(150000000)  # 1.5 KAS

# Formatted string
formatted = sompi_to_kaspa_string_with_suffix(150000000, "mainnet")
# "1.5 KAS"