Verification
What is proved, by what, and — the part that usually goes missing — what is not.
The chain is the authority
Facts on this site come in three strengths, and they are labelled wherever they appear rather than blended together.
| chain-attested | Read from gnoland-1 and onyx-1 and proved by verify_catalog.py: deploy transactions, heights, deployers, gas, storage, and the digest of the deployed bytes. Each chain is read separately and proved against separately; a deployment is never credited with a sibling chain's evidence. |
|---|---|
| chain-derived | Produced by the node from the deployed bytes via vm/qdoc: every signature, type and doc comment in the API sections. The node derives it from the code, so it cannot disagree with the code. |
| curated | Written by people: summaries, categories, roles and the limitation lists. Useful, and not attested by anything. The manifest says so itself, in its own about field. |
The tools
Each of these has its own test suite, and each is run before its verdict is quoted. A checker that only ever reports success is worse than no checker, so the suites include deliberate sabotage runs that confirm the tests actually bite. Each one below states what it proves and over which chains; open a row for how.
tools/verify_catalog.py
Proves the manifest against the chain, in both directions.
Reads every chain in the manifest, separately: gnoland-1, onyx-1.
Why that holds
Every entry must exist on chain with matching bytes, and every package in the namespace must appear in the manifest. An undocumented deploy and a phantom record both fail it.
tools/gen_api_docs.py
Generates the API reference from the node's own vm/qdoc.
Documents each package from exactly ONE chain. Across the reference that is gnoland-1 and onyx-1.
Why that holds
--check re-derives the reference to detect drift between the committed pages and the chain. The pages are keyed by package name, which two chains' output would silently overwrite — hence one chain per page. Where a package is deployed on more than one chain, its page names the chain it was read from and states what makes that one API answer for the others: equal bytes, or a difference the deploying namespace fully accounts for, with each chain's own value printed for any exported constant the namespace reaches.
tools/verify_depclosure.py
Proves that a package compiled against the dependency bytes the chain actually holds.
Reads every chain in the manifest, separately: gnoland-1, onyx-1.
Why that holds
Byte-verifying a root package proves nothing about its imports, and dependency fetches resolve to mainnet by default whatever the deploy target is. Read the coverage line it prints, not just its verdict: a run that silently skipped a package would otherwise report the portfolio clean.
tools/gen_site.py
Builds this site, offline, from the artifacts the three tools above produce.
Reads no chain at all. It renders gnoland-1, onyx-1, and refuses to build if the manifest describes a chain it does not render.
Why that holds
It has no network access, so it cannot introduce a fact none of those three checked. Every figure on these pages is computed from their artifacts at build time rather than typed into a page, so a number cannot go stale without the build changing with it.
Source against chain
51 of 52 deployments have committed source that is byte-identical to what that chain runs. The unit is the deployment, not the package: 52 deployments across 43 packages, and a package deployed to more than one chain was checked once per chain, against each node separately.
The other 1 is recorded rather than hidden. A package's bytes are immutable once deployed, so where the two differ the live version stays as it was, and the committed file is not edited to match it — that would be the comparison deleting itself. Each one below names its cause and where the account is kept; open a row for that account in full.
BYTE-MATCH FAILED, and the divergence is 10 bytes in two '//' comment lines.
Unintended divergence on onyx-1 testnet, recorded in a document this build withholds.
The record in full
Same length (38,013 B both sides): offsets 3677-3683 and 3685-3687, lines 71-72 of the package doc comment's LapseDispute trust bullet, where the inline gno_addpkg emitter re-wrapped the word 'who' across a line break -- a word and a space trading places, which is why the length is preserved. The committed file is NOT edited to match the chain: that would be the comparison deleting itself. The divergence is proven INERT by running the chain's own bytes in a standalone gnowork workspace against the committed test file: 25 --- PASS / 0 --- FAIL, gno lint silent, gofmt -l clean, and the full 75-line test+gas signature IDENTICAL to the committed bytes' (a RenderLimit 20->19 negative control moves it, so the comparison is sensitive). CAUSE: gno_addpkg takes inline files[].body strings and no path, so the emitted string is the real subject; the two-emission gas-reference control agreed to the byte because MsgAddPackage gas is dominated by stored byte count and this edit moved zero bytes -- a gas-reference control is structurally blind to any constant-length edit. Realm bytes are immutable after addpkg, so this is permanent.
What this site does not prove
Stating the boundary is the point of having one.
This site is generated offline from committed artifacts.
It shows what the gates concluded when they last ran; it does not re-run them, and it is not a live view of the chain. For that, follow any of the gnoweb links.
The curated fields — summaries, categories, limitation lists — are human-authored and unattested.
They are marked curated everywhere they appear.
An empty limitations list means no caveat was recorded, not that none exists.
The deployment records are the fuller account and each carries an explicit "what was not verified live" section.
No third party has audited any of this code.
Where the records say a package was audited, they mean an adversarial review conducted inside this project, against a committed catalogue of Gno-specific bug classes, with the findings and the refusals written down. Read the findings rather than the word.
Dependency-closure equivalence covers packages fetched from the chain.
Standard-library packages execute from the node binary and are outside that check; the residual is stated in the records.
The chains do not carry the same thing.
gno.land mainnet carries 7 reusable
/p/packages and 11 application/r/realms; onyx-1 testnet carries 13 reusable/p/packages and 21 application/r/realms. A onyx-1 deployment is not a durable artifact in the way a mainnet one is: testnet state can be reset by its operators. And the namespace is part of the import path, so a path on one chain does not resolve on another — they are not interchangeable and none is a mirror of another.A shared name is not always the same program.
1 of the 9 packages deployed on more than one chain —
budgets— are deliberately different code on each chain, declared and authorized in the repository rather than discovered. A deployed realm's bytes are immutable, so a correction made after one chain already holds it can only ever land on the other; where that has happened this site says so on the package page instead of presenting one artifact as though it described both. The build still requires the exported function set to be identical on every chain before it will share a page, and refuses the build outright for any byte difference that is neither a namespace nor declared.A shared name is not always shared bytes.
2 of the 8 packages deployed on more than one chain —
crowdfund,subscriptions— have a different digest on each chain. The deploying namespace is part of every gno.land path, so a package that names a path under its own namespace is a different file on each chain by construction. Each page prints both digests and the command that checks each against its own node. The 6 that are byte-identical on every chain (coinio,duebook,feeledger,journal,spendbook,spendbook/v2) have no first-party dependency at all, which is why they can be. The build will not merge two deployments onto one page unless substituting each chain's own namespace out of its committed source makes the two byte-equal and no differing line declares an exported symbol; anything else is a real code difference and fails the build.The chains do not attest the same fields.
gno.land mainnet and onyx-1 testnet emit no
StorageDepositEventwhen a package is created, so stored size and storage deposit are simply absent per package there rather than zero; the cost is stated in that chain's deployment record instead, which is a written claim and not a chain-attested one. That qualifier is not a hedge. It was added after the unqualified sentence was measured false: an ordinary call does emit the event on every live chain where that has been measured — onyx-1 on 2026-10-13 and gnoland-1 on 2026-10-17, the latter over 45 call transactions of which six emitted one — so the absence is a fact about package creation and never about the chain.
Reproduce the whole thing
From a checkout of the project repository, every claim on this site re-derives with four commands:
/usr/bin/python3 tools/verify_catalog.py
/usr/bin/python3 tools/gen_api_docs.py --check
sh tools/portfolio_depclosure.sh
/usr/bin/python3 tools/gen_site.pyThe first three talk to the chains — see the coverage note on each in the table above, which is not the same for all three. The fourth talks to nothing, and rebuilds this site. The repository is not currently public, so this path is open to the maintainers rather than to every reader — which is exactly why the checks that need no repository come first on this page: the byte-digest command, and every gnoweb and RPC link on this site, work for anyone.
The written record
Behind every verdict on this page sits a committed document: deployment records with transactions, costs and live-verification transcripts; discovery records behind each decision to build or not to build; audit findings and their fixes; the standards the work is held to.
Those documents are not published on this site. They live in the project repository, which is not public. Everything on this site that can be checked without them — the byte digests, the transaction hashes, the heights, and every gnoweb and RPC link — is checkable by anyone, which is why those checks come first above.