Pure package detail
grc721
gno.land/p/nym-helox118/grc721
Indexed deployment identity with independently loaded latest RPC source. Functions and Render are realm-only RPC capabilities.
Indexed deployment
Identity
- Package path
- gno.land/p/nym-helox118/grc721
- Block
- 29234
- Deployed (UTC)
- Transaction
- WvI3JaOGQdU0Y+qqS8KEhCaLDXFy1hFl8ZSjdMZ/BYI=
Latest RPC state
Source
package grc721
import (
"chain"
"math/overflow"
"strconv"
"gno.land/p/nt/seqid/v0"
"gno.land/p/nt/ufmt/v0"
)
// NewToken creates a core token and its ledger.
// rlm must be the caller's own captured cur (IsCurrent); its PkgPath becomes the unforgeable origRealm.
//
// Every successful call emits a NewToken event carrying the resulting Token.ID().
// Because Token's fields are unexported, NewToken is the only way a Token can come
// into existence, so this event makes token creation fully observable: an indexer
// that sees the same Token.ID() announced twice knows the realm built two
// independent ledgers behind one identifier, and that every later
// Mint/Burn/Transfer/Approval carrying that id is ambiguous. Such a realm is
// emitting untrustworthy events and should be flagged or ignored wholesale.
func NewToken(name, symbol string, id seqid.ID, rlm realm) (*Token, *PrivateLedger) {
if !rlm.IsCurrent() {
panic(ErrSpoofedRealm)
}
pkgPath := rlm.PkgPath()
if pkgPath == "" {
panic(ErrNotRealm)
}
if !validName(name) {
panic(ErrInvalidName)
}
if !validSymbol(symbol) {
panic(ErrInvalidSymbol)
}
// origRealm is the host path, with any ":subpath" synthesized by realm.Sub
// stripped. guardHome strips the invoking path the same way, so a token
// created from a sub frame is not locked out of its own host realm, and a
// host's sub-realms are not falsely rejected. Token.ID() keeps the raw
// pkgPath, so identities and the NewToken event are unaffected.
origRealm, _, _ := chain.SplitPkgSubPath(pkgPath)
ledger := &PrivateLedger{}
token := &Token{
id: pkgPath + "." + symbol + "." + id.String(),
name: name,
symbol: symbol,
ledger: ledger,
origRealm: origRealm,
}
ledger.token = token
chain.Emit(
NewTokenEvent,
"token", token.id,
"name", name,
"symbol", symbol,
)
return token, ledger
}
func validName(name string) bool {
if name == "" || len(name) > MaxNameLen {
return false
}
for _, c := range name {
if c < 0x20 || c == 0x7f {
return false
}
}
return true
}
func validSymbol(s string) bool {
if s == "" || len(s) > MaxSymbolLen {
return false
}
for _, c := range s {
if !isAlnum(c) && c != '_' && c != '-' {
return false
}
}
return true
}
func isAlnum(c rune) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
}
func (tok *Token) GetName() string { return tok.name }
func (tok *Token) GetSymbol() string { return tok.symbol }
func (tok *Token) ID() string { return tok.id }
func (tok *Token) TotalSupply() int64 { return tok.ledger.totalSupply }
func (tok *Token) KnownAccounts() int { return tok.ledger.balances.Size() }
// EIP-721: balanceOf throws for queries about the zero address,
// since NFTs assigned to it are considered invalid.
func (tok *Token) BalanceOf(addr address) (int64, error) {
if !addr.IsValid() {
return 0, ErrInvalidAddress
}
return tok.ledger.balanceOf(addr), nil
}
func (tok *Token) OwnerOf(tid TokenID) (address, error) { return tok.ledger.ownerOf(tid) }
// EIP-721: getApproved throws for a token that is not a valid NFT, so a missing
// token is ErrInvalidTokenId and a live but unapproved one is ErrTokenIdNotApproved.
func (tok *Token) GetApproved(tid TokenID) (address, error) {
if !tok.ledger.exists(tid) {
return zeroAddress, ErrInvalidTokenId
}
addr := tok.ledger.tokenApprovals.Get(tid.String())
if addr == nil {
return zeroAddress, ErrTokenIdNotApproved
}
return addr.(address), nil
}
func (tok *Token) IsApprovedForAll(owner, operator address) bool {
return tok.ledger.isApprovedForAll(owner, operator)
}
func (tok *Token) RenderHome() string {
str := ""
str += ufmt.Sprintf("# %s ($%s)\n\n", tok.name, tok.symbol)
str += ufmt.Sprintf("* **Total supply**: %d\n", tok.ledger.totalSupply)
str += ufmt.Sprintf("* **Known accounts**: %d\n", tok.KnownAccounts())
return str
}
// RegisterExtension attaches an issuer-supplied hook set. Issuer-only (holds the
// PrivateLedger), which keeps the surface from being attacker-attachable — but the
// attachment is still a privilege: per the Extension doc, an attached hook can veto
// transfers (a panic aborts the transaction before the Transfer event is emitted) and
// adds gas to every movement.
// One extension per kind; a second registration of an attached kind panics.
func (led *PrivateLedger) RegisterExtension(ext Extension) {
if ext == nil {
panic("grc721: nil extension")
}
kind := ext.ExtensionKind()
for _, e := range led.extensions {
if e.ExtensionKind() == kind {
panic("grc721: extension kind already registered: " + kind)
}
}
led.extensions = append(led.extensions, ext)
}
func (led *PrivateLedger) ReadToken() *Token { return led.token }
func (led *PrivateLedger) Mint(to address, tid TokenID) error {
if !to.IsValid() {
return ErrInvalidAddress
}
if tid == "" {
return ErrInvalidTokenId
}
if led.exists(tid) {
return ErrTokenIdAlreadyExists
}
led.balances.Set(to.String(), overflow.Add64p(led.balanceOf(to), 1))
led.owners.Set(tid.String(), to)
led.totalSupply = overflow.Add64p(led.totalSupply, 1)
for _, ext := range led.extensions {
ext.OnMint(to, tid)
}
// Mint emits Transfer from the empty address (EIP-721); indexers reconstruct ownership from Transfer alone.
chain.Emit(
TransferEvent,
"token", led.token.id,
"from", "",
"to", to.String(),
"tokenId", tid.String(),
)
return nil
}
// Fans OnBurn out to every extension so per-token state cannot resurface if tid is re-minted.
func (led *PrivateLedger) Burn(tid TokenID) error {
owner, err := led.ownerOf(tid)
if err != nil {
return err
}
tidStr := tid.String()
led.tokenApprovals.Remove(tidStr)
led.setBalance(owner, overflow.Sub64p(led.balanceOf(owner), 1))
led.owners.Remove(tidStr)
led.totalSupply = overflow.Sub64p(led.totalSupply, 1)
for _, ext := range led.extensions {
ext.OnBurn(tid)
}
// Burn emits Transfer to the empty address (EIP-721).
chain.Emit(
TransferEvent,
"token", led.token.id,
"from", owner.String(),
"to", "",
"tokenId", tidStr,
)
return nil
}
// spender must own or be approved for tid; callers derive it from a trusted per-frame identity.
func (led *PrivateLedger) TransferFrom(spender, from, to address, tid TokenID) error {
if !led.isApprovedOrOwner(spender, tid) {
return ErrCallerIsNotOwnerOrApproved
}
return led.transfer(from, to, tid)
}
// caller must own tid or be an approved operator of the owner.
// EIP-721: approving the zero address clears (revokes) any single-token approval.
func (led *PrivateLedger) Approve(caller, to address, tid TokenID) error {
revoke := to == zeroAddress
if !revoke && !to.IsValid() {
return ErrInvalidAddress
}
owner, err := led.ownerOf(tid)
if err != nil {
return err
}
if owner == to {
return ErrApprovalToCurrentOwner
}
if caller != owner && !led.isApprovedForAll(owner, caller) {
return ErrCallerIsNotOwnerOrApproved
}
if revoke {
led.tokenApprovals.Remove(tid.String())
} else {
led.tokenApprovals.Set(tid.String(), to)
}
chain.Emit(
ApprovalEvent,
"token", led.token.id,
"owner", owner.String(),
"to", to.String(), // empty for a revoke
"tokenId", tid.String(),
)
return nil
}
func (led *PrivateLedger) SetApprovalForAll(owner, operator address, approved bool) error {
if !owner.IsValid() || !operator.IsValid() {
return ErrInvalidAddress
}
if owner == operator {
return ErrApprovalToCurrentOwner
}
key := operatorKey(owner, operator)
if approved {
led.operatorApprovals.Set(key, approved)
} else {
led.operatorApprovals.Remove(key)
}
chain.Emit(
ApprovalForAllEvent,
"token", led.token.id,
"owner", owner.String(),
"to", operator.String(),
"approved", strconv.FormatBool(approved),
)
return nil
}
func (led *PrivateLedger) transfer(from, to address, tid TokenID) error {
if !from.IsValid() || !to.IsValid() {
return ErrInvalidAddress
}
if from == to {
return ErrCannotTransferToSelf
}
owner, err := led.ownerOf(tid)
if err != nil {
return err
}
if owner != from {
return ErrTransferFromIncorrectOwner
}
tidStr := tid.String()
led.tokenApprovals.Remove(tidStr)
led.setBalance(from, overflow.Sub64p(led.balanceOf(from), 1))
led.setBalance(to, overflow.Add64p(led.balanceOf(to), 1))
led.owners.Set(tidStr, to)
for _, ext := range led.extensions {
ext.OnTransfer(from, to, tid)
}
chain.Emit(
TransferEvent,
"token", led.token.id,
"from", from.String(),
"to", to.String(),
"tokenId", tidStr,
)
return nil
}
func (led *PrivateLedger) ownerOf(tid TokenID) (address, error) {
owner := led.owners.Get(tid.String())
if owner == nil {
return zeroAddress, ErrInvalidTokenId
}
return owner.(address), nil
}
func (led *PrivateLedger) balanceOf(addr address) int64 {
balance := led.balances.Get(addr.String())
if balance == nil {
return 0
}
return balance.(int64)
}
func (led *PrivateLedger) setBalance(addr address, balance int64) {
if balance == 0 {
led.balances.Remove(addr.String())
return
}
led.balances.Set(addr.String(), balance)
}
func (led *PrivateLedger) isApprovedForAll(owner, operator address) bool {
approved := led.operatorApprovals.Get(operatorKey(owner, operator))
if approved == nil {
return false
}
return approved.(bool)
}
func (led *PrivateLedger) isApprovedOrOwner(addr address, tid TokenID) bool {
owner := led.owners.Get(tid.String())
if owner == nil {
return false
}
ownerAddr := owner.(address)
if addr == ownerAddr || led.isApprovedForAll(ownerAddr, addr) {
return true
}
approved := led.tokenApprovals.Get(tid.String())
return approved != nil && approved.(address) == addr
}
func (led *PrivateLedger) exists(tid TokenID) bool {
return led.owners.Has(tid.String())
}
func operatorKey(owner, operator address) string {
return owner.String() + ":" + operator.String()
}
The verified vm/qfuncs operation accepts realm paths only.
Pure packages expose source files but do not have Realm Render.