Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

v0 source realm

Package zones is a curated registry of gno.land networks: mainnet, the testnets, staging chains, anybody's gnodev. Ea...

Readme View source

zones

A curated registry of gno.land networks and the endpoints that reach them: mainnet, the testnets, staging chains, anybody's gnodev. Anybody proposes a zone; a curator approves, rejects or retires it, and verifies or flags its endpoints. The latest decision on each is recorded with who made it and when, and a rejection, a retirement, a flag or an edit to an approved zone must also say why, on the zone's page. Nothing is hidden while it waits: proposals and unverified endpoints are listed, and labelled.

The model, validation and state machine are p/moul/zones. This realm owns only who may write, and the pages.

It ships with four zones

All approved at deploy, every address probed on 2026-10-01. Peers are the persistent_peers each network's VALIDATOR.md publishes in the gnolang/gno monorepo.

slug chain id endpoints
mainnet gnoland-1 rpc, gnoweb, 2 peers, indexer, explorer, all verified
onyx onyx-1 rpc, gnoweb, 2 peers, indexer, faucet, explorer, all verified
staging staging rpc, gnoweb, flagged: neither answered when seeded, and the flag says what each returned
moul-staging moulstaging-1 rpc, gnoweb, faucet, all verified

Read it from a node

The reads take plain values, so they work from gnokey query vm/qeval and from another realm alike. An empty kind or status matches anything; a slug is required wherever one is asked for.

1gnokey query vm/qeval -remote https://rpc.gno.land \
2  -data 'gno.land/r/moul/zones/v0.ListAddresses("onyx", "peer", "verified")'
function answers
ListZones(status, kind) the zones, in the order they were proposed; approved is the official list
GetZone(slug) one zone, and whether it exists
ListEndpoints(slug, kind, status) one zone's endpoints, oldest first
ListAddresses(slug, kind, status) the same, reduced to the address strings a config file wants
GetEndpoint(id) one endpoint, and whether it exists
IsCurator(addr), Curators() who curates
IsInvited(addr), Invited() who has an open invitation to curate

Turning these into a node's config.toml is deliberately not this realm's job. It is public (gnomod.toml says why) so that a separate realm can import it and do that. Two things an importer must know: a slice these return is read-only in the importing realm, elements included (copy it before sorting or changing it; a struct returned on its own is already the caller's; a slice the importer keeps in its own state stays this realm's object, so copy it before storing it too), and a kind or status the p/moul/zones Parse functions do not accept, or a blank slug, panics, which no recover in the caller catches, so check one taken from a query string with them first.

Write to it

