gnft.gno
7.13 Kb · 272 lines
1package gnft
2
3import (
4 "chain"
5 "errors"
6
7 "gno.land/p/gnoswap/deps/grc721"
8 ufmt "gno.land/p/nt/ufmt/v0"
9 "gno.land/r/gnoswap/access"
10
11 prabc "gno.land/p/gnoswap/rbac"
12 _ "gno.land/r/gnoswap/rbac"
13)
14
15var nft *grc721.BasicNFT
16
17func init(cur realm) {
18 nft = grc721.NewBasicNFT(0, cur, "GNOSWAP NFT", "GNFT")
19}
20
21// Name returns the NFT collection name.
22func Name() string {
23 return nft.Name()
24}
25
26// Symbol returns the NFT symbol.
27func Symbol() string {
28 return nft.Symbol()
29}
30
31// TotalSupply returns the total number of NFTs minted.
32func TotalSupply() int64 {
33 return nft.TokenCount()
34}
35
36// TokenURI returns the metadata URI for the specified token ID.
37// If stored value is in parameter format (x1,y1,x2,y2,color1,color2),
38// it converts to full base64-encoded SVG image URI on read.
39func TokenURI(tid grc721.TokenID) (string, error) {
40 stored, err := nft.TokenURI(tid)
41 if err != nil {
42 return "", err
43 }
44
45 params, err := parseImageParams(stored)
46 if err == nil {
47 return params.generateImageURI(), nil
48 }
49
50 return stored, nil
51}
52
53// BalanceOf returns the number of NFTs owned by the specified address.
54func BalanceOf(owner address) (int64, error) {
55 assertIsValidAddress(owner)
56 return nft.BalanceOf(owner)
57}
58
59// OwnerOf returns the owner address for the specified token ID.
60func OwnerOf(tid grc721.TokenID) (address, error) {
61 return nft.OwnerOf(tid)
62}
63
64// MustOwnerOf returns the owner address for the specified token ID.
65// It panics if the token ID is invalid.
66func MustOwnerOf(tid grc721.TokenID) address {
67 ownerAddr, err := nft.OwnerOf(tid)
68 checkErr(err)
69 return ownerAddr
70}
71
72// SetTokenURI sets the metadata URI for the specified token.
73//
74// Parameters:
75// - tid: token ID
76// - tURI: token URI
77//
78// Only callable by position contract.
79func SetTokenURI(cur realm, tid grc721.TokenID, tURI grc721.TokenURI) (bool, error) {
80 caller := cur.Previous().Address()
81 access.AssertIsPosition(caller)
82
83 assertIsValidTokenURI(tid)
84
85 checkErr(setTokenURI(0, cur, tid, tURI))
86
87 return true, nil
88}
89
90// SafeTransferFrom transfers token ownership with receiver validation.
91//
92// Parameters:
93// - from: current owner address
94// - to: recipient address
95// - tid: token ID to transfer
96//
97// Returns error if transfer fails.
98//
99// Permission model:
100// - Tokens held by the staker contract (i.e. currently staked) can only be
101// moved by the staker itself; the underlying staked LP position is
102// non-transferable.
103// - Otherwise, ownership and approval are enforced by the GRC721 layer
104// (owner / approved-for-token / approved-for-all).
105func SafeTransferFrom(cur realm, from, to address, tid grc721.TokenID) error {
106 assertFromIsValidAddress(from)
107 assertToIsValidAddress(to)
108 caller := cur.Previous().Address()
109 assertIsAllowedTransfer(caller, tid)
110
111 err := nft.SafeTransferFrom(caller, from, to, tid)
112 checkTransferErr(err, caller, from, to, tid)
113 return nil
114}
115
116// TransferFrom transfers a token from one address to another.
117//
118// Parameters:
119// - from: current owner address
120// - to: recipient address
121// - tid: token ID
122//
123// Returns error if transfer fails.
124//
125// Permission model:
126// - Tokens held by the staker contract (i.e. currently staked) can only be
127// moved by the staker itself; the underlying staked LP position is
128// non-transferable.
129// - Otherwise, ownership and approval are enforced by the GRC721 layer
130// (owner / approved-for-token / approved-for-all).
131func TransferFrom(cur realm, from, to address, tid grc721.TokenID) error {
132 assertFromIsValidAddress(from)
133 assertToIsValidAddress(to)
134 caller := cur.Previous().Address()
135 assertIsAllowedTransfer(caller, tid)
136
137 err := nft.TransferFrom(caller, from, to, tid)
138 checkTransferErr(err, caller, from, to, tid)
139 return nil
140}
141
142// Approve grants permission to transfer a specific token ID to another address.
143//
144// Parameters:
145// - approved: address to approve
146// - tid: token ID to approve for transfer
147//
148// Returns error if approval fails.
149func Approve(cur realm, approved address, tid grc721.TokenID) error {
150 assertIsValidAddress(approved)
151
152 caller := cur.Previous().Address()
153 err := nft.Approve(caller, approved, tid)
154 checkApproveErr(err, caller, approved, tid)
155 return nil
156}
157
158// SetApprovalForAll enables/disables operator approval for all tokens.
159//
160// Parameters:
161// - operator: address to set approval for
162// - approved: true to approve, false to revoke
163//
164// Returns error if operation fails.
165func SetApprovalForAll(cur realm, operator address, approved bool) error {
166 assertIsValidAddress(operator)
167
168 checkErr(nft.SetApprovalForAll(cur.Previous().Address(), operator, approved))
169 return nil
170}
171
172// GetApproved returns approved address for token ID.
173//
174// Parameters:
175// - tid: token ID to check
176//
177// Returns approved address and error if token doesn't exist.
178func GetApproved(tid grc721.TokenID) (address, error) {
179 return nft.GetApproved(tid)
180}
181
182// IsApprovedForAll checks if operator can manage all owner's tokens.
183//
184// Parameters:
185// - owner: token owner address
186// - operator: operator address to check
187//
188// Returns true if operator is approved for all owner's tokens.
189func IsApprovedForAll(owner, operator address) bool {
190 return nft.IsApprovedForAll(owner, operator)
191}
192
193// Mint creates new NFT and transfers to address.
194//
195// Parameters:
196// - to: recipient address
197// - tid: token ID
198//
199// Returns minted token ID.
200// Only callable by position contract.
201func Mint(cur realm, to address, tid grc721.TokenID) grc721.TokenID {
202 caller := cur.Previous().Address()
203 access.AssertIsPosition(caller)
204
205 positionAddr := access.MustGetAddress(prabc.ROLE_POSITION.String())
206 checkErr(nft.Mint(positionAddr, tid))
207
208 // Store only the gradient parameters instead of full base64 SVG to reduce storage costs.
209 // Parameters are converted to full SVG on read via TokenURI().
210 imageParams := genImageParamsString(generateRandInstance())
211 checkErr(setTokenURI(0, cur, tid, grc721.TokenURI(imageParams)))
212
213 checkErr(nft.TransferFrom(positionAddr, positionAddr, to, tid))
214
215 return tid
216}
217
218// Exists checks if token ID exists.
219func Exists(tid grc721.TokenID) bool {
220 _, err := nft.OwnerOf(tid)
221 return err == nil
222}
223
224// Burn removes a specific token ID.
225//
226// Parameters:
227// - tid: token ID to burn
228//
229// Only callable by position.
230func Burn(cur realm, tid grc721.TokenID) {
231 caller := cur.Previous().Address()
232 access.AssertIsPosition(caller)
233
234 checkErr(nft.Burn(tid))
235}
236
237// Render returns the HTML representation of the NFT.
238func Render(path string) string {
239 if path == "" {
240 return nft.RenderHome()
241 }
242 return "404\n"
243}
244
245// setTokenURI sets the metadata URI for a specific token ID.
246func setTokenURI(_ int, rlm realm, tid grc721.TokenID, tURI grc721.TokenURI) error {
247 if !rlm.IsCurrent() {
248 return errors.New(errSpoofedRealm)
249 }
250
251 previousRealm := rlm.Previous()
252 previousAddr := previousRealm.Address()
253
254 _, err := nft.SetTokenURI(previousAddr, tid, tURI)
255 if err != nil {
256 return makeErrorWithDetails(err.Error(), ufmt.Sprintf("token id (%s)", tid))
257 }
258 tokenURI, err := TokenURI(tid)
259 if err != nil {
260 return makeErrorWithDetails(err.Error(), ufmt.Sprintf("token id (%s)", tid))
261 }
262
263 chain.Emit(
264 "SetTokenURI",
265 "prevAddr", previousAddr.String(),
266 "prevRealm", previousRealm.PkgPath(),
267 "tokenId", string(tid),
268 "tokenURI", tokenURI,
269 )
270
271 return nil
272}