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 pure

Package mygnoscan builds links into a mygnoscan block explorer.

Readme View source

gno.land/p/moul/mygnoscan

Builds links into a mygnoscan block explorer, from inside a realm.

A realm knows things its reader cannot see: which chain it is on, what its own address is, which block it was last written at. Turning any of those into a link means knowing the explorer's route table, and a route table copied into thirty realms is thirty places to fix when a route moves. This is the one place.

 1s := mygnoscan.Default()
 2
 3s.Realm("gno.land/r/moul/home")                 // .../realm/r/moul/home?network=mainnet
 4s.Realm("r/moul/home", mygnoscan.TabSource)     // ...&tab=source
 5s.RealmFunc("r/moul/config", "Set")             // source tab, at one function
 6s.Address(addr)                                 // an account
 7s.Block(runtime.ChainHeight())                  // the block we are in
 8s.Tx(hash)                                      // one transaction, base64 hash
 9s.Proposal(7)                                   // one GovDAO proposal
10s.Page(mygnoscan.PageGas)                       // a list page
11
12s.RealmFooter("gno.land/r/moul/home")           // the markdown line for a Render

Default() is DefaultBase (moul's instance) on whichever network answers for the running chain. New(base) points somewhere else, WithNetwork(id) overrides the network. Nothing here reads chain state beyond ChainDomain, ChainID and ChainHeight, and nothing writes: building a link is free.

For a realm that wants the target to be changeable without a redeploy, read the base from r/moul/config instead of calling Default(): config.Scanner() returns exactly this type, configured.

The routes are measured

Every path was read out of the explorer's own router (route() in its single-page frontend) on 2026-09-22, not inferred from clicking the UI. Two realm tabs that the UI still redirects are deliberately absent because they no longer exist: ?tab=graph (folded into deps) and ?tab=inert (folded into the default tab, and dropped from the URL).

Three things that will bite otherwise

  • An unknown path renders the home page. The explorer's router falls through to home rather than to a 404, so a misspelled route is not visible as an error: the link works and goes somewhere else. That is why the page names are constants and Page is the only door to them.
  • Omitting ?network= is not the same as asking for mainnet. The explorer then answers for every chain it indexes at once, which looks plausible and is wrong. A Scanner on a chain NetworkFor does not know emits no network parameter, so New under gnodev (chain-id dev) produces links to the blend. WithNetwork is how you fix that.
  • The network ids belong to the instance, not to the chain. DefaultBase names mainnet mainnet; the upstream default configuration names the same chain gnoland1. NetworkFor maps chain-ids to what DefaultBase serves, so pointing New at another instance usually means setting WithNetwork too.

Everything here can end up inside a markdown link, so a path carrying ) would close the link early and render the rest as page text. url.PathEscape does not help: ( and ) are legal URL sub-delims and it leaves both alone.

So the two shapes are handled differently, and neither trusts its input:

  • Package paths and page names are refused, not escaped, because escaping their separators would break the route. Realm, RealmFunc, RealmFile, RealmFooter and Page fall back to a list page for anything outside [A-Za-z0-9._/-] or with an empty segment.
  • Hashes, token keys and addresses are escaped with encodeURIComponent semantics, which is what the explorer's decodeURIComponent round-trips. address is a string type and nothing stops a realm casting a caller's input into one, so it gets the same treatment.

Link(text, target) does not escape its text. Pass a constant. A label a caller typed belongs in ui.Inline first.

CurrentRealm and CurrentRealmFooter stack-walk (unsafe.CurrentRealm), so inside a helper borrowed by another realm they name the borrower. That is the right answer for a link and the wrong one for authorisation: never branch on them. A realm calling through a shared config realm should pass its own path explicitly, because the stack seen from in there has the config realm on it.


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

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

Overview

Package mygnoscan builds links into a mygnoscan block explorer.

