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