kascov · builder guide

Deploy → spend → replay: covenants on Kaspa testnet-10 in 15 minutes

The working end-to-end path: compile a SilverScript Mecenas, deploy it as a real covenant (KIP-20) on testnet-10, spend it under its own rules, then replay that spend opcode-by-opcode in the browser. Along the way: the compute-budget trap that has stopped most first covenant deploys, and exactly how to avoid it.

Everything here is tested. Every command was run against the live chain and the shipped kascov toolchain in July 2026; every transaction id links to a real, replayable on-chain spend. Nothing is hypothetical.

0Prerequisites — 5 minutes, once

You need a Rust toolchain (stable) and the kascov repo. kascov-lab is the covenant lab CLI: it builds, signs and broadcasts real covenant transactions on testnet-10.

git clone https://github.com/Knitser/kascov.git
cd kascov
cargo build -p kascov-lab              # one-time; grabs the pinned rusty-kaspa rev

cargo run -p kascov-lab -- keygen      # makes a throwaway TN10 key, prints your address
address:         kaspatest:qq…
pubkey (x-only): 3f9c…
blake2b(pubkey): e377…                # you'll paste these two into the generator

Fund the printed address at faucet-testnet.kaspanet.io (it’s Cloudflare-gated — use a browser), then confirm it arrived:

cargo run -p kascov-lab -- balance
balance: 100.00000000 TKAS

In a hurry? One command runs this entire guide’s loop — deploy a Mecenas, then spend it so it names itself on the explorer:

cargo run -p kascov-lab -- contract-demo  (or escrow-demo for a dispute-resolution flow). kascov-lab examples prints every recipe. The rest of this page does the same thing step by step, so you can see the moving parts.

1Compile a Mecenas

A Mecenas is a patronage contract: after every period, anyone may push one pledge to the recipient (receive); the funder can reclaim the rest at any time with a signature (reclaim). Two ways to get the compiled hex:

Option A — the generator (no toolchain)

Open kascov.io/decode → “make a Mecenas” — fill in recipient (your keygen pubkey), funder (your keygen blake2b(pubkey) — this is what lets you reclaim), a pledge, and a period, and copy the compiled hex. Pick a short period like 600 DAA (≈ 1 minute on testnet-10, which runs ~10 DAA per second) so you can trigger receive within this guide.

Option B — the compile API

The same compiler (silverc) runs behind POST /data/testnet-10/compile. Constructor args are bare positional strings: 0x… hex for pubkey/byte[32], plain decimals for int:

curl -s -X POST -H 'content-type: application/json' \
  https://kascov.io/data/testnet-10/compile \
  -d '{"source": "<the Mecenas source from kascov.io/decode>",
       "args": ["0x<recipient pubkey>", "0x<funder blake2b>", "100000000", "600"]}'
{"ok":true,"hex":"6b6c76009c6375…"}

Heads-up on arg encodings. The upstream silverc --constructor-args CLI wants a different shape — tagged Expr JSON like {"kind":"int","data":600}, where a byte[32] is an array of 32 byte objects — while the debugger and the endpoints above take the bare forms. The zero-dep converter clients/js/kascov-encode.mjs translates both directions (node kascov-encode.mjs 0x44…44 600), so you never hand-write 32 nested byte objects.

2Deploy it as a covenant

cargo run -p kascov-lab -- deploy --program-hex <the compiled hex> --value 1000000000
program:   SilverScript · Mecenas (181 bytes)
BIRTH      covenant b4ade48e3ad1eda5…
           tx a9a7df1a71307c05…
           program blake2b 3483c24b…

What just happened, precisely:

The coin appears on kascov.io within about a minute, labeled “p2sh commitment” until the reveal.

3The trap: “script units exceeded” and the v0-UTXO bootstrap