A realm knows things a reader cannot see from its Render output: which chain it is on, what its address is, which block it was last written at. Turning any of those into a link means knowing the explorer's route table, and a route table hand-written into thirty realms is thirty places to fix when it moves. This package is the one place.

The routes are measured, not guessed

Every path below was read out of the explorer's own router (its single-page frontend, `route()`), not inferred from the UI, on 2026-09-22. Two of them are retired and deliberately absent: `?tab=graph` (folded into `deps`, still redirected) and `?tab=inert` (folded into the default tab, silently dropped).

An unknown path renders the home page

The explorer's router falls through to `home` rather than to a 404, so a misspelled route is not visible as an error: the link works, it just goes somewhere else. That is why the page names here are constants and why Page is the only door to them.

The network is per instance, not per chain

The explorer takes `?network=<id>`, and the ids are chosen by whoever runs the instance: moul's names mainnet `mainnet`, while the upstream default config names the same chain `gnoland1`. NetworkFor maps a chain-id to the ids DefaultBase serves. Point Scanner at another instance and you very likely also want WithNetwork.

Omitting the parameter is not the same as asking for mainnet: the explorer then blends every chain it indexes, which looks plausible and is wrong. A Scanner whose network is empty emits no parameter, so New on a chain nobody indexes produces a link to the blend. Set it explicitly when it matters.

Usage

The whole point is a realm footer that stays right when the chain changes:

Example
1func Render(path string) string {
2	return body + "\n\n---\n\n" + mygnoscan.Default().CurrentRealmFooter()
3}

and, for a realm that wants to hand out sharper links:

Example
1s := mygnoscan.Default()
2s.Address(someAddr)                              // an account
3s.Block(runtime.ChainHeight())                   // the block we are at
4s.Realm("gno.land/r/moul/home", mygnoscan.TabSource)
5s.RealmFunc("gno.land/r/moul/config", "Set")     // straight at one function

Nothing here reads chain state except ChainDomain, ChainID and ChainHeight, and nothing here writes. Building a link is free.

Constants 4

const TabInfo, TabDocs, TabSource, TabCalls, TabEvents, TabStorage, TabDefi, TabDeps

 1const (
 2	TabInfo    = "info"    // the default; passing it emits no parameter
 3	TabDocs    = "docs"    // exported symbols
 4	TabSource  = "source"  // the .gno files as deployed
 5	TabCalls   = "calls"   // transactions that called this realm
 6	TabEvents  = "events"  // events it emitted
 7	TabStorage = "storage" // what it pays to store
 8	TabDefi    = "defi"    // balances and token positions
 9	TabDeps    = "deps"    // imports, dependents and the graph over them
10)
source

The realm-page tabs, as the explorer's own tabNames list has them. An unknown tab name lands on the default tab rather than erroring, so these exist to keep a typo out of a deployed realm.

const PageRealms, PagePackages, PageContracts, PageApps, PageTxs, PageBlocks, PageAccounts, PageDefi, PageCoins, PageGRC20, PageValidators, PageGovDAO, PageProposals, PageVoters, PageOptions, PageParams, PageEvents, PageGas, PageGasRealms, PageGasUsers, PageGasTxs, PageStorage, PageAnalytics, PageDashboards, PageSanity, PageWatch

 1const (
 2	PageRealms     = "realms"
 3	PagePackages   = "packages"
 4	PageContracts  = "contracts"
 5	PageApps       = "apps"
 6	PageTxs        = "txs"
 7	PageBlocks     = "blocks"
 8	PageAccounts   = "accounts"
 9	PageDefi       = "defi"
10	PageCoins      = "coins"
11	PageGRC20      = "grc20"
12	PageValidators = "validators"
13	PageGovDAO     = "govdao"
14	PageProposals  = "govdao/proposals"
15	PageVoters     = "govdao/voters"
16	PageOptions    = "govdao/options"
17	PageParams     = "params"
18	PageEvents     = "events"
19	PageGas        = "gas"
20	PageGasRealms  = "gas/realms"
21	PageGasUsers   = "gas/users"
22	PageGasTxs     = "gas/txs"
23	PageStorage    = "storage"
24	PageAnalytics  = "analytics"
25	PageDashboards = "dashboards"
26	PageSanity     = "sanity"
27	PageWatch      = "watch"
28)
source