function who
ProposeZone(slug, chainID, title, description, kind, gnowebURL, rpcURL, genesisURL) anybody; a curator also when the review queue is full, and into the registry's last 16 places
EditZone(slug, revision, chainID, ..., reason) a curator, or the proposer while pending. Before review the reason must be empty; on an approved zone it is required and replaces the review on record. Every edit bumps the zone's revision, and a new chain id sends its verified endpoints back to unverified. Leaving local drops every endpoint on a private host, at most 64 in one edit (more, remove some first), and is refused while one carries a curator's ruling (a curator removes it first; a verified one its registrant may withdraw); the proposer's edit only drops endpoints that are theirs and that they could withdraw on their own. A rejected or retired zone is not editable
ApproveZone(slug, revision, reason) a curator
RejectZone(slug, revision, reason), RetireZone(slug, revision, reason) a curator, reason required; the zone's verified endpoints go back to unverified. Rejecting a rejected zone, or retiring a retired one, with a new reason restates it; so does approving an approved one
RemoveZone(slug, revision) a curator, on a pending or rejected zone; or its proposer, on a pending one, 100 blocks after it was proposed or last edited, while every endpoint on it is theirs and one they could withdraw on its own (below). One that was ever official is not removable; a retired one is kept until 128 newer retirements push it out
RegisterEndpoint(slug, kind, addr, label) anybody on an approved zone; on a pending one, its proposer or a curator. The zone's own main RPC under rpc and gnoweb under gnoweb only a curator lists, or the proposer while the zone is pending (every proposer right ends at approval), since the page marks those URLs by that listing's verdict. An edit cannot make a stranger's listing the zone's own either: when it changes the main RPC or gnoweb to a URL a stranger (on an approved zone, the proposer too) lists under that kind, on a curator's edit the listing is dropped if nobody ruled on it (so pre-listing a zone's next URL cannot hold its move off) and the edit is refused if a curator did; a proposer's edit refuses rather than drop what is not theirs, and a flagged listing refuses the edit whoever holds it. A curator also registers past the review queue and the 16-per-address cap, and into the last 16 of the zone's 128 places, which a registration through the queue may not take
VerifyEndpoint(id, zoneRevision, revision, reason) a curator, naming the endpoint's revision (the endpoint table's column) and the zone's (the zone page's), both as read
FlagEndpoint(id, revision, reason), UnverifyEndpoint(id, revision, reason) a curator, naming the endpoint's revision as read. A flag needs a reason; any verdict given again with a new reason restates it
ClearUnreviewed(slug, throughRevision) a curator: removes, in one call, every endpoint on the zone that nobody ruled on and that was registered through the review queue, not past it by a curator (never more than the 64 the queue holds), up to the revision named, on every page and every kind (the link on the zone's unfiltered page carries the revision it was rendered at, so nothing registered after is touched). For a flood that withdraws and registers again faster than one removal at a time
RemoveEndpoint(id, revision) a curator; or its registrant, on a pending or approved zone, 100 blocks after registering it, unless a curator flagged or unverified it (a reset, which a chain-id edit, a rejection or a retirement makes, leaves the withdrawal a verified one had). Naming the revision means a removal fails if a verdict landed since. A verified endpoint its registrant may take down; a flagged or curator-unverified one is a warning, and everything on a rejected or retired zone is a record: only a curator removes those
AddCurator(addr) a curator; it is an invitation, at most 16 curators and invitations together
AcceptCurator() the invited address, to take up the invitation
RemoveCurator(addr) a curator; it withdraws an invitation, or removes a curator along with every invitation they sent. The last curator cannot be removed

Every decision on a zone (edit, approve, reject, retire, remove) takes its revision, the one you read (the zone page shows it, and its action links carry it): if the zone changed since, its content or its status, the call fails and you read it again. A verdict on an endpoint, and its removal, take the endpoint's own revision (the endpoint table's column), so they fail if another curator ruled on it since; a verification also takes the zone's. The two are named apart: one number combined from two reads could pair a stale value with a fresh one. The $help links fill a revision in; an edit, a verdict, a restated decision and an endpoint's removal have no link, so copy them from the zone page. A zone's revision covers its own fields and status, not its endpoints' verdicts. A proposer edits or withdraws a pending zone only 100 blocks (ReviewWindow) after it was proposed or last edited, by anybody, and a registrant withdraws an endpoint only 100 blocks after registering it: a rate bound (100 blocks is minutes), so neither can change an entry every block.

Curators are equals: any one may remove any other, the admin included. That is the trust a curator set is, and it is why there are few of them. A removed curator keeps what they registered, a listing of a zone's own URL included, until a curator removes it.

A storage deposit is refunded to whoever signs the transaction that frees it (on a chain with transfers locked, it goes to the storage fee collector). That is why a proposer cannot remove a zone carrying somebody else's endpoints, and why a curator's removal collects what the remover did not pay.

Pages

Every list is 25 rows a page (?page=), and a page reads the records it shows plus a bounded handful of lookups (each row's main RPC verdict, the flags on a zone's own URLs, the zone serving the current chain for the printed command, the curators), never a whole list: vm/qrender is gas-metered, and a Render that outgrows it stops answering. Paths are exact, up to slashes at either end; anything else is Not found.

path shows
(root) the official zones; ?status=retired for the retired ones
zone/<slug> one zone: its facts and revision, its last review, its endpoints; ?kind=peer narrows them
proposals pending proposals; ?status=rejected for rejected ones, with the reason

An approved zone's gnoweb and genesis URLs are links; any other zone's show as code, to copy and check. A main RPC or gnoweb URL the zone also lists, under that same kind, as a flagged endpoint is code and marked wherever it is shown, and the printed gnokey line leaves out a main RPC so marked. Only that kind's flag counts: a listing under another kind is anybody's to make, so its flag never marks the zone's own URL, and under its own kind only a curator lists it (or the proposer, while pending). RPCs and endpoint addresses are always code. An action link is shown only when the call could pass the caps, and where a cap is full a note says which cap is full; a full review queue still takes a curator's call, so its link stays, labelled for curators, and a zone with endpoints awaiting review offers curators, on its unfiltered page and only while something is clearable, a Clear link that says how many it clears and that it reaches all pages and kinds, bound to the revision the page was rendered at. Where a Rejection or a Retirement would evict the oldest record of its state, the page says so beside it. Addresses are shown in full everywhere, never shortened: an 8+4 shortening is within reach of a vanity grinder who wants to look like a curator. Free text has @ and bare g1 addresses neutralised, so a label cannot turn into a profile link.


Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.

