Search Apps Documentation Source Content File Folder Download Copy Actions Download

transfer.gno

3.81 Kb · 107 lines
  1package gnogle_nft2
  2
  3import (
  4	"chain"
  5	"strconv"
  6
  7	"gno.land/p/g18wk4a80cr7dqa25vfka2yug5n3pd50udled6y3/grc721"
  8	"gno.land/p/nt/avl/v0"
  9)
 10
 11// tokenApprovals is the recommended, granular approval: a user approves an
 12// operator (e.g. the marketplace) for ONE specific token — the one they want to
 13// sell — not all of them. Key "collID/tokenID" -> approved operator address.
 14var tokenApprovals avl.Tree
 15
 16// operators is the optional "approve for all" map ("owner:operator" -> bool).
 17// Kept for ERC721 completeness, but the front-end defaults to per-token Approve.
 18var operators avl.Tree
 19
 20func tokKey(collID, tokenID string) string  { return collID + "/" + tokenID }
 21func opKey(owner, operator address) string  { return owner.String() + ":" + operator.String() }
 22
 23// Approve grants `operator` permission to transfer ONE token. The caller must
 24// own that token (or be an approve-for-all operator). Pass the zero address to
 25// revoke. The approval is cleared automatically when the token is transferred.
 26func Approve(cur realm, collID, tokenID string, operator address) {
 27	coll := mustGetCollection(collID)
 28	tid := grc721.TokenID(tokenID)
 29	owner, err := coll.nft.OwnerOf(tid)
 30	if err != nil {
 31		panic(err)
 32	}
 33	caller := cur.Previous().Address()
 34	if caller != owner && !IsApprovedForAll(owner, caller) {
 35		panic("only the owner (or an approved operator) can approve this token")
 36	}
 37	if operator == zeroAddr {
 38		tokenApprovals.Remove(tokKey(collID, tokenID))
 39	} else {
 40		tokenApprovals.Set(tokKey(collID, tokenID), operator)
 41	}
 42	chain.Emit("Approval", "collection", collID, "tokenId", tokenID,
 43		"owner", owner.String(), "approved", operator.String())
 44}
 45
 46// GetApproved returns the operator approved for a single token (or the zero
 47// address if none).
 48func GetApproved(collID, tokenID string) address {
 49	v, ok := tokenApprovals.Get(tokKey(collID, tokenID))
 50	if !ok {
 51		return zeroAddr
 52	}
 53	return v.(address)
 54}
 55
 56// SetApprovalForAll grants/revokes an operator for ALL of the caller's tokens.
 57// Optional power-user convenience — the recommended flow is per-token Approve.
 58func SetApprovalForAll(cur realm, operator address, approved bool) {
 59	owner := cur.Previous().Address()
 60	if !operator.IsValid() {
 61		panic("invalid operator address")
 62	}
 63	operators.Set(opKey(owner, operator), approved)
 64	chain.Emit("ApprovalForAll", "owner", owner.String(), "operator", operator.String(),
 65		"approved", strconv.FormatBool(approved))
 66}
 67
 68// IsApprovedForAll reports whether operator may move all of owner's tokens.
 69func IsApprovedForAll(owner, operator address) bool {
 70	v, ok := operators.Get(opKey(owner, operator))
 71	return ok && v.(bool)
 72}
 73
 74// isAuthorized reports whether `operator` may transfer (collID,tokenID) owned by
 75// `owner`: the owner, the per-token approved operator, or an approve-for-all
 76// operator.
 77func isAuthorized(owner, operator address, collID, tokenID string) bool {
 78	return operator == owner ||
 79		GetApproved(collID, tokenID) == operator ||
 80		IsApprovedForAll(owner, operator)
 81}
 82
 83// TransferFrom moves a token from `from` to `to`. The caller must be the owner,
 84// the per-token approved operator, or an approve-for-all operator. The per-token
 85// approval is cleared on transfer (so it never carries to the new owner).
 86func TransferFrom(cur realm, collID string, from, to address, tokenID string) {
 87	caller := cur.Previous().Address()
 88	coll := mustGetCollection(collID)
 89	tid := grc721.TokenID(tokenID)
 90
 91	owner, err := coll.nft.OwnerOf(tid)
 92	if err != nil {
 93		panic(err)
 94	}
 95	if owner != from {
 96		panic("from is not the current owner")
 97	}
 98	if !isAuthorized(from, caller, collID, tokenID) {
 99		panic("caller is not the owner or an approved operator")
100	}
101	if err := coll.nft.TransferFrom(from, from, to, tid); err != nil {
102		panic(err)
103	}
104	tokenApprovals.Remove(tokKey(collID, tokenID))
105	chain.Emit("Transfer", "collection", collID, "tokenId", tokenID,
106		"from", from.String(), "to", to.String())
107}