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/samcrew/launchpad/tokens/v1

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v1
Namespace
samcrew / launchpad / tokens
Files
3 (gnomod.toml)
Exported functions
21
Module
gno.land/r/samcrew/launchpad/tokens/v1
gno
0.9
This realm implements a recognized token.T1 →

Files (3)

  • gnomod.tomltoml
  • tokens.gnogno
  • views.gnogno
tokens.gnogno
1// Package tokens creates registered Launchpad GRC20 ledgers for the issuer2// realms listed in config. A token belongs to the issuer that created it: no3// other realm, and no admin, can mint into it.4package tokens56import (7	"chain"8	"strconv"910	"gno.land/p/nt/avl/v0"11	"gno.land/p/nt/grc20/v0"12	"gno.land/p/nt/seqid/v0"13	"gno.land/p/samcrew/launchpad/currency/v1"14	"gno.land/p/samcrew/launchpad/meta/v1"15	sm "gno.land/p/samcrew/launchpad/safemath/v1"16	"gno.land/r/nt/grc20reg/v0"17	"gno.land/r/samcrew/launchpad/config/v1"18)1920const (21	ModeDirectFixed = "direct_fixed"22	ModeDirectCap   = "direct_capped"23	ModeFairSale    = "fairsale"24	maxPublicID     = 9_999_999_999 // T plus ten decimal digits: GRC20 MaxSymbolLen is 11.25)2627// Metadata is editable only by the original creator until frozen.28type Metadata struct {29	Description string30	Image       string31	Website     string32	XHandle     string33	Telegram    string34}3536// createSpec is assembled within this realm: a foreign Gno realm cannot37// allocate a struct declared here, so the crossing interface uses scalars.38type createSpec struct {39	Creator         address40	Mode            string41	Name            string42	Ticker          string43	Decimals        int44	InitialSupply   int6445	MaxSupply       int6446	CurrencyKey     string47	ExpectedVersion int6448	Metadata        Metadata49}5051// Info excludes the private ledger capability and can be returned safely.52type Info struct {53	ID              string54	RegistryKey     string55	GRC20ID         string56	Issuer          address57	Creator         address58	Mode            string59	Name            string60	Ticker          string61	Decimals        int62	InitialSupply   int6463	MaxSupply       int6464	ConfigVersion   int6465	CurrencyKey     string66	Metadata        Metadata67	MetadataFrozen  bool68	MintAuthority   address69	PendingMintAuth address70	MintRenounced   bool71}7273type entry struct {74	info   Info75	token  *grc20.Token76	ledger *grc20.PrivateLedger77}7879// Tokens live in an AVL tree: creating one touches its own entry and the path80// to it, whatever the number of tokens. A Go map, or a slice of their ids,81// would be rewritten whole on every creation. Ids are T1, T2, ... in creation82// order and no token is ever removed, so a page of the list is a range of ids.83var (84	nextID  seqid.ID85	entries = avl.NewTree() // token id -> *entry86)8788// requireIssuer returns the immediate caller, which config must list as a89// token issuer. Only a realm's own address is ever listed, so an account or a90// script never passes.91func requireIssuer(cur realm) address {92	if !cur.IsCurrent() {93		panic("launchpad/tokens: stale realm context")94	}95	issuer := cur.Previous().Address()96	if !config.IsIssuer(config.LedgerTokens, issuer) {97		panic("launchpad/tokens: token issuer only")98	}99	return issuer100}101102func check(err error) {103	if err != nil {104		panic(err.Error())105	}106}107108// caller is the immediate user or realm. Coins a user attaches to a direct109// call would land in this realm, which has no way to return them.110func caller(cur realm) address {111	if !cur.IsCurrent() {112		panic("launchpad/tokens: stale realm context")113	}114	currency.AssertNoCoins(0, cur)115	return cur.Previous().Address()116}117118// The ledger keys balances and allowances by the address text, so an address119// in any spelling but the one the chain gives a caller would hold something120// no caller can reach.121func requireAddress(a address) {122	if !meta.ValidAddress(a) {123		panic("launchpad/tokens: invalid address")124	}125}126127func validateMetadata(m Metadata) {128	check(meta.Description(m.Description))129	check(meta.Image(m.Image))130	check(meta.Website(m.Website))131	check(meta.Handle(m.XHandle))132	check(meta.Handle(m.Telegram))133}134135// The founding names remain unavailable even if governance globally clears a136// config reservation. A creator-specific grant needs its own reviewed proof.137func protectedTicker(ticker string) bool {138	switch ticker {139	case "MEMBA", "GNOT", "UGNOT", "WUGNOT", "GNS", "USDC", "USDT", "ATOM", "BTC", "ETH", "SAMCREW":140		return true141	}142	return false143}144145func validateSpec(s createSpec) {146	requireAddress(s.Creator)147	check(meta.Name(s.Name))148	check(meta.Ticker(s.Ticker))149	validateMetadata(s.Metadata)150	if s.Decimals < 0 || s.Decimals > grc20.MaxDecimals ||151		s.InitialSupply <= 0 || s.MaxSupply < s.InitialSupply ||152		protectedTicker(s.Ticker) || config.IsReserved(s.Ticker) {153		panic("launchpad/tokens: invalid launch terms")154	}155	switch s.Mode {156	case ModeFairSale:157		if s.Decimals > 12 || s.MaxSupply != s.InitialSupply {158			panic("launchpad/tokens: invalid fair-sale terms")159		}160	case ModeDirectFixed:161		if s.Decimals > 12 || s.MaxSupply != s.InitialSupply {162			panic("launchpad/tokens: invalid direct terms")163		}164	case ModeDirectCap:165		if s.Decimals > 12 || s.MaxSupply <= s.InitialSupply {166			panic("launchpad/tokens: invalid capped terms")167		}168	default:169		panic("launchpad/tokens: invalid mode")170	}171}172173func laneForMode(mode string) string {174	switch mode {175	case ModeDirectFixed, ModeDirectCap:176		return config.LaneDirect177	case ModeFairSale:178		return config.LaneFairSale179	}180	panic("launchpad/tokens: invalid mode")181}182183func nextPublicID() (string, seqid.ID) {184	if uint64(nextID) >= maxPublicID {185		panic("launchpad/tokens: token ID exhausted")186	}187	idNumber := nextID.Next()188	return "T" + strconv.FormatUint(uint64(idNumber), 10), idNumber189}190191// Create registers exactly one new GRC20 for a listed issuer, which must192// authenticate the creator, reserve the launch terms and collect payment193// before this crossing call. The currency key names the config action194// currency: creation-fee currency for a direct token, quote currency for a195// fair sale; the issuer binds it to the corresponding payment route. The196// public symbol T<n> is distinct from the nonunique creator-selected ticker.197// The whole initial mint lands in the issuer's custody for atomic allocation198// and escrow setup, and the token stays bound to that issuer.199func Create(cur realm, creator address, mode, name, ticker string,200	decimals int, initialSupply, maxSupply int64,201	currencyKey string, expectedVersion int64,202	description, image, website, xHandle, telegram string) string {203	issuer := requireIssuer(cur)204	s := createSpec{205		Creator: creator, Mode: mode, Name: name, Ticker: ticker,206		Decimals: decimals, InitialSupply: initialSupply, MaxSupply: maxSupply,207		CurrencyKey: currencyKey,208		ExpectedVersion: expectedVersion,209		Metadata: Metadata{210			Description: description, Image: image, Website: website,211			XHandle: xHandle, Telegram: telegram,212		},213	}214	validateSpec(s)215	config.AssertNewAction(laneForMode(s.Mode), s.CurrencyKey, s.ExpectedVersion)216	id, idNumber := nextPublicID()217	token, ledger := grc20.NewToken(s.Name, id, s.Decimals, idNumber, cur)218	check(ledger.Mint(issuer, s.InitialSupply))219	registryKey := grc20reg.Register(cross(cur), token, id)220	info := Info{221		ID: id, RegistryKey: registryKey, GRC20ID: token.ID(),222		Issuer: issuer, Creator: s.Creator,223		Mode: s.Mode, Name: s.Name, Ticker: s.Ticker, Decimals: s.Decimals,224		InitialSupply: s.InitialSupply, MaxSupply: s.MaxSupply,225		ConfigVersion: s.ExpectedVersion, CurrencyKey: s.CurrencyKey,226		Metadata: s.Metadata,227	}228	if s.Mode == ModeDirectCap {229		info.MintAuthority = s.Creator230	}231	entries.Set(id, &entry{info: info, token: token, ledger: ledger})232	chain.Emit("LaunchpadTokenCreated", "id", id, "registry_key", registryKey,233		"grc20_id", token.ID(), "issuer", issuer.String(),234		"creator", s.Creator.String(),235		"mode", s.Mode, "ticker", s.Ticker, "currency_key", s.CurrencyKey)236	return id237}238239func mustEntry(id string) *entry {240	e, ok := entries.Get(id).(*entry)241	if !ok || e.info.ID != id {242		panic("launchpad/tokens: unknown token")243	}244	return e245}246247func GetInfo(id string) Info { return mustEntry(id).info }248249// Count is the number of tokens created: ids are T1 to T<Count>, none removed.250func Count() int64 { return int64(nextID) }251252func RegistryKeyOf(id string) string { return mustEntry(id).info.RegistryKey }253254func BalanceOf(id string, owner address) int64 {255	requireAddress(owner)256	return mustEntry(id).token.BalanceOf(owner)257}258259func TotalSupply(id string) int64 { return mustEntry(id).token.TotalSupply() }260261// MintCapped can only be reached through the issuer that created the token.262// That issuer authenticates the live caller as the current mint authority263// and must partition the newly minted balance into recorded allocations in264// this same transaction. The ledger independently rechecks the authority and265// the immutable cap, and takes no mint while the direct lane is closed: the266// pauser stops every issuer here, whatever each one checks by itself.267func MintCapped(cur realm, id string, authority address, amount int64) {268	issuer := requireIssuer(cur)269	e := mustEntry(id)270	config.AssertLaneOpen(config.LaneDirect, e.info.CurrencyKey)271	if e.info.Issuer != issuer || e.info.Mode != ModeDirectCap || e.info.MintRenounced ||272		e.info.MintAuthority != authority || amount <= 0 ||273		sm.Add(e.token.TotalSupply(), amount) > e.info.MaxSupply {274		panic("launchpad/tokens: capped mint denied")275	}276	check(e.ledger.Mint(issuer, amount))277	chain.Emit("LaunchpadTokenCappedMinted", "id", id,278		"authority", authority.String(), "amount", strconv.FormatInt(amount, 10))279}280281func Allowance(id string, owner, spender address) int64 {282	requireAddress(owner)283	requireAddress(spender)284	return mustEntry(id).token.Allowance(owner, spender)285}286287// Standard wrappers operate only on the immediate caller's balance or288// allowance, accept no coins, and take an address only as the chain writes289// one. A realm can also move its own balance, or an allowance it holds,290// through the token in the registry, where the GRC20 library alone checks the291// receiver. A script an account runs is such a realm with the account's292// address, so these wrappers are an account's only way in only while the293// chain does not let that account submit scripts.294func Transfer(cur realm, id string, to address, amount int64) {295	caller(cur)296	requireAddress(to)297	check(mustEntry(id).ledger.CallerTeller().Transfer(0, cur, to, amount))298}299300func Approve(cur realm, id string, spender address, amount int64) {301	caller(cur)302	requireAddress(spender)303	check(mustEntry(id).ledger.CallerTeller().Approve(0, cur, spender, amount))304}305306func TransferFrom(cur realm, id string, from, to address, amount int64) {307	caller(cur)308	requireAddress(from)309	requireAddress(to)310	check(mustEntry(id).ledger.CallerTeller().TransferFrom(0, cur, from, to, amount))311}312313func Burn(cur realm, id string, amount int64) {314	who := caller(cur)315	if amount <= 0 {316		panic("launchpad/tokens: invalid burn")317	}318	check(mustEntry(id).ledger.Burn(who, amount))319}320321func SetMetadata(cur realm, id, description, image, website, xHandle, telegram string) {322	who := caller(cur)323	e := mustEntry(id)324	if e.info.MetadataFrozen || who != e.info.Creator {325		panic("launchpad/tokens: creator metadata only")326	}327	m := Metadata{328		Description: description, Image: image, Website: website,329		XHandle: xHandle, Telegram: telegram,330	}331	validateMetadata(m)332	e.info.Metadata = m333	chain.Emit("TokenMetadataChanged", "id", id, "creator", e.info.Creator.String())334}335336func FreezeMetadata(cur realm, id string) {337	who := caller(cur)338	e := mustEntry(id)339	if e.info.MetadataFrozen || who != e.info.Creator {340		panic("launchpad/tokens: creator metadata only")341	}342	e.info.MetadataFrozen = true343	chain.Emit("TokenMetadataFrozen", "id", id, "creator", e.info.Creator.String())344}345346// A capped token's authority moves only through its own two-step handoff.347// The issuer checks this live authority before each later mint.348func ProposeMintAuthority(cur realm, id string, next address) {349	who := caller(cur)350	e := mustEntry(id)351	if e.info.Mode != ModeDirectCap || e.info.MintRenounced ||352		who != e.info.MintAuthority ||353		!meta.ValidAddress(next) || next == e.info.MintAuthority {354		panic("launchpad/tokens: mint authority only")355	}356	e.info.PendingMintAuth = next357	chain.Emit("MintAuthorityProposed", "id", id, "next", next.String())358}359360func CancelMintAuthority(cur realm, id string) {361	who := caller(cur)362	e := mustEntry(id)363	if e.info.Mode != ModeDirectCap || e.info.MintRenounced ||364		who != e.info.MintAuthority || e.info.PendingMintAuth == "" {365		panic("launchpad/tokens: no mint authority proposal")366	}367	e.info.PendingMintAuth = ""368	chain.Emit("MintAuthorityCancelled", "id", id)369}370371func AcceptMintAuthority(cur realm, id string) {372	who := caller(cur)373	e := mustEntry(id)374	if e.info.Mode != ModeDirectCap || e.info.MintRenounced ||375		e.info.PendingMintAuth == "" || who != e.info.PendingMintAuth {376		panic("launchpad/tokens: pending mint authority only")377	}378	e.info.MintAuthority = who379	e.info.PendingMintAuth = ""380	chain.Emit("MintAuthorityAccepted", "id", id, "authority", who.String())381}382383func RenounceMintAuthority(cur realm, id string) {384	who := caller(cur)385	e := mustEntry(id)386	if e.info.Mode != ModeDirectCap || e.info.MintRenounced ||387		who != e.info.MintAuthority {388		panic("launchpad/tokens: mint authority only")389	}390	e.info.MintAuthority = ""391	e.info.PendingMintAuth = ""392	e.info.MintRenounced = true393	chain.Emit("MintAuthorityRenounced", "id", id)394}395396func Render(path string) string {397	if path == "" {398		return "# Launchpad tokens\n\nRegistered GRC20 ledgers created by the issuers listed in config.\n"399	}400	e := mustEntry(path)401	return "# " + meta.EscapeMarkdown(e.info.Name) + "\n\nTicker: " +402		meta.EscapeMarkdown(e.info.Ticker) + "\n"403}404

Functions

  • AcceptMintAuthority(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}, id string)

  • Allowance(id string, owner string, spender string) int64

  • Approve(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}, id string, spender string, amount int64)

  • BalanceOf(id string, owner string) int64

  • Burn(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}, id string, amount int64)

  • CancelMintAuthority(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}, id string)

  • Count() int64

  • Create(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}, creator string, mode string, name string, ticker string, decimals int, initialSupply int64, maxSupply int64, currencyKey string, expectedVersion int64, description string, image string, website string, xHandle string, telegram string) string

  • FreezeMetadata(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}, id string)

  • GetInfo(id string) struct{ID string; RegistryKey string; GRC20ID string; Issuer .uverse.address; Creator .uverse.address; Mode string; Name string; Ticker string; Decimals int; InitialSupply int64; MaxSupply int64; ConfigVersion int64; CurrencyKey string; Metadata gno.land/r/samcrew/launchpad/tokens/v1.Metadata; MetadataFrozen bool; MintAuthority .uverse.address; PendingMintAuth .uverse.address; MintRenounced bool}

  • ListTokensJSON(page int64, size int64) string

  • MintCapped(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}, id string, authority string, amount int64)

  • ProposeMintAuthority(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}, id string, next string)

  • RegistryKeyOf(id string) string

  • Render(path string) string

  • RenounceMintAuthority(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}, id string)

  • SetMetadata(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}, id string, description string, image string, website string, xHandle string, telegram string)

  • TokenJSON(id string) string

  • TotalSupply(id string) int64

  • Transfer(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}, id string, to string, amount int64)

  • TransferFrom(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}, id 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.