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.
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 is born holding 10 TKAS with a P2SH commitment state:
OpBlake2b <blake2b-256(program)> OpEqual. The program itself stays hidden until you spend. - Output 0 carries a
CovenantBindingwith a fresh covenant id —blake2b(outpoint ‖ authorized outputs)— the identity that persists across every future state transition (KIP-20). - The command prints a link like
kascov.io/testnet-10/c/<id>?program=<hex>— the explorer verifies your program hash against the on-chain commitment in the browser, so you can prove what you deployed before revealing it.
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:
| fact | number |
|---|---|
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:
- It is not the v0 UTXO limiting you. A v1 transaction can spend v0 (pre-Toccata) UTXOs — that is the bootstrap path; every covenant on testnet-10 started from a plain faucet UTXO. The budget is committed by the spending input, not derived from the UTXO.
- You can’t patch the budget in afterwards. For v1 transactions the
computeBudgetfield is covered by the signature hash. Setting it after signing invalidates the signature; and SDKs pinned to pre-Toccata wire formats silently drop the field when serializing — the node then sees budget 0 and you getlimit=9999no matter what your local object said. If your tooling predates Toccata (e.g. wasm/py SDKs frozen at 2.0.x), it cannot express this transaction.
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:
- The witness reveals the program:
push(selector) ++ push(program)forreceive(no signature args); pure-signature entrypoints prependpush(pubkey) ++ push(sig). From this moment kascov shows the coin as SilverScript · Mecenas, constructor args labeled, permanently. - The tx has a second, plain input paying the real network fee — the contract’s
internal math hardcodes a 1000-sompi fee, and the node’s compute-mass fee must hold on top.
Both inputs commit
--compute-budget(see step 3). - The funder’s exit is
--entrypoint reclaim: a pure-signature spend that works immediately, if your key’s blake2b is the coin’sfunderfield.
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:
| event | txid |
|---|---|
| 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.