Gno and Tell / gno.land’s Tech

The Chain Is a Contract

Written by Joseph Toba for Tanjira Validator

Gno.land’s entire chain binary has four keepers. Its validator set, its governance, its fees and its IBC stack are not in there. They are smart contracts, and the chain reads them.

Opening

Gno.land has no staking module.

It has no governance module either. No distribution module, no slashing module, no IBC module. If you open the chain’s application code and count the keepers, the pieces of Go that own a chunk of consensus state, you find four.

  • params
  • auth
  • bank
  • vm

That is the entire chain. For comparison, a normal Cosmos SDK chain runs fifteen to twenty modules. Staking, slashing, distribution, governance, evidence, IBC, transfer, interchain accounts, feegrant, authz, each one compiled into the binary, each one requiring a coordinated network upgrade to change.

And yet gno.land has validators. It has governance. It has a name registry, a user registry, transaction fees, a fee collector, and a working IBC connection to another chain. All of the things those missing modules would provide.

So where are they?

The answer

They are contracts

Every one of them lives in userland, as a realm, which is what gno.land calls a stateful smart contract.

The validator set is gno.land/r/sys/validators/v3. Governance is gno.land/r/gov/dao. Chain parameters are gno.land/r/sys/params. Transaction fees are gno.land/r/sys/txfees. Rewards are gno.land/r/sys/rewards. The name registry is gno.land/r/sys/names. Users are gno.land/r/sys/users. Even the chain’s IBC implementation, the thing that lets it talk to other blockchains, is a contract: gno.land/r/aib/ibc/core.

None of these are modules pretending to be contracts. They are ordinary Gno code, deployed at ordinary package paths, callable by anyone, and readable by anyone as source. You can go and read the code that decides who validates this blockchain, in the same way you would read the code of a token or a game.

That is a genuinely unusual architecture, and the first time you notice it the obvious question is how it can possibly work. Consensus cannot ask a smart contract for permission to produce a block. The validator set has to exist before the VM does.

The answer is in how the two halves talk to each other, and it is worth walking through, because this is where the design stops being a curiosity and starts being clever.

Mechanism

How a contract tells consensus who validates

The validators realm documents its own mechanism at the top of the file, and the description is better than any paraphrase:

“The realm exposes a public proposal constructor for GovDAO; on approval, the proposal callback applies the captured deltas to the chain’s effective valset and publishes the new full set via gno.land/r/sys/params. The chain’s EndBlocker reads the result on the next block and propagates to consensus.”

examples/gno.land/r/sys/validators/v3/validators.gno

So the flow runs contract first, chain second. GovDAO, itself a contract, approves a proposal. The validators realm computes the resulting set and writes it into the params store. A dirty flag gets set. At the end of the next block, the chain’s EndBlocker notices the flag, reads the proposed set, parses it, and hands it to consensus as an ABCI validator update.

The Go binary does not decide anything here. It is a courier. The decision was made in a smart contract, and the chain’s job is to notice and comply.

There is a detail in the validators realm worth pausing on, because it shows how seriously the separation is taken:

// No in-realm validator state. All reads go through
// sysparams.GetValsetEffective (proposed-if-dirty, else current).

The contract does not even keep its own copy. There is one source of truth, in the params store, and both the contract and the chain read from it. No drift, no reconciliation, no two versions of who the validators are.

The same EndBlocker holds one more line worth knowing:

// Check if GovDAO has requested a halt at this height.

Governance, a contract, can set a halt height, and the chain will stop itself there, deterministically, on every node at once. Coordinating that on most chains means operators agreeing out of band and everyone running the same command at roughly the same time. Here it is one governance proposal. The chain does what its contracts tell it, including stopping.

Cryptography

A contract verifies another blockchain

The validator set is one contract making a decision the chain trusts. There is a harder case worth looking at, because it tests the same idea against the part of a blockchain everyone agrees should never be improvised: verifying that another chain is telling the truth.

