1// Package erc721 is an idiomatic gno.land port of the Solidity ERC-7212// non-fungible token standard. Each token has a unique integer id owned by3// exactly one address; ids are minted sequentially. Ownership, per-owner4// balances and single-token approvals are kept in ordered avl trees so that5// Render can iterate deterministically.6package erc72178import (9 "strconv"1011 "chain"12 "chain/runtime/unsafe"1314 "gno.land/p/nt/avl/v0"15)1617const (18 name = "Gno NFT"19 symbol = "GNFT"20)2122var (23 owners avl.Tree // tokenID (zero-padded string) -> address24 balances avl.Tree // owner address (string) -> uint6425 approvals avl.Tree // tokenID (zero-padded string) -> approved address26 nextID int64 = 127 minted int64 // total tokens ever minted (== live supply, no burn)28)2930// key formats a token id into a zero-padded, lexicographically-sortable key.31func key(id int64) string {32 s := strconv.FormatInt(id, 10)33 for len(s) < 12 {34 s = "0" + s35 }36 return s37}3839// Mint creates the next token id and assigns it to `to`. Only sequential40// minting is supported (id is returned via the Mint event).41func Mint(cur realm, to address) int64 {42 if !to.IsValid() {43 panic("erc721: mint to invalid address")44 }45 id := nextID46 nextID++47 minted++4849 owners.Set(key(id), to)50 balances.Set(to.String(), balanceOf(to)+1)5152 chain.Emit("Mint", "to", to.String(), "tokenID", strconv.FormatInt(id, 10))53 return id54}5556// Transfer moves token `id` from the caller to `to`. The caller must own the57// token (approvals are cleared on transfer).58func Transfer(cur realm, to address, id int64) {59 if !to.IsValid() {60 panic("erc721: transfer to invalid address")61 }62 caller := unsafe.PreviousRealm().Address()63 from := ownerOf(id) // panics if the token does not exist6465 if caller != from {66 // allow the single-token approved operator too67 if approvedOf(id) != caller {68 panic("erc721: caller is neither owner nor approved")69 }70 }71 if from == to {72 panic("erc721: transfer to current owner")73 }7475 owners.Set(key(id), to)76 balances.Set(from.String(), balanceOf(from)-1)77 balances.Set(to.String(), balanceOf(to)+1)78 approvals.Remove(key(id)) // clear approval on transfer7980 chain.Emit("Transfer", "from", from.String(), "to", to.String(),81 "tokenID", strconv.FormatInt(id, 10))82}8384// Approve grants `spender` the right to transfer token `id`. Only the current85// owner may approve.86func Approve(cur realm, spender address, id int64) {87 caller := unsafe.PreviousRealm().Address()88 owner := ownerOf(id)89 if caller != owner {90 panic("erc721: approve caller is not owner")91 }92 approvals.Set(key(id), spender)93 chain.Emit("Approval", "owner", owner.String(), "spender", spender.String(),94 "tokenID", strconv.FormatInt(id, 10))95}9697// --- read-only helpers (safe to call from tests and Render) ---9899// ownerOf returns the owner of token `id`, panicking if it does not exist.100func ownerOf(id int64) address {101 v := owners.Get(key(id))102 if v == nil {103 panic("erc721: query for nonexistent token")104 }105 return v.(address)106}107108// approvedOf returns the approved address for token `id`, or the zero address.109func approvedOf(id int64) address {110 v := approvals.Get(key(id))111 if v == nil {112 return address("")113 }114 return v.(address)115}116117// balanceOf returns how many tokens `owner` holds.118func balanceOf(owner address) uint64 {119 v := balances.Get(owner.String())120 if v == nil {121 return 0122 }123 return v.(uint64)124}125126// exists reports whether token `id` has been minted (and not since moved away).127func exists(id int64) bool {128 return owners.Has(key(id))129}130131// totalSupply returns the number of tokens in circulation.132func totalSupply() int64 { return minted }133134// OwnerOf is the exported read-only accessor for ownerOf.135func OwnerOf(id int64) address { return ownerOf(id) }136137// BalanceOf is the exported read-only accessor for balanceOf.138func BalanceOf(owner address) uint64 { return balanceOf(owner) }139140// TotalSupply is the exported read-only accessor for totalSupply.141func TotalSupply() int64 { return totalSupply() }142143// Render displays collection metadata and a token -> owner table.144func Render(path string) string {145 out := "# " + name + " (" + symbol + ")\n\n"146 out += "**Total supply:** " + strconv.FormatInt(totalSupply(), 10) + "\n\n"147148 if owners.Size() == 0 {149 out += "_No tokens minted yet._\n"150 return out151 }152153 out += "| Token ID | Owner | Approved |\n"154 out += "|---------:|-------|----------|\n"155 owners.Iterate("", "", func(k string, v interface{}) bool {156 owner := v.(address)157 id, _ := strconv.ParseInt(k, 10, 64)158 appr := approvedOf(id)159 apprStr := "—"160 if appr != address("") {161 apprStr = appr.String()162 }163 out += "| " + strconv.FormatInt(id, 10) + " | " +164 owner.String() + " | " + apprStr + " |\n"165 return false166 })167 return out168}169Approve(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}, spender string, id int64)
BalanceOf(owner string) uint64
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}, to string) int64
OwnerOf(id int64) string
Render(path string) string
TotalSupply() 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}, to string, id 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.