Onchain Graveyard · Pre-submission evidence
Before committing to a Catalyst proposal, we set ourselves a hard gate: prove that an in-house, CIP-113-conformant non-transferable certificate actually works on a Cardano ledger — minting, refusing transfer, rising in tier for the same owner, and refusing a second certificate on a plot that already has one. This is what the gate produced.
Onchain Graveyard sells one thing: a permanent, personal memorial that cannot be resold. That single product promise is also the hardest technical constraint in the proposal, and it is worth stating plainly why.
On Cardano's eUTxO ledger, a minting policy alone cannot make a token non-transferable. A minting policy runs when tokens are minted or burned — it does not run when an owner moves a native token that is already sitting in an ordinary wallet address. Any design that says "mint it straight to the buyer's wallet and it will be soulbound" is mistaken, and we corrected exactly that mistake in our own earlier draft.
Non-transferability therefore requires the token to live at a spend validator that forces a continuing output on every spend. And because we refuse to be custodial, that validator must be shared custody: the owner holds the spending key, the operator holds no key at all over the owner's certificate. That is the core of what CIP-113 describes, and it is the part we had to prove we can build and run before asking anyone to fund it.
An earlier, custodial design of ours failed its own internal audit on precisely this point. So the gate below is not a formality — a negative result would have meant not submitting.
Two Aiken validators, roughly 350 lines together, written for this gate and open in the
repository at onchain/validators/.
| Validator | Shape | What it enforces |
|---|---|---|
memorial |
multi-purpose: one script hash serves both mint and spend |
The certificate is minted into its own validator, so the policy id and the holding address are the same hash. Every later spend must return a continuing output at that same address with an identical value and an unchanged owner. There is no operator parameter and no branch that reads an operator signature. There is no burn branch. |
plot_ticket |
multi-purpose: mint + spend, one hash | One beacon token per plot, seeded by the operator. Minting a certificate requires burning that plot's beacon in the same transaction, so a plot can be engraved exactly once, ever — structurally, not by a uniqueness check in a database. Unused tickets are reclaimable, so the min-ADA the operator fronts is working capital, not a sunk cost. |
The bench is a Hydra Head running in offline mode. A Hydra Head is isomorphic with Cardano layer 1: the same Conway ledger rules, the same Plutus V3 cost model, the same min-UTxO arithmetic. What differs is that transactions settle in milliseconds and no node, faucet or block wait is involved. Every "accepted" or "rejected" below is therefore a verdict of the real ledger rules, applied to a real, signed transaction.
Each gate is one transaction. The valid case and its attack variant are produced by the same builder with one parameter changed, so a passing gate cannot be an artefact of two differently written transactions.
Two verification layers back each other up: the Aiken unit tests (23 on these two validators, 54 across the package) name which condition in the validator fires, while the Head gives the ledger's verdict on a fully signed transaction built through the same SDK path the production service will use.
What the bench cannot do. An offline Head charges no fees, never rolls back, and is not signed by a real CIP-30 wallet. Fees in §6 are therefore computed from measured size and execution units using layer-1 protocol parameters, not observed. This gate is a necessary condition, not a substitute for the Preview/preprod run and the independent audit listed in §8.
Thirteen signed transactions. Two had to settle; eleven had to be refused. All thirteen behaved as required.
| ID | Scenario | Required | Blocked at | Result |
|---|---|---|---|---|
| Minting a certificate | ||||
| G1 | Owner signs, ticket is consumed, base price paid, token lands in the validator | must settle | — | ✓ settled |
| G5 | Mint the token straight to the owner's ordinary wallet | must be refused | ledger | ✓ refused |
| G6 | Mint while underpaying the base price by 1 lovelace | must be refused | ledger | ✓ refused |
| G7 | A third party mints a memorial in someone else's name | must be refused | ledger | ✓ refused |
| Non-transferability | ||||
| G2a | Move the token out to a buyer's wallet | must be refused | ledger | ✓ refused |
| G2b | Keep the token in the validator but rewrite the owner in the datum | must be refused | ledger | ✓ refused |
| G2c | Drain ADA out of the certificate's UTxO | must be refused | ledger | ✓ refused |
| G2d | A third party upgrades someone else's memorial | must be refused | ledger | ✓ refused |
| Same-owner tier rise | ||||
| G3 | Earth → Stone, same owner, exact price difference paid | must settle | — | ✓ settled |
| G2e | Rise a tier while underpaying the difference | must be refused | ledger | ✓ refused |
| G2f | Jump two tiers in one transaction | must be refused | ledger | ✓ refused |
| One certificate per plot | ||||
| G4a | Mint a second certificate on an occupied plot, without a ticket | must be refused | ledger | ✓ refused |
| G4b | Mint a second certificate by burning the already-consumed ticket | must be refused | unbalanceable | ✓ refused |
A Head does not report how much a script actually consumed, so we did not estimate it. For each script slot we lowered that one redeemer's execution budget — leaving the others untouched — and binary-searched down to the exact point where the ledger starts refusing the transaction. That threshold is the real consumption. Min-ADA was found the same way, by walking the certificate's UTxO down until the ledger's minimum-value rule rejects it.
Roughly 150 transactions go into those searches. A failing submission changes no state, so the same target can be probed repeatedly; only a successful probe consumes a plot and needs a fresh one.
| Transaction | Size | Memory | Steps | Fee on L1 |
|---|---|---|---|---|
| Engrave — burn ticket, mint certificate, pay treasury | 4,773 B · 29.1% | 394,529 · 2.4% | 140,624,999 · 1.4% | ~0.398 ADA |
| Tier rise — one step, same owner | 3,245 B · 19.8% | 257,812 · 1.6% | 102,539,062 · 1.0% | ~0.320 ADA |
| Script slot | Memory | Steps |
|---|---|---|
| Spend the plot ticket | 128,906 | 49,072,265 |
| Mint the certificate | 229,491 | 80,932,617 |
| Burn the plot ticket beacon | 36,132 | 10,620,117 |
| UTxO | Lovelace | ADA |
|---|---|---|
| Certificate — longest allowed inscription (24 B name, 12 B years, 6 × 32 B lines) | 2,384,032 | 2.384 |
| Certificate — typical inscription (two lines) | 1,534,423 | 1.534 |
| Plot ticket — fronted by the operator, reclaimable | 1,142,577 | 1.143 |
What a player actually pays at the cheapest tier is the service price (1 ADA) plus the locked min-ADA plus the network fee: ≈ 2.93 ADA for a typical inscription and ≈ 3.78 ADA for the longest one. All three components must be shown on the payment screen before signing.
What the operator fronts to open the pilot's 1,500 plots is ≈ 1,714 ADA of reclaimable working capital — our own money, not a Catalyst budget line.
The point of measuring is to be corrected. Three planning assumptions in our own proposal did not survive contact with the ledger, and we would rather publish that than carry it into a budget.
| Assumption | We had written | Measured | Consequence |
|---|---|---|---|
| Min-ADA locked per certificate | ~1.5 ADA | 1.534 – 2.384 ADA | Inscription length is now a pricing decision, not only a UX one; the payment screen must quote the real locked amount for the text actually written. |
| Real cost at the cheapest tier | ~2.7 ADA | 2.93 – 3.78 ADA | Friction at the signing step is higher than modelled; the draft-to-certificate conversion target has to be measured against this number, not the old one. |
| Working capital for 1,500 plot tickets | ~1,740 ADA | ~1,714 ADA | Cheaper than planned; the reclaimable capital line goes down. |
One optimisation is already visible in the numbers: both transactions currently carry the full validator inline. Publishing the two validators as reference scripts should bring the engrave transaction down to roughly 1–1.5 KB, trading size for the reference-script fee. That is month-1 work, deliberately not done in this proof.
The harness builds the validators, starts a throwaway Head in Docker, runs all thirteen gates, performs the measurement searches and writes both a report and its raw data — including the full ledger error for every refused transaction. Deterministic keys mean a rerun produces the same addresses and script hashes.
# from the repository root pnpm --filter @hydra-graveyard/engine phase0 # gates + measurements (~2 min) pnpm --filter @hydra-graveyard/engine phase0 -- --keep # leave the Head running to inspect cd onchain && aiken check # 54 on-chain unit tests
| Item | Value |
|---|---|
| Certificate policy / holding script | 6740f9591225a45d2f6292cadce833aceb9af0cb5f7cc777207dcf53 |
| Plot ticket policy / holding script | 598a60a1c20e9e29847e097871966fef67a2211247ffefed58306dd1 |
| Aiken | v1.1.21, Plutus V3, stdlib v2.1.0 |
| hydra-node | 2.3.0 (arm64 image), offline mode, protocol version 11 |
| Layer-1 fee parameters used |
txFeePerByte 44, txFeeFixed 155381,
priceMemory 0.0577, priceSteps 0.0000721,
utxoCostPerByte 4310
|