When you use a traditional escrow — a bank, a platform, a Telegram "guarantor" — the fee is whatever the middleman says it is. They hold the funds, they set the terms, and you trust them to take only what's fair. Kaspa Escrow removes that trust requirement. The fee structure isn't a policy document; it's code embedded in the on-chain covenant that holds your funds. The amount, the recipient address, and the conditions under which fees are charged are locked in before a single sompi arrives.
This article breaks down how that enforcement works: the three fee tiers, the output structure that carries fees on-chain, the invariant that prevents both parties from colluding to skip fees, and the griefing cap that protects against fee-burning attacks.
The Problem: Service Fees Without Custody
In a custodial escrow, fee enforcement is trivial: the middleman holds the money and deducts their cut before disbursing. In a non-custodial escrow, funds sit in a covenant that neither party nor the service can unilaterally modify. The challenge: how do you guarantee the service gets paid when the service never holds the funds?
Kaspa's answer is output introspection. The covenant script doesn't just check *who* signs — it checks *where the money goes*. Each spending path inspects the transaction's outputs and requires a specific output to the fee address, with a value matching the fee tier. If the output is missing, wrong, or underfunded, the transaction is invalid. Miners won't include it, nodes won't relay it.
Covenant — a Kaspa on-chain script (written in Silverscript) that defines programmable spending conditions. Unlike a simple signature lock, covenants can inspect the spending transaction's inputs, outputs, and state to enforce complex rules. Kaspa covenants became available on mainnet with the Toccata hardfork — see the Kaspa wiki for protocol background.
The Covenant: 10 Paths, Three Fee Behaviors
The escrow.sil contract defines 10 spending paths organized around two roles (buyer, seller), an arbiter pubkey, a dispute window, and an arbiter deadline. The contract state is a single field mode — 0 = ACTIVE, 1 = DISPUTED.
Not every path charges a fee. The covenant defines three distinct behaviors:
Resolution fee (feeResolve)
Applied to the four "normal resolution" paths:
release(buyerSig)— buyer confirms receipt → seller gets paidrefund(sellerSig)— seller returns funds → buyer gets refundedmutual(buyerSig + sellerSig)— both agree to close (split, partial refund, any arrangement)autoRelease()— no signature required; dispute window expired without a dispute → seller gets paid
In production, feeResolve is calculated as:
max(0.5% of deal value, 1.2 KAS minimum)
A 500 KAS deal pays 2.5 KAS in service fees. A 50 KAS deal (the Kaspa Escrow minimum) pays 1.2 KAS — the floor prevents dust-level fees that cost more to spend than they're worth.
Dispute fee (feeDispute)
Applied when an arbiter resolves a contested deal:
arbitrateToBuyer(arbSig)— arbiter rules for buyerarbitrateToSeller(arbSig)— arbiter rules for sellerarbitrateSplit(arbSig)— arbiter splits between both parties
max(2% of deal value, 5 KAS minimum)
The higher rate reflects additional work: AI forensics, encrypted chat transcript review, evidence analysis (media verification, cross-chain tx probing), and a human arbiter's final signature. A 500 KAS disputed deal costs 10 KAS.
No fee (timeout)
timeoutToBuyer()— arbiter deadline expired,timeoutTo==0→ buyer gets everythingtimeoutToSeller()— arbiter deadline expired,timeoutTo==1→ seller gets everything
These paths carry zero service fee. They activate only when the arbiter fails to act within the deadline. Charging a fee when the service didn't deliver arbitration would be perverse. It also creates a natural incentive: the service earns nothing from disputes it doesn't resolve.
For a full breakdown of each path's mechanics, see the spending paths explainer.
How Fees Hit the Blockchain: The feeSpk Output
Every fee-carrying path constructs its transaction with a required output structure:
output[0] → feeSpk (service fee address, 36-byte script pubkey)
output[1] → recipient (buyer, seller, or split)
The covenant checks that outputs[0] pays the correct feeSpk with the correct value. The feeSpk is a constructor parameter — set when the escrow contract instance is compiled from escrow.sil. The service operator provides it, but the covenant locks it in at deal creation.
This is the enforcement mechanism. The covenant doesn't request a fee — it requires the fee output with the right destination and minimum amount, or the transaction is invalid.
For arbitrateSplit, the output structure becomes three entries: fee, buyer's share, seller's share. Each party share must meet a MIN_OUT threshold of 1 KAS to avoid dust outputs.
The Anti-Bypass Invariant: Why mutual Can't Skip Fees
Here's the subtle part. The mutual path requires both buyer and seller signatures. With both keys, the parties have full authority over the transaction — they could theoretically send all funds to one party with no fee output.
The covenant prevents this. The mutual path enforces what we call the K1 invariant: the fee output is mandatory even when both parties would prefer to skip it.
// inside the mutual path:
require(outputs[0].value >= feeResolve)
require(outputs[0].script_pubkey == feeSpk)
Without the K1 invariant, buyer and seller could collude to bypass fees by routing through mutual instead of release or refund. The invariant closes this at the protocol level. Both parties can still redirect funds between themselves — split, partial refund, any arrangement — but the fee output is non-negotiable.
The trade-off is that mutual is less flexible than its two-signature authority might suggest. Users who expect full bilateral control may find the mandatory fee surprising. But the alternative — optional fees — would make the entire fee structure voluntary.
The Fee Budget: Capping Network Fees on Keyless Paths
Service fees and network (miner) fees are different things. The covenant controls service fees through the feeSpk output. Network fees are controlled through a separate mechanism: the feeBudget constructor parameter.
feeBudget is the 11th parameter of escrow.sil. It caps the maximum network fee that any keyless spending path can consume:
require(feeBudget > 0 && feeBudget <= MAX_FEE_BUDGET)
// MAX_FEE_BUDGET = 10,000,000 sompi (0.1 KAS)
The attack it prevents
Keyless paths — autoRelease and timeout* — can be broadcast by anyone, not just the deal parties. The off-chain watcher broadcasts them automatically, but so could any third party monitoring the blockchain. Without a fee cap, a malicious observer could craft a keyless transaction that satisfies the covenant's output requirements while burning most of the deal value as an inflated miner fee — benefiting themselves as a miner or via pool arrangements.
With the feeBudget cap, even the worst-case keyless transaction consumes at most 0.1 KAS in network fees. On a 500 KAS deal, that's 0.02%.
Production bounds
Kaspa Forge's off-chain builders enforce a tighter range through the binding layer:
feeBudget ∈ [0.01 KAS, 0.1 KAS]
The lower bound prevents deals with a budget so tiny that keyless paths become economically impossible — the relay fee alone might exceed the budget, making the transaction unrelayable. The on-chain lower bound is only > 0; the production floor is enforced off-chain.
The migrate exception
The migrate path — requiring both hot and alarm key signatures — is the only entrypoint that does NOT enforce the feeBudget constraint. This is intentional: with both signatures, the owner has unilateral authority over the transaction. Constraining the fee adds complexity without security benefit.
Across the entire Kaspa Forge platform (7 vault paths + 10 escrow paths = 17 entrypoints total), the feeBudget cap applies to 16. The single exception is Kaspa Safe's migrate path, which requires both keys and therefore full owner consent.
The Known v3 Limitation
The current covenant version (v3) has a documented constraint: it checks that outputs[0] has the correct value and script pubkey, but does not enforce tx.outputs.length. A transaction author could theoretically add extra outputs within the feeBudget window — routing value that would normally be a network fee to additional destinations instead.
This does not compromise the escrow body itself. The value conservation invariant (out[0] + out[1] >= in - feeBudget) ensures deal funds can't be siphoned. But the network fee boundary has a theoretical bypass via additional outputs. The practical impact is minimal given that Kaspa Forge's builders always produce canonical transactions with the minimum number of outputs, and the feeBudget itself is small (max 0.1 KAS). The limitation is a known item for the v4 upgrade cycle, paired with an external audit.
How Kaspa Forge Uses This in Production
The fee architecture spans several components of the Kaspa Escrow service:
Deal creation (deals.rs): the fees_for() function computes exact feeResolve and feeDispute amounts based on deal value. Both are stored alongside the covenant parameters and visible to both parties before funding. The minimum deal value is 50 KAS — below that, the dispute fee (min 5 KAS) would consume more than 10% of the deal.
WASM builders (escrow_core.rs): browser-side transaction builders embed the fee output as outputs[0] in every fee-carrying path. The feeBudget is a constructor parameter baked into the P2SH address — it cannot change without redeploying the contract to a new address.
Watcher (watcher.rs): the off-chain watcher drives keyless paths — autoRelease after the dispute window, timeout* after the arbiter deadline. Each broadcast transaction includes the correct fee output within the feeBudget constraint. The watcher also sends pre-alerts at 50% and 90% of the dispute window to keep both parties informed.
Dispute flow (dispute_api.rs): when an arbiter signs a verdict, the arbitrate path's fee is feeDispute — the higher tier. The covenant enforces this; the arbiter cannot waive it.
The fee recipient address is shared across all Kaspa Forge covenant tools — Kaspa Safe, Escrow, Deposit, and Marketplace — providing unified, transparent fee collection.
Kaspa Escrow encodes its fee structure directly in the on-chain covenant — no middleman can change the terms after you fund a deal. If you want to see the covenant in action, try creating an escrow deal on Kaspa Forge. On-chain operations are free; only closed deals carry a transparent, auditable service fee.
Trade-offs and Honest Boundaries
Predictability vs. flexibility. Fixed fee tiers (0.5% / 2%) are transparent and auditable on-chain, but they can't adapt to market conditions without deploying a new contract version. This is a feature for trust — you know the fee before you fund — but a constraint on pricing agility.
The K1 invariant limits mutual flexibility. Both parties must accept that mutual closure always includes a fee, even when they agree to split without the service's involvement. Users who expect full bilateral control with two signatures may find this surprising. The alternative — voluntary fees — would undermine the entire fee model.
The v3 output-length gap. Fee enforcement is strong but not watertight. The escrow body is safe, but the fee boundary has a theoretical bypass via additional outputs. The practical impact is minimal given canonical builder behavior and the small feeBudget cap, but it's worth knowing.
Fee-free timeout creates a timing asymmetry. Because timeout carries no service fee, a party who benefits from timeout might try to stall disputes until the arbiter deadline. The timeoutTo parameter (set at contract creation, defaults to buyer) mitigates this: the timeout direction is known in advance, not chosen after the fact.
Minimum deal threshold. The 50 KAS minimum exists because below it, the dispute fee (minimum 5 KAS) would consume more than 10% of the deal value. You can create smaller deals on-chain — the covenant doesn't enforce a minimum — but the economics don't work for dispute resolution.
---
FAQ
Can the escrow service change fees after I fund a deal?
No. Fee amounts and the fee recipient address are constructor parameters baked into the covenant's P2SH address at deal creation. Changing them requires deploying a new contract — your existing deal is unaffected.
Why are dispute fees higher than resolution fees?
Disputes involve AI forensics, encrypted chat transcript review, evidence analysis, and a human arbiter's final signature. The 2% dispute fee (min 5 KAS) reflects this additional work versus the 0.5% resolution fee.
What happens if the arbiter disappears?
The covenant's timeout paths activate when the arbiter deadline expires. They require no signature and charge no service fee — funds go to the party specified at contract creation (defaults to buyer).
Can buyer and seller collude to skip fees?
Not on the mutual path. The K1 invariant requires the fee output even when both parties sign. The only fee-free paths are timeout, which requires the arbiter deadline to actually expire.
What is the maximum network fee on a keyless transaction?
The feeBudget parameter caps it at 0.1 KAS (10,000,000 sompi). In production, Kaspa Forge enforces a range of 0.01–0.1 KAS, preventing griefing via inflated miner fees.
Is the fee structure auditable?
Yes. The escrow.sil covenant source is open, fee parameters are visible in the P2SH address, and every spending path can be verified on-chain.
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