The explorer's list pages. Page takes one of these.

const PackagesAll, PackagesInert, AccountsActivity, AccountsBalances

1const (
2	PackagesAll      = "all"      // every package
3	PackagesInert    = "inert"    // the parked queue: submitted, not yet approved
4	AccountsActivity = "activity" // ranked by what they have done
5	AccountsBalances = "balances" // ranked by what they hold
6)
source

The sub-views of the packages and accounts pages, the only two list pages that restore a view from the URL.

Every other list page writes its state into the query string (?by=, ?page=, ?status=, ?window=, ?section=, ?failed=, ?storage=, ?txs=) and does NOT read any of it back on load: a link carrying one of those opens the page on its default view instead, silently. So there are no helpers for them here. Measured against the explorer's router on 2026-09-22.

const DefaultBase

1const DefaultBase = "https://mygnoscan.moul.p2p.team"
source

DefaultBase is moul's instance, the one the rest of this repo links to.

Functions 5

func NetworkFor

1func NetworkFor(chainID string) string
source

NetworkFor maps a chain-id to the network id DefaultBase serves it under, or "" when that instance does not index the chain (a gnodev, a local test).

Verified against DefaultBase's /api/networks on 2026-09-22: it serves mainnet, pearl and staging. sapphire is mapped because the chain exists and an instance configured for it uses that id; DefaultBase currently answers for it with the blend, which is the same failure mode as an unknown chain and is why the caller can override with WithNetwork.

func TrimDomain

1func TrimDomain(pkgPath string) string
source

TrimDomain strips the chain domain from a package path, so both "gno.land/r/moul/home" and "r/moul/home" become "r/moul/home". A path that is not shaped like a package path (see safePath) comes back empty, and every caller here treats empty as "no such target" and falls back to a list page.

It tries the running chain's domain first, then the literal "gno.land/", because the explorer's own realm route reassembles the path by prepending "gno.land/" unconditionally: on a chain whose domain is something else, a bare path is still what the link must carry.

func Default

1func Default() Scanner
source

Default returns a Scanner pointed at DefaultBase, on whichever network answers for the running chain.

func New

1func New(base string) Scanner
source

New returns a Scanner pointed at base, on whichever network answers for the running chain. A trailing slash on base is dropped so New("…/") and New("…") build the same links.

Types 1

type Scanner

struct
1type Scanner struct {
2	base    string
3	network string
4}
source

Scanner is one explorer instance plus the network its links should open on. It is a value: copying one is free and there is nothing to close.

Methods on Scanner

func Accounts

method on Scanner
1func (s Scanner) Accounts(view string) string
source

Accounts links the account list, on the AccountsActivity or AccountsBalances view. The empty string takes the page's own default, which is AccountsActivity.

func Address

method on Scanner
1func (s Scanner) Address(addr address) string
source

Address links an account page: its balance, its transactions, what it deployed.

The address is escaped rather than trusted. `address` is a string type and nothing stops a realm casting a caller's input into one, so a value reaching here is not necessarily bech32.

func Base

method on Scanner
1func (s Scanner) Base() string
source

Base returns the instance root, without a trailing slash.

func Block

method on Scanner
1func (s Scanner) Block(height int64) string
source

Block links one block by height.

func CurrentBlock

method on Scanner
1func (s Scanner) CurrentBlock() string
source

CurrentBlock links the block this call is executing in.

func CurrentRealm

method on Scanner
1func (s Scanner) CurrentRealm(tab ...string) string
source

CurrentRealm links the page of the realm that called into this package.

