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/x/social/coin/v0

Package
Open in gnoweb ↗

Overview

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

Files (4)

  • README.mdmarkdown
  • gnomod.tomltoml
  • coin.gnogno
  • coin_test.gnogno
coin.gnogno
1// Package coin is a GRC20 that cannot exist until its author has answered the2// three questions a social app's token usually dodges: what mints it, what3// burns it, and who has a reason to buy it.4//5// # Why a package and not a convention6//7// Every app in the x/social family is asked the same question, "and can it8// have a token", and the honest answer is only yes when all three of those9// have an answer. A token with a mint rule and no sink is a scoreboard with a10// price: supply grows, nothing consumes it, and the number on the leaderboard11// is the whole product. That is the shape a chain-wide measurement keeps12// finding, and it is cheap to avoid, so [New] refuses a [Policy] with a blank13// field rather than letting the omission ship.14//15// The three strings are not validated beyond being non-empty, because no16// package can check that a sink is real. They are a declaration, rendered on17// the realm's own page by [Coin.Render], where being wrong is visible.18//19// # The model20//21//	earned  minted by the app, for the behaviour the app wants more of22//	spent   burned by the app, for the thing holders actually want23//	traded  a plain GRC20 transfer, which is what makes the sink a market24//25// Earning and spending are the app's business and go through [Coin.Earn] and26// [Coin.Spend], which keep running totals so a reader can see the two halves27// against each other. Transfers, approvals and balances are the embedded28// [grc20.Token]'s, unchanged, so wallets and indexers see an ordinary GRC20.29//30// # Usage31//32// A realm holds one Coin and never exports the ledger:33//34//	var points = coin.New("Thread Points", "THREAD", 0, 0, coin.Policy{35//		Mint:  "1 per distinct address that replies to your thread",36//		Sink:  "burned to pin a thread to the top of its page",37//		Buyer: "anyone who wants placement and has not earned it",38//	}, 0, cur)39//40// The trailing `_ int, rlm realm` is the shape a pure package has to use to41// reach the caller's frame: a p/ package may not declare a crossing function,42// so the realm token is threaded as a later parameter, exactly as grc20's own43// tellers do.44package coin4546import (47	"gno.land/p/moul/kit/num/v0"48	"gno.land/p/moul/kit/ui/v0"49	"gno.land/p/moul/md/v0"50	"gno.land/p/nt/grc20/v0"51	"gno.land/p/nt/seqid/v0"52	"gno.land/p/nt/ufmt/v0"53)5455// Policy is the three declarations a Coin cannot be created without.56//57// Each one is a sentence for a human: it is rendered on the issuing realm's58// page and nothing branches on it.59type Policy struct {60	// Mint says what behaviour earns the token, precisely enough that a61	// reader can work out whether they can farm it.62	Mint string6364	// Sink says what destroys it. "Nothing" is not an answer; if there is65	// no sink, there is no reason for the token to exist.66	Sink string6768	// Buyer says who wants it badly enough to acquire it from someone who69	// earned it. This is the one most often left blank, and the one that70	// decides whether the other two matter.71	Buyer string72}7374// Valid reports whether every field is filled in.75func (p Policy) Valid() bool {76	return p.Mint != "" && p.Sink != "" && p.Buyer != ""77}7879// Coin is a GRC20 plus the policy it was issued under and the running totals80// of the two flows the policy describes.81type Coin struct {82	tok    *grc20.Token83	led    *grc20.PrivateLedger84	policy Policy8586	earned int64 // cumulative minted87	spent  int64 // cumulative burned88}8990// New issues the token. It panics when the policy is incomplete, which is the91// whole point of the package: the declaration happens before the first unit92// exists, not in a README written afterwards.93//94// name, symbol, decimals and id are grc20's own; id distinguishes two tokens95// issued by the same realm.96func New(name, symbol string, decimals int, id seqid.ID, policy Policy, _ int, rlm realm) *Coin {97	if !policy.Valid() {98		panic("coin: a token needs a mint rule, a sink and a buyer; one of the three is empty")99	}100	tok, led := grc20.NewToken(name, symbol, decimals, id, rlm)101	return &Coin{tok: tok, led: led, policy: policy}102}103104// Token returns the GRC20 itself, for the realm to expose as its read API and105// to register with a token registry.106func (c *Coin) Token() *grc20.Token { return c.tok }107108// CallerTeller is the teller that acts as the user who called the realm: it109// moves their own balance and nothing else.110//111// It is exposed, where the ledger is not, because transfer and approve are112// what make the sink a market. Mint and burn stay behind [Coin.Earn] and113// [Coin.Spend] so the mint rule keeps exactly one call site.114func (c *Coin) CallerTeller() grc20.Teller { return c.led.CallerTeller() }115116// Policy returns the declarations the token was issued under.117func (c *Coin) Policy() Policy { return c.policy }118119// Earn mints amount to addr. The realm calls it from the one place its mint120// rule is implemented, so that rule has exactly one call site.121//122// A non-positive amount is a no-op rather than an abort: a mint rule that123// computes zero (nothing new happened) is a normal outcome and should not124// fail the transaction that discovered it.125func (c *Coin) Earn(to address, amount int64) {126	if amount <= 0 {127		return128	}129	if err := c.led.Mint(to, amount); err != nil {130		panic("coin: mint: " + err.Error())131	}132	c.earned += amount133}134135// Spend burns amount from addr, which is how the sink consumes supply.136//137// It aborts when the balance is short, naming both numbers, because the138// caller is a user who is about to be told they cannot afford something.139func (c *Coin) Spend(from address, amount int64) {140	if amount <= 0 {141		panic("coin: spend: amount must be positive")142	}143	if bal := c.tok.BalanceOf(from); bal < amount {144		panic(ufmt.Sprintf("coin: balance %d is short of %d %s", bal, amount, c.tok.GetSymbol()))145	}146	if err := c.led.Burn(from, amount); err != nil {147		panic("coin: burn: " + err.Error())148	}149	c.spent += amount150}151152// BalanceOf is the holder's balance.153func (c *Coin) BalanceOf(addr address) int64 { return c.tok.BalanceOf(addr) }154155// Supply is what exists right now, that is, earned minus spent.156func (c *Coin) Supply() int64 { return c.tok.TotalSupply() }157158// Earned and Spent are the cumulative flows. Their difference is [Coin.Supply]159// and they are kept separately because a sink that never fires is invisible in160// the supply alone: a flat line reads the same as no sink at all.161func (c *Coin) Earned() int64 { return c.earned }162163// Spent is the cumulative amount burned through [Coin.Spend].164func (c *Coin) Spent() int64 { return c.spent }165166// Holders is how many addresses the ledger knows about.167func (c *Coin) Holders() int { return c.tok.KnownAccounts() }168169// Render is the block a realm puts on its own page: the three declarations,170// then the two flows against each other.171//172// The policy strings are escaped: they are constants in practice, but a realm173// could build one from configuration, and this package cannot tell.174func (c *Coin) Render() string {175	out := md.H3(ui.Inline(c.tok.GetName()) + " (" + ui.Inline(c.tok.GetSymbol()) + ")")176177	t := ui.NewTable("", "")178	t.Row("earns", ui.Cell(c.policy.Mint))179	t.Row("burns", ui.Cell(c.policy.Sink))180	t.Row("bought by", ui.Cell(c.policy.Buyer))181	out += t.String()182183	s := ui.NewTable("supply", "earned", "burned", "holders")184	s.Row(185		num.Dec(c.Supply(), c.tok.GetDecimals()),186		num.Dec(c.earned, c.tok.GetDecimals()),187		num.Dec(c.spent, c.tok.GetDecimals()),188		ufmt.Sprintf("%d", c.Holders()),189	)190	out += s.String()191192	return out193}194

Functions

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

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