Kaspa Forge
Deep dive

How a Kaspa Wallet Manages UTXOs Across Multiple Addresses

5 Aug 2026 By OfficeForge's AI team · human-reviewed 9 min read
Kaspa Wallet UTXO: HD Addresses, Coin Selection & Restore

Kaspa uses the UTXO model — the same accounting scheme as Bitcoin. There is no "account balance" stored on-chain. Instead, the ledger tracks discrete unspent transaction outputs: each one a fixed amount locked to a specific address and spending condition. This design gives each coin a clear, independently verifiable lineage, but it also means the wallet has to do real work behind the scenes — deriving addresses, tracking outputs across them, assembling transactions from multiple inputs, and scanning for funds during restore.

This article walks through how a non-custodial Kaspa wallet handles UTXOs in practice, and how Kaspa Forge implements each step in its browser-only Desk profile.

From one seed to many addresses

A non-custodial wallet begins with a single master seed — a random 256-bit value that never leaves the user's device. From that seed, a deterministic key-derivation function produces a sequence of keypairs, each mapping to one Kaspa address.

In the Desk, derivation uses HMAC-SHA512 with a domain-separated message:

HMAC-SHA512(
  key  = master_seed,
  msg  = "kaspaforge/v1/vault/0"   // first vault key
)
// → first 32 bytes = private key

The domain string (kaspaforge/v1/vault/<index>) ensures keys derived for vaults, escrow deals, the regular wallet, and the Kasia chat protocol never collide — even though they share the same seed. A pinned known-vector test verifies the expected output for each domain on every build, catching any drift in the scheme early.

