1// Package grc20faucet issues two free GRC20 tokens, RED and BLUE, and gives2// them away on request.3//4// It exists so that anything needing a token to experiment on has one that is5// worthless, unlimited and shared. Both tokens are registered in6// gno.land/r/nt/grc20reg/v0, which is what lets another realm find and move7// them by key without importing this package: that indirection is the whole8// point, and it is what gno.land/r/moul/x/grc20wrapdemo/v0 wraps.9//10// Two tokens rather than one, because the interesting patterns start at two: a11// meta-token over a pair needs a pair.12package grc20faucet1314import (15 "chain/runtime"1617 "gno.land/p/nt/avl/v0"18 "gno.land/p/nt/grc20/v0"19 "gno.land/p/nt/ufmt/v0"20 "gno.land/r/nt/grc20reg/v0"21)2223// Tokens are exported so an importing realm can read them directly; the24// ledgers stay private, since they carry unrestricted mint and burn.25var (26 Red *grc20.Token27 Blue *grc20.Token2829 redLedger *grc20.PrivateLedger30 blueLedger *grc20.PrivateLedger3132 // RedKey and BlueKey are the grc20reg lookup keys, the way a realm that33 // does NOT import this package refers to these tokens.34 RedKey string35 BlueKey string3637 lastClaim = avl.NewTree() // address -> block height of its last claim38)3940const (41 decimals = 44243 // ClaimAmount is minted of EACH token per claim: 100.0000 units.44 ClaimAmount = int64(1_000_000)4546 // ClaimEvery is the cooldown, in blocks, between two claims by the same47 // account. Free money with no cooldown is a spam vector, not a faucet.48 ClaimEvery = int64(100)49)5051func init(cur realm) {52 Red, redLedger = grc20.NewToken("Red Token", "RED", decimals, 0, cur)53 Blue, blueLedger = grc20.NewToken("Blue Token", "BLUE", decimals, 1, cur)54 RedKey = grc20reg.Register(cross(cur), Red, "red")55 BlueKey = grc20reg.Register(cross(cur), Blue, "blue")56}5758// Claim mints ClaimAmount of both RED and BLUE to the caller.59func Claim(cur realm) {60 who := caller(cur)61 now := runtime.ChainHeight()62 if next, ok := NextClaim(who); ok && now < next {63 panic(ufmt.Sprintf("too soon: next claim at block %d, current %d", next, now))64 }65 lastClaim.Set(who.String(), now)66 checkErr(redLedger.Mint(who, ClaimAmount))67 checkErr(blueLedger.Mint(who, ClaimAmount))68}6970// NextClaim returns the block height at which `who` may claim again, and71// whether they have ever claimed at all.72func NextClaim(who address) (int64, bool) {73 v := lastClaim.Get(who.String())74 if v == nil {75 return 0, false76 }77 return v.(int64) + ClaimEvery, true78}7980// Transfer moves the caller's own units of `symbol`.81func Transfer(cur realm, symbol string, to address, amount int64) {82 checkErr(ledgerOf(symbol).CallerTeller().Transfer(0, cur, to, amount))83}8485// Approve lets `spender` draw `amount` of `symbol` from the caller's balance.86//87// This is the call that makes every wrapper in grc20wrapdemo work: the wrapper88// realm has no authority over anybody's tokens until its address is named here.89func Approve(cur realm, symbol string, spender address, amount int64) {90 checkErr(ledgerOf(symbol).CallerTeller().Approve(0, cur, spender, amount))91}9293// TransferFrom spends an allowance the caller was granted.94func TransferFrom(cur realm, symbol string, from, to address, amount int64) {95 checkErr(ledgerOf(symbol).CallerTeller().TransferFrom(0, cur, from, to, amount))96}9798// BalanceOf returns `owner`'s balance of `symbol`.99func BalanceOf(symbol string, owner address) int64 {100 return tokenOf(symbol).BalanceOf(owner)101}102103// Allowance returns what `owner` let `spender` draw of `symbol`.104func Allowance(symbol string, owner, spender address) int64 {105 return tokenOf(symbol).Allowance(owner, spender)106}107108// TotalSupply returns how much of `symbol` has been claimed so far.109func TotalSupply(symbol string) int64 {110 return tokenOf(symbol).TotalSupply()111}112113func tokenOf(symbol string) *grc20.Token {114 switch symbol {115 case "RED":116 return Red117 case "BLUE":118 return Blue119 }120 panic("unknown symbol " + symbol + " (RED or BLUE)")121}122123func ledgerOf(symbol string) *grc20.PrivateLedger {124 switch symbol {125 case "RED":126 return redLedger127 case "BLUE":128 return blueLedger129 }130 panic("unknown symbol " + symbol + " (RED or BLUE)")131}132133// caller is the account or realm that crossed into this one.134func caller(cur realm) address {135 if !cur.IsCurrent() {136 panic("grc20faucet: stale realm token")137 }138 return cur.Previous().Address()139}140141func checkErr(err error) {142 if err != nil {143 panic(err)144 }145}146147func Render(path string) string {148 s := "# GRC20 faucet: RED and BLUE\n\n"149 s += "Two worthless tokens to experiment on. `Claim` gives you "150 s += ufmt.Sprintf("%d of each, once every %d blocks.\n\n", ClaimAmount, ClaimEvery)151152 s += "| token | symbol | decimals | supply | registry key |\n"153 s += "|---|---|---|---|---|\n"154 s += ufmt.Sprintf("| %s | RED | %d | %d | `%s` |\n",155 Red.GetName(), Red.GetDecimals(), Red.TotalSupply(), RedKey)156 s += ufmt.Sprintf("| %s | BLUE | %d | %d | `%s` |\n",157 Blue.GetName(), Blue.GetDecimals(), Blue.TotalSupply(), BlueKey)158159 s += "\n## Use it\n\n"160 s += "```\n"161 s += "gnokey maketx call -pkgpath gno.land/r/moul/x/grc20faucet/v0 -func Claim ...\n"162 s += "gnokey maketx call -pkgpath gno.land/r/moul/x/grc20faucet/v0 -func Approve \\\n"163 s += " -args RED -args <spender> -args 1000000 ...\n"164 s += "```\n\n"165 s += "Both tokens are registered in [grc20reg](/r/nt/grc20reg/v0), so a realm can\n"166 s += "move them by key without importing this one. That is what\n"167 s += "[grc20wrapdemo](/r/moul/x/grc20wrapdemo/v0) does with them.\n"168169 if path != "" {170 s += "\n---\n\n"171 s += ufmt.Sprintf("Balance of `%s`: RED %d, BLUE %d\n",172 path, Red.BalanceOf(address(path)), Blue.BalanceOf(address(path)))173 }174 return s175}176Allowance(symbol 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}, symbol string, spender string, amount int64)
BalanceOf(symbol string, owner string) int64
Claim(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})
NextClaim(who string) (int64, bool)
Render(path string) string
TotalSupply(symbol 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}, symbol 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}, symbol string, from string, to string, amount int64)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
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.