You can recover a Kaspa Safe vault without ever visiting kaspaforge.org. The vault is an on-chain covenant — a smart contract on Kaspa's BlockDAG — not a row in our database. Every rule governing your funds lives in the script, not on our server. The website is a convenience layer; the contract is the authority.
This article walks through the full recovery chain: how your encrypted profile stores vault parameters, how the standalone keyfile-decrypt.html decryptor extracts them offline, and how the open-source vaultctl CLI reconstructs and operates your vault against any Kaspa v2+ node.
Why On-Chain Contracts Change the Recovery Equation
A traditional custodial wallet stores your balance as a database entry. If the company disappears, your "balance" disappears with it — there is no independent way to move those coins because the company held the keys.
Kaspa Safe inverts this. When you create a vault, the browser (using the Kaspa WASM SDK) generates your keys locally, constructs a covenant script, and broadcasts a funding transaction. From that moment, the funds live in a Toccata covenant UTXO on Kaspa's BlockDAG. The contract encodes every spending rule — delay periods, alarm cancellation, inheritance timers, migration authority — directly in the script. The server never sees your private keys and never holds your coins.
Recovery is therefore not about restoring access to a third-party account. It is about reconstructing the on-chain state and signing transactions locally — something any tool that speaks the Kaspa protocol can do.
The Recovery Chain: Three Layers
Recovery uses three components, each independent of the Kaspa Forge website:
1. Your .age profile file — an encrypted archive saved to your device during vault creation. It contains profile.json (vault parameters, public keys, covenant metadata) and, within that, one or more vault.json entries (one per vault you created). 2. keyfile-decrypt.html — a single-file HTML page with the age decryption library compiled to WASM and embedded as base64. It runs entirely in your browser at file:// with zero network requests. You open it, drag in your .age file, enter your passphrase, and it outputs the decrypted profile.json so you can select your vault record. 3. vaultctl — an open-source offline CLI tool. It takes the vault record from your decrypted profile, connects to a Kaspa node, queries the on-chain UTXO state, and lets you perform every vault operation: check status, initiate withdrawals, cancel unvaultings, check in, inherit, or migrate.
Think of it as a lockbox analogy: the .age file is your sealed envelope with the combination, keyfile-decrypt.html is the letter opener you keep in your desk drawer, and vaultctl is the bank teller who can open the vault once you present the right paperwork. The teller does not need to work for any particular bank — any Kaspa node will do.
Step by Step: Recovering Your Vault
Step 1 — Decrypt Your Profile
Open keyfile-decrypt.html in a browser (locally, via file:// — no server needed). The file is self-contained: the age decryption library is compiled to WASM and embedded directly in the HTML as a base64 data URI. There are no external script tags, no CDN dependencies, no automatic network requests.
Drag your .age profile file into the page, enter the passphrase you set during vault creation, and the tool decrypts and displays profile.json. From the profile, select the vault.json entry for the vault you want to recover. This gives you the vault's parameters: public keys, delay values, heir configuration, and the covenant address.
The canonical build of this file is produced by tools/build-keyfile-decrypt.py and published in the recovery kit on GitHub.
Step 2 — Install vaultctl
vaultctl is published in the Kaspaforge/kaspaforge GitHub repository under spike/vaultctl/. You can build it from source (it is a standard Rust project) or use a pre-built binary. The tool is fully offline for all local operations; it only needs a network connection when querying a node or broadcasting transactions.
Step 3 — Query Vault Status
Point vaultctl at a Kaspa node and feed it your vault record:
vaultctl --node grpc://your-node:16110 status --vault vault.json
The status command queries every covenant UTXO belonging to your vault address and reports, for each one: the outpoint (txid:index), the value, the current age (in DAA units), and the remaining time on any active timer (unvaulting delay or inheritance delay). It also lists any plain (non-covenant) UTXOs sitting at the vault address — these are accidental deposits that the covenant cannot spend directly but that vaultctl can sweep via a checkin transaction.
The default node is grpc://node.kaspaforge.org:16110 (a public gRPC front), but you can — and for maximum independence, should — point it at your own Kaspa node with --node.
Step 4 — Perform Vault Operations
With the vault record loaded, vaultctl exposes every covenant spending path:
initiate— start a withdrawal (requires your hot key; vault enters UNVAULTING state with the delay timer running).cancel— abort an unvaulting (requires your alarm key; returns funds to VAULT state).complete— finalize a withdrawal after the delay expires (no signature needed — anyone can broadcast this; funds go to the destination locked during initiation).checkin— reset the inheritance timer (requires your hot key).inherit— claim funds as the heir (requires the heir key, after the inheritance delay has elapsed with no checkins).migrate— move the entire vault to a new address or contract version (requires both hot and alarm keys; instant, full authority).
Each of these corresponds to one of the seven on-chain entrypoints in the vault.sil covenant script. The script enforces the rules — vaultctl just constructs and signs the transactions.
Handling Multiple UTXOs
If your vault holds multiple covenant UTXOs (from repeated deposits, for example), vaultctl handles them with a fail-closed batch strategy. The --all flag pre-builds the full set of transactions and sends one single-input transaction per UTXO. If any transaction in the batch is rejected by the node, the batch stops immediately and reports which outpoints were accepted and which were not. You can then retry the remaining ones. The --outpoint txid:index flag targets a single specific UTXO.
The migrate --all command is specifically designed for emergency migration of a vault with many UTXOs — it explicitly surfaces the operational cost (one transaction per UTXO) rather than hiding it.
How Kaspa Forge Uses This Architecture
The recovery chain is not an afterthought — it is a deliberate architectural choice built into every Kaspa Forge product that holds user funds.
Kaspa Safe is the primary example. The vault creation wizard generates your .age profile and prompts you to save it. The recovery page (recover.html) documents the full offline procedure. The vaultctl CLI and keyfile-decrypt.html decryptor are published in the public recovery kit on GitHub, with SHA-256 checksums for verification.
Kaspa Escrow and Deposit use the same on-chain covenant model: funds sit in contract UTXOs, not in a platform wallet. The spending paths are encoded in the script, and the same principle applies — the contract is the authority, not the server.
Desk stores your encrypted profile locally in the browser. The profile is the same .age archive that keyfile-decrypt.html can decrypt offline. Desk is a convenience interface; the recovery tools are the independence guarantee.
The recovery tools — keyfile-decrypt.html, vaultctl, and the vault contract source — are published on GitHub with checksums and are designed to work against any Kaspa v2+ node. If you hold a vault, download the recovery kit now and verify it works on your machine. Start at kaspaforge.org/recover.html for the guided procedure, or go directly to the Kaspaforge GitHub repository for the source.
Trade-offs and Honest Limitations
No recovery system is without friction. Here is what this design costs and what it does not solve:
You must keep your .age profile file safe. If you lose the encrypted profile and your passphrase, the vault parameters (public keys, delay values, heir address) are not trivially recoverable. The on-chain script is deterministic from those parameters, but reconstructing them from the blockchain alone requires knowing the exact constructor arguments. Save the profile in multiple locations — it is encrypted, so the passphrase is the real secret.
You must keep your keys. The hot key, alarm key, and heir key are generated client-side and printed on your recovery sheet during vault creation. Kaspa Forge never sees them. If you lose both your hot and alarm keys, the only paths available are complete (after an unvaulting delay) and inheritAuto (after the inheritance delay, if enabled). There is no master override.
The complete path is keyless by design. Anyone who knows the vault address and sees an active unvaulting can broadcast the complete transaction after the delay expires. This is intentional — it means even if you lose your hot key, you can set a destination you control via migrate (with the alarm key plus a borrowed hot key, or by initiating before loss) and wait for the delay. But it also means you must cancel promptly if an attacker initiates an unvaulting.
vaultctl needs a Kaspa node. The CLI connects via gRPC to query UTXOs and broadcast transactions. You can use the public front at node.kaspaforge.org:16110, but for true independence, run your own node. The decryption step (keyfile-decrypt.html) is fully offline — only the node interaction requires connectivity.
The single-input invariant changes addresses. Each covenant UTXO must be spent individually (one input per transaction). This is a security feature — it prevents multi-UTXO siphon attacks — but it means a vault with many deposits requires multiple transactions to fully migrate. vaultctl handles this transparently with --all and clear error reporting, but the cost in fees and time is real.
Fee budget is capped on-chain. The covenant enforces a maximum fee budget of 0.1 KAS per keyless spending path (complete, inheritAuto). This protects against griefing — no one can burn your vault in fees — but it also means that in extreme fee environments, keyless paths could become uneconomical. The migrate path (requiring both keys) has no such cap, by design.
v3 has a known output-count limitation. The constrained spending paths verify the required output value but do not enforce tx.outputs.length. A transaction author could direct remaining fee budget to an additional output rather than the network fee. The official builders always produce canonical single-output transactions, and this is slated for a tighter guard in v4. This is a defense-in-depth gap, not a fund-loss vulnerability — the vault's value invariant still holds.
The Principle Behind the Design
The core idea is simple: a covenant vault's rules live on the blockchain, not on any server. The website, the watcher, the Telegram alerts — these are convenience layers. They make the vault easier to use day-to-day. But they are not load-bearing for fund safety.
If every Kaspa Forge server vanished tomorrow, your vault would still enforce its delay periods, still honor its inheritance timers, and still accept your signatures. The vaultctl CLI and keyfile-decrypt.html decryptor give you the tools to interact with that contract from any machine, against any node, with nothing but your encrypted profile and your keys.
That is what non-custodial means in practice — not a marketing claim, but an engineering guarantee backed by open-source code and on-chain enforcement.
FAQ
Can I recover my Kaspa Safe vault if kaspaforge.org goes offline permanently?
Yes. The vault is an on-chain covenant contract. With your .age profile file, the open-source keyfile-decrypt.html decryptor, and the vaultctl CLI, you can manage your vault from any terminal against any Kaspa v2+ node — no website required.
What files do I need to recover my vault?
Your encrypted .age profile file (saved during vault creation), the keyfile-decrypt.html single-file decryptor, and the vaultctl binary or source from the Kaspaforge GitHub repository.
Does vaultctl require an internet connection?
Decryption is fully offline. vaultctl needs a connection to a Kaspa node (gRPC) to query UTXOs and broadcast transactions, but you can point it at your own local node with the --node flag.
What if I only have my hot key but not my alarm key?
You can still initiate a withdrawal (after the delay period completes via the keyless complete path) or perform a checkin. The migrate path — which allows instant, full-control spending — requires both keys. The alarm key alone can cancel an active unvaulting.
Is the vaultctl source code publicly auditable?
Yes. vaultctl is published in the Kaspaforge/kaspaforge GitHub repository under an open-source license. The vault contract source (vault.sil) and its selftest suite are also public.
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