Why derive many addresses instead of using one? Two reasons:

  • Compartmentalization. A vault key and a spending key serve different roles. Compromise of one doesn't expose the others.
  • Linkability management. Using a fresh address for each receive makes casual on-chain analysis harder (though, as we'll see, the UTXO model has limits here).

The wallet maintains a counter — the next unused derivation index — and hands out a new address each time the user requests one.

Tracking unspent outputs across addresses

After the wallet has derived, say, ten addresses, each of which may have received KAS at different times, the question becomes: *what's the balance?*

Kaspa nodes expose a UTXO index. The wallet queries it for all derived addresses at once and collects every unspent output. The balance is the sum:

address_0 → [UTXO(200 KAS), UTXO(50 KAS)]
address_1 → [UTXO(1000 KAS)]
address_2 → []                          // never used
─────────────────────────────────────────
total     = 1250 KAS

In the Desk wallet, this aggregation spans two address slots: wallet (the current receiving address) and walletOld (the previous one, still holding unspent outputs from past transactions). The Desk sums UTXOs from both and displays a single combined balance. There is no separate "account" on-chain — the aggregated view exists only in the wallet software.

This is a subtle difference from account-based chains. On Ethereum, the node stores balance[address] directly. On Kaspa, the wallet must do the summation itself, and it must know *which* addresses to check.

Building transactions from multiple UTXOs

Here is where the UTXO model shows its practical teeth.

Suppose you want to send 800 KAS, but your wallet holds:

UTXOAmountAddressKey
A200 KASaddress_0key_0
B50 KASaddress_0key_0
C1000 KASaddress_1key_1

The wallet's coin-selection algorithm might pick all three (total 1250 KAS), create two outputs — 800 KAS to the recipient and the remainder as change — and sign each input individually.

The critical detail: each input in a transaction needs its own signature, because each UTXO is locked to a specific address, and each address has its own key. A transaction spending from two different addresses requires two separate signing operations with two different private keys.

Definition

Coin selection is the algorithm a wallet uses to choose which UTXOs to spend. It must cover the output amount plus fees, minimize leftover change, and avoid creating dust outputs — tiny UTXOs that cost more in future fees to spend than they are worth.

In the Desk, spending builds a single transaction with one P2PK input per UTXO. A signing loop iterates over the selected inputs, matches each to its derivation index, derives the private key from the master seed, and produces a signature. The keys exist only in browser memory during this process and are never transmitted.

Kaspa's fee model is per-UTXO: the Kaspa wiki notes a base fee of 0.0001 KAS per UTXO utilized in a transaction (this is a node/wallet policy minimum, not a hard protocol constant — miners can accept lower fees). A transaction with 10 inputs pays roughly 10× the base fee of a single-input transaction. This creates a practical incentive to consolidate UTXOs: if you hold 50 small deposits, sweeping them into a single output in one consolidation transaction is cheaper than spending them individually over time.

Contrast this with covenant transactions — the kind used in Kaspa Safe or Kaspa Escrow. Covenant entrypoints enforce a single-input invariant: require(tx.inputs.length == 1). This prevents multi-UTXO siphoning attacks, but it also means a covenant UTXO must be spent on its own, never batched with others. For regular wallet spending, multi-input is normal and often necessary.

Restoring from seed: the gap-scan problem

A master seed backed up today should restore all funds — including deposits that arrive six months from now. But there's a subtlety: the wallet must discover *which* derived addresses hold UTXOs.

It can't just check addresses 0 through 9 and stop. What if the user generated address 15 during a future session? The standard solution is the gap-scan algorithm:

1. Derive addresses 0, 1, 2, … sequentially from the seed. 2. Query the node (or an index) for UTXOs on each address. 3. Track the gap — the number of consecutive addresses with no activity. 4. Stop when the gap reaches a threshold (commonly 20).

If the user has touched addresses 0–4 and 10, the scanner finds 0–4, encounters five unused addresses (5–9), hits 10 (used), then encounters 20+ unused addresses (11–30) and stops. All funds are found.

In Kaspa Forge's restore flow, the browser derives addresses from the master seed and asks the server to check which ones have UTXOs. The server matches by exact public key — it doesn't act as a general oracle for address existence. This preserves the non-custodial boundary: the server sees a list of public keys but never the seed or private keys.

An important property: since all keys are deterministically derived from the same seed, an encrypted backup taken today restores vaults created months later. The gap-scan simply extends the derivation range until it finds the new addresses.

One complication: Kaspa nodes prune old blocks and maintain only the current UTXO set. This means a local node cannot provide full transaction history. For the wallet to show past incoming and outgoing transactions (not just the current balance), it needs a separate transaction-history source — an index service that retained block data before pruning. Kaspa Forge proxies this through its server with a 30-second cache, classifying transactions as incoming, outgoing, or self-transfers. Vault addresses are tagged so the Desk can distinguish vault activity from regular wallet operations. The proxy also prevents the user's IP from leaking to the external indexer.

How Kaspa Forge puts it all together

The Desk — Kaspa Forge's encrypted browser profile — ties every piece above into a single system:

  • Master seed stored in an .age-encrypted key-file (scrypt passphrase, ASCII-armored, compatible with the upstream age CLI tool). The seed never leaves the browser except as this encrypted backup.
  • HD derivation with domain separation produces keys for the regular wallet, each vault, each escrow deal, and the Kasia chat protocol. One seed governs everything.
  • wallet + walletOld address slots track the current and previous receiving addresses. The Desk queries the node's UTXO index for both and sums the results into one balance.
  • Multi-input P2PK spending iterates over selected UTXOs, derives each signing key, and builds a single transaction with one signature per input. The user confirms with a password re-entry that shows the exact amount and recipient.
  • Gap-scan restore runs against the server's UTXO-matching endpoint. An automatic scan runs once per device; the user can trigger a manual rescan at any time.
  • Tx-history proxy forwards requests to an external index service, caches for 30 seconds, and tags vault addresses so the Desk can separate vault activity from wallet activity.
  • Vault deposits are tracked separately from the wallet balance. The Desk draws a split display: teal for "in the vault" and amber for "withdrawal in progress."

Kaspa Safe locks KAS behind a time-delayed on-chain vault — and the same Desk profile that manages your wallet keys also manages your vault keys, backed by a single seed and a single encrypted file. Explore how the vault works →

Create a vault

Trade-offs and honest limitations

Linkability across addresses. When a transaction spends UTXOs from multiple derived addresses in one input set, an on-chain observer can infer those addresses belong to the same entity. HD derivation provides organizational separation, not strong privacy.

UTXO fragmentation. Receiving many small deposits creates many small UTXOs. Consolidating them later costs proportional fees. Wallets can suggest periodic consolidation, but the user must initiate it.

Restore depends on a scanning service. A purely local restore — from seed, using only a pruned Kaspa node — is impractical without a UTXO index or gap-scan API. Kaspa Forge's restore endpoint accepts only public keys (no seed, no private data), which limits exposure, but it is still an external dependency.

No account abstraction. The wallet must track addresses, aggregate UTXOs, handle multi-input signing, and manage change outputs. Account-based chains hide this complexity behind the protocol. On Kaspa, the wallet carries the burden — and the transparency.

Fee scales with input count. The per-UTXO fee model means that transactions with many inputs are proportionally more expensive. For users who receive frequent small payments, this is a real cost to monitor.

Despite these trade-offs, the UTXO model offers something account chains don't: each output is a self-contained, independently verifiable unit. Covenant transactions in particular rely on this — the single-input invariant, the deterministic state machine, the keyless spending paths — all depend on UTXOs being discrete, inspectable, and immutable once confirmed. That's the foundation tools like Kaspa Safe and Kaspa Escrow are built on.

FAQ

Why does my Kaspa wallet show multiple addresses?

HD wallets derive a sequence of addresses from a single seed for privacy and organizational purposes. Each address can hold its own set of UTXOs, and the wallet tracks all of them to compute your balance.

How does the wallet know my total balance?

It queries the Kaspa node's UTXO index for every derived address and sums the values of all unspent outputs. There is no on-chain "account balance" — the aggregation happens in the wallet software.

What if I send KAS to an old address my wallet generated?

The funds arrive normally. During a gap-scan restore, the algorithm will find them as long as that address falls within the scanned derivation range.

Why do transactions with many inputs cost more?

Kaspa charges a small fee per UTXO utilized as an input. Ten inputs cost roughly ten times the base fee of a single input.

Can I restore my wallet using only a Kaspa node?

Not easily. Nodes prune old blocks and maintain only the current UTXO set. Restoring requires scanning derived addresses against that set — which is what the gap-scan algorithm does, typically with help from an indexing service.

This article was researched, written and illustrated by OfficeForge's AI team — the same AI employees that built and run Kaspa Forge. Founder-directed, human-reviewed.

Non-custodial · open source

Put your KAS where theft can be cancelled

A covenant vault on Kaspa mainnet: your keys, your rules, our tooling. Free on-chain, forever.

Create a vault