Gno.land talks to other chains over IBC, and the realm doing it, gno.land/r/aib/ibc/core, imports a package called gno.land/p/aib/ibc/lightclient/tendermint. It is deployed and live on Pearl today, maintained separately from the core gnolang/gno repository, and its source is readable from gno.land’s own export of what is actually running on-chain. Open it and it is not a wrapper around someone else’s verifier. It is the verifier: trust-level thresholds, header expiry, clock-drift checks, adjacent and non-adjacent commit logic, and at the center of it, this line.

if !ed25519.Verify(val.PubKey, voteSignBytes, commitSig.Signature) {
    return ufmt.Errorf("invalid signature")
}

That is a real signature check, against a real validator’s public key, over data a real blockchain signed, written in Gno and calling Go’s own crypto/ed25519 standard library directly. On almost every chain in Cosmos, this exact logic is the one piece nobody would dream of putting in application code, because a bug here does not corrupt a token balance, it forges consensus. Gno.land put it in a package anyone can import.

And it is not the only bridge running. A second, independent stack called Union connects gno.land to Ethereum, and it does not speak Cosmos’ usual event format at all. Its packets are ABI-encoded, Ethereum’s own binary wire format, decoded by hand against a real transaction to confirm it: a gno-native asset on one side, an Ethereum address on the other, chain id 11155111, which is Sepolia’s actual EIP-155 identifier, not a placeholder. Wrapped Sepolia ETH and a bridged USDT have both moved across it as real, if small, testnet balances.

That stack went live on topaz, gno.land’s previous testnet, and was already live at genesis on the one after it, sapphire. On pearl, the current chain, it has not been redeployed yet. Given that every other piece of this ecosystem came back within days of the last three resets, that gap looks temporary rather than abandoned.

Rationale

Why put system functions in userland

Three reasons, and they compound.

You can read it. Gno.land publishes contract source, not bytecode. A deployed realm is stored as the code its author actually wrote. There is no compile step to trust, no verification service to believe, no “this bytecode allegedly came from that repository.” When the validator set logic lives in a realm, auditing it means reading it. Compare that to auditing a staking module, which means finding the right binary, checking it matches the tag, and trusting the build.

You can change it without a hard fork. A Go module lives in the binary. Changing it means a new release, a governance proposal, an upgrade height, and every validator restarting in a coordinated window. Changing a realm means deploying a new realm and pointing governance at it. The version suffixes in these paths, validators/v3, boards2/v1, gov/dao/v3, are not cosmetic. They are the upgrade history, visible on chain, with the old versions still sitting there readable.

You can fork it. Anything readable and deployable is also copyable. If you dislike how the name registry works, the code is right there and nothing stops you deploying your own. The system realms are only systemic because governance points at them, not because the chain privileges their code.

Reuse

Nobody writes their own token logic twice

Forking is what you reach for when you disagree with something and want your own version. There is a quieter, far more common kind of reuse sitting right next to it: importing someone else’s code as a dependency instead of writing your own, and gno.land’s ecosystem runs on it.

Almost every token on the chain, regardless of who deployed it, is built on one shared package: gno.land/p/demo/tokens/grc20. Its own documentation shows the entire interface in one line:

Token, ledger := grc20.NewToken("Foo", "FOO", 4, nextTokenID.Next(), cur)

That single call hands back a token and a private ledger, with minting, balances, transfers and allowances already implemented, tested, and unforgeable by construction, the token’s identity is bound to whichever realm created it, so nothing else can spoof it. Nobody importing it has to write that logic themselves, and nobody using their token has to take their word that they got it right.