On mainnet: deployment status transactions unique callers deployed revision

Dependency graph:

gno.land/r/moul/zones/v0 dependency graph

⚠️ Disclaimer: provided as-is, without warranty; not security-audited. Full disclaimer: DISCLAIMER.

Overview

Package zones is a curated registry of gno.land networks: mainnet, the testnets, staging chains, anybody's gnodev. Each zone carries what a node or a wallet needs to join it (chain id, RPC, gnoweb, genesis) plus a growing list of endpoints: RPCs, seeds and peers, indexers, faucets, explorers.

Curation is the point. Anybody may propose a zone and register endpoints on an approved one (on a pending one, its proposer or a curator; a zone's own main RPC and gnoweb, only them); a curator approves or rejects a proposal, retires a zone that stopped running, and verifies or flags an endpoint. The latest decision on each is recorded with who made it and when, and a rejection, a retirement, a flag or an edit to an approved zone must also say why, on the zone's page. Nothing is hidden while it waits: proposals and unverified endpoints are listed, and labelled.

This realm only manages the information and answers questions about it. The read helpers (GetZone, ListZones, GetEndpoint, ListEndpoints, ListAddresses, IsCurator, Curators, IsInvited, Invited) take plain values so they work from `gnokey query vm/qeval` as well as from another realm, and turning them into a node's config.toml is a separate realm's job.

The model, its validation and its state machine are p/moul/zones. This realm owns only who may write.

Constants 3

const MaxCurators

1const MaxCurators = 16
source

MaxCurators bounds curators and open invitations together. The curator list renders on the index page, and only curators can grow it, but a bound costs nothing and an unbounded list on a public page is a page someone can break.

const PageSize

1const PageSize = 25
source

PageSize is how many rows any table here shows at once. Every list is paginated and every page reads only its own records, because vm/qrender is gas-metered (3B per query) and a Render that outgrows it stops answering instead of slowing down. At the caps a page reads its 25 records, one lookup per URL it may mark flagged (each row's main RPC on the index; the zone's RPC and gnoweb on its page), the zone serving this chain for the printed command, the curators, and a few index nodes. Escaping free text is what dominates: about 97k gas a character on a node, so a page of full-length fields costs on the order of a billion, still under the ceiling.

const ReviewWindow

1const ReviewWindow = 100
source

ReviewWindow is how many blocks must pass before the author of a change may change it again, a rate bound rather than a review deadline (100 blocks is minutes): after a zone was proposed or last edited (by anybody) before its proposer may edit or withdraw it, and after an endpoint was registered before its registrant may withdraw it. Every edit bumps the revision a curator's decision must name, and a withdrawal plus a fresh proposal or registration does the same with a new revision or id, so without a wait a proposer or a registrant acting every block would keep their entry out of every curator's reach. Curators are not limited.

Functions 23

func AcceptCurator

crossing Action
1func AcceptCurator(cur realm)
source

AcceptCurator makes the caller a curator, if a curator invited it.

func AddCurator

crossing Action
1func AddCurator(cur realm, addr address)
source

AddCurator invites another address to curate. Only a curator may. The address becomes a curator when it calls AcceptCurator itself.

Two steps, deliberately: a curator set is the one thing a mistake here can lose for good. With a one-step add, a sole curator who invites a mistyped address and then steps down leaves a registry nobody controls; accepting is the proof that somebody holds the key. An invitation dies with its inviter: removing a curator withdraws every invitation they sent.

func ApproveZone

crossing Action
1func ApproveZone(cur realm, slug string, revision int64, reason string)
source

ApproveZone makes a zone official. revision is the zone's Revision as you read it (the zone page's Approve link carries it): if the zone changed since, its content or its status, the approval fails and you read it again. The reason is optional.

func ClearUnreviewed

crossing Action
1func ClearUnreviewed(cur realm, slug string, throughRevision int64) int
source

