the covenant explorer for Kaspa L1
Kaspa coins just learned to carry rules.
like a token explorer, but for coins that carry their own rules: kascov watches every smart coin on the Kaspa mainnet — born, moving, retiring — and saves the history ordinary nodes prune after roughly 30 hours.
network activity
every life event the index has recorded, bucketed by DAA score
who stood out in the last 24 hours
how to read this
-
Borna coin gets an identity and rules
-
Movesit changes hands or state — identity travels with it
-
Retiredits story ends, recorded forever here
new here? take the 30-second tour →
wait — what is a smart coin, really?
Normal KAS is like cash: interchangeable bills with no memory. Spend some, and brand-new bills come out the other side — nothing connects them to the old ones.
Since the Toccata upgrade, a transaction can take ordinary KAS from any wallet and wrap it into something new: an output stamped with a permanent identity (a covenant id) and, usually, a program. That moment is a birth — ordinary money becoming a named coin that carries rules. The id is a fingerprint of that exact moment, so it can't be faked.
When its owner spends it and the transaction stamps the same identity onto new outputs, that's a move: same coin, new state — maybe a new owner, maybe split into pieces, maybe merged back. The name travels with it, however many hands it passes through.
And when it's spent without passing the identity on, the KAS inside flows back out as ordinary cash and the identity ends — retired. Ordinary nodes prune the story after roughly 30 hours. kascov doesn't.
Why bother? Because the program travels with the identity: a coin can refuse to move more than 10% a week, demand two signatures, wait for a delivery confirmation, or verify a zero-knowledge proof — the coin itself enforces its rules, no middleman.
the galaxy
every app at once, live — one zoomable map of the whole network. each dot is a smart coin, each link a shared transaction.
script decoder
paste any Kaspa script, read its post-Toccata opcodes — covenant ops highlighted. runs in your browser, nothing leaves the page.
JSON API
everything kascov sees, as plain JSON with CORS on. build a bot, a chart, a better explorer.
make a smart coin
build a covenant without writing code and deploy it on testnet in one click — it reveals itself here, named forever.
built with covenants
what builders are shipping on Kaspa’s smart coins — real projects, their contracts verifiable right here.
KASPA COVENANT EXPLORER · mainnet
Explore Kaspa smart coins
What is running, what it holds and how it changes. Read straight from the chain.
your coins — the ones you starred
life on the testnet
in the mempool
covenant transactions the node has seen but no block has confirmed yet, each clears the moment it lands on chain.
record holders
the live footprint
launchpads and exchanges
every venue whose programs kascov names from the chain’s own bytes — tap one for all of its launches
how the network is being used
Follow daily activity across the indexed history. Pick a period, then inspect any day.
90 days of covenant activity
A longer view of the chain. Every day keeps its births, state updates and retirement events.
what the index can prove
apps · coins that move together
a single transaction moving several smart coins is a multi-contract flow — a covenant app.
based-app activity · by namespace
smart coins whose transactions carry an app payload, grouped by its namespace — the first per-namespace view of Kaspa's based apps.
inscriptions · what’s being written on-chain
the JSON payloads, decoded straight from the bytes — each covenant’s protocol, operation and token ticker, exactly as written. no registry, no labels we invented.
the galaxy · the whole network
every app at once — each dot is a smart coin, each link a shared transaction. scroll to zoom, drag to pan, click a dot to open the coin.pinch to zoom · tap a dot
← explorer overviewbirth-to-retirement intervals
Time from a recorded birth to each retirement event. A covenant can contribute more than one event; ongoing lives are excluded.
history and indexer details
more charts
Cumulative life events, recorded retirement intervals and recent virtual-chain rollbacks.
cumulative birth and output-end events
Event counts over the indexed history. An output-end event can leave other cells of the same covenant alive.
retirement-event intervals
coins by contract type
recent reorgs
latest stories
all smart coins
open to browse
Browse individual covenant identities and their histories.
the playground
Read any script.
paste any Kaspa script as hex and read it as opcodes — including the post-Toccata set: transaction introspection (KIP-17), covenant ops (KIP-20), zk verification (KIP-16). recognizing, disassembling & linting run in your browser; simulate, verify-proof & compile call the kascov node. recognized contracts can be re-made with your own parameters.
or a real build, straight off chain (ten programs, fetched only when you pick one)
each one is the program a live coin revealed when it was spent, and the link opens a coin running exactly these bytes. where kascov names a build, the chip carries that name: the recognizer’s template, the pinned market skeleton, or the argent build it came out of. one chip below kascov does not name at all, and it says so when you open it.
what the colors mean
- push data pushed onto the stack
- standard the classic script opcodes (checks, math, stack)
- introspection KIP-17 — the script reads its own transaction
- covenant KIP-20 — smart-coin identity & state rules
- zk KIP-16 — zero-knowledge proof verification
- unknown not in the post-Toccata opcode table
this page reads scripts in your browser. the program names kascov shows come from kascov-decode, which runs on your own machine too: cargo install kascov-decode, then kascov-decode name <hex>. its check command takes the signature script of any spend and the output it spent, and confirms the spend revealed exactly the program that output committed to.
the lab
Make your own smart coin.
Deploy a real covenant on Kaspa testnet-10 and watch it run itself: no Solidity, no VM bytecode, just Kaspa script. Fill in a template below and deploy it on testnet-10 in one click, with no local toolchain, and it reveals itself right here on kascov.
build one without writing code
Pick a covenant, fill in the parties, amounts and timelocks in plain terms, and kascov assembles byte-perfect Kaspa script in your browser — the exact bytes the silverc compiler emits, live as you type. Then deploy it in one click on testnet-10, with no local toolchain, or download the same deploy as a deploy.sh that needs nothing but curl.
write & compile
Write a covenant and compile it to real Kaspa script right here, powered by the actual compilers: silverc for SilverScript and argentc for Argent, at one pinned generation each. Edit the source, set the constructor args, and get deployable hex you can check against any covenant on chain.
silverc generation fork · michaelsutton/silverscript d57e5df · argentc 05ba4b2 · fork builds dispatch on a numeric selector and hash templates with BLAKE2b · this page’s own default until the worker reports its own pair
submit an argent artifact
Paste the artifact.json that argentc build wrote, from either generation of the compiler: the fork-era file (script_hex, selectors) or the SilverScript 1.0 file (bytecode, dispatch tags). kascov reads which generation the program's own dispatch rungs belong to, recomputes every template hash from the artifact's bytes under that generation's rule (BLAKE2b for the fork, BLAKE3 for 1.0), refuses the file if one does not reproduce, and from then on every covenant whose revealed program hashes to a template carries the app's name, entrypoints and state layout instead of an anonymous family label, and each of its reveals is named by the entry it called. The hash is proven from the bytes; the names are what the artifact declares.
the whole loop
- 1Decoderead any covenant on-chain as SilverScript — its rules, its parameters, labeled.
- 2Editchange the constructor arguments in your browser; verified byte-for-byte on every keystroke.
- 3Deployone click births it on testnet: a real coin, its rules committed as a P2SH state.
- 4Spendsatisfy the contract's own rules; the transaction reveals the program on-chain.
- 5Revealedkascov names the coin its contract, forever — for everyone, hash-verified.
the contracts you can make
Three canonical SilverScript covenants are built into the builder. Open one above, put your own keys and amounts in, and it compiles to deployable hex in your browser.
do it yourself, step by step
Steps 01 to 04 run from this page. You need a testnet-10 address funded at the faucet and nothing else: no repo, no Rust, no local tooling. Step 05 is optional and runs on your own machine.
Get a testnet address, fund it
Any Kaspa wallet gives you a testnet-10 address and its x-only pubkey. Fund the address at the faucet (it is Cloudflare-gated, so use a browser). The builder above can read your address straight from KasWare.
Generate your contract
In the builder above, pick Mecenas / Escrow / LastWill, fill in the fields (your pubkey, and its blake2b where the contract asks for a hash), and byte-perfect script appears as you type. Prefer to write it? Use write & compile and the real compilers run on the worker.
Deploy it
Press deploy on testnet-10 in the builder. The kascov worker funds, signs and broadcasts it, and the coin is born holding a hidden P2SH commitment. It appears on kascov within about a minute. The same call as one curl is in the builder’s own deploy it on testnet-10 block, and in the deploy.sh it offers.
Reveal it: spend it
Spending a covenant means signing and broadcasting a transaction, and kascov does not do that for you. Build the spend with your own signing tooling; check it before you broadcast with the preflight and the /simulate endpoint, both live here. Once it lands, the spend reveals the program on chain and kascov names the coin your contract, permanently.
Check the name yourself
Once your spend lands, kascov names your coin’s contract. You do not have to take that on trust: the decoder that names it is kascov-decode on crates.io. Install it with cargo install kascov-decode, then run kascov-decode check with your spend’s signature script and --spk with your coin’s script public key. It confirms your spend revealed exactly the program your coin committed to, and names it, with no node and no network.
The CLI kascov uses for its own deploys and spends is an internal tool and is not published, so no step asks you to run it; the decoder in step 05 is published on crates.io. The step-by-step walkthrough is the builder guide on this site.
the playground · preflight
Will it pass?
paste a transaction as JSON (the SDK / RPC shape) and find out before you broadcast: every input’s compute budget checked against the classic “script units exceeded” trap, masses measured against the block limit, an honest fee estimate — and for inputs that carry both their witness and their UTXO’s script, a real run through Kaspa’s script engine.
all but the budget trap start from one real testnet-10 spend, the accepted spend of a SilverScript · Mecenas coin. one of them is that spend exactly as mined, witness and utxo and payout; the others edit it, by as little as one field, to reach the verdict their label names. the trap stays the hand-made illustration it has always been.
your transaction goes into the preflight call and nowhere else — the worker computes the verdict and forgets it (no-store). nothing is broadcast, nothing is kept.
reference
The kascov API.
Every covenant on Kaspa, indexed live and kept long after nodes prune it — as plain JSON. No keys, no rate cards, CORS open to everyone. Build a bot, a chart, a better explorer.
Overview
The indexer follows Kaspa's chain tip live, classifies every covenant event (born / moved / retired), decodes spend-time script reveals, and serves same-origin JSON through the production Caddy proxy. Reads are cheap and cacheable — poll as often as you like.
| Base URL | https://kascov.io |
|---|---|
| Format | JSON · application/json |
| Auth | None — public |
| CORS | CORS enabled incl. preflight — call any route, GET or POST, straight from a browser page |
| Caching | responses carry explicit Cache-Control, tip_daa + tip_at_ms; the live stream is never buffered or cached |
| Compatibility | changes are additive: fields are added, and existing ones are never renamed, retyped or removed. A value can still be corrected where kascov had read it wrong: from API 0.2.0, owners on testnet-10 KCC-20 layout tokens are named by their KCC-2 scheme (a key hash is p2pkh:, no longer script:; a P2SH owner is script:, no longer presence:), and an owner_address is absent where the owner is a key hash, which names no address. Ignore fields you don't recognise. Fields added from API 0.2.0 on are absent when they don't apply (the one deliberate null among them is standard.conformant: no verdict, because the build is unreviewed or its program artifact is unpublished); older fields keep their shapes, and some of those are null when they don't apply, such as template and market. Treat value lists such as layout, metadata_source, token_kind, borrow_proof, an issuer's role and proven_by, a cell's proven_by and owner prefixes as open sets that can grow. Token quantities on token rows, holders, events, cells, issuer allowances, class_supply, address holdings and prove-holding carry an exact _str twin; trade rows, book orders and vesting amounts do not yet |
Networks
Every path takes a network segment. Swap testnet-10 for mainnet anywhere. Mainnet's first smart coins arrived July 2, 2026; their full history starts there.
Check it yourself
The program names this API returns come from kascov-decode, a Rust library and command published on crates.io. It names a covenant program from its bytes alone, with no node, no network and no database, so any name here can be checked against your own node. Its check command takes the signature script of the spend that revealed a program and the script of the output that spend consumed, confirms the program hashes to that output's commitment, and names it.
cargo install kascov-decode
kascov-decode check <signature script hex> --spk <spent output script hex>
The library's documentation is on docs.rs.
/data/{network}-live.jsonThe small, fast feed — network stats, the chain tip, and the ~150 newest life events. This is the one to poll (a few KB, refreshes every few seconds).
curl -s https://kascov.io/data/testnet-10-live.json{
"network": "testnet-10",
"stats": { "covenants": 192852, "active": 14190, "burned": 178662,
"events": 876383, "live_value": 307204412838957,
"last_activity_daa": 513875477 },
"tip_daa": 513875491,
"tip_at_ms": 1783746272524,
"processed_daa": 513875490,
"generated_at_ms": 1783746272524,
"recent_events": [
{ "covenant_id": "2ab7dcd2…35115",
"kind": "transition", "txid": "f9bbca…f2ba",
"accepting_daa": 513875477, "seq": 5, "tx_index": 50 }
]
}| Field | Type | Notes |
|---|---|---|
| stats.covenants | int | distinct smart coins ever seen |
| stats.active | int | coins with a live UTXO right now |
| tip_daa · processed_daa | int | chain tip vs. last block applied — the gap is the honest sync lag |
| recent_events[].kind | string | genesis · transition · burn |
| recent_events[].seq | int | the event's 0-based position in its own coin's timeline |
| recent_events[].tx_index | int? | 0-based index in the accepting block's accepted-tx list (consensus acceptance order); omitted on rows indexed before capture |
| recent_events[].exit_claim | bool? | present, true, only on a vprog exit claim: its kind stays structural (often burn), but it ends one output and retires nothing |
/data/{network}/pendingThe node’s current unconfirmed covenant transactions. Reconcile this snapshot after opening or reconnecting the event stream: stream messages are fast hints; this response is the authoritative current set.
curl -s https://kascov.io/data/testnet-10/pending{
"status": "live",
"last_poll_ms": 1784875200123,
"generated_at_ms": 1784875200200,
"revision": 421,
"pending": [{
"txid": "f9bbca…f2ba",
"covenant_id": "2ab7dcd2…35115",
"tx_kind": "transition",
"age_ms": 77,
"events": [
{ "covenant_id": "2ab7dcd2…35115",
"tx_kind": "transition" }
]
}]
}| Field | Type | Notes |
|---|---|---|
| status | string | starting · live · stale · reconnecting · disabled |
| last_poll_ms | int? | unix ms of the latest successful node mempool poll; remains visible while reconnecting |
| revision | int | changes whenever feed state or tracked rows change; useful for observability, not as a durable cursor |
| pending[].events | array | every covenant touched by the transaction; the scalar covenant fields remain as a backward-compatible preview |
| pending[].exit_claim · events[].exit_claim | bool? | present, true, only on a vprog exit claim: its kind stays structural, but it ends one output and retires nothing |
/data/{network}.json?after_daa=&after_id=&limit=The grid — stats plus one summary row per covenant, ordered by last activity, newest first. A bare request is a first page capped at 20,000 rows; when more remain the response carries next_after_daa + next_after_id — pass them back to keep walking.
| Query | Type | Notes |
|---|---|---|
| after_daa | int | exclusive cursor — resume below this last_activity_daa |
| after_id | hex | tie-breaker within the boundary DAA (the last row's covenant_id) |
| limit | int | page size, default 5000, capped at 20000; invalid values degrade to the bare first page |
{
"stats": { … },
"covenants": [
{ "covenant_id": "e55322e7…60697", "name": "sleepy-olive-crane",
"status": "active", "born_value": 460831908462, "live_value": 230415954231,
"event_count": 2, "live_utxos": 1, "lineage_complete": true,
"genesis_daa": 513863444, "last_activity_daa": 513870198,
"template": "p2pk state" }
],
"next_after_daa": 513870178,
"next_after_id": "a348a978…f3a29"
}| Field | Type | Notes |
|---|---|---|
| name | string | the coin's friendly name — derived deterministically from the id, the same name everywhere kascov shows it |
| status | string | active or burned |
| born_value · live_value | int | sompi held at birth / right now |
| last_activity_daa | int | the DAA of the coin's newest life event — the sort key of this feed |
| template | string? | recognized contract, or a state shape like p2sh commitment |
| next_after_daa · next_after_id | int? · hex? | present only when another page exists — feed them to ?after_daa=&after_id= |
/data/{network}/events?after_daa=&after_seq=&limit=The chain-wide event feed — every life event on the network, oldest first, in the canonical deterministic order: accepting_daa, then tx_index, then txid. Every event-shaped surface (this feed, the live feed, the stream, webhooks) serves the same object, so you write one parser. Walk history from any point by passing the response's cursor pair back.
| Query | Type | Notes |
|---|---|---|
| after_daa | int | resume at this DAA — 0 starts from the beginning of history |
| after_seq | int | how many events of that boundary DAA were already taken — pass the returned pair straight back; a stable cursor because order inside a DAA is total and history only appends at higher DAAs |
| limit | int | page size, clamped server-side |
{
"events": [
{ "covenant_id": "2ab7dcd2…35115", "seq": 5, "kind": "transition",
"txid": "f9bbca…f2ba", "accepting_daa": 513875477,
"accepting_block": "58ef8d…c44f0", "tx_index": 50,
"payload_len": 137 }
],
"next_after_daa": 513875477,
"next_after_seq": 12
}| Field | Type | Notes |
|---|---|---|
| events[].seq | int | 0-based position in the event's own coin's timeline |
| events[].tx_index | int? | consensus acceptance order within the accepting block; omitted (not null) on rows indexed before capture |
| events[].payload_len | int? | byte length of the transaction's v1 payload; omitted when it carried none |
| next_after_daa · next_after_seq | int | the resume cursor — feed to ?after_daa=&after_seq= to keep walking |
/data/{network}/stream?covenant=Server-sent events — one JSON message the moment the indexer sees each confirmed life event or mempool change. The stream opens with a : connected hello and sends a : ka keep-alive comment every 25s. Treat messages as hints and reconcile the matching snapshot after every connection. It works from a browser against https://kascov.io directly, because the production proxy streams SSE without buffering.
| Query | Type | Notes |
|---|---|---|
| covenant | hex | optional — exactly 64 hex characters narrows the stream to one coin's events; anything else is a 400, never a silent wildcard |
curl -N 'https://kascov.io/data/testnet-10/stream': connected
data: {"covenant_id":"2ab7dcd2…","kind":"transition",
"txid":"f9bbca…","accepting_daa":513875477,
"tx_index":50}
data: {"kind":"pending","tx_kind":"transition",
"txid":"a15d4e…","covenant_id":"8c91d2…",
"events":[…],"revision":421}| Field | Type | Notes |
|---|---|---|
| kind | string | genesis · transition · burn · pending · pending_resolved — plus reorg notices {"kind":"reorg","rolled_back":N} (filtered streams never see them) |
| tx_kind | string? | the predicted covenant event kind on pending messages |
| resolution | string? | confirmed or dropped on pending_resolved |
| exit_claim | bool? | present, true, only on a vprog exit claim: its kind stays structural, but it ends one output and retires nothing (on confirmed and pending messages) |
/data/price.jsonThe KAS→USD rate behind the site's ≈ $ hints — one tiny object, no network segment. Answers 503 while no fresh rate is available; treat any non-200 as "no price right now".
Normally this is a kaspulse feed: a MAD-filtered median of several exchange venues, threshold-signed by a five-key committee, and re-checkable in your browser. kascov does not depend on that oracle being up — if the read is missing, stale, halted or under quorum it falls back to a single unsigned Kraken (then CoinGecko) read, and source plus fallback_reason say so on the wire. Always branch on source; never assume a rate here is signed.
curl -s https://kascov.io/data/price.json{
"kas_usd": 0.028545,
"updated_at_ms": 1787540697080,
"source": "kaspulse",
"signed": true,
"signers": 5,
"signatures": 5,
"threshold": 3,
"signed_ts": 1787540696,
"venues": 4,
"degraded": false,
"verify_url": "https://pulse.kascov.io/#/feed/KAS-USD"
}{
"kas_usd": 0.02851,
"updated_at_ms": 1787540705380,
"source": "kraken",
"signed": false,
"fallback_reason": "unreachable"
}| Field | Type | Notes |
|---|---|---|
| kas_usd | number | US dollars per 1 KAS |
| updated_at_ms | int | when the rate was last refreshed (unix ms) |
| source | string | kaspulse, kraken or coingecko — which read is live |
| signed | bool | true only on the kaspulse path; the exchange fallbacks are unsigned |
| signers | int? | committee keys published with the feed (signed only) |
| signatures | int? | how many of them signed this round (signed only) |
| threshold | int? | how many are required (signed only) |
| signed_ts | int? | unix seconds of the signature; refused past 120s old (signed only) |
| venues | int? | exchange venues behind the median, after the oracle's outlier filter |
| degraded | bool? | the oracle is publishing on fewer venues than it wants — surfaced, not acted on, since a degraded signed median still beats one unsigned read |
| verify_url | string? | the in-browser verifier for this exact feed (signed only) |
| fallback_reason | string? | why the signed read was refused: unreachable, unreachable_backoff, http_error, malformed, wrong_pair, halted, peg, no_price, no_threshold, under_quorum, no_signed_ts, stale, future_ts |
/feed.xmlWhat’s new, as an Atom feed — the same milestones as the changelog, one entry each with a stable id, newest first. Lives on the site root next to /sitemap.xml (no network segment), cached for an hour. Point any feed reader at it. Served by newer workers; older deploys answer 404.
curl -s https://kascov.io/feed.xml/data/{network}/c/{covenant_id}.jsonOne coin's whole life: its event timeline and every UTXO with decoded scripts, recognized templates, and spend-time reveals. Over 500 events the timeline keeps the first indexed event (its birth when the lineage is complete) and the newest ones; the rest page through /c/{covenant_id}/events. 404 if the id is unknown.
{
"covenant_id": "a348a978…f3a29", "name": "brave-jade-osprey",
"status": "burned", "event_count": 3, "lineage_complete": true,
"genesis_txid": "82aabd…25dcb", "genesis_daa": 513863030,
"last_activity_daa": 513870178,
"events": [
{ "seq": 0, "kind": "genesis", "txid": "82aabd…25dcb",
"accepting_daa": 513863030, "accepting_block": "bc5aaf…8daac",
"tx_index": 56 },
{ "seq": 2, "kind": "burn", "txid": "8e7040…3e2bb",
"accepting_daa": 513870178, "accepting_block": "3d1c00…45e7e2",
"tx_index": 18 }
],
"events_truncated": false,
"utxos": [
{ "outpoint": "82aabd…25dcb:0", "value": 230415954231, "live": false,
"template": "p2sh commitment",
"state_fields": [ { "name": "program_hash", "value": "d9a9e982…f475d17" } ],
"revealed_template": "SilverScript · Escrow",
"revealed_fields": [ { "name": "arbiter_hash", "value": "f4445661…" } ] }
]
}| Field | Type | Notes |
|---|---|---|
| name | string | the coin's friendly name — deterministic from the id |
| lineage_complete | bool | false means kascov started watching mid-life; earlier history is honestly missing |
| events_truncated | bool | true when the timeline was capped: event_count still counts everything, and each kept event carries kind_n, its place among events of its kind |
| events[].vprog · vprog | array? · object? | on a vprog's covenant: what each settlement or exit claim's own transaction states (state and lane tip before and after, the chain block it proves to, deposits, exits), and recheck, kascov's own re-run of the settlement (pass true or false; null when it could not run, with verdict saying why; absent until kascov has tried); the covenant-level object sums them up |
| events[].payload · payload_len | hex? · int? | the tx's v1 payload — inlined as hex when ≤ 512 bytes, collapsed to its byte length above that |
| events[].with_covenants | array? | up to 4 other coins the same transaction moved (multi-contract flows) |
| utxos[].revealed_template | string? | the contract a P2SH state actually ran, decoded and hash-verified when it was spent |
| argent_build.kcc20 | object? | on a coin of an Argent build that is one of a pinned KCC-20 build's actors: actor (token · mint · seed), name, pin, standing and conformant, plus artifact as on a token's standard. The last two describe the build, never a token of it: a coin of a reviewed build reads reviewed KCC-20 build here whatever its token's standing. Whether that token is proven is its own status on /token/{id} |
| …_fields[] | array | { name, value } — labeled constructor arguments |
| holders[] | array | up to 100 p2pk-state owners: { pubkey, controls_now, states_seen, first_seen_daa, last_seen_daa } |
| utxos[].zk_system | string? | on zk-using states, a size heuristic over the proof push: risc0 (≥1024 B) or groth16 (128–256 B) |
| utxos[].revealed_zk_system | string? | the same heuristic applied to the program revealed at spend |
/data/{network}/c/{covenant_id}/events?after_seq=&limit=&only=vprogOne coin's whole event history, oldest first, a page at a time (up to 500, default 200), in the coin answer's event shape. only=vprog keeps the events that took a vprog step: a vprog's settlements and exit claims, the settlements numbered (n) over its whole history. Feed next_after_seq back as after_seq to keep walking; it is absent on the last page.
curl -s 'https://kascov.io/data/testnet-10/c/{covenant_id}/events?only=vprog&limit=100'/data/{network}/coins?ids=Batch summaries — several coins in one request, each row in the exact shape the snapshot grid serves. ids is comma-separated 64-hex covenant ids; ids the index doesn't know are simply absent from the answer, never an error.
curl -s 'https://kascov.io/data/testnet-10/coins?ids=e55322e7…,a348a978…'{
"coins": [
{ "covenant_id": "e55322e7…60697", "name": "sleepy-olive-crane",
"status": "active", "last_activity_daa": 513870198, … },
{ "covenant_id": "a348a978…f3a29", "name": "brave-jade-osprey",
"status": "burned", "last_activity_daa": 513870178, … }
]
}/data/{network}/tx/{txid}.jsonWhat did this transaction do, in covenant terms? Newer workers answer the whole story — the per-covenant events, the cells created and spent, and the decoded token actions — behind the site's #/{network}/tx/{txid} pages. Older workers answer only covenant_id / covenant_ids; every added field is additive, so feature-detect and fall back gracefully.
{
"txid": "8e7040…3e2bb",
"covenant_id": "b11acf6c…459c6",
"covenant_ids": [ "b11acf6c…459c6" ],
"accepting_daa": 513870178, "accepting_block": "3d1c00…45e7e2",
"events": [
{ "covenant_id": "b11acf6c…459c6", "name": "brave-jade-osprey",
"seq": 2, "kind": "burn", "tx_index": 18 }
],
"cells": {
"created": [ { "covenant_id": "…", "index": 0,
"value": 230415954231, "template": "p2sh commitment" } ],
"spent": [ { "covenant_id": "…", "txid": "82aabd…25dcb", "index": 0,
"value": 230415954231,
"revealed_template": "SilverScript · Escrow",
"has_witness": true } ]
},
"token_actions": [
{ "token_id": "9cc4fb51…68f50", "name": "humble-crimson-magpie",
"token_kind": "transfer", "amount": 1000000 }
]
}{ "error": "not found" }| Field | Type | Notes |
|---|---|---|
| covenant_id · covenant_ids | hex · array | the coin(s) this transaction touched — the only fields every worker serves |
| accepting_daa · accepting_block | int? · hex? | newer workers — where the chain accepted this transaction |
| events[] | array? | newer workers — one row per covenant life event: covenant_id, name, seq, kind (genesis · transition · burn), tx_index?, and exit_claim: true on a vprog exit claim (its kind stays structural; it retires nothing) |
| cells.created[] | array? | newer workers — covenant state this transaction made: covenant_id, output index, value (sompi), template? |
| cells.spent[] | array? | newer workers — covenant state it consumed: the outpoint's txid + index, value, revealed_template?, and has_witness (the captured unlocking script — replayable through the spend debugger) |
| token_actions[] | array? | newer workers — the decoded token view: token_id, name, token_kind (the same open set as token events), amount? in raw token units, market_action? (what the transaction did on the token's own launch market) and locked_forever? (true on a delta into the address of the one-byte script OP_RETURN under a reviewed KCC-20 build: locked, not burned, and the supply does not change) |
| actions[] | array? | what the transaction did to a launch whose own transactions bind its curve or pool (Kasseal and KasForge), one row per market: action (launch · buy · sell · graduation · fee payout · LP setup · add liquidity · remove liquidity · buyback, an open set), entry, role, market_label, venue, the covenant ids, and exact_replay. On a Kasseal market the action is read from the entry its witness called, and entry is that entry's name in the program. On a KasForge market the action is kascov's replay's reading of how the move changed the market's proven state, and entry is the replay's own name for the move (such as launch buy, graduation buy or lp bind); a KasForge buyback spent a share of the creator's fees (half on the v3.2 and v3.3 pool and the Oct 17 curve, the share the creator set on a v3.8 pool) on tokens that stay in the market's cell outside the reserve. Only when exact_replay is true: figures with the amounts the replay proved (a liquidity change's tokens, kas_sompi and shares). On a Kasseal market a fee payout or a graduation that paid fees names treasury_address and creator_address |
| trade | object? | newer workers, when kascov stored this transaction as a trade row: token_id, side, quote_sompi, base_amount, the market covenant's KAS and token balances before and after, and market_recognized. false means kascov does not read the program of the covenant the token moved through: the row is a movement against KAS that kascov never prices |
/data/{network}/addr/{address-or-pubkey}.jsonWhich smart coins has this address touched? Accepts kaspa:… / kaspatest:… or raw 32/33-byte pubkey hex. Matches p2pk covenant states and adds fail-soft KNS identity from the external KNS indexer.
{
"address": "kaspatest:qp9…hzy0",
"pubkey": "4b3402be…e9f2",
"kns": {
"status": "resolved", "source": "kns-indexer",
"primary_name": null, "display_name": "wallet.kas",
"display_name_source": "verified_owned_fallback",
"verified_names": ["wallet.kas", "other.kas"],
"verified_names_complete": true
},
"covenants": [
{ "covenant_id": "…", "name": "…", "role": "controls_now" }
]
}| Field | Type | Notes |
|---|---|---|
| kns.status | enum | resolved · not_found · partial · unavailable; KNS failure never fails the address response |
| kns.primary_name | string? | official KNS primary when configured; always preferred as the display name |
| kns.display_name | string? | primary, otherwise the shortest owner-matched verified name with a lexical tie-break |
| kns.verified_names[] | array | up to 100 names the KNS indexer marks verified and returns for this exact owner; external identity, not independently chain-replayed by kascov |
| covenants[].role | string | controls_now · states_seen |
| trades[].market_skeleton | string? | newer workers: the matched build of the covenant the row moved through, null when kascov does not read that program (market_recognized false). A row on a KaspaRocket build (KaspaRocket lp-amm …) is priced at what it executed, quote_sompi / base_amount: the sompi that entered or left the reserve per base unit, the platform fee left out. A row that carries display_decimals (KaspaRocket: 3) is priced per whole token at 10display_decimals times that pair, as KaspaRocket’s own app prices it |
/data/{network}/names/owners?addresses=The names a table shows beside its owners, for up to 200 addresses in one request. Answered from cache: an address kascov has not looked up yet comes back pending and is looked up in the background, so ask again a few seconds later. Explorer ownership, not payment resolution.
| Query | Type | Notes |
|---|---|---|
| addresses | string | required: comma-separated addresses on this network, at most 200 |
{
"network": "mainnet",
"owners": [
{ "address": "kaspa:qz…0hu2s", "display": "example.kaspa",
"primary": null, "pending": false,
"names": [ { "name": "example.kaspa", "source": "kaspaname", … } ] },
{ "address": "kaspa:qr…8ud", "display": null,
"primary": null, "pending": true, "names": [] }
],
"pending": 1,
"purpose": "explorer ownership; not payment resolution"
}| Field | Type | Notes |
|---|---|---|
| owners[].display | string? | what a table shows: the owner's KNS primary name if they set one, otherwise the alphabetically first name across every service, the same rule an address page heading uses |
| owners[].pending | bool | true while kascov is still looking the address up; ask again |
| pending | int | how many owners in this answer are still pending |
/data/{network}/names/address/{address}Every name one address owns across the name services kascov reads (.kas, .kaspa, .k), each with the covenant cell that holds it. Explorer ownership, not payment resolution.
{
"network": "mainnet",
"names": [
{ "name": "example.kaspa", "source": "kaspaname",
"address": "kaspa:qz…0hu2s",
"covenant_id": "7a88…952c",
"transaction_id": "caaa…fbb8", "output_index": 0,
"indexed_daa": 549212814,
"ownership_verification": "covenant-checked",
"namespace_verification": "provider-reported" }
],
"partial": false,
"purpose": "explorer ownership; not payment resolution"
}| Field | Type | Notes |
|---|---|---|
| names[].ownership_verification | string | covenant-checked when kascov found the cell holding the name unspent in its own current index, with the covenant and script the service reported; otherwise provider-reported. A name whose cell kascov sees spent or different is left out. |
| names[].namespace_verification | string | provider-reported: the spelling comes from the name service; registry-covenant-checked for .k names whose registry cell kascov checked too |
| names[].indexed_daa | int? | the index height the ownership was checked at |
| partial | bool | a name service did not answer or cut its list short, so there may be more names |
/data/{network}/names/resolve/{name}Who owns one name, with the covenant cell that holds it, in the same record shape as an address's names. Use it to show whose a name is; do not send funds on it alone. Explorer ownership, not payment resolution.
| Field | Type | Notes |
|---|---|---|
| names[].ownership_verification | string | covenant-checked when kascov found the cell holding the name unspent in its own current index, with the covenant and script the service reported; otherwise provider-reported. A name whose cell kascov sees spent or different is left out. |
| names[].namespace_verification | string | provider-reported: the spelling comes from the name service; registry-covenant-checked for .k names whose registry cell kascov checked too |
| names[].indexed_daa | int? | the index height the ownership was checked at |
| partial | bool | a name service did not answer or cut its list short, so there may be more names |
/data/{network}/digest.jsonThe last 24 hours in one object — births, moves, burns, value born, the busiest coin, the biggest birth, and how many coins are alive. Refreshes about once a minute. A vprog exit claim counts as a move, not a burn.
{
"births": 24762, "moves": 25948, "burns": 55074,
"value_born": 4831200000000, "alive": 6566,
"busiest": { "covenant_id": "…", "name": "curious-jade-zebra-6366", "events": 1185 },
"biggest_birth": { "covenant_id": "…", "name": "rapid-violet-seal-b788", "value": 937465200000000 }
}/data/{network}/templates.jsonWhat runs on this network — recognized script templates aggregated over every state UTXO. Unrecognized scripts are counted, never hidden.
{
"templates": [
{ "name": "p2sh commitment", "live_states": 6297, "live_value": 62970000000,
"ever_seen": 14459, "covenants": 5990, "revealed_runs": 3 }
],
"unrecognized": { "ever_seen": 12, "live_states": 4 }
}| Field | Type | Notes |
|---|---|---|
| revealed_runs | int | compiled contracts proven at spend time; a tx sweeping N states counts N |
/data/{network}/activity.json?range=24hThe histogram behind the activity chart — life-event counts per DAA bucket. Empty buckets are omitted; treat them as zeros. A vprog exit claim counts as a move, not a burn.
| Query | Type | Notes |
|---|---|---|
| range | enum | 1h · 6h · 24h · 48h · all (default 24h) |
{
"range": "24h", "bucket_daa": 6000,
"buckets": [ { "daa": 509540000, "births": 812, "moves": 903, "burns": 1740 } ]
}/data/{network}/families.jsonCovenant apps — clusters of coins that moved together in a transaction (multi-contract flows), union-found over shared txids. Each family lists its members with their recognized template.
{
"families": [
{ "size": 2, "members": [
{ "covenant_id": "09ef275e…", "template": "p2sh commitment", "shared_txs": 8 },
{ "covenant_id": "901be291…", "template": "p2sh commitment", "shared_txs": 8 }
] }
]
}/data/{network}/galaxy.json?fmt=2&tier=coreThe whole-network app graph behind the galaxy map — precomputed positions, weighted edges, and status for every smart coin. Built server-side and kept warm, so even a 168k-coin network answers instantly.
| Query | Type | Notes |
|---|---|---|
| fmt | enum | 2 — columnar arrays instead of one object per node (much smaller payload) |
| tier | enum | core — the big clusters only, for a fast first paint; fetch the full graph after |
Unknown params and unknown values degrade to the legacy full-object shape — old clients keep working unchanged.
/data/{network}/lanes.jsonBased-app traffic by namespace — KIP-21 lanes, 4-byte app tags, and the JSON-inscription bucket, sorted by event count. Printable tags get their ASCII label; the rest show as 0x….
{
"tip_daa": 509571904,
"lanes": [
{ "label": "kspr", "hex": "6b737072", "ascii": true,
"kind": "tag", "events": 40312, "covenants": 6120 },
{ "label": "JSON inscriptions", "hex": null, "ascii": false,
"kind": "inscription", "events": 8123, "covenants": 950 }
]
}| Field | Type | Notes |
|---|---|---|
| lanes[].kind | string | lane (KIP-21 namespace) · tag (4-byte app tag) · inscription (JSON payloads) |
| lanes[].hex | hex? | the raw 4-byte namespace; null for the JSON-inscription bucket |
/data/{network}/lane/{ns}One namespace's dashboard — headline counts, the 50 newest events, and an hourly activity series over the lane's whole life. {ns} is the 4-byte tag as exactly 8 hex characters — anything else is a 400.
curl -s https://kascov.io/data/testnet-10/lane/6b737072{
"namespace": "6b737072",
"events": 40312, "covenants": 6120,
"recent": [ { "covenant_id": "…", "txid": "…",
"accepting_daa": 509571333, "kind": "transition" } ],
"activity": [ { "daa": 509540000, "count": 812 } ],
"bucket_daa": 36000
}| Field | Type | Notes |
|---|---|---|
| bucket_daa | int | bucket width of the activity series — 36,000 DAA ≈ 1 hour |
/data/{network}/inscriptions.jsonThe JSON-inscription breakdown — events and distinct coins per decoded label (KRC-20 ops and friends).
{
"inscriptions": [
{ "label": "krc-20 mint", "events": 5123, "covenants": 640 }
]
}/data/{network}/tokens.json?limit=&after_daa=&after_id=&status=&phase=&kind=&q=The covenant-token directory behind the tokens page — every KCC20-shaped coin the indexer decoded, its ticker and fields read straight from the chain's bytes, plus a conservative validation verdict per token: status is verified only when every event in the token's history matched a known KCC20 rule and supply is conserved; anything unknown or ambiguous stays unvalidated with a reason — never a false "valid". The response explains itself in note — surface it to your users too. Older workers answer the pre-validation shape (status is the coin's active/burned liveness, no supply fields) — treat those tokens as unvalidated.
{
"note": "validated from chain — verified means every event matched …",
"tip_daa": 513875491, "tip_at_ms": 1783746272524,
"tokens": [
{ "covenant_id": "9cc4fb51…68f50", "name": "humble-crimson-magpie",
"template": "KCC20 token", "layout": "kcc20-46",
"fields": { "owner_identifier": "3f9a…c221", "identifier_type": "00",
"amount": "00752b7d00000000", "is_minter": "00" },
"live_value": 100000000,
"status": "verified",
"supply": 2100000000, "minted": 2100000000, "burned": 0,
"holders": 12,
"claimed_name": "Nacho", "claimed_ticker": "NACHO",
"metadata_source": "genesis_payload",
"last_activity_daa": 513875477 }
]
}| Field | Type | Notes |
|---|---|---|
| kind | query? | token · minter. A minter row is the legacy KCC20 minter template, a stateless two-token vault governing two legacy kcc20-46 tokens (so far seen only on testnet-10), and never a KCC-20 issuer. A KCC-20 layout token's issuers are on /token/{id}/minter; a cell's is_minter is the legacy isMinter byte, always false on KCC-20 cells |
| tokens[].layout | string | the state layout the cells were read by: kcc20-46 (the original 46-byte block most mainnet tokens use) · kcc0020-112 (the KCC-20 state layout). Switch on this, never on template |
| tokens[].template | string | a display name, and for a KCC-20 layout token it names the token, not only its build. A token of a reviewed build with no departure found from the KCC-20 Final rules kascov checks reads KCC-20 token only once its own status is verified; until then it reads reviewed KCC-20 build, this token is not proven yet (reviewed KCC-20 build when invalid). Other builds read KCC-20 state layout (not judged yet) or KCC-20 state layout, pre-Final draft build; older workers said KCC-0020 token |
| tokens[].fields | object | the decoded key/values exactly as the covenant wrote them — no registry, no labels kascov invented. kcc20-46: owner_identifier · identifier_type · amount (8-byte little-endian hex) · is_minter. kcc0020-112: amount · owner · owner_scheme · borrow_scheme · borrow_guard · extension_commitment, and on newer workers owner_scheme_name, the KCC-2 registry name (p2pk-schnorr/v1 · p2pkh-schnorr/v1 · p2pkh-ecdsa/v1 · p2sh/v1 · covenant-id/v1) |
| tokens[].classes | int? | how many extension classes the live cells span — present only when the token has them; cells of different classes never merge |
| tokens[].standard | object? | newer workers, KCC-20 layout tokens: kcc, spec_commit (the kccs commit kascov reads the build's rules by: 1ed87ae, the KCC-20 Final text, unchanged since 1516a6c, which older workers send; ea5176aa for a pre-Final draft build), template_hash, dispatch (kcc1-tags · silverc-selector · custom-tags), owner_schemes, view_template_hash? (the hash of the 112-byte record view, for a build whose state window is wider), review (reviewed · unreviewed), conformant and nonconformities. conformant is about the build, never about kascov or this token: true once a reviewed build shows no departure from the KCC-20 Final rules kascov checks (dispatch tags, hash-chain link, P2PKH hash, borrow and owner schemes, the BLAKE3 template hash), false with each departure listed in nonconformities, null while the build is unreviewed, or when its program artifact is unpublished. artifact is unpublished when the build's publisher released no program artifact (KCC-20 asks every covenant to publish one): kascov describes the build from its bytes, cannot check what the artifact would say, and gives no verdict either way, which lists no departure; the field is absent otherwise. Whether the token itself is proven is its status |
| tokens[].claimed_* | string? · int? | claimed_name · claimed_ticker · claimed_decimals · claimed_image · claimed_image_hash — what the deployer wrote on chain; a claim, never unique |
| tokens[].display_decimals | int? | newer workers: a venue's display convention, never a claim on chain. Present only when the token declared no decimals (no claimed_decimals; a claim always wins, 0 included) and kascov binds its market to a byte-matched build of a venue whose own app draws amounts at that many decimals, that build's own state naming this token. Today that is KaspaRocket (KaspaRocket lp-amm v1, and tn-d on testnet): 3, the rawPerToken 1000 its public protocol states, so 1,000,000,000 base units show as 1,000,000 tokens. A name, a ticker or an art host never binds it. Every integer on the row stays in base units: divide an amount by 10display_decimals to show whole tokens, multiply a sompi-per-base-unit price (quote_sompi / base_amount, spot_num_sompi / spot_den) by 10display_decimals for sompi per whole token (then divide by 108 for KAS), and never feed a scaled figure back as an amount |
| tokens[].display_decimals_source · display_decimals_note | string? | present exactly when display_decimals is: whose convention (venue:kasparocket, the venue at /data/{network}/venue/{slug}, whose venue.display_decimals says where it was read) and the sentence to show beside a scaled figure (3 decimals: KaspaRocket’s display convention, not declared on chain). The same three fields ride on /token/{id}'s token, venue launches, market and pool pages, address token_holdings[] and trades[] rows, the global trade feed's rows, a transaction's trade and token_actions[], and once at the top of /token/{id}/holders and /token/{id}/trades |
| tokens[].metadata_source | string? | where the claim was written: kcc23_declaration (a KCC-23 preparation transaction, newer workers) · genesis_payload (the birth transaction, older tokens) · market_genesis_payload · cell_program |
| tokens[].live_value | int? | sompi the coin holds right now |
| tokens[].status | string | verified · invalid · unvalidated — conservative: verified means every event in the history matched a known rule |
| tokens[].invalid_reason | string? | why the verdict isn't verified — present on invalid (and unvalidated) tokens |
| tokens[].supply | int? | live supply in raw token units — minted − burned |
| tokens[].minted / burned | int? | lifetime totals in raw token units |
| tokens[].*_str | string? | newer workers — supply_str, minted_str, burned_str, held_by_*_str: the exact decimal twin of each integer amount. JavaScript rounds integers above 253, so read the twin; the integers stay |
| tokens[].issuance_remaining · supply_cap | int? | newer workers, tokens with issuer covenants: what the live minters may still mint, summed over their proven allowances, and the supply that implies — absent unless every live minter's allowance is known |
| tokens[].holders | int? | owners with a non-zero balance right now |
/data/{network}/token/{id}/holders?limit=&after_balance=&after_owner=&after_class=/data/{network}/token/{id}/events?limit=&after_seq=&before_seq=&order=/data/{network}/token/{id}/trades?limit=&before_seq=/data/{network}/token/{id}/cells?owner=&limit=Dedicated stable pages for one token. Holder pages list owners with a non-zero balance, and holders_total counts their distinct owners. Holder cursors are the opaque balance/owner/class triple returned by the previous page (the class leg only on tokens with classes). A holder row is owner, owner_address?, balance (with balance_str on newer workers), cells, class?, and on KCC-20 layout tokens owner_scheme + owner_scheme_name. Event limits count whole events, so a multi-delta transfer is never split or skipped. Trades are newest first and publish only after the token validates and the market byte-matches an allowlisted build, has enough exercised trades, and passes invariant replay. On newer workers the holder and trade pages of a token a venue's display convention binds carry display_decimals, display_decimals_source and display_decimals_note once at the top (see the token directory); every balance and base_amount stays in base units.
Cells are the token's live cells with the exact program to reveal, served only when its blake2b equals the cell's committed hash; a cell kascov cannot prove is counted in omitted_unproven, never filled in. On newer workers a KCC-20 layout cell says how its state was proven in proven_by (an open set): issue, an issuer's mint or create rebuilt it; declared, its transaction's leader declared it; genesis_rebuild, a never-spent output of the token's genesis that the build's reviewed program, rebuilt with a state the genesis transaction itself determines, commits to, with no other candidate doing so. A genesis_rebuild cell also carries rebuilt_from: genesis_txid, sibling? (the proven output of the genesis whose owner tuple or amount the rebuild carries) and declarer? with declarer_covenant? (the output of another covenant the genesis spent, passing this build's template halves as arguments, whose number or id it carries). A genesis output the rebuild does not match stays unproven, as before.
/data/{network}/token/{id}/minterNewer workers. The live issuers of a KCC-20 layout token, the reference app's PublicMint and TokenSeed, each with the exact program to reveal to spend it. Issuers share the token's covenant id, so every live output of the family is already on the coin page; this route says which of them kascov can prove to be an issuer, and serves its bytes. Nothing is spliced: a program is served only when kascov proved it from chain bytes (the issue that created the output rebuilt it, an earlier output of the family revealed the very same bytes, or, for a never-spent output of the token's genesis, the build's reviewed seed rebuilt with the key and class the genesis's own proven outputs carry is the only candidate that opens it) and its blake2b equals the output's committed P2SH hash, checked again for every answer. Outputs kascov cannot prove are listed in dark with the reason, never filled in. The answer is a list, because a split leaves several issuers live, and kascov never picks one.
Every answer carries the standard object of the token's build, which describes the build and never this token, and the token's own status. A hash match proves which program an output commits to and what its state fields read. It does not prove that minting through it is safe, that the issuer enforces what its reference source says, or what the token's supply is, and while standard.review is unreviewed kascov has not reviewed the bytecode. PublicMint's mint and TokenSeed's create take no signature, so anyone can spend the same outpoint: treat it as advisory, and fetch again after a double-spend rejection. The continuation a mint creates must keep the minter's value_sompi.
{ "network": "testnet-10", "token_id": "…",
"generated_at_ms": …, "indexed_daa": …, "tip_daa": …, "tip_at_ms": …,
"standard": { "kcc": 20, "template_hash": "9703112e…3cf7",
"review": "reviewed", "conformant": true, … },
"status": "unvalidated",
"status_reason": "seq 0 (daa …): genesis cell …:1 dark until first spend",
"issuer_templates": [
{ "role": "mint", "name": "PublicMint", "template_hash": "14b0f06b…3b49",
"issue_entry": { "name": "mint", "select": "0fe04b09",
"args": "KCC20State,byte[],byte[]" } },
{ "role": "seed", "name": "TokenSeed", … } ],
"kcc20_template": { "prefix_hex": "6b", "suffix_hex": "6c76…",
"template_hash": "9703112e…3cf7" },
"issuers": [
{ "outpoint": "…:0", "value_sompi": …, "script_hex": "aa20…87",
"created_daa": …, "role": "mint", "name": "PublicMint",
"template_hash": "14b0f06b…3b49",
"program_hex": "6b20…", "program_hash": "…",
"proven_by": "identical_reveal", "proof_outpoint": "…:0",
"remaining": …, "remaining_str": "…",
"per_issue_limit": …, "per_issue_limit_str": "…",
"max_issue": …, "max_issue_str": "…", "mintable": true,
"extension_commitment": "…", "allowance_owner_pubkey": "…" } ],
"live_count": 1,
"dark": [ { "outpoint": "…:1", "value_sompi": …, "script_hex": "aa20…87",
"reason": "genesis cell …:1 dark until first spend" } ],
"provenance": "… it does not make minting through it safe. …" }| Field | Type | Notes |
|---|---|---|
| indexed_daa | int? | the DAA score of the last block the indexer applied: how current issuers and dark are. tip_daa is the node's tip, which runs ahead while the indexer catches up after a restart or a stall; an outpoint served then may already be spent. generated_at_ms is when the body was built, not how fresh the index is |
| issuers[] | array | mints first, then the largest max_issue, then the oldest, then by outpoint. role is the role the token's pin declares (mint · seed, an open set) and name the issuer's name in the pinned artifact; an issuer no committed pin declares is never named or served |
| issuers[].program_hex | string | the exact program to reveal: its blake2b is the hash inside script_hex, and program_hash is that hash |
| issuers[].proven_by | string | issue: rebuilt from the issuer the issue spent, its allowance reduced by the amount (mint) or unchanged (seed) · identical_reveal: byte-identical to a program an earlier output of the family revealed · genesis_rebuild: a never-spent output of the token's genesis that the build's reviewed seed, rebuilt with the owner key and class the genesis's own proven outputs carry, commits to, with no other candidate doing so (an open set). proof_outpoint is the output whose spend carries that proof; for genesis_rebuild, the proven output of the genesis whose key the rebuild took (proven by its own reveal or as an identical_reveal, so not necessarily ever spent) |
| issuers[].rebuilt_from | object? | newer workers, a genesis_rebuild issuer only: genesis_txid (the output's own transaction) and sibling, the proven output of it whose owner key the rebuild took (the same as proof_outpoint). provenance then says so in a sentence of its own |
| issuers[].remaining · per_issue_limit · max_issue | int? | a mint's allowance, its per-mint limit and the smaller of the two, each with its _str twin; absent on a seed. mintable is false once max_issue is 0; a live seed is always mintable, meaning it can create its zero-amount cell |
| issuers[].allowance_owner_pubkey | string? | the pubkey field of the issuer's state, as read: in the reviewed reference build, the key whose signature retiring this issuer (a reclaim) asks for. A mint asks no signature of it |
| issuer_templates[] | array | every issuer the token's pin declares, with the entry it issues through: select is the KCC-1 tag a reveal pushes before the program |
| kcc20_template | object | the KCC20 template halves a mint or create takes as its last two arguments, cut from a proven token cell of this family and hashed back to template_hash |
| dark[] | array | live family outputs this route does not serve, each with kascov's reason: a genesis output nobody has spent that no rebuild opens (its program is not on chain), an output of a spend kascov cannot read, the outputs of an allowance split until their own spend, an issuer that failed a serve-time check (another build, a commitment mismatch), the outputs of an issue that broke its own rules, or a token cell kascov could not prove |
| last_spent[] | array? | one entry per issuer role that nothing live in the family is or may be: no live issuer of that role naming this build, and no output in dark created by a spend of one (a split's outputs). The entry is that role's newest spent issuer output (outpoint, role) and the spent_txid that spent it. Absent when no role qualifies, so a live seed never hides a retired minter |
| 404 | answer | the id is no token, or a legacy kcc20-46 token, whose is_minter byte is on its cells. A KCC-20 layout token with no live issuer answers 200 with an empty list |
/data/{network}/trades?limit=&token_id=&market_id=&side=&before_daa=&before_token=&before_seq=/data/{network}/markets?limit=&after_id=&phase=&priced=/data/{network}/market/{id}/data/{network}/pools?limit=&after_id=&priced=/data/{network}/pool/{id}/data/{network}/token/{id}/marketVerified bonding curves and graduated pools, plus a global compound-cursor trade feed. Prices never become floats: use the exact quote_sompi and base_amount pair, which is sompi per base unit. A trade feed row or market token of a token a venue's display convention binds carries display_decimals (KaspaRocket: 3); multiply the pair by 10display_decimals for sompi per whole token, as that venue's own app prices it. Unmatched programs, failed invariants, and unverified tokens remain unpriced and cannot enter the trade feed.
/data/{network}/venues.json/data/{network}/venue/{slug}The launchpads and DEXes kascov names, from its public registry (web/venues.json): the roster with each venue’s families and counts, and one venue’s listing, its registry entry (venue), its families and one row per launch (launches[], sealed launches included), each joined to the token it holds when kascov’s token index has one. An unknown slug, or a venue not active on the network, is a 404; a former slug answers with the renamed venue.
| Field | Type | Notes |
|---|---|---|
| venue.display_decimals | object? | newer workers, only on a venue whose own app draws its tokens at a display convention (KaspaRocket today): decimals (3), note (the sentence a token row repeats as display_decimals_note) and source, the venue’s own public statement kascov read it from (its protocol’s rawPerToken 1000). A convention, never a claim on chain, and the venue object does not apply it: whether it binds a launch’s token is that token row’s own display_decimals (see the token directory) |
| launches[].token | object? | the token row the launch holds, without its market; null when kascov’s token index has none. A token the convention binds carries display_decimals, display_decimals_source and display_decimals_note, and its integers stay in base units |
/data/{network}/transfers?limit=&token_id=&owner=&kind=&before_daa=&before_token=&before_seq=The verified transfer feed: complete, newest-first ownership events across every token bound to a verified supported market. Events are classified by cell topology — transfer · split · merge — and each groups its proven output-cell deltas, so a multi-delta event is never split across pages. Raw delta amounts are absolute output-cell balances, served as exact decimal strings JavaScript cannot round; attribution removes same-owner change where the chain proves it, and multi-owner sources stay explicitly ambiguous rather than guessed. market_activity appears only when the event is an exact admitted trade row — the verified_market binding alone never claims an event was a trade.
{
"network": "mainnet", "order": "desc",
"scope": "verified_market_tokens",
"transfers": [ {
"token_id": "7a98370c…0544e4", "seq": 5463,
"kind": "transfer", "accepting_daa": 513875477, "txid": "…",
"verified_market": { "market_id": "10f2155e…726c4f",
"build": "KRON pool v1", "program_hash": "…" },
"attribution": { "status": "exact", "movements": [
{ "owner_from": "3f9a…c221", "owner_to": "b2b4…d4e1",
"amount": "48759" } ] },
"market_activity": { "kind": "trade", "side": "buy",
"market_id": "10f2155e…726c4f" },
"deltas": [ { "seq": 5463, "delta_idx": 0,
"token_kind": "transfer", "amount": "48759", "txid": "…" } ]
} ],
"next_before_daa": 513875476,
"next_before_token": "7a98370c…0544e4", "next_before_seq": 5462
}| Field | Type | Notes |
|---|---|---|
| limit | query | whole events per page — default 100, maximum 200 |
| token_id | query? | filter by exact token covenant id |
| owner | query? | match an indexed source or destination owner — bare 64-hex means a public key; script, covenant and presence owners need their explicit prefix. Multi-owner merge sources are unavailable by design |
| kind | query? | transfer · split · merge — the cell-topology classification |
| before_daa / before_token / before_seq | query? | the exclusive compound cursor — echo all three next_before_* fields together for the next page |
| transfers[].attribution | object | status is exact (change removed, movements proven) · self · ambiguous (with a reason); movements carry exact string amounts |
| transfers[].verified_market | object | the token’s current verified market binding — the reason the token is in scope, not a claim about this event |
| transfers[].market_activity | object? | present only for an exact admitted trade row: side is buy or sell |
| transfers[].deltas[] | array | the proven output-cell deltas; amount is that cell’s absolute balance, not automatically a transferred amount |
| scope | const | verified_market_tokens — only tokens whose market program passed the proof gate can enter the feed |
/data/{network}/vesting?limit=&after_id=/data/{network}/vesting/{token_or_lock_id}/data/{network}/vesting/{token_or_lock_id}/claimsCommitment-proven vesting schedules, states, and claim deltas. Registry data supplies only a candidate schedule; kascov publishes it only when the complete audited program reproduces the genesis lock's P2SH commitment. Continuations are independently matched to their output commitments. start_score and duration_score are DAA scores, not timestamps.
/openapi.json/data/{network}/index.jsonThe standard OpenAPI 3.1 contract and the smaller per-network discovery document. Both use kascov project identity only; there is no generator or agent branding in the contract.
/data/{network}/token/{covenant_id}One token's whole story behind its token page — the directory row, the top 100 balances, every classified event with amounts and owners, and the validation verdict. Same conservative contract as the directory: a token is verified only when every checked event matched a known KCC20 rule. 404 when the id isn't a token this worker knows (or the worker predates token pages).
{
"token": { "covenant_id": "9cc4fb51…68f50",
"name": "humble-crimson-magpie", "template": "KCC20 token",
"layout": "kcc20-46", "status": "verified",
"supply": 2100000000, "minted": 2100000000, "burned": 0,
"holders": 12, "last_activity_daa": 513875477 },
"balances": [ { "owner": "3f9a…c221", "balance": 1250000000 } ],
"events": [ { "txid": "…", "accepting_daa": 513875477,
"kind": "transition", "token_kind": "mint",
"amount": 1000000, "owner_to": "3f9a…c221" } ],
"validation": { "checked": 266 }
}| Field | Type | Notes |
|---|---|---|
| token | object | the same row the directory serves for this token |
| balances | array | the top 100 balances, largest first — balance is raw token units, owner is the owner's display form: bare 64-hex for a public key, otherwise prefixed. Legacy tokens use script: · covenant: · presence:; KCC-20 layout tokens on newer workers follow KCC-2: p2pkh: · p2pkh-ecdsa: (a key hash, no address) · script: (p2sh) · covenant: · custom-xx: |
| events[].token_kind | string | genesis · mint · transfer · split · merge · burn (an open set) — the canonical event object plus the token classification, amount, owner_from/owner_to and, on tokens with classes, class_from/class_to |
| events[].auth · borrow_proof | string? | present when someone other than the owner authorised the row: borrowed (the cell's borrow scheme) with borrow_proof proven (kascov reproduced every condition) or structural (every condition but a signature, which consensus checked); issuer on a mint is new supply from an issuer covenant: new supply. Where kascov could rebuild the issuer's continuation it also checked the issuer's own limits; a breach, or an issue it could not check, shows in the token's status |
| class_supply[] | array? | newer workers, KCC-20 layout tokens with a proven supply: class, supply, holders (owners with a non-zero balance in it) per extension class |
| token.kcc23 | object? | newer workers, when a KCC-23 declaration names the token: preparation_txid, output_index, binding (verified · unverified), reason?, declaration (the exact JSON, verified only), payload_hex (the preparation payload it came from: 00 and that JSON) and shared_with: how many other covenants bind the same declaration through that preparation transaction. Only a verified binding feeds claimed_* |
| trades[] · trades_total | array · int | the newest 100 stored trade rows, newest first, and how many admitted trades the trades page holds. Each row carries market_recognized on newer workers: false means the row moved through a covenant whose program kascov does not read, so it is a movement against KAS that kascov never prices, and the trades page leaves it out |
| validation | object | the verdict's working: checked events walked; reason says what stopped a verified verdict when the status isn't verified |
/data/{network}/lifespans.jsonHow long do smart coins live? A bucketed lifespan histogram over every retired coin, plus the median. Times derive from DAA at 10 per second, so median_ms is an estimate.
{
"buckets": [ { "label": "< 1 min", "count": 41230 } ],
"median_daa": 1840, "median_ms": 184000, "total": 83181
}/data/{network}/reorgs.jsonThe indexer's own reorg ledger — the last 500 virtual-chain reorgs it applied, newest first. Every entry is a rewind the index actually performed, not a heuristic.
{
"reorgs": [ { "daa": 509571904, "at_ms": 1751799000000, "rolled_back": 2 } ]
}/data/{network}/consistency.jsonAn automated cross-check between independent ecosystem indexers — kascov's decoded token set compared against another indexer's, token by token. Differences usually mean one of us has a bug; we fix ours and share theirs kindly. Served by newer workers; older ones 404.
{
"network": "mainnet", "checked_at_ms": 1783948200000,
"our_tip_daa": 513875477, "other_source": "…",
"other_blue_score": 513875470,
"tokens_ours": 270, "tokens_other": 268, "intersection": 265,
"agree": 261, "differ": 4,
"only_kascov": 5, "only_other": 3, "not_comparable": 0,
"details": [ { "token_id": "…", "field": "supply",
"ours": 2100000000, "theirs": 2099999000 } ],
"note": "tips differ — small disagreements near the tip are expected"
}| Field | Type | Notes |
|---|---|---|
| network · checked_at_ms | string · int | which network was compared, and when the check ran |
| our_tip_daa | int | kascov's chain tip at check time |
| other_source · other_blue_score | string · int | the other indexer's identity and its chain position — two indexers at different tips can honestly disagree |
| tokens_ours · tokens_other · intersection | int | how many tokens each side knows, and how many both do |
| agree · differ | int | of the intersection: tokens whose compared fields match, and tokens where they don't |
| only_kascov · only_other | int | tokens one side has indexed that the other hasn't (yet) |
| not_comparable | int | tokens skipped because the two sides expose incompatible shapes for them |
| details[] | array | the actual disagreements, per token — what we say, what they say |
| note | string | plain-language context for reading the numbers |
/data/{network}/search?q=&limit=Find a coin — by covenant-id hex prefix (4+ characters), friendly-name prefix, or template substring. Each hit says which way it matched.
| Query | Type | Notes |
|---|---|---|
| q | string | required, 1–64 characters; 400 otherwise |
| limit | int | max results, default 10, clamped |
curl -s 'https://kascov.io/data/testnet-10/search?q=mece'{
"query": "mece",
"results": [
{ "id": "b4ade48e…", "name": "brave-amber-lynx-b4ad",
"template": "SilverScript · Mecenas",
"status": "active", "matched": "template" }
]
}| Field | Type | Notes |
|---|---|---|
| results[].matched | string | id · name · template |
/data/{network}/debug/{txid}Replay a real on-chain covenant spend through the actual script engine, with the witness captured at index time — a concrete per-opcode trace of what really ran. The transaction context is fabricated, so signature/introspection checks may diverge from the original; the response says so in note.
{
"ok": true, "pass": true, "verdict": "script accepted",
"covenant_id": "…", "outpoint": { "txid": "…", "index": 0 },
"value": 500000000,
"trace": [ { "op": "OpBlake2b", "dstack": ["…"], "astack": [] } ],
"trace_truncated": false,
"note": "replayed with a fabricated tx context"
}| Field | Type | Notes |
|---|---|---|
| ok | bool | false when the txid spent no tracked state, or no witness was captured (reason says which) |
| trace_truncated | bool | the trace is capped at 2,000 opcodes |
/share/{network}/{covenant_id}The crawler-visible page for one coin (the SPA is hash-routed, so scrapers never see #/… urls) — OG/Twitter meta plus a redirect into the app for humans. Its picture is a server-rendered PNG card, and /sitemap.xml lists every share page so search engines find every coin. These live on the site root, not under /data.
GET /og/{network}/{covenant_id}.png
→ 1200×630 Open Graph card (name, avatar,
holdings, story), rendered on demandGET /sitemap.xml
→ every coin's /share/… url/data/{network}/simulateRun a hypothetical spend of a compiled contract through the real script engine, off-chain — the same PASS/FAIL a node would decide, with the violated rule in plain English on failure. Pure computation: the network segment is cosmetic, and responses are no-store.
{
"program_hex": "20d9a9…87", // ≤ 20,000 hex chars
"entrypoint": "spend", // spend · reclaim · cold · inherit
"recipient": "buyer", // buyer · seller · other · self
"value": 500000000, // the state coin, sompi
"amount": 499999000, // output 0; default value − 1000
"trace": true // capture per-opcode stacks
}{
"ok": true, "pass": false,
"verdict": "…", "rule": "output 0 must pay the buyer exactly value − 1000",
"template": "SilverScript · Escrow",
"trace": [ { "op": "OpDup", "dstack": ["…"], "astack": [] } ],
"note": "simulated with a stand-in signer"
}/data/{network}/preflightWill this transaction pass? Post a transaction as JSON (the SDK / RPC shape — {version, inputs, outputs, lockTime, …}) and get a verdict before you broadcast: per-input compute-budget checks (the classic “script units exceeded” trap), compute/storage mass against the block limit, an honest fee estimate — and for inputs that carry both their signatureScript and their utxo.scriptPublicKey, a real run through the script engine with the budget the transaction declares. Nothing is broadcast, nothing is stored: responses are no-store and the transaction goes into the call and nowhere else. The network segment picks the mass limits. Served by newer workers; older ones answer 404. Also behind the playground’s check-a-transaction mode.
{
"version": 1,
"inputs": [
{ "previousOutpoint": { "transactionId": "…", "index": 0 },
"signatureScript": "…", "sequence": 0,
"sigOpCount": 1, "computeBudget": 20,
"utxo": { "amount": 1000000000,
"scriptPublicKey": { "version": 0, "script": "…" } } }
],
"outputs": [ { "value": 999999000,
"scriptPublicKey": { "version": 0, "script": "…" } } ],
"lockTime": 0
}{
"ok": true, "verdict": "will_fail",
"findings": [
{ "severity": "error", "code": "compute_budget_missing",
"message": "input 0 is a v1 input with no computeBudget — its
effective allowance is 9,999 script units and one
CheckSig needs 100,000",
"input_index": 0, "suggestion": "set computeBudget: 10" }
],
"masses": { "compute": 12345, "storage": 0,
"transient": 512, "limit": 500000 },
"fee": { "estimate_sompi": 202400, "note": "an estimate, not consensus" },
"executed": [
{ "input_index": 0, "pass": false,
"script_units_used": 100000, "allowance": 9999 }
]
}| Field | Type | Notes |
|---|---|---|
| verdict | string | ready — every check passed · will_fail — a node would reject this as-is · incomplete — not enough of the transaction to decide |
| findings[] | array | each check’s answer: severity (error · warn · info), a stable machine-readable code, a plain-English message, the offending input_index?, and a concrete suggestion? (the fix, e.g. set computeBudget: 10) |
| masses | object? | compute / storage / transient against the network’s block limit; omitted when budget findings block the mass calculation |
| fee | object? | estimate_sompi + note — labeled an estimate honestly: no consensus min-fee primitive exists |
| executed[] | array? | per executable input: pass, script_units_used, allowance — the real engine, the transaction’s own declared budget |
/data/{network}/compileCompile SilverScript source server-side. Body is { source, args } — source up to 40,000 characters, at most 16 constructor args of ≤200 characters each.
{ "ok": true, "hex": "20d9a9…87" }
// or
{ "ok": false, "error": "compile error: …" }/data/{network}/publishVerify-and-publish, Etherscan-style: submit { source, args }; the worker compiles it and stores the source keyed by the compiled program's blake2b-256 hash. Any coin whose revealed program hashes the same now shows your source as community-verified.
{ "ok": true, "hash": "d9a9e982…f475d17",
"template": "SilverScript · Mecenas" }/data/{network}/verified/{hash}The published source for a program hash — what /publish stored. { "ok": false } when nothing is published for that hash.
{ "ok": true, "source": "contract Mecenas(…) { … }",
"args": "…", "template": "SilverScript · Mecenas",
"verified_at": 1752130000000 }/data/{network}/kcc23/proofBind a KCC-23 declaration kascov did not see in time: the preparation was accepted before kascov kept preparations, or a node pruned it first. Body is { covenant_id, preparation, genesis }, both transactions in kascov's transaction JSON (txid, version, inputs, outputs, hex payload, subnetwork_id, lock_time, gas; the OpenAPI Kcc23ProofRequest spells it out), at most 256 KiB. Nothing in it is trusted: the genesis must hash to the genesis txid this index recorded for the covenant, and the pair must pass the KCC-23 §3.3 verifier the follower runs. Only a verified binding is written, and a declaration over 16 KiB is refused.
{ "ok": true, "network": "testnet-10",
"covenant_id": "…", "outcome": "recorded",
"kcc23": { "preparation_txid": "…", "output_index": 0,
"binding": "verified", "declaration": "{\"name\":…}",
"payload_hex": "007b…", "shared_with": 0 } }| Answer | When | Notes |
|---|---|---|
| 200 | verified | outcome is recorded, or already when a verified binding was on record; kcc23 is the object token pages serve |
| 422 | not backed by the chain | error is the verifier's reason: no genesis on record, a genesis that does not hash to it, a preparation it does not spend, a covenant id that does not reproduce, an invalid declaration |
| 400 | malformed | the body is not { covenant_id, preparation, genesis } |
| 413 | too large | over 256 KiB |
| 429 | limited | the hourly tool allowance or the shared engine pool refused it |
| 503 | busy | the index was busy writing; nothing was recorded, so send the same proof again after Retry-After |
/data/{network}/subscribeRegister a webhook: your URL gets one JSON POST per matching covenant event, delivered as it happens. Filter to one coin with covenant_id (must be exactly 64 hex characters — anything else is rejected, never a silent wildcard) and/or one event kind; omit both for everything. The response hands you a generated secret — shown once, never readable back: every delivery is signed with it, and only someone holding it can unsubscribe the hook. Unsubscribe with POST /data/{network}/unsubscribe, the returned id, and that secret.
{
"url": "https://example.com/hook",
"covenant_id": "b4ade48e…", // optional, 64 hex
"kind": "burn" // optional: genesis · transition · burn
}
// → { "ok": true, "id": 7, "secret": "9f2ab4…" } // save it — shown once
// unsubscribe: { "id": 7, "secret": "9f2ab4…" }
// → { "ok": true, "deleted": true }POST your-url · application/json
X-Kascov-Signature: 3f9c41… (keyed with your subscription's secret)
{
"network": "testnet-10",
"covenant_id": "b4ade48e…",
"kind": "burn",
"txid": "1461f8…07ed",
"accepting_daa": 509571520,
"tx_index": 18
}| Semantics | Notes | |
|---|---|---|
| authenticity | every delivery carries X-Kascov-Signature: the hex keyed blake2b-256 of the exact request body, keyed with the secret /subscribe returned — recompute it over the raw bytes you received and compare before trusting the event | |
| delivery | up to 3 attempts with backoff, 5s timeout, redirects never followed; a subscription that keeps failing is retired automatically | |
| targets | public http(s) hosts only — every resolved address is checked, and loopback / private / link-local targets are rejected (SSRF guard) | |
| guarantees | best-effort hints, not a durable feed — confirm through the polled endpoints |
/data/{network}/deployOne-click deploy: the server births your compiled contract as a real covenant on testnet-10 using its own faucet key — no local toolchain. Gated off by default: the route exists only when the server is armed (KASCOV_DEPLOY_KEY) and the network is testnet-10; otherwise it answers 404. Never on mainnet.
{
"program_hex": "20d9a9…87", // ≤ 20,000 hex chars
"value": 1000000000 // 1–10 TKAS, in sompi
}{ "ok": true,
"covenant_id": "b4ade48e…",
"network": "testnet-10" }| Limit | Notes | |
|---|---|---|
| global | token bucket — 5 burst, refilling 1 per 10 minutes (≈144/day); throttled requests get 429 | |
| per IP | 20 deploys per day (best-effort) | |
| in flight | one deploy at a time — they share the funding wallet |
Served by an always-on indexer that follows the chain live. Every fact it publishes is re-derivable from the chain’s own revealed programs.
builder guide · testnet-10 · 15 minutes
Deploy → spend → replay a covenant.
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 was run for real. Every transaction id on this page links to a real, replayable on-chain spend, and the compile, the deploy and the replay run on this site as you read them. Nothing is hypothetical.
0Prerequisites — one funded address
You need a testnet-10 address funded at the faucet. Nothing else. The compiler, the deploy and the replay all run on this site, in your browser and on the kascov worker.
Any Kaspa wallet gives you a testnet-10 address and its x-only pubkey; the guided builder on the Build tab reads both straight from KasWare if you connect it. Two contracts in this guide ask for blake2b(pubkey) rather than the pubkey itself, and the builder computes that for you as you type.
Fund the address at faucet-testnet.kaspanet.io (it is Cloudflare-gated, so use a browser). Paste the address into the search box above to see it here.
About kascov’s own tooling: the CLI kascov uses to deploy and spend its own covenants is internal and is not published, so this guide never asks you to run it. Everything below is either a hosted call you can make from this page, or an honest note about the one step kascov does not do for you.
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 the script playground → “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. The guided builder does the same thing as a form, and can deploy it for you.
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 the playground>",
"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 hosted /compile endpoint takes the bare forms, so you never hand-write 32 nested byte objects here.
2Deploy it as a covenant
Two ways, both hosted. In the guided builder, press deploy on testnet-10: the kascov worker funds, signs and broadcasts it for you. The same thing as one call:
curl -s -X POST -H 'content-type: application/json' \
https://kascov.io/data/testnet-10/deploy \
-d '{"program_hex": "<the compiled hex>", "value": 1000000000}'
{"ok":true,"network":"testnet-10",
"covenant_id":"b4ade48e3ad1eda5…",
"txid":"a9a7df1a71307c05…"}The coin value is given in sompi and must be between 1 and 10 TKAS. The worker funds it from its own faucet-funded lab wallet, and the endpoint is rate-limited.
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 answer carries the covenant id, and
kascov.io/testnet-10/c/<id>?program=<hex>verifies your program hash against the on-chain commitment in the browser, so you can prove what you deployed before revealing it. The one-click deploy opens that page for you once the indexer catches the coin.
The coin appears on the explorer 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=9999Three 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, as kascov’s own spend tooling does it (that tooling is internal and not published; the mechanics are not):
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 commits 20 per input by default; 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. kascov 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 the hosted 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 — the one step kascov does not do for you
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.
Said plainly: spending a covenant means signing and broadcasting a transaction, and kascov does not sign or broadcast a spend. There is no hosted endpoint for it, and the tool kascov uses for its own spends is internal and not published. You build the spend with your own signing tooling. What this site gives you is every way to be sure it will pass before you broadcast it.
Two checks, both live here. /simulate runs a hypothetical spend of your compiled program through the real Kaspa script engine and answers with the rule it breaks, if any:
curl -s -X POST -H 'content-type: application/json' \
https://kascov.io/data/testnet-10/simulate \
-d '{"program_hex": "<the same hex>", "entrypoint": "receive",
"value": 1000000000, "trace": true}'
{"ok":true,"pass":true,"template":"SilverScript · Mecenas",
"verdict":"the spend satisfies the contract",…}Then, once you have built the real transaction, the preflight takes it as JSON and answers ready / will fail / incomplete, with every input’s compute budget checked against the step 3 trap above. Neither call broadcasts anything, and neither keeps your transaction.
Mechanics worth knowing when you build the spend:
- The witness reveals the program:
push(selector) ++ push(program)forreceive(no signature args); pure-signature entrypoints prependpush(pubkey) ++ push(sig). A program built by silverscript 1.0 pushes a 4-byte KCC-1 dispatch tag (blake3("receive()")[..4]) where the fork generation pushes a numeric selector; the Build tab states which generation compiled yours. 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 the
reclaimentrypoint: a pure-signature spend that works immediately, if your key’s blake2b is the coin’sfunderfield.
5Replay it right here
Open your coin’s page (kascov.io/testnet-10/c/<covenant-id>, or paste the txid into the search box above). 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 (API docs):
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 KCC-23 (MJ: Metadata JSON, a Draft in the KCC standards track): before the genesis, send a version-1 preparation transaction whose payload is one 0x00 byte followed by a UTF-8 JSON object, and let the genesis spend an output of it. The covenant id commits to that outpoint and the preparation’s transaction id commits to the payload, so the declaration is bound to the token for good and anyone can check it from the chain.
{"name": "My Token", "symbol": "MTK", "decimals": 8,
"image": "https://…/mtk.png",
"image_hash": "sha256 hex of the image bytes"}Rules kascov applies when reading it: strict I-JSON (no duplicate names, no byte-order mark, nesting at most 64 deep), name up to 48 characters, symbol (or ticker) up to 12, image as an https:// or ipfs:// URL, at least a name or a symbol, and declarations up to 16 KB. Invisible formatting characters are stripped before anything is shown. The token page reads KCC-23 · binding verified once kascov has recomputed the binding; one that does not reproduce is shown with its reason, and none of its fields are used. A declaration is a claim, not an identity: every output of one preparation transaction binds the same JSON, and the page says how many tokens share it. KCC-23 leaves field names to conventions; kascov reads name, symbol/ticker, decimals, image and image_hash.
The capture window: kascov keeps preparation transactions as the chain accepts them, and nodes forget them after about 30 hours. A preparation from before kascov began keeping them, or one a node pruned first, can still be bound: send the preparation and the genesis transaction to POST /data/{network}/kcc23/proof, and kascov checks the genesis against its own index and runs the same verifier on the pair. Older tokens that wrote the same JSON into the birth (genesis) transaction’s payload keep their names: kascov still reads that form, and every page says which of the two a name came from.
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 chain pins the hash 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.
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 through the reclaim entrypoint:
| 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.
the changelog
What kascov learned.
the milestones, newest first.