What stands out is not that the package exists. It is who reaches for it. GnoSwap’s own governance token and its staked-voting token both import it, confirmed in the deployed source on three separate testnets in a row: Topaz, Sapphire, and Pearl. So does the aib IBC realm from earlier in this piece, the one doing raw Ed25519 signature verification against another blockchain’s validators. When it needs to represent a bridged voucher as a spendable token, it does not write its own token code either. It imports the same package everyone else does. Onbloc’s token utilities import it. Samcrew’s Memba marketplace imports it for payments. Independent contributor moul’s demo apps import it. A long tail of one-off tokens, deployed straight from ordinary wallet addresses with names like pearl_token and testtoken, import it too.

None of these teams coordinated with each other. Most of them have no reason to know the others exist. What they share is a package path, and that is enough. A dependency that gets pulled into a DEX’s governance token, a cross-chain bridge, a marketplace, and a stranger’s weekend project without any of them asking permission from the others is reuse in the sense that actually matters: not a right you could theoretically exercise, but one that quietly runs.

Art

A contract can draw its own picture

Render() returns markdown. Markdown can contain an image. The obvious question is where that image actually lives, and gno.land’s web frontend answers it with a policy narrow enough to be a real decision rather than an oversight, found directly in the code that enforces it:

AllowSvgDataImage is gnoweb’s image policy: every data: URI is rejected except image/svg+xml.”

gno.land/pkg/gnoweb/markdown/ext_imgvalidator.go

An ordinary link still works for anything, PNG on IPFS, a photo on a normal server. But a realm can also embed the entire image inline, as a self-contained data: URI, and gno.land’s renderer will display it with nothing external to fetch, nothing to pin, nothing that can go offline. It only works for one format: SVG. Every raster equivalent is explicitly blocked at the same check.

That is a narrower claim than “gno.land replaces IPFS,” and a more honest one. It replaces IPFS specifically for generative, vector art, which turns out to be most of what an on-chain NFT actually wants to be.

GnoSwap’s own NFT is the clearest live example. Every liquidity position on the exchange is represented by an NFT, the same idea as Uniswap’s position tokens on Ethereum, and its image is generated on demand rather than stored as a file:

// MUST BE IMMUTABLE, DO NOT MODIFY.
// SVG template structure: ...
return "data:image/svg+xml;base64," + sEnc

That comment is doing real work. The template is treated the way an audited constant is treated elsewhere in this piece, frozen on purpose, because it is rendered fresh on every view rather than written once and forgotten. The realm imports gno.land/p/gnoswap/deps/tokens/grc721 for the token standard itself, the same reuse pattern as the shared GRC20 package, just one door over. And it is not a one-off: the same import, the same generated-SVG pattern, is present in the deployed source on Topaz, Sapphire, and Pearl in a row.

It is not the only example, either. A name-service visualizer from an independent deployer has iterated through six versions, each with its own BuildSVG function, surviving the reset from Sapphire to Pearl intact. And there is a general-purpose SVG toolkit published as an ordinary importable package, Canvas, Circle, Rectangle, Path, composable the way any drawing library is composable, deployed the same way any other realm dependency is deployed. Whether other teams have started pulling it in yet, I could not confirm. That it exists as a plain package rather than a special chain feature is the point regardless.

Developer view

Why this is practical, not just elegant

There is a second thing that makes this work, and it is the part that surprised me most when I first read the documentation.

In Gno, state persistence is automatic.

“Realms are stateful, meaning they keep their state between transaction calls. In practice, global variables used in realms are automatically persisted after a transaction has been executed. Thanks to this, Gno developers do not need to bother with the intricacies of state management and persistence, like they do with other languages.”

docs/resources/realms.md

Declare a global variable. Change it in a function. It is still changed in the next transaction, next week, next year.

If you have written Solidity you have thought carefully about storage layout and slots. If you have written CosmWasm you have serialized structs in and out of a key-value store and paid attention to what that costs. In Gno you write a Go program and the state is just there.

That is why putting the validator set in a contract is reasonable rather than reckless. The contract is not a workaround around a hostile execution environment. It is a normal Go program whose variables happen to survive.