ClearUnreviewed removes, in one call, every endpoint on a zone that is zones.Endpoint.Clearable (nobody ruled on it, and no reviewer registered it past the caps), and returns how many. There are never more than zones.MaxUnverifiedPerZone: clearable endpoints are a part of the review queue its gate holds there, and no reset makes one. It is the answer to a flood that cycles: registrants who withdraw and register again each review window keep the queue full, and one removal per transaction lets them refill it before a curator is done. throughRevision bounds it to what the curator read: an endpoint registered after it (a later Revision) is kept. The deposits go to the curator who signs. At most zones.MaxEndpointsPerZone are read.

func EditZone

crossing Action
1func EditZone(cur realm, slug string, revision int64, chainID, title, description, kind, gnowebURL, rpcURL, genesisURL, reason string)
source

EditZone replaces a zone's Info, every field but its slug and status. A curator may edit any pending or approved zone; the proposer may edit their own while it is pending. revision is the zone's Revision the edit was written against: it fails if the zone changed in between, its content or its status. Every edit bumps the revision, so a decision prepared against the old one fails too. A proposer edits only ReviewWindow blocks after the zone was proposed or last edited, and an edit that changes nothing is refused. On a pending zone the reason must be empty; on an approved one it is required and replaces the review on record, so the page names who changed the values, and why. A new chain id sends the zone's verified endpoints back to unverified. Leaving the local kind drops every endpoint on a private host, and is refused while one carries a curator's ruling (a verified one its registrant may withdraw first); the proposer's edit only drops endpoints that are theirs and that they could withdraw on their own (RemoveEndpoint), as for RemoveZone. A new main RPC or gnoweb a stranger (on an approved zone, its proposer too) already lists under its kind would make that listing the zone's own: on a curator's edit it is dropped if nobody ruled on it, and refuses the edit if a curator did; a proposer's edit refuses rather than drop what is not theirs; and a flagged listing refuses the edit whoever holds it. The registry validates the edit before it drops or resets anything, and a refusal reverts the edit with it. A rejected or retired zone cannot be edited.

func FlagEndpoint

crossing Action
1func FlagEndpoint(cur realm, id, revision int64, reason string)
source

FlagEndpoint tells readers not to use an endpoint. The reason is required. revision is the endpoint's, as for VerifyEndpoint; a flag is not bound to the zone, so editing the zone cannot hold off a warning.

func GetEndpoint

Action
1func GetEndpoint(id int64) (zones.Endpoint, bool)
source

GetEndpoint returns the endpoint with that id, and whether there is one.

func GetZone

Action
1func GetZone(slug string) (zones.Zone, bool)
source

GetZone returns the zone under slug, and whether there is one.

func IsInvited

Action
1func IsInvited(addr address) bool
source

IsInvited reports whether addr holds a curator invitation it has not accepted yet.

func ListAddresses

Action
1func ListAddresses(slug, kind, status string) []string
source

ListAddresses is ListEndpoints reduced to the addresses, which is what a config file wants: ListAddresses("onyx", "peer", "verified").

func ListEndpoints

Action
1func ListEndpoints(slug, kind, status string) []zones.Endpoint
source

ListEndpoints returns a zone's endpoints of that kind and verification, oldest first. "" matches any kind or verdict, so ("onyx", "", "") is everything on onyx and ("onyx", "peer", "verified") is what a cautious node should dial. The zone is required: one zone is at most MaxEndpointsPerZone rows, every zone together is a response no node should be asked for.

func ListZones

Action
1func ListZones(status, kind string) []zones.Zone
source

ListZones returns the zones with that status and kind, in the order they were proposed. "" matches any; "approved" is the official list.

func ProposeZone

crossing Action
1func ProposeZone(cur realm, slug, chainID, title, description, kind, gnowebURL, rpcURL, genesisURL string)
source

ProposeZone files a new zone for review. Anybody may; it is listed as a proposal until a curator approves or rejects it. The review queue's caps (zones.MaxPending, zones.MaxPendingPerProposer) do not stop a curator, who also has the registry's last zones.ReservedForReviewers places, so neither a flood nor a full registry locks out the people who clear it.

kind is mainnet, testnet, devnet or local. gnowebURL and genesisURL may be empty; rpcURL may not, and is <scheme>://<host>[:<port>] with no path, which is what gnokey -remote takes.

func RegisterEndpoint

crossing Action
1func RegisterEndpoint(cur realm, slug, kind, addr, label string) int64
source