This is where most first deploys die (rusty-kaspa#1073 is the canonical report). You build a version-1 covenant transaction, set a huge computeBudget, spend a plain faucet UTXO — and the node answers:

failed to verify the signature script: script units exceeded
the amount committed in the input: used=100000, limit=9999

Three facts decode that message:

factnumber
Toccata (v1) transactions replace the per-input sigOpCount: u8 with a per-input computeBudget: u16 commitment. 1 budget unit = 100 grams = 10,000 script units.1 → 10,000
Every input gets a free allowance of one budget unit minus one script unit — that is the limit=9999 you hit when your committed budget is effectively 0.9,999 free
One Schnorr OpCheckSig costs 1,000 grams × 100 units/gram — the used=100000. So even the most boring pay-to-pubkey input needs budget ≥ 10 in a v1 transaction. Witness bytes you push are charged 1:1 on top.100,000 / sig

And the two misconceptions the error invites:

The fix (what kascov-labkit does, verbatim from the shipped code):

Every input of a v1 transaction gets an explicit commitment — TransactionInput::new_with_mass(outpoint, script, seq, ComputeCommit::ComputeBudget(ComputeBudget(n)))including the plain fee input, the one everybody forgets because it “never needed anything” pre-Toccata. kascov-lab spend exposes it as --compute-budget (default 20 per input; 1 unit = 10,000 script units).

Committed budget is charged as compute mass whether the script uses it or not, so the fee must scale with it. labkit prices two-input constrained spends as fee = 100 sompi × (2 × budget × 100 grams + size headroom) — commit generously, but not 65535.

For plain (non-covenant) transfers, rusty-kaspa’s own consensus-core sign() already stamps ComputeBudget(10) on every v1 input — exactly one signature check. That is what labkit’s deploy relies on.

Or let the tool run this whole section for you: kascov’s transaction preflight takes your transaction as JSON and answers ready / will fail / incomplete before you broadcast — per-input budgets checked against exactly this trap, masses against the block limit, and the fix spelled out (“set computeBudget: 10”). Your transaction goes into the call and nowhere else.

4Spend it — --entrypoint receive

receive is the interesting entrypoint: it is output-constrained and needs no signature at all. Once the coin is older than period, anyone may build the spend — but the contract’s own introspection (KIP-17) forces the outputs: exactly pledge to the recipient, and the remainder re-bound to the same P2SH so the covenant lives on. Dry-run it first — this runs the real Kaspa script engine locally, without broadcasting:

cargo run -p kascov-lab -- spend --program-hex <the same hex> --entrypoint receive --dry-run
SIMULATE   SilverScript · Mecenas . receive  (not broadcast)
           ✓ PASS —  the spend SATISFIES the contract — a node would accept it
           output[0] 1.00000000 TKAS
           output[1] 8.99999000 TKAS [covenant continues]

(If you dry-run before period has elapsed you’ll see the engine reject honestly — the age gate compiles to OpCheckSequenceVerify. Wait out your period and re-run.) Then drop --dry-run to broadcast:

cargo run -p kascov-lab -- spend --program-hex <the same hex> --entrypoint receive
SPEND      SilverScript · Mecenas . receive
           tx 05128801…

Mechanics worth knowing:

5Replay it on kascov.io

Open your coin’s page (kascov.io/testnet-10/c/<covenant-id>, or paste the txid into the search box). In the UTXO panel, every spent state has a “⧉ replay this spend” button — the worker fetches the captured witness and runs it through the actual TxScriptEngine, streaming an opcode-by-opcode trace with live stacks: the age check, the introspection ops reading your outputs, the final verdict.

The same replay is a plain JSON endpoint:

curl -s https://kascov.io/data/testnet-10/debug/<txid> | jq '.verdict, .trace[0:3]'

Honest caveat, stated by the endpoint itself: the replay fabricates a 1-in/1-out context, so signature and output-introspection checks can diverge from the original transaction — the witness, program and data flow are the real on-chain bytes, but a pass: false replay does not mean the on-chain spend was invalid (it was accepted by consensus).

Name your token on chain (launchpads, this one’s for you)

kascov shows a token’s real name only when that name lives on the chain itself — never from anyone’s private database. The convention is simple: put a small JSON document in the genesis transaction’s payload when you launch. Whoever authors the genesis (you, or your launchpad) is exactly the right party to claim the name, and anyone can verify it from the chain forever.

{"name": "My Token", "ticker": "MTK",
 "image": "https://…/mtk.png",
 "image_hash": "sha256 hex of the image bytes"}

Rules kascov applies when reading it: name up to 48 characters, ticker (or symbol) up to 12, plain JSON or hex-encoded JSON both work, and the whole payload stays under 4 KB. The token page and directory then show the claimed name with a named on chain badge — honestly labeled as a deployer claim (names aren’t unique; the canonical kascov name never disappears).

Images: include image_hash — the SHA-256 of the exact image bytes — next to the URL. Today kascov surfaces the image as a link. Hash-committed images are the path to being rendered: because the genesis pins the bytes forever, kascov can fetch, verify, and serve the art itself, and nobody can swap it afterwards. An image without a hash stays a link permanently — an unpinned URL can change under everyone’s feet, and kascov doesn’t display what the chain can’t hold to account. The KCC-0020 token draft doesn’t cover display metadata yet; if a metadata convention lands in the standards track, kascov will read that too.

6A real production example you can inspect right now

This exact loop ran end-to-end on the live site on July 10, 2026 — deploy → genesis → reclaim → revealed. The coin is jolly-pearl-quail (b4ade48e3ad1eda5…), a Mecenas born via the one-click web deploy, then reclaimed by the funder key with kascov-lab spend --entrypoint reclaim:

eventtxid
genesis (deploy — P2SH commitment, provable)a9a7df1a71307c0518aa001920b42b9afecbaa503d61c279c08e45ee6bfc8bdb
reclaim (reveals the program; funds exit → burn)051288013eecf1cc3f890005236b96567f743423390a540650af65f50c4288b3

Replay the reveal yourself — one curl, no setup:

curl -s https://kascov.io/data/testnet-10/debug/051288013eecf1cc3f890005236b96567f743423390a540650af65f50c4288b3 \
  | jq '{verdict, steps: (.trace|length), fields: .trace[3].op}'

The trace shows the witness pushing the funder’s pubkey and signature, then the full 181-byte Mecenas program whose blake2b matches the deploy commitment — the coin page shows the decoded constructor args (recipient, funder_hash, pledge, period) it recovered from those bytes.

And it isn’t just this guide’s own loop anymore — builders are shipping on covenants. The Kaspa Covenant Game Kit (by @w00c00) runs 1v1 games on player-funded covenant escrow with on-chain settlement, and its escrow contract verifies byte-identical on kascov — inspect the verified escrow coin yourself. More projects live in the “built with covenants” showcase on the landing page.