It stack-walks (unsafe.CurrentRealm), so it names whichever realm was current when it ran. For a plain read like this one that is the BORROWER, which is the wanted answer: a realm asking for "my page" gets its own, and so does one asking through a shared config realm, because a borrowed call opens no realm frame. Measured in the test harness on 2026-09-22.

Inside a CROSSING function it names that function's realm instead. That is also correct and rarely what a link wants, so pass the path explicitly there. Never branch on it for authorisation either way.

func CurrentRealmFooter

method on Scanner
1func (s Scanner) CurrentRealmFooter() string
source

CurrentRealmFooter is RealmFooter for the realm that called in. It carries the same stack-walking caveat as CurrentRealm.

func Home

method on Scanner
1func (s Scanner) Home() string
source

Home links the explorer's front page on this network.

func Network

method on Scanner
1func (s Scanner) Network() string
source

Network returns the network id these links carry, empty when they carry none.

func Packages

method on Scanner
1func (s Scanner) Packages(view string) string
source

Packages links the package list, on the PackagesAll or PackagesInert view. The empty string takes the page's own default, which is PackagesAll.

func Page

method on Scanner
1func (s Scanner) Page(name string) string
source

Page links one of the Page* list pages. A name that is not shaped like one falls back to the front page rather than to the explorer's own unknown-path-renders-home behaviour, which would look identical and mean something else.

func Proposal

method on Scanner
1func (s Scanner) Proposal(id int) string
source

Proposal links one GovDAO proposal by id.

func Realm

method on Scanner
1func (s Scanner) Realm(pkgPath string, tab ...string) string
source

Realm links a package or realm page, optionally opening one tab.

pkgPath is accepted either fully qualified ("gno.land/r/moul/home") or bare ("r/moul/home"): TrimDomain normalises it. Passing TabInfo, or no tab at all, emits no tab parameter.

func RealmFile

method on Scanner
1func (s Scanner) RealmFile(pkgPath, file string, line int) string
source

RealmFile links a realm's source tab scrolled to one line of one file. A line of 0 or less links the file without an anchor.

func RealmFooter

method on Scanner
1func (s Scanner) RealmFooter(pkgPath string) string
source

RealmFooter is the line a realm appends under its Render output: the four views of itself worth one click, separated by middots.

Deliberately plain markdown with no leading rule, so the caller decides where it sits and what separates it from the body.

func RealmFunc

method on Scanner
1func (s Scanner) RealmFunc(pkgPath, fn string) string
source

RealmFunc links a realm's source tab scrolled to one exported function.

func Token

method on Scanner
1func (s Scanner) Token(key string) string
source

Token links one GRC20 asset by its ledger key, which looks like "gno.land/r/gnoswap/gns.GNS.0000000". That key carries both slashes and dots, and neither is a route separator, so it is percent-escaped whole.

func Tx

method on Scanner
1func (s Scanner) Tx(hash string) string
source

Tx links one transaction. The hash is the base64 form the indexer and gnokey print, which carries "+", "/" and "="; the whole segment is percent-escaped so the explorer's decodeURIComponent gives it back byte for byte.

func URL

method on Scanner
1func (s Scanner) URL(path string, query ...string) string
source

URL is the one place a link is assembled: an absolute link to path on this instance, carrying query as key/value pairs plus the network. A pair with an empty key or value is dropped, so an optional parameter needs no branch at the call site.

Prefer the typed helpers below; this is the escape hatch for a route added to the explorer after this package was last touched.

func Validator

method on Scanner
1func (s Scanner) Validator(addr address) string
source

Validator links one validator by address.

func WithNetwork

method on Scanner
1func (s Scanner) WithNetwork(id string) Scanner
source

WithNetwork returns a copy of s whose links carry ?network=id. The empty string removes the parameter, which asks the explorer for every chain at once rather than for this one.

Imports 5

  • chain/runtime stdlib
  • chain/runtime/unsafe stdlib
  • net/url stdlib
  • strconv stdlib
  • strings stdlib

Source Files 4