RegisterEndpoint lists an endpoint on a zone and returns its id. On an approved zone anybody may, except the zone's own main RPC under rpc and gnoweb under gnoweb, which only a curator lists (or the proposer while the zone is pending: every proposer right ends at approval); on a pending one only the proposer or a curator, so a stranger cannot pin a proposal the proposer then cannot withdraw. It shows as unverified until a curator checks it. A curator also registers past the review queue's caps and into the zones.ReservedForReviewers places a gated registration may not take, never past zones.MaxEndpointsPerZone.

kind is rpc, gnoweb, seed, peer, indexer, faucet or explorer. addr is a URL, or <node id>@<host>:<port> for a seed or a peer. label is optional: who runs it, in your words.

func RejectZone

crossing Action
1func RejectZone(cur realm, slug string, revision int64, reason string)
source

RejectZone turns a proposal down. The reason is required, and public. revision is the zone's Revision you read, as for ApproveZone.

func RemoveCurator

crossing Action
1func RemoveCurator(cur realm, addr address)
source

RemoveCurator revokes a curator, along with every invitation they sent, or withdraws one invitation. Only a curator may, and the last curator cannot be removed: a registry nobody can curate can never retire a dead zone.

Curators are equals: any one may remove any other, the admin included. That is the trust a curator set is, and it is why there are few of them.

func RemoveEndpoint

crossing Action
1func RemoveEndpoint(cur realm, id, revision int64)
source

RemoveEndpoint deletes an endpoint. revision is its revision as read: the removal fails if a verdict landed since, rather than delete one nobody saw. A curator may remove any. Its registrant may remove it unless a curator flagged or unverified it (a flag is a warning, an unverify carries why, and removing and registering it again would wipe either; a verified one the registrant may take down, since listing it again starts it unverified, and so one a reset sent back to unverified, since the reset says the zone changed, not it), only while its zone is pending or approved (a rejected or retired zone is a record, endpoints and all), and only ReviewWindow blocks after registering it (removing and registering again every block would give it a new id faster than a curator could flag the old one).

func RemoveZone

crossing Action
1func RemoveZone(cur realm, slug string, revision int64)
source

RemoveZone deletes a pending or rejected zone and its endpoints. revision is the zone's Revision you read: a removal meant for one proposal fails on another proposed again under the same slug. A curator may remove any pending or rejected zone. The proposer may withdraw their own only while it is pending, ReviewWindow blocks after it was proposed or last edited (so withdrawing and proposing again cannot dodge the edit wait), while every endpoint on it is theirs (the deposit is refunded to whoever signs the removal, so removing somebody else's endpoints would collect what they paid) and each one is one they could withdraw on its own (RemoveEndpoint): removing the zone must not wipe a curator's flag or unverify, or skip an endpoint's own wait. A rejected zone is the curators' record: only a curator removes it, or newer rejections push it out, and the rejection's deposit, paid by the curator, is not the proposer's to collect. A zone that was ever official is never removed this way: an approved one is retired, and a retired one is kept until newer retirements push it out.

func Render

1func Render(path string) string
source

Render is the whole public surface, three pages: the zone list (official, or retired with ?status=retired), one zone (?kind= narrows its endpoints), and the proposals (pending, or rejected with ?status=rejected). Every list takes ?page=.

func RetireZone

crossing Action
1func RetireZone(cur realm, slug string, revision int64, reason string)
source

RetireZone marks an official zone as no longer running, and sends its verified endpoints back to unverified. The reason is required: it is what an operator still holding the chain id will read. revision is the zone's Revision you read.

func UnverifyEndpoint

crossing Action
1func UnverifyEndpoint(cur realm, id, revision int64, reason string)
source

UnverifyEndpoint puts an endpoint back to unverified, for one that changed hands or needs checking again. revision is as for FlagEndpoint.

func VerifyEndpoint

crossing Action
1func VerifyEndpoint(cur realm, id, zoneRevision, revision int64, reason string)
source

VerifyEndpoint marks an endpoint as checked against its zone. revision is the endpoint's revision as read (the endpoint table's revision column), and zoneRevision the zone's (the zone page shows it): the verdict fails if either changed since, another curator's verdict on the endpoint, or any edit or status change of the zone (what is verified is that it answers for this zone's chain id). The reason is optional. Each verdict may be given again with a new reason, to restate it.

Imports 8

Source Files 8