Two related distinctions worth knowing. Code under gno.land/p/ are pure packages: immutable once deployed, no state, safe to import. Code under gno.land/r/ are realms: stateful, callable, upgradeable by whoever governs them. Libraries and applications, separated at the path level, with the immutability guarantee enforced by the chain rather than by convention.

Economics

Storage is a loan, not a fee

“The state is just there” understates something. It is there because somebody is paying for it to be there, and gno.land keeps that payment separate from ordinary gas in a way worth being precise about.

Every operation burns gas, non-refundable, proportional to the bytes it touches, same as any chain. That is the cost of the chain doing the work. Storing new data triggers a second, independent charge on top of it: a deposit.

“Storing data → GNOT locked. Deleting data → GNOT refunded.”

docs/resources/storage-deposit.md

The rate is a governed parameter, not a constant buried in the binary: StoragePrice, currently 100ugnot per byte, so roughly 1 GNOT buys 10KB. Write data and that much GNOT locks into an address the chain derives deterministically from the realm’s own path. Delete the same data later and the deposit flows back out of that address. Gas is gone the moment you spend it. A deposit is a loan the chain is holding, not a fee it has kept.

The detail that makes this more than bookkeeping: deposits are tracked per realm, not per user. Whoever deletes unused data collects the refund, regardless of who originally paid it in. That turns cleanup into an incentive rather than a chore, a standing bounty for anyone willing to go find bloated, abandoned state and clear it out. Nobody has to be nagged into housekeeping. Somebody just has to notice it pays.

And StoragePrice itself is set the same way this whole piece keeps finding things get decided here: not hardcoded, governed. Change what storage costs on gno.land and you are proposing it the same way you would propose changing who validates.

Evidence

What it looks like in practice

I run an indexer for this chain, so rather than argue in the abstract, here is what the current testnet actually shows.

Pearl, the latest gno.land testnet, launched on the 27th of August as a completely fresh chain. Nothing carried over: zero balances, no deployed contracts, every registered name released. Nine days later:

224,053 Blocks
19,596 Transactions
298 Realms deployed
~2,000 Distinct callers

Inside that, the system realms are not ornaments. The validator realm, gno.land/r/sys/validators/v3, has been called 195 times by 87 distinct addresses, with the validator operator registry at r/gnops/valopers showing an almost identical 87. That is eighty-seven separate parties interacting with the validator set the way you would interact with any other contract. On a normal Cosmos chain, becoming a validator is a MsgCreateValidator to a module. Here it is a function call to code you can read.

The user registry has 230 callers, the name registry 228. The IBC core realm has real traffic on it. Governance is live at r/gov/dao with a member store and an implementation realm beside it, each carrying its own version suffix, each one a contract somebody can inspect.

And the ecosystem around them rebuilt itself from nothing in nine days, which it has now done four times this summer as gno.land tore down and replaced its testnet: test-13, then topaz, then sapphire, now pearl. Every reset, the contracts come back within days. That happens because redeploying a realm is just publishing code again.

The bet

What gno.land is actually claiming

There is a philosophical claim buried in this architecture, and I think it is the most interesting thing about the project.

Most blockchains draw a hard line between the chain and the applications on it. The chain is privileged, written in a systems language, changed by coordinated upgrade, audited by specialists. Applications sit on top in a sandbox, restricted, metered, and treated as untrusted by default.

Gno.land erases most of that line. The application language is good enough to write the system in, so the system is written in it. Validators, governance, naming, fees and interchain communication are not special. They are just the contracts that governance happens to point at, running in the same VM, readable by the same tools, upgradeable by the same process as a tic-tac-toe game deployed by a stranger.

That is a bet that a smart contract platform can be good enough to build a blockchain out of. It is a much bigger claim than “we support smart contracts,” and gno.land is one of very few chains actually making it.

The four keepers are the proof. Params, auth, bank, VM. Everything else is code you can read.