Most crypto platforms that offer multiple financial tools — a vault, an escrow service, a marketplace — run them as separate microservices. Each has its own database, its own deployment pipeline, its own authentication layer. That is the orthodox engineering approach.
Kaspa Forge takes a different path. Kaspa Safe, Kaspa Escrow, Kaspa Deposit, and Kaspa Marketplace all run on a single Rust server process, backed by one SQLite database and one encrypted user profile in the browser. There are no internal API calls between services because there are no separate services.
This article explains why, how it works mechanically, and what trade-offs come with the choice.
Why One Process Instead of Four
Microservices exist to solve coordination problems: separate teams, separate scaling needs, separate failure domains. Kaspa Forge does not have those problems.
All four tools share the same core requirement — they build and verify covenant transactions against the same Kaspa node. A vault withdrawal and an escrow release both go through the same UTXO lifecycle: find the covenant output, build a transaction satisfying one of the script's spending paths, sign it, broadcast it. The underlying logic lives in one Rust crate. Splitting it across processes would mean duplicating that crate, adding network hops between services, and inventing an inter-service authentication layer that adds attack surface without adding capability.
Covenant — a Kaspa transaction output whose spending conditions are enforced by on-chain script logic (compiled from Silverscript), not just by a private-key signature. Covenants enable vaults, escrows, and time-locks without custodians.
The Toccata hardfork brought covenants to Kaspa mainnet, and with them the ability to encode complex spending rules directly in UTXO scripts. That means the server does not hold funds and does not enforce rules — it only needs to build valid transactions and watch the chain. A single process can do that for any number of tools.
The Shared WASM Crate
The technical keystone is a single Rust crate, compiled two ways:
cdylib(dynamic library) → compiled to WebAssembly viawasm-pack, loaded in the browserrlib(static library) → linked into the server binary
Both copies contain the same transaction builders: vault paths (initiate, cancel, complete, checkin, inheritAuto, inheritSigned, migrate), escrow paths (release, refund, dispute, autoRelease, arbitrate*, timeout*, mutual), HD key derivation, and address computation. When a user clicks "Withdraw" in the browser, the WASM module builds and signs the transaction client-side. When the server's watcher triggers an autoRelease or complete, the same Rust code builds the transaction server-side.
// Same function, same logic, same correctness proof —
// whether called from WASM bindings or from the server binary
fn build_vault_complete_tx(utxo, dest, fee_budget) -> Transaction { ... }
One audit surface. One set of tests. If the complete path works in the browser, it works on the server, because it is the same function compiled to a different target.
The crate also includes the Silverscript compiler, so covenant scripts are compiled from source at build time via Rust's include_str! macro — the vault.sil and escrow.sil contracts are embedded in the binary, not loaded from the filesystem at runtime. A change to the contract source triggers a recompile of both the browser WASM bundle and the server binary, which is exactly the behavior you want when the spending rules for someone's funds are at stake.
One SQLite, Multiple Registries
The server runs a single SQLite file. Each tool opens its own connection pool against that file — a vault registry, a deals registry (shared between Escrow and Deposit, since Deposit reuses the same ten-path escrow covenant), a listings registry for Marketplace, and a swap log.
Schema is defined inline with CREATE TABLE IF NOT EXISTS and idempotent ALTER TABLE statements. No external migration tooling. This is a deliberate simplicity choice: SQLite handles the current load well, atomic writes prevent cross-tool inconsistency, and a single file backup captures the entire application state.
The trade-off is obvious: SQLite does not scale horizontally. If Kaspa Forge ever needs to serve orders of magnitude more concurrent users, the storage layer would need rethinking. For the current scale — a handful of background watchers scanning UTXO sets every 10 seconds, and a modest number of active vaults and deals — it is more than sufficient.
The Desk: One Encrypted Profile
The most user-visible consequence of the unified architecture is the Desk — a single encrypted profile in the browser that works across all tools.
When a user creates a vault through Kaspa Safe, the Desk generates a master seed and derives a hot key, an alarm key, and a funding key using domain-separated HMAC-SHA512:
HMAC-SHA512(
key = master_seed,
msg = "kaspaforge/v1/vault/<index>"
) → first 32 bytes = secret key
When the same user opens an Escrow deal, the Desk derives a separate chat key and escrow-side key from the same seed, using a different domain string. One seed. One .age encrypted backup file under a user-chosen passphrase. All current and future keys are recoverable — even keys for vaults and deals created *after* the backup was made, because the derivation is deterministic and the index is monotonic.
The encrypted profile (version 3) stores per-tool key material: for each vault, the hot secret key, the alarm secret key (or a flag indicating the alarm key lives on a physical card), the funding key, the fee budget, and a user note. Keys never leave the browser. The server receives only public keys and signed transactions. There is nothing to steal from the server because there is nothing sensitive there.
Session security applies globally: an auto-lock timer, a password re-confirm on any signing operation (showing the specific amount and recipient as context), and a boot guard that requires unlock on every page load. The .age key file is the only portable copy — encrypted with age's scrypt-based passphrase scheme, ASCII-armored, compatible with the standard age -d CLI for offline decryption.
Watcher Loops: One Server, Parallel Monitoring
The server runs two background watcher loops, each on a 10-second cycle:
1. Vault watcher — scans all registered vault UTXOs, compares against stored snapshots, triggers alerts (deposit detected, withdrawal initiated, cancellation, completion), sends check-in reminders when the inheritance timer nears 80% of its delay, and auto-broadcasts complete or inheritAuto transactions when their keyless paths become valid. It relies on DAA scores — a monotonically increasing counter that tracks blue blocks and successfully merged red blocks (as described in the Kaspa wiki) — to determine when a time-locked path has matured.
2. Escrow watcher — scans escrow and deposit UTXOs, handles the entire deal lifecycle (draft expiration, auto-release after the dispute window, timeout after the arbiter deadline), manages Marketplace listing reservations and republishing, and escalates unresolved AI verdicts to the human arbiter after 24 hours.
Both loops share the same gRPC connection to the Kaspa node. Both use the same notification infrastructure — Telegram bot, email, and web push. A single notification service fans out to all channels regardless of which tool triggered the event.
This is where the single-process architecture pays off: there is no "which service owns the notification queue" problem, no distributed locking on UTXO snapshots, no retry logic for inter-service calls. The watcher sees the full picture because it *is* the full picture.
How Kaspa Forge Uses It in Production
The practical result for users is seamless cross-tool operation within one encrypted profile.
A user creates a vault through Kaspa Safe, opens an escrow deal through Kaspa Escrow, and lists an item on the Marketplace — all from the same Desk. The Marketplace listing is backed by a real escrow deal on the same escrow.sil covenant. A Deposit reuses the same ten spending paths with roles mapped differently: the depositor maps to the covenant's seller role, the holder maps to buyer, and the standard release, refund, and timeout mechanics apply as-is.
The covenant design makes this composability possible. The single-input invariant — enforced across all 17 spending paths (7 vault + 10 escrow) — prevents multi-UTXO siphoning attacks. The feeBudget cap prevents fee-griefing on keyless transactions. These constraints hold across every tool because every tool shares the same contract source and the same build pipeline.
Try it yourself. Creating a Kaspa Safe vault takes about 60 seconds in the browser — keys are generated locally, the covenant compiles on-chain, and all on-chain operations are free. If you want to see how an escrow deal works with the same profile, the Escrow pages walk through the full lifecycle including disputes. No account, no KYC, no custody.
Trade-offs and Honest Boundaries
The single-process architecture has real costs:
- Single failure domain. A server crash takes down all four tools at once. This is mitigated by the fact that funds are on-chain — a server outage delays notifications and keyless transaction broadcasts, but does not threaten stored value. The open-source
vaultctlandescrowctlCLIs can manage funds against any public Kaspa node.
- SQLite ceiling. The current database handles the load well, but horizontal scaling would require a different storage layer. This is a future problem, not a present one.
- WASM snapshot generations. The browser side uses multiple frozen WASM builds (designated v3, v5, v7, v8 in the asset directories) for different page contexts — Safe pages load one build, Escrow pages load another, the React Desk loads a third. Each generation is a compiled snapshot of the core crate with specific bindings exported. Adding a new export to the wrong generation causes a "function is not a function" error on only some pages. This is managed carefully but is an ongoing maintenance cost.
- Shared database contention. All tools writing to one SQLite file means a slow query in one connection pool could briefly block another's. In practice this has not been an issue — the queries are simple and the pools are separate — but it is a constraint to be aware of.
The fundamental insight is that covenants move the trust boundary on-chain. The server does not enforce rules; the blockchain does. The server's job is to build valid transactions and broadcast them at the right time. That job does not require microservices — it requires correctness in one place, which is easier to verify with one crate, one test suite, and one audit surface. ---
FAQ
Why does Kaspa Forge use a single server instead of microservices?
A single Rust process eliminates inter-service authentication, simplifies deployment, and lets all four tools share one database and one encrypted user profile without distributed transactions.
What happens to my funds if Kaspa Forge goes offline?
Funds live in on-chain covenants, not on the server. Kaspa Safe vaults can be managed with the open-source vaultctl CLI against any public Kaspa node. Escrow deals resolve through on-chain timeout paths.
How does one Desk profile work across Safe, Escrow, and Deposit?
The Desk holds a single HD master seed. Keys for each tool are derived deterministically using domain-separated HMAC-SHA512, so one encrypted backup covers all current and future vaults, deals, and deposits.
Is the Kaspa Forge code open source?
Yes. Covenant contracts and tooling are published on GitHub. The vaultctl and escrowctl CLIs allow full offline management of on-chain funds without the server.
What database does Kaspa Forge use?
A single SQLite file with separate connection pools for each tool's registry. Inline schema definitions keep migrations idempotent without external tooling.
How does Kaspa's BlockDAG help this architecture?
Kaspa's high block rate means DAA-based time-locks resolve in minutes, not hours. The watcher loops stay responsive and covenant state transitions close quickly for users.
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
