PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/p/moul/mygnoscan/v0

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
moul / mygnoscan
Files
4 (README)(gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/moul/mygnoscan/v0
gno
0.9

Files (4)

  • README.mdmarkdown
  • gnomod.tomltoml
  • mygnoscan.gnogno
  • mygnoscan_test.gnogno
mygnoscan_test.gnogno
1package mygnoscan23import (4	"testing"56	"gno.land/p/nt/uassert/v0"7)89// mainnet is what a Scanner looks like on the chain that matters. Tests build10// it explicitly rather than calling Default(), because the test harness runs11// with chain-id "dev", which NetworkFor maps to no network at all: pinning12// Default()'s output would pin the absence of the parameter and prove nothing13// about the case every real link is in.14func mainnet() Scanner { return New(DefaultBase).WithNetwork("mainnet") }1516func TestNetworkFor(t *testing.T) {17	tests := []struct {18		chainID string19		want    string20	}{21		{"gnoland-1", "mainnet"},22		{"pearl-1", "pearl"},23		{"sapphire-1", "sapphire"},24		{"staging", "staging"},25		{"dev", ""},          // gnodev, and what `gno test` reports26		{"", ""},             // no chain at all27		{"gnoland1", ""},     // the upstream default instance's id, not a chain-id28		{"test3", ""},        // a retired testnet29		{"mainnet", ""},      // the network id, fed back in by mistake30	}31	for _, tt := range tests {32		uassert.Equal(t, tt.want, NetworkFor(tt.chainID), "NetworkFor("+tt.chainID+")")33	}34}3536func TestTrimDomain(t *testing.T) {37	tests := []struct {38		in   string39		want string40	}{41		{"gno.land/r/moul/home", "r/moul/home"},42		{"r/moul/home", "r/moul/home"},43		{"/r/moul/home", "r/moul/home"},44		{"gno.land/p/moul/mygnoscan/v0", "p/moul/mygnoscan/v0"},45		{"gno.land/r/gnoland/blog", "r/gnoland/blog"},46		{"", ""},47		{"gno.land/", ""},48		// A path whose own name starts with the domain is only trimmed once,49		// so a realm cannot be linked out of its own namespace by its name.50		{"gno.land/r/moul/gno.land", "r/moul/gno.land"},51		// Anything not shaped like a package path comes back empty, so the52		// caller falls back to a list page instead of emitting it.53		{"r/moul/home)", ""},54		{"r/moul/ho me", ""},55		{"r/moul//home", ""},56		{"r/moul/home\n", ""},57	}58	for _, tt := range tests {59		uassert.Equal(t, tt.want, TrimDomain(tt.in), "TrimDomain("+tt.in+")")60	}61}6263func TestBaseNormalisation(t *testing.T) {64	uassert.Equal(t, DefaultBase, New(DefaultBase+"/").Base())65	uassert.Equal(t, DefaultBase, New(DefaultBase).Base())66	uassert.Equal(t, "https://scan.example.com", New("https://scan.example.com/").Base())67}6869func TestTargets(t *testing.T) {70	s := mainnet()71	const b = DefaultBase72	tests := []struct {73		name string74		got  string75		want string76	}{77		{"home", s.Home(), b + "/?network=mainnet"},78		{"page", s.Page(PageTxs), b + "/txs?network=mainnet"},79		{"page nested", s.Page(PageGasRealms), b + "/gas/realms?network=mainnet"},80		{"page slashed", s.Page("/blocks/"), b + "/blocks?network=mainnet"},81		{"realm qualified", s.Realm("gno.land/r/moul/home"), b + "/realm/r/moul/home?network=mainnet"},82		{"realm bare", s.Realm("r/moul/home"), b + "/realm/r/moul/home?network=mainnet"},83		{"realm tab", s.Realm("r/moul/home", TabSource), b + "/realm/r/moul/home?network=mainnet&tab=source"},84		// TabInfo is the tab the explorer lands on, so asking for it adds nothing.85		{"realm tab info", s.Realm("r/moul/home", TabInfo), b + "/realm/r/moul/home?network=mainnet"},86		{"realm empty", s.Realm(""), b + "/?network=mainnet"},87		{"realm func", s.RealmFunc("r/moul/config", "Set"), b + "/realm/r/moul/config?fn=Set&network=mainnet&tab=source"},88		{"realm file", s.RealmFile("r/moul/home", "render.gno", 42), b + "/realm/r/moul/home?file=render.gno&line=42&network=mainnet&tab=source"},89		{"realm file no line", s.RealmFile("r/moul/home", "render.gno", 0), b + "/realm/r/moul/home?file=render.gno&network=mainnet&tab=source"},90		{"address", s.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5"), b + "/address/g1manfred47kzduec920z88wfr64ylksmdcedlf5?network=mainnet"},91		{"address empty", s.Address(""), b + "/accounts?network=mainnet"},92		{"block", s.Block(80618), b + "/block/80618?network=mainnet"},93		{"block zero", s.Block(0), b + "/blocks?network=mainnet"},94		{"validator", s.Validator("g1manfred47kzduec920z88wfr64ylksmdcedlf5"), b + "/validator/g1manfred47kzduec920z88wfr64ylksmdcedlf5?network=mainnet"},95		{"proposal", s.Proposal(7), b + "/govdao/7?network=mainnet"},96		{"proposal negative", s.Proposal(-1), b + "/govdao/proposals?network=mainnet"},97		{"token empty", s.Token(""), b + "/grc20?network=mainnet"},98	}99	for _, tt := range tests {100		uassert.Equal(t, tt.want, tt.got, tt.name)101	}102}103104// TestEscaping covers the two routes whose argument is not URL-safe. Both are105// read back with decodeURIComponent by the explorer, so percent-escaping the106// whole segment is what round-trips; leaving the slashes alone would split the107// segment and land on a different route entirely.108func TestEscaping(t *testing.T) {109	s := mainnet()110111	// A 32-byte hash in the base64 form the indexer and gnokey print, picked112	// to carry all three of the characters that make base64 awkward in a URL.113	// Everything outside the unreserved set moves, which is what the114	// explorer's decodeURIComponent undoes byte for byte.115	uassert.Equal(t,116		DefaultBase+"/tx/BppSxd7qRiAPsMHzDx5mys%2BYpeDCMUzxI1qeb%2FEhaYc%3D?network=mainnet",117		s.Tx("BppSxd7qRiAPsMHzDx5mys+YpeDCMUzxI1qeb/EhaYc="))118	uassert.Equal(t, DefaultBase+"/txs?network=mainnet", s.Tx(""))119120	// A GRC20 ledger key carries both slashes and dots.121	uassert.Equal(t,122		DefaultBase+"/grc20/gno.land%2Fr%2Fgnoswap%2Fgns.GNS.0000000?network=mainnet",123		s.Token("gno.land/r/gnoswap/gns.GNS.0000000"))124}125126func TestURLQuery(cur realm, t *testing.T) {127	s := mainnet()128129	uassert.Equal(t, DefaultBase+"/anything?network=mainnet", s.URL("anything"))130	uassert.Equal(t, DefaultBase+"/anything?network=mainnet", s.URL("/anything"))131	uassert.Equal(t, DefaultBase+"/a?k=v&network=mainnet", s.URL("/a", "k", "v"))132133	// An empty key or value is dropped, so an optional parameter needs no134	// branch at the call site.135	uassert.Equal(t, DefaultBase+"/a?network=mainnet", s.URL("/a", "k", ""))136	uassert.Equal(t, DefaultBase+"/a?network=mainnet", s.URL("/a", "", "v"))137138	uassert.PanicsWithMessage(t, cur, "mygnoscan: odd number of query arguments", func() {139		s.URL("/a", "k")140	})141}142143// TestNoNetwork pins what a Scanner emits when it does not know the chain:144// nothing. The explorer then answers for every chain it indexes at once, which145// is a real answer to a different question, so this is worth being explicit146// about rather than discovering from a plausible-looking page.147func TestNoNetwork(t *testing.T) {148	s := New(DefaultBase).WithNetwork("")149	uassert.Equal(t, "", s.Network())150	uassert.Equal(t, DefaultBase+"/realm/r/moul/home", s.Realm("r/moul/home"))151	uassert.Equal(t, DefaultBase+"/realm/r/moul/home?tab=calls", s.Realm("r/moul/home", TabCalls))152	uassert.Equal(t, DefaultBase+"/", s.Home())153}154155// TestDefaultUnderTest pins Default() as the test harness sees it: chain-id156// "dev" is indexed nowhere, so the links carry no network. A realm rendering157// under gnodev gets exactly this, which is why a local preview link is not158// proof that the mainnet one is right.159func TestDefaultUnderTest(t *testing.T) {160	s := Default()161	uassert.Equal(t, DefaultBase, s.Base())162	uassert.Equal(t, "", s.Network())163	uassert.Equal(t, DefaultBase+"/realm/r/moul/home", s.Realm("gno.land/r/moul/home"))164}165166func TestTabArity(cur realm, t *testing.T) {167	s := mainnet()168	uassert.PanicsWithMessage(t, cur, "mygnoscan: at most one tab", func() {169		s.Realm("r/moul/home", TabSource, TabCalls)170	})171}172173func TestLink(t *testing.T) {174	uassert.Equal(t, "[text](https://example.com)", Link("text", "https://example.com"))175}176177func TestRealmFooter(t *testing.T) {178	s := mainnet()179	const want = "[explorer](https://mygnoscan.moul.p2p.team/realm/r/moul/home?network=mainnet)" +180		" · [source](https://mygnoscan.moul.p2p.team/realm/r/moul/home?network=mainnet&tab=source)" +181		" · [calls](https://mygnoscan.moul.p2p.team/realm/r/moul/home?network=mainnet&tab=calls)" +182		" · [deps](https://mygnoscan.moul.p2p.team/realm/r/moul/home?network=mainnet&tab=deps)"183	uassert.Equal(t, want, s.RealmFooter("gno.land/r/moul/home"))184	uassert.Equal(t, want, s.RealmFooter("r/moul/home"))185	uassert.Equal(t, "", s.RealmFooter(""))186}187188// TestOtherInstance is the whole reason base and network are separate knobs:189// a second instance serving the same chain under a different id.190func TestOtherInstance(t *testing.T) {191	s := New("https://scan.example.com").WithNetwork("gnoland1")192	uassert.Equal(t,193		"https://scan.example.com/realm/r/moul/home?network=gnoland1",194		s.Realm("gno.land/r/moul/home"))195}196197// TestMarkdownBreakout is the reason safePath and escapeSegment exist. A realm198// that links a path or a hash a caller typed would otherwise let that caller199// close the markdown link early and write page text of their choosing.200//201// url.PathEscape is not enough on its own: "(" and ")" are legal URL202// sub-delims and it leaves both alone.203func TestMarkdownBreakout(t *testing.T) {204	s := mainnet()205206	// A path is refused outright, because escaping its separators would break207	// the route. The caller gets the front page, not a broken link.208	uassert.Equal(t, DefaultBase+"/?network=mainnet",209		s.Realm("r/evil/x)](https://evil.example.com"))210	uassert.Equal(t, "", s.RealmFooter("r/evil/x)](https://evil.example.com"))211212	// A hash and a token key are opaque, so they are escaped rather than213	// refused, and the parentheses move.214	uassert.Equal(t,215		DefaultBase+"/tx/x%29%5D%28evil?network=mainnet",216		s.Tx("x)](evil"))217	uassert.Equal(t,218		DefaultBase+"/grc20/x%29%5D%28evil?network=mainnet",219		s.Token("x)](evil"))220221	// An address is a string type; nothing stops a realm casting into it.222	uassert.Equal(t,223		DefaultBase+"/address/g1%29%5D%28evil?network=mainnet",224		s.Address(address("g1)](evil")))225226	// A page name that is not a page name lands on the front page, rather227	// than on the explorer's own unknown-path-renders-home behaviour, which228	// would look identical and mean something else.229	uassert.Equal(t, DefaultBase+"/?network=mainnet", s.Page("txs)](evil"))230	uassert.Equal(t, DefaultBase+"/?network=mainnet", s.Page(""))231}232233func TestSafePath(t *testing.T) {234	tests := []struct {235		in   string236		want bool237	}{238		{"r/moul/home", true},239		{"p/moul/mygnoscan/v0", true},240		{"gas/realms", true},241		{"a", true},242		{"a.b-c_d", true},243		{"", false},244		{"a//b", false},245		{"a/", false},246		{"a b", false},247		{"a)b", false},248		{"a(b", false},249		{"a]b", false},250		{"a\nb", false},251		{"a?b", false},252		{"a#b", false},253		{"a%b", false},254	}255	for _, tt := range tests {256		uassert.Equal(t, tt.want, safePath(tt.in), "safePath("+tt.in+")")257	}258}259260func TestSubViews(t *testing.T) {261	s := mainnet()262	uassert.Equal(t, DefaultBase+"/packages?network=mainnet&pv=inert", s.Packages(PackagesInert))263	uassert.Equal(t, DefaultBase+"/packages?network=mainnet", s.Packages(""))264	uassert.Equal(t, DefaultBase+"/accounts?av=balances&network=mainnet", s.Accounts(AccountsBalances))265	uassert.Equal(t, DefaultBase+"/accounts?network=mainnet", s.Accounts(""))266}267

Functions

not supported for pure packages by the node (vm/qfuncs)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.