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}