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/nt/grc721/v0

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
nt / grc721
Files
8 (gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/nt/grc721/v0
gno
0.9

Files (8)

  • gnomod.tomltoml
  • tellers.gnogno
  • token.gnogno
  • types.gnogno
  • newtoken_event_filetest.gnogno
  • tellers_test.gnogno
  • token_test.gnogno
  • veto_no_event_filetest.gnogno
  • token.gnogno
    1package grc72123import (4	"chain"5	"math/overflow"6	"strconv"78

    Functions

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

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

    "gno.land/p/nt/seqid/v0"
    9 "gno.land/p/nt/ufmt/v0"
    10)
    11
    12// NewToken creates a core token and its ledger.
    13// rlm must be the caller's own captured cur (IsCurrent); its PkgPath becomes the unforgeable origRealm.
    14//
    15// Every successful call emits a NewToken event carrying the resulting Token.ID().
    16// Because Token's fields are unexported, NewToken is the only way a Token can come
    17// into existence, so this event makes token creation fully observable: an indexer
    18// that sees the same Token.ID() announced twice knows the realm built two
    19// independent ledgers behind one identifier, and that every later
    20// Mint/Burn/Transfer/Approval carrying that id is ambiguous. Such a realm is
    21// emitting untrustworthy events and should be flagged or ignored wholesale.
    22func NewToken(name, symbol string, id seqid.ID, rlm realm) (*Token, *PrivateLedger) {
    23 if !rlm.IsCurrent() {
    24 panic(ErrSpoofedRealm)
    25 }
    26
    27 pkgPath := rlm.PkgPath()
    28 if pkgPath == "" {
    29 panic(ErrNotRealm)
    30 }
    31
    32 if !validName(name) {
    33 panic(ErrInvalidName)
    34 }
    35
    36 if !validSymbol(symbol) {
    37 panic(ErrInvalidSymbol)
    38 }
    39
    40 // origRealm is the host path, with any ":subpath" synthesized by realm.Sub
    41 // stripped. guardHome strips the invoking path the same way, so a token
    42 // created from a sub frame is not locked out of its own host realm, and a
    43 // host's sub-realms are not falsely rejected. Token.ID() keeps the raw
    44 // pkgPath, so identities and the NewToken event are unaffected.
    45 origRealm, _, _ := chain.SplitPkgSubPath(pkgPath)
    46
    47 ledger := &PrivateLedger{}
    48 token := &Token{
    49 id: pkgPath + "." + symbol + "." + id.String(),
    50 name: name,
    51 symbol: symbol,
    52 ledger: ledger,
    53 origRealm: origRealm,
    54 }
    55 ledger.token = token
    56
    57 chain.Emit(
    58 NewTokenEvent,
    59 "token", token.id,
    60 "name", name,
    61 "symbol", symbol,
    62 )
    63
    64 return token, ledger
    65}
    66
    67func validName(name string) bool {
    68 if name == "" || len(name) > MaxNameLen {
    69 return false
    70 }
    71
    72 for _, c := range name {
    73 if c < 0x20 || c == 0x7f {
    74 return false
    75 }
    76 }
    77
    78 return true
    79}
    80
    81func validSymbol(s string) bool {
    82 if s == "" || len(s) > MaxSymbolLen {
    83 return false
    84 }
    85
    86 for _, c := range s {
    87 if !isAlnum(c) && c != '_' && c != '-' {
    88 return false
    89 }
    90 }
    91
    92 return true
    93}
    94
    95func isAlnum(c rune) bool {
    96 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
    97}
    98
    99func (tok *Token) GetName() string { return tok.name }
    100
    101func (tok *Token) GetSymbol() string { return tok.symbol }
    102
    103func (tok *Token) ID() string { return tok.id }
    104
    105func (tok *Token) TotalSupply() int64 { return tok.ledger.totalSupply }
    106
    107func (tok *Token) KnownAccounts() int { return tok.ledger.balances.Size() }
    108
    109// EIP-721: balanceOf throws for queries about the zero address,
    110// since NFTs assigned to it are considered invalid.
    111func (tok *Token) BalanceOf(addr address) (int64, error) {
    112 if !addr.IsValid() {
    113 return 0, ErrInvalidAddress
    114 }
    115
    116 return tok.ledger.balanceOf(addr), nil
    117}
    118
    119func (tok *Token) OwnerOf(tid TokenID) (address, error) { return tok.ledger.ownerOf(tid) }
    120
    121// EIP-721: getApproved throws for a token that is not a valid NFT, so a missing
    122// token is ErrInvalidTokenId and a live but unapproved one is ErrTokenIdNotApproved.
    123func (tok *Token) GetApproved(tid TokenID) (address, error) {
    124 if !tok.ledger.exists(tid) {
    125 return zeroAddress, ErrInvalidTokenId
    126 }
    127
    128 addr := tok.ledger.tokenApprovals.Get(tid.String())
    129 if addr == nil {
    130 return zeroAddress, ErrTokenIdNotApproved
    131 }
    132
    133 return addr.(address), nil
    134}
    135
    136func (tok *Token) IsApprovedForAll(owner, operator address) bool {
    137 return tok.ledger.isApprovedForAll(owner, operator)
    138}
    139
    140func (tok *Token) RenderHome() string {
    141 str := ""
    142 str += ufmt.Sprintf("# %s ($%s)\n\n", tok.name, tok.symbol)
    143 str += ufmt.Sprintf("* **Total supply**: %d\n", tok.ledger.totalSupply)
    144 str += ufmt.Sprintf("* **Known accounts**: %d\n", tok.KnownAccounts())
    145
    146 return str
    147}
    148
    149// RegisterExtension attaches an issuer-supplied hook set. Issuer-only (holds the
    150// PrivateLedger), which keeps the surface from being attacker-attachable — but the
    151// attachment is still a privilege: per the Extension doc, an attached hook can veto
    152// transfers (a panic aborts the transaction before the Transfer event is emitted) and
    153// adds gas to every movement.
    154// One extension per kind; a second registration of an attached kind panics.
    155func (led *PrivateLedger) RegisterExtension(ext Extension) {
    156 if ext == nil {
    157 panic("grc721: nil extension")
    158 }
    159
    160 kind := ext.ExtensionKind()
    161 for _, e := range led.extensions {
    162 if e.ExtensionKind() == kind {
    163 panic("grc721: extension kind already registered: " + kind)
    164 }
    165 }
    166
    167 led.extensions = append(led.extensions, ext)
    168}
    169
    170func (led *PrivateLedger) ReadToken() *Token { return led.token }
    171
    172func (led *PrivateLedger) Mint(to address, tid TokenID) error {
    173 if !to.IsValid() {
    174 return ErrInvalidAddress
    175 }
    176
    177 if tid == "" {
    178 return ErrInvalidTokenId
    179 }
    180
    181 if led.exists(tid) {
    182 return ErrTokenIdAlreadyExists
    183 }
    184
    185 led.balances.Set(to.String(), overflow.Add64p(led.balanceOf(to), 1))
    186 led.owners.Set(tid.String(), to)
    187 led.totalSupply = overflow.Add64p(led.totalSupply, 1)
    188
    189 for _, ext := range led.extensions {
    190 ext.OnMint(to, tid)
    191 }
    192
    193 // Mint emits Transfer from the empty address (EIP-721); indexers reconstruct ownership from Transfer alone.
    194 chain.Emit(
    195 TransferEvent,
    196 "token", led.token.id,
    197 "from", "",
    198 "to", to.String(),
    199 "tokenId", tid.String(),
    200 )
    201
    202 return nil
    203}
    204
    205// Fans OnBurn out to every extension so per-token state cannot resurface if tid is re-minted.
    206func (led *PrivateLedger) Burn(tid TokenID) error {
    207 owner, err := led.ownerOf(tid)
    208 if err != nil {
    209 return err
    210 }
    211
    212 tidStr := tid.String()
    213 led.tokenApprovals.Remove(tidStr)
    214 led.setBalance(owner, overflow.Sub64p(led.balanceOf(owner), 1))
    215 led.owners.Remove(tidStr)
    216 led.totalSupply = overflow.Sub64p(led.totalSupply, 1)
    217
    218 for _, ext := range led.extensions {
    219 ext.OnBurn(tid)
    220 }
    221
    222 // Burn emits Transfer to the empty address (EIP-721).
    223 chain.Emit(
    224 TransferEvent,
    225 "token", led.token.id,
    226 "from", owner.String(),
    227 "to", "",
    228 "tokenId", tidStr,
    229 )
    230
    231 return nil
    232}
    233
    234// spender must own or be approved for tid; callers derive it from a trusted per-frame identity.
    235func (led *PrivateLedger) TransferFrom(spender, from, to address, tid TokenID) error {
    236 if !led.isApprovedOrOwner(spender, tid) {
    237 return ErrCallerIsNotOwnerOrApproved
    238 }
    239
    240 return led.transfer(from, to, tid)
    241}
    242
    243// caller must own tid or be an approved operator of the owner.
    244// EIP-721: approving the zero address clears (revokes) any single-token approval.
    245func (led *PrivateLedger) Approve(caller, to address, tid TokenID) error {
    246 revoke := to == zeroAddress
    247 if !revoke && !to.IsValid() {
    248 return ErrInvalidAddress
    249 }
    250
    251 owner, err := led.ownerOf(tid)
    252 if err != nil {
    253 return err
    254 }
    255
    256 if owner == to {
    257 return ErrApprovalToCurrentOwner
    258 }
    259
    260 if caller != owner && !led.isApprovedForAll(owner, caller) {
    261 return ErrCallerIsNotOwnerOrApproved
    262 }
    263
    264 if revoke {
    265 led.tokenApprovals.Remove(tid.String())
    266 } else {
    267 led.tokenApprovals.Set(tid.String(), to)
    268 }
    269
    270 chain.Emit(
    271 ApprovalEvent,
    272 "token", led.token.id,
    273 "owner", owner.String(),
    274 "to", to.String(), // empty for a revoke
    275 "tokenId", tid.String(),
    276 )
    277
    278 return nil
    279}
    280
    281func (led *PrivateLedger) SetApprovalForAll(owner, operator address, approved bool) error {
    282 if !owner.IsValid() || !operator.IsValid() {
    283 return ErrInvalidAddress
    284 }
    285
    286 if owner == operator {
    287 return ErrApprovalToCurrentOwner
    288 }
    289
    290 key := operatorKey(owner, operator)
    291 if approved {
    292 led.operatorApprovals.Set(key, approved)
    293 } else {
    294 led.operatorApprovals.Remove(key)
    295 }
    296
    297 chain.Emit(
    298 ApprovalForAllEvent,
    299 "token", led.token.id,
    300 "owner", owner.String(),
    301 "to", operator.String(),
    302 "approved", strconv.FormatBool(approved),
    303 )
    304
    305 return nil
    306}
    307
    308func (led *PrivateLedger) transfer(from, to address, tid TokenID) error {
    309 if !from.IsValid() || !to.IsValid() {
    310 return ErrInvalidAddress
    311 }
    312
    313 if from == to {
    314 return ErrCannotTransferToSelf
    315 }
    316
    317 owner, err := led.ownerOf(tid)
    318 if err != nil {
    319 return err
    320 }
    321
    322 if owner != from {
    323 return ErrTransferFromIncorrectOwner
    324 }
    325
    326 tidStr := tid.String()
    327 led.tokenApprovals.Remove(tidStr)
    328 led.setBalance(from, overflow.Sub64p(led.balanceOf(from), 1))
    329 led.setBalance(to, overflow.Add64p(led.balanceOf(to), 1))
    330 led.owners.Set(tidStr, to)
    331
    332 for _, ext := range led.extensions {
    333 ext.OnTransfer(from, to, tid)
    334 }
    335
    336 chain.Emit(
    337 TransferEvent,
    338 "token", led.token.id,
    339 "from", from.String(),
    340 "to", to.String(),
    341 "tokenId", tidStr,
    342 )
    343
    344 return nil
    345}
    346
    347func (led *PrivateLedger) ownerOf(tid TokenID) (address, error) {
    348 owner := led.owners.Get(tid.String())
    349 if owner == nil {
    350 return zeroAddress, ErrInvalidTokenId
    351 }
    352
    353 return owner.(address), nil
    354}
    355
    356func (led *PrivateLedger) balanceOf(addr address) int64 {
    357 balance := led.balances.Get(addr.String())
    358 if balance == nil {
    359 return 0
    360 }
    361
    362 return balance.(int64)
    363}
    364
    365func (led *PrivateLedger) setBalance(addr address, balance int64) {
    366 if balance == 0 {
    367 led.balances.Remove(addr.String())
    368
    369 return
    370 }
    371
    372 led.balances.Set(addr.String(), balance)
    373}
    374
    375func (led *PrivateLedger) isApprovedForAll(owner, operator address) bool {
    376 approved := led.operatorApprovals.Get(operatorKey(owner, operator))
    377 if approved == nil {
    378 return false
    379 }
    380
    381 return approved.(bool)
    382}
    383
    384func (led *PrivateLedger) isApprovedOrOwner(addr address, tid TokenID) bool {
    385 owner := led.owners.Get(tid.String())
    386 if owner == nil {
    387 return false
    388 }
    389
    390 ownerAddr := owner.(address)
    391 if addr == ownerAddr || led.isApprovedForAll(ownerAddr, addr) {
    392 return true
    393 }
    394
    395 approved := led.tokenApprovals.Get(tid.String())
    396
    397 return approved != nil && approved.(address) == addr
    398}
    399
    400func (led *PrivateLedger) exists(tid TokenID) bool {
    401 return led.owners.Has(tid.String())
    402}
    403
    404func operatorKey(owner, operator address) string {
    405 return owner.String() + ":" + operator.String()
    406}
    407