When you send KAS to a Telegram "guarantor" for a P2P deal, you are trusting a stranger's promise. That stranger can vanish with your money at any moment — and there is nothing the blockchain can do about it, because the money is already in their wallet.
An on-chain escrow contract replaces that promise with math. The funds sit in a Kaspa covenant — a UTXO whose spending conditions are baked into the blockchain itself — where every possible path to unlock the money routes it to either the buyer, the seller, or a fixed service-fee address. No third party can redirect the funds. Not the platform. Not the arbiter. Not a compromised server. Here is how those spending paths work.
What Is a Kaspa Covenant?
Covenant A spending condition attached to a UTXO that can inspect the spending transaction itself — not just verify a signature, but check destinations, amounts, and timing before allowing the coin to move.
Kaspa's Toccata hardfork introduced covenants to mainnet. A covenant is a script (written in Silverscript) that constrains how a specific coin can be spent. Think of it as a programmable lock: the coin will only move when the transaction satisfies every condition the script demands — correct signatures, correct destinations, correct timing.
Kaspa's blockDAG provides a built-in clock: the DAA score, a difficulty-adjusted counter that increments with each block merge. Covenant scripts use DAA scores for time-locked paths rather than wall-clock timestamps, making timing deterministic from the chain's perspective. The same covenant pattern powers Kaspa Safe, our vault tool — if you've read how the vault works, the mental model transfers directly.
The Contract: Parameters and State
The escrow.sil contract takes a fixed set of parameters at creation:
# escrow.sil — constructor parameters
buyer, seller, arbiter # three public keys
disputeWindow # DAA scores: the "return period"
arbiterDeadline # DAA scores: time for arbiter to act
timeoutTo # 0 = buyer, 1 = seller (fallback)
feeSpk # 36-byte script pubkey for service fee
feeResolve, feeDispute # service fee amounts (sompi)
feeBudget # max network fee, capped at 0.1 KAS
initMode # starting mode (0 = ACTIVE)
The contract holds one piece of mutable state: mode, which is either 0 (ACTIVE) or 1 (DISPUTED). When a deal is funded, the UTXO starts in ACTIVE mode. The mode determines which spending paths are available — and once you see all ten, the design becomes clear.
The Deal Lifecycle: Ten Paths
Walk through a deal from funding to resolution. Each stage reveals which paths open up.
Funding → ACTIVE
A party sends KAS to the escrow address. The UTXO comes into existence in ACTIVE mode. From here, the contract can be spent through any of the paths below that match the current mode.
Cooperative close (any mode — ACTIVE or DISPUTED)
At any point, either party — or both — can sign a cooperative exit. These three paths work regardless of whether a dispute has been raised:
release(buyerSig)— The buyer confirms satisfaction. The entire balance, minus the resolve fee, goes to the seller. This is the happy ending.refund(sellerSig)— The seller admits inability to deliver. The entire balance, minus the resolve fee, goes to the buyer. Graceful retreat.mutual(buyerSig + sellerSig)— Both parties agree. They can split funds between buyer and seller in any proportion, but the service-fee output is mandatory. (This is the K1 anti-avoidance rule: even if both sides conspire to skip the fee, the covenant rejects the transaction.)
Cooperative paths always work. A dispute does not lock the parties into adversarial mode — they can settle directly at any time.
The dispute window (ACTIVE only)
While the UTXO is ACTIVE, the buyer has a window measured in DAA scores to raise a dispute. Two things can happen:
- The window expires without a dispute →
autoRelease()fires. This path requires no signature — anyone can broadcast it. The seller gets paid, minus the resolve fee. This is the optimistic default: deliver the goods, and the deal closes automatically. - The buyer signs
dispute(buyerSig)→ The contract flips to DISPUTED. The UTXO is recreated (its age resets to zero), and a new countdown begins: thearbiterDeadline. No funds move — the money is still locked in the covenant, but the rules have changed.
Arbitrated resolution (DISPUTED only)
Once a dispute is active, the arbiter's signing key becomes relevant. Three paths open:
arbitrateToBuyer(arbSig)— Full refund to the buyer, minus the dispute fee.arbitrateToSeller(arbSig)— Full payment to the seller, minus the dispute fee.arbitrateSplit(arbSig)— A split between buyer and seller. Both outputs must be ≥ 1 KAS (the KIP-9 dust guard). All three arbitrate paths charge the dispute fee, which is higher than the resolve fee.
The arbiter can choose between buyer and seller. There is no fourth option — the contract does not contain one.
The dead man's switch (DISPUTED only)
If the arbiterDeadline expires without an arbitrated resolution:
timeoutToBuyer()— Fires whentimeoutTo == 0. No signature required. No service fee charged. Buyer gets the full balance.timeoutToSeller()— Fires whentimeoutTo == 1. Same conditions. Seller gets the full balance.
The contract refuses to hold funds hostage. If the arbiter disappears, the blockchain itself enforces the payout.
Complete reference
All ten entrypoints, at a glance:
release(buyerSig)— buyer → seller · resolve feerefund(sellerSig)— seller → buyer · resolve feemutual(buyerSig + sellerSig)— custom split · resolve fee (mandatory)dispute(buyerSig)— ACTIVE → DISPUTED · age resetsautoRelease()— no signature · age ≥ window → seller · resolve feearbitrateToBuyer(arbSig)— arbiter → buyer · dispute feearbitrateToSeller(arbSig)— arbiter → seller · dispute feearbitrateSplit(arbSig)— arbiter → buyer + seller, each ≥ 1 KAS · dispute feetimeoutToBuyer()— no signature · age ≥ deadline, timeoutTo=0 → buyer · no feetimeoutToSeller()— no signature · age ≥ deadline, timeoutTo=1 → seller · no fee
The Invariant That Kills Exit Scams
Look at the list above. There is no entrypoint where funds leave the contract to a fourth address. Not one. The arbiter's signature can only route money to the buyer or the seller — the contract literally does not contain a path anywhere else.
Three additional guards reinforce this:
Single-input invariant. Every path begins by requiring tx.inputs.length == 1. This prevents a multi-UTXO transaction from bundling the escrow coin with other coins and siphoning the difference into miner fees. The escrow UTXO must be spent alone.
# Every path enforces:
require(tx.inputs.length == 1)
# Every output script must be one of:
require(out.scriptPublicKey == buyer_spk ||
out.scriptPublicKey == seller_spk ||
out.scriptPublicKey == feeSpk)
Fee-budget cap. The network fee (what goes to miners) is bounded by feeBudget, which itself cannot exceed 10,000,000 sompi — 0.1 KAS. This stops an attacker, a buggy watcher, or a compromised relay from burning the escrow balance in transaction fees.
Value conservation on arbitrated paths. The arbitrate paths explicitly enforce out[0] + out[1] >= in - feeBudget. No value leaks between outputs.
Together, these invariants mean: the only parties who can ever receive funds from this contract are the buyer, the seller, and the fee-address. Full stop.
How Kaspa Forge Runs It in Production
At Kaspa Forge, escrow.sil is compiled into our WASM core (kaspa-safe-core) via include_str!. The browser-side code and the backend share the exact same contract source — there is no secondary transcription that could drift out of sync.
The deal lifecycle on our backend (kaspa-safe-server) mirrors the state machine:
1. Create — the initiating party picks role, deal type, amount, and dispute window (from presets: 24/48/72/96/120/168 DAA-calibrated hours). The server generates a one-time join code (TTL 72 hours). 2. Join — the counterparty enters the code. The server computes the escrow address from both public keys and all constructor parameters. The address is deterministic — either side can independently verify it. 3. Fund — KAS arrives at the escrow address. The watcher loop (10-second cycle) detects the UTXO and transitions the deal to funded. 4. Chat — each side gets a separate chat key (distinct from the escrow key — revealing a chat key in a dispute does not risk funds). Messages are E2E-encrypted with ECIES (k256 ECDH + ChaCha20-Poly1305) and anchored in the BlockDAG as on-chain transactions. The server relays ciphertext only. 5. Close or dispute — signatures are produced locally in the browser through Kaspa Forge's desk profile; keys never leave the device. The signed transaction is broadcast. 6. AI mediation — if the buyer disputes, both sides can reveal their chat keys to an AI mediator (currently Xiaomi's MiMo v2.5 Pro via OpenRouter). The mediator reconstructs the conversation, runs forensics on attached evidence (image EXIF, tracking numbers, crypto transaction IDs), and produces a non-binding verdict. Either party can accept or escalate to the human arbiter. 7. Human arbitration — the arbiter signs with an offline key. The private key is never stored on the server; only the public key is configured. The covenant enforces that even the arbiter's signature can only route funds to buyer or seller. 8. Timeout — if neither party acts and the arbiter deadline expires, the watcher broadcasts the timeout transaction automatically. No keys required.
Recovery without the service. If Kaspa Forge goes offline entirely, the buyer and seller can still execute the cooperative paths (release, refund, mutual) using any Kaspa wallet that supports Toccata covenants. The timeout path broadcasts without signatures at all. The only path that requires the platform infrastructure is the dispute-then-arbitrate flow, and even there, the fallback is the timeout — funds do not get stuck.
Kaspa Escrow is live on mainnet — deals from 50 to 10,000 KAS, non-custodial by design. Create a deal or read the landing page for a visual walkthrough of every spending path.
Trade-offs and Honest Limitations
Honest accounting of what this design does not solve and where it constrains users:
- Beta caps. Current limits are 50–10,000 KAS per deal. Below 50 KAS, the dispute-fee floor (minimum 5 KAS) would consume more than 10 % of the escrow. Above 10,000 KAS, we want more mainnet mileage before opening the ceiling.
- Arbiter dependency. If the arbiter key is lost, disputed deals can only resolve through the timeout path. The timeout destination is fixed at construction — you cannot change your mind about who gets the fallback after the fact. This is a deliberate trade-off: a recoverable arbiter key would introduce a different attack surface.
- DAA score, not wall-clock time. Dispute windows and arbiter deadlines are measured in DAA scores, not minutes or hours. On Kaspa's current block rate, the presets approximate wall-clock durations (e.g., 72 hours), but the exact mapping depends on hashrate and network conditions. We present presets in human-readable hours, calibrated against current DAA pacing.
- Non-binding AI verdict. The AI mediator's opinion is advisory — the contract cannot enforce it. It helps parties reach an informed mutual agreement, but only a real signature (from a party or the arbiter) can move funds. This is a feature, not a bug: we do not want an LLM with spending authority.
- Single-input, single UTXO. Each deposit to the escrow address creates a separate UTXO. The single-input invariant means you cannot batch-merge escrow UTXOs in one transaction — by design, since multi-input bundling was the exact vector this invariant was written to block.
- Dust guard. The minimum output of 1 KAS on arbitrated split paths (from KIP-9) means the arbiter cannot create micro-outputs. This is a network-level constraint that protects against dust accumulation in the UTXO set.
FAQ
Can the Kaspa escrow contract send funds to the platform or to the arbiter?
No. Every one of the ten spending paths routes funds exclusively to the buyer, the seller, or a fixed fee-address. The arbiter has signing authority on dispute paths but cannot redirect funds to themselves or any third party.
What happens if neither party cooperates and the arbiter disappears?
The timeout path activates after the arbiter deadline expires. It requires no signature at all — the blockchain itself enforces the payout to whichever side the constructor specified (buyer or seller).
What is the dispute window and how is it measured?
The dispute window is a DAA-score interval set when the escrow is created. During this window the buyer can raise a dispute. If no dispute arrives and the window expires, auto-release sends funds to the seller automatically.
Does the platform custody any of the escrowed KAS?
No. Kaspa Forge is non-custodial: the KAS sits in an on-chain covenant whose rules are public. The server relays encrypted chat and signed transactions but never holds keys or funds.
What happens to my escrow if Kaspa Forge goes offline?
The cooperative paths (release, refund, mutual) work directly on-chain via any Kaspa wallet that supports Toccata covenants. The timeout path broadcasts without any signature. Only the dispute flow requires an arbiter key — and if the arbiter is unreachable, timeout is the fallback.
Why is there a minimum deal amount?
The dispute-fee floor (minimum 5 KAS) would consume more than 10 % of a very small escrow, making dispute protection impractical. The current minimum of 50 KAS keeps the fee-to-value ratio reasonable.
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
