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/r/nt/grc20reg/v0

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v0
Namespace
nt / grc20reg
Files
3 (gnomod.toml)
Exported functions
8
Module
gno.land/r/nt/grc20reg/v0
gno
0.9

Files (3)

  • gnomod.tomltoml
  • grc20reg.gnogno
  • grc20reg_test.gnogno
grc20reg.gnogno
1package grc20reg23import (4	"chain"5	"strings"67	"gno.land/p/moul/md/v0"8	"gno.land/p/nt/avl/rotree/v0"9	"gno.land/p/nt/avl/v0"10	"gno.land/p/nt/fqname/v0"11	"gno.land/p/nt/grc20/v0"12	"gno.land/p/nt/ufmt/v0"13)1415var registry = avl.NewTree() // rlmPath.symbol -> *Token1617// Construction lives in grc20.NewToken — it takes rlm realm last18// and binds origRealm from rlm.PkgPath() under an IsCurrent assertion.19// The registry key is the canonical fqname rlmPath.symbol (one token per20// realm+symbol), independent of Token.ID()'s trailing sequence id, so21// callers can look a token up from the (realm, symbol) pair they already22// know:23//24//	Token, ledger := grc20.NewToken(name, symbol, decimals, id, cur)25//	key := grc20reg.Register(cross(cur), Token, "")2627// Register records token under its rlmPath.symbol key and returns that key.28// Token.ID() carries a trailing sequence id (rlmPath.symbol.<id>) that keeps29// token identities/events unique, but the registry deliberately keys by30// rlmPath.symbol so lookups don't need to know the id, and so a realm cannot31// register two tokens under the same symbol (overwrite/alias guard).32func Register(cur realm, token *grc20.Token, slug string) string {33	if token == nil {34		panic("grc20reg: nil token")35	}36	if slug != "" {37		validateSlug(slug)38	}39	rlmPath := cur.Previous().PkgPath()40	key := fqname.Construct(rlmPath, token.GetSymbol())41	// Token.ID() == key + "." + <id>; verify the token originates from the42	// registering realm and symbol.43	if !strings.HasPrefix(token.ID(), key+".") {44		panic("grc20reg: token must be registered from its own realm")45	}46	if registry.Has(key) {47		panic("grc20reg: token already registered")48	}49	registry.Set(key, token)50	chain.Emit(51		registerEvent,52		"token_path", key,53		"pkgpath", rlmPath,54		"slug", slug,55		"symbol", token.GetSymbol(),56	)57	return key58}5960func Get(key string) *grc20.Token {61	token := registry.Get(key)62	if token == nil {63		return nil64	}65	return token.(*grc20.Token)66}6768func MustGet(key string) *grc20.Token {69	token := Get(key)70	if token == nil {71		panic("unknown token: " + key)72	}73	return token74}7576// Write wrappers: a registered token can be moved through the registry without77// importing the token's realm, which is the point of a registry. What makes78// that safe is the calling convention, so it is worth stating once here rather79// than three times below.80//81// These are NOT crossing functions. `_ int, rlm realm` is the only shape that82// gives a non-crossing realm parameter — a realm parameter in first position83// must be named `cur`, which makes the function crossing — and the distinction84// is load-bearing, not stylistic:85//86//   - Crossing (`func Transfer(cur realm, …)`) mints a fresh `cur` for THIS87//     realm. RealmTeller would then bind the actor to the registry's own88//     address and the registry would spend its own balance. Useless at best.89//   - Non-crossing (`func Transfer(_ int, rlm realm, …)`) declaring-borrows to90//     the registry without a realm-context change, so `rlm` is still the91//     caller's own live token and the actor is the caller.92//93// The safety comes from RealmTeller's IsCurrent() assertion. The actor is94// rlm.Address() on a token that must be the live crossing frame, so it is95// provably the immediate caller: a stale or foreign token is refused with96// ErrSpoofedRealm. Debiting anyone else would mean holding their live `cur`,97// which means executing inside their frame — authority they handed over98// deliberately, and the same trust model RealmTeller already carries.99//100// This is deliberately not grc20.CallerTeller. "Act as whoever called me" is101// the confused deputy: the debited account ends up chosen by whoever the hub102// can be induced to serve, and an intermediate realm frame silently changes who103// pays. CallerTeller is confined to the token's own realm for that reason and104// is not reachable from a *Token. "Act as the realm that called me, verified105// current" has nothing to induce — the caller cannot name a victim, only106// itself.107//108// Realm-only by construction: MsgCall cannot build a realm argument109// (convertArgToGno rejects non-primitive parameter types), so a signing user110// cannot reach these at all and there is no in-band case to guard against.111// Users move their own tokens through the token realm's own entry points112// (wugnot.Transfer, foo20.Transfer, …).113114// Transfer moves `amount` out of the CALLING REALM's own balance.115//116// Call it non-crossing, forwarding your own `cur`:117//118//	grc20reg.Transfer(0, cur, "gno.land/r/demo/defi/foo20.FOO", to, 100)119func Transfer(_ int, rlm realm, tokenKey string, to address, amount int64) {120	checkErr(MustGet(tokenKey).RealmTeller(0, rlm).Transfer(0, rlm, to, amount))121}122123// Approve sets an allowance owned by the CALLING REALM, letting `spender` draw124// on the calling realm's balance. It does not touch the signing user's125// allowances.126func Approve(_ int, rlm realm, tokenKey string, spender address, amount int64) {127	checkErr(MustGet(tokenKey).RealmTeller(0, rlm).Approve(0, rlm, spender, amount))128}129130// TransferFrom spends an allowance with the CALLING REALM as the spender.131//132// Note the allowance direction this implies: `from` must have approved the133// calling realm, not the signing user. That is the supported way for a realm to134// move a user's funds — the user grants the realm an allowance, and the realm135// draws on it as itself, so every debit is one the owner authorized against136// that specific realm.137func TransferFrom(_ int, rlm realm, tokenKey string, from, to address, amount int64) {138	checkErr(MustGet(tokenKey).RealmTeller(0, rlm).TransferFrom(0, rlm, from, to, amount))139}140141func checkErr(err error) {142	if err != nil {143		panic(err)144	}145}146147func Render(path string) string {148	switch {149	case path == "": // home150		// TODO: add pagination151		s := ""152		count := 0153		registry.Iterate("", "", func(key string, tokenI any) bool {154			count++155			token := tokenI.(*grc20.Token)156			rlmPath, tokenID := fqname.Parse(key)157			rlmLink := fqname.RenderLink(rlmPath, tokenID)158			infoLink := "/r/nt/grc20reg/v0:" + key159			s += "- " + md.Bold(md.EscapeText(token.GetName())) + " - " + rlmLink + " - " + md.Link("info", infoLink) + "\n"160			return false161		})162		if count == 0 {163			return "No registered token."164		}165		return s166	default: // specific token167		key := path168		token := MustGet(key)169		rlmPath, tokenID := fqname.Parse(key)170		rlmLink := fqname.RenderLink(rlmPath, tokenID)171		s := ufmt.Sprintf("# %s\n", md.EscapeText(token.GetName()))172		s += "- symbol: " + md.Bold(md.EscapeText(token.GetSymbol())) + "\n"173		s += ufmt.Sprintf("- realm: %s\n", rlmLink)174		s += ufmt.Sprintf("- decimals: %d\n", token.GetDecimals())175		s += ufmt.Sprintf("- total supply: %d\n", token.TotalSupply())176		return s177	}178}179180const (181	registerEvent = "register"182	maxSlugLen    = 128183)184185func GetRegistry() *rotree.ReadOnlyTree {186	return rotree.Wrap(registry, nil)187}188189// validateSlug panics if the slug is too long or contains non-alphanumeric characters.190// Only letters, digits, dashes, and underscores are allowed.191func validateSlug(slug string) {192	if len(slug) > maxSlugLen {193		panic("grc20reg: slug too long")194	}195	for _, c := range slug {196		if !isAlphanumeric(c) && c != '_' && c != '-' {197			panic("grc20reg: invalid slug character: " + string(c))198		}199	}200}201202func isAlphanumeric(c rune) bool {203	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')204}205

Functions

  • Approve(int, rlm interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, tokenKey string, spender string, amount int64)

  • Get(key string) *gno.land/p/nt/grc20/v0.Token

  • GetRegistry() *gno.land/p/nt/avl/rotree/v0.ReadOnlyTree

  • MustGet(key string) *gno.land/p/nt/grc20/v0.Token

  • Register(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, token *gno.land/p/nt/grc20/v0.Token, slug string) string

  • Render(path string) string

  • Transfer(int, rlm interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, tokenKey string, to string, amount int64)

  • TransferFrom(int, rlm interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, tokenKey string, from string, to string, amount int64)

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

Rendered

RenderedRawgnoweb ↗

vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.