1package tokenfactory23import (4 "chain"5 "chain/banker"6 "chain/runtime/unsafe"78 "gno.land/p/nt/avl/v0"9 "gno.land/p/nt/grc20/v0"10 "gno.land/p/nt/ownable/v0"11 "gno.land/p/nt/seqid/v0"12 "gno.land/r/nt/grc20reg/v0"13)1415const creationFee = int64(40000000) // 40 GNOT1617var (18 instances avl.Tree19 nextTokenID seqid.ID2021 feeRecipient = address(22 "g17mgsf396a9ysyj4z22ft0zc0xv8ka6dns4fx7f",23 )24)2526type instance struct {27 token *grc20.Token28 ledger *grc20.PrivateLedger29 admin *ownable.Ownable30}3132// CreationFee returns the token creation fee in ugnot.33func CreationFee() int64 {34 return creationFee35}3637// FeeRecipient returns the address receiving creation fees.38func FeeRecipient() address {39 return feeRecipient40}4142// CreateToken creates a new GRC20 token.43//44// initialSupply is expressed in the token's smallest unit.45//46// Example:47// decimals = 648// initialSupply = 100000000000049// => 1,000,000 tokens.50//51// A direct user call must attach exactly 40 GNOT.52func CreateToken(53 cur realm,54 name string,55 symbol string,56 decimals int,57 initialSupply int64,58) string {59 // Creation must come directly from a user wallet.60 if !cur.Previous().IsUserCall() {61 panic("token creation must be called directly by a user")62 }6364 // Exactly 40 GNOT must be attached.65 if unsafe.OriginSend().AmountOf("ugnot") != creationFee {66 panic("invalid token creation fee")67 }6869 if name == "" {70 panic("token name cannot be empty")71 }7273 if symbol == "" {74 panic("token symbol cannot be empty")75 }7677 if decimals < 0 {78 panic("decimals cannot be negative")79 }8081 if initialSupply < 0 {82 panic("initial supply cannot be negative")83 }8485 if instances.Has(symbol) {86 panic("token symbol already exists")87 }8889 creator := cur.Previous().Address()9091 token, ledger := grc20.NewToken(92 name,93 symbol,94 decimals,95 nextTokenID.Next(),96 cur,97 )9899 // Mint the user-selected initial supply directly100 // to the wallet that created the token.101 if initialSupply > 0 {102 checkErr(103 ledger.Mint(104 creator,105 initialSupply,106 ),107 )108 }109110 inst := instance{111 token: token,112 ledger: ledger,113 admin: ownable.NewWithAddress(creator),114 }115116 instances.Set(117 symbol,118 &inst,119 )120121 // Register the token in the canonical GRC20 registry.122 grc20reg.Register(123 cross(cur),124 token,125 symbol,126 )127128 // Transfer the 40 GNOT creation fee129 // from the Token Factory realm directly130 // to the GNO Explorer fee recipient.131 banker_ := banker.NewBanker(132 banker.BankerTypeRealmSend,133 cur,134 )135136 feeCoins := chain.NewCoins(137 chain.NewCoin(138 "ugnot",139 creationFee,140 ),141 )142143 banker_.SendCoins(144 cur.Address(),145 feeRecipient,146 feeCoins,147 )148149 return token.ID()150}151152// Bank returns the GRC20 token instance.153func Bank(symbol string) *grc20.Token {154 return mustGetInstance(symbol).token155}156157// TokenID returns the unique GRC20 token ID.158func TokenID(symbol string) string {159 return mustGetInstance(symbol).token.ID()160}161162// TotalSupply returns the total token supply.163func TotalSupply(symbol string) int64 {164 return mustGetInstance(symbol).165 token.166 ReadonlyTeller().167 TotalSupply()168}169170// BalanceOf returns the token balance of an address.171func BalanceOf(172 symbol string,173 owner address,174) int64 {175 return mustGetInstance(symbol).176 token.177 ReadonlyTeller().178 BalanceOf(owner)179}180181// Mint allows the token owner to mint additional tokens.182func Mint(183 cur realm,184 symbol string,185 to address,186 amount int64,187) {188 if amount <= 0 {189 panic("mint amount must be greater than zero")190 }191192 inst := mustGetInstance(symbol)193194 inst.admin.AssertOwnedBy(195 cur.Previous().Address(),196 )197198 checkErr(199 inst.ledger.Mint(200 to,201 amount,202 ),203 )204}205206// Burn allows the token owner to burn tokens.207func Burn(208 cur realm,209 symbol string,210 from address,211 amount int64,212) {213 if amount <= 0 {214 panic("burn amount must be greater than zero")215 }216217 inst := mustGetInstance(symbol)218219 inst.admin.AssertOwnedBy(220 cur.Previous().Address(),221 )222223 checkErr(224 inst.ledger.Burn(225 from,226 amount,227 ),228 )229}230231// TransferTokenOwnership transfers administration232// of a token to another address.233func TransferTokenOwnership(234 cur realm,235 symbol string,236 newOwner address,237) {238 inst := mustGetInstance(symbol)239240 checkErr(241 inst.admin.TransferOwnership(242 0,243 cur,244 newOwner,245 ),246 )247}248249// DropTokenOwnership permanently removes250// the administrator of the token.251func DropTokenOwnership(252 cur realm,253 symbol string,254) {255 inst := mustGetInstance(symbol)256257 checkErr(258 inst.admin.DropOwnership(259 0,260 cur,261 ),262 )263}264265func mustGetInstance(266 symbol string,267) *instance {268 value := instances.Get(symbol)269270 if value == nil {271 panic("token does not exist")272 }273274 return value.(*instance)275}276277func checkErr(err error) {278 if err != nil {279 panic(err.Error())280 }281}BalanceOf(symbol string, owner string) int64
Bank(symbol string) *gno.land/p/nt/grc20/v0.Token
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}, symbol string, from string, amount int64)
CreateToken(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}, name string, symbol string, decimals int, initialSupply int64) string
CreationFee() int64
DropTokenOwnership(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)
FeeRecipient() string
Mint(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)
TokenID(symbol string) string
TotalSupply(symbol string) int64
TransferTokenOwnership(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, newOwner string)
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.