gnft.gno
7.17 Kb · 273 lines
1package gnft
2
3import (
4 "chain"
5 "errors"
6
7 "gno.land/p/gnoswap/deps/tokens/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(tid), 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 assertIsNonEmptyTokenURI(tURI)
84 assertIsValidTokenURI(tid)
85
86 checkErr(setTokenURI(0, cur, tid, tURI))
87
88 return true, nil
89}
90
91// SafeTransferFrom transfers token ownership with receiver validation.
92//
93// Parameters:
94// - from: current owner address
95// - to: recipient address
96// - tid: token ID to transfer
97//
98// Returns error if transfer fails.
99//
100// Permission model:
101// - Tokens held by the staker contract (i.e. currently staked) can only be
102// moved by the staker itself; the underlying staked LP position is
103// non-transferable.
104// - Otherwise, ownership and approval are enforced by the GRC721 layer
105// (owner / approved-for-token / approved-for-all).
106func SafeTransferFrom(cur realm, from, to address, tid grc721.TokenID) error {
107 assertFromIsValidAddress(from)
108 assertToIsValidAddress(to)
109 caller := cur.Previous().Address()
110 assertIsAllowedTransfer(caller, tid)
111
112 err := nft.SafeTransferFrom(caller, from, to, tid)
113 checkTransferErr(err, caller, from, to, tid)
114 return nil
115}
116
117// TransferFrom transfers a token from one address to another.
118//
119// Parameters:
120// - from: current owner address
121// - to: recipient address
122// - tid: token ID
123//
124// Returns error if transfer fails.
125//
126// Permission model:
127// - Tokens held by the staker contract (i.e. currently staked) can only be
128// moved by the staker itself; the underlying staked LP position is
129// non-transferable.
130// - Otherwise, ownership and approval are enforced by the GRC721 layer
131// (owner / approved-for-token / approved-for-all).
132func TransferFrom(cur realm, from, to address, tid grc721.TokenID) error {
133 assertFromIsValidAddress(from)
134 assertToIsValidAddress(to)
135 caller := cur.Previous().Address()
136 assertIsAllowedTransfer(caller, tid)
137
138 err := nft.TransferFrom(caller, from, to, tid)
139 checkTransferErr(err, caller, from, to, tid)
140 return nil
141}
142
143// Approve grants permission to transfer a specific token ID to another address.
144//
145// Parameters:
146// - approved: address to approve
147// - tid: token ID to approve for transfer
148//
149// Returns error if approval fails.
150func Approve(cur realm, approved address, tid grc721.TokenID) error {
151 assertIsValidAddress(approved)
152
153 caller := cur.Previous().Address()
154 err := nft.Approve(caller, approved, tid)
155 checkApproveErr(err, caller, approved, tid)
156 return nil
157}
158
159// SetApprovalForAll enables/disables operator approval for all tokens.
160//
161// Parameters:
162// - operator: address to set approval for
163// - approved: true to approve, false to revoke
164//
165// Returns error if operation fails.
166func SetApprovalForAll(cur realm, operator address, approved bool) error {
167 assertIsValidAddress(operator)
168
169 checkErr(nft.SetApprovalForAll(cur.Previous().Address(), operator, approved))
170 return nil
171}
172
173// GetApproved returns approved address for token ID.
174//
175// Parameters:
176// - tid: token ID to check
177//
178// Returns approved address and error if token doesn't exist.
179func GetApproved(tid grc721.TokenID) (address, error) {
180 return nft.GetApproved(tid)
181}
182
183// IsApprovedForAll checks if operator can manage all owner's tokens.
184//
185// Parameters:
186// - owner: token owner address
187// - operator: operator address to check
188//
189// Returns true if operator is approved for all owner's tokens.
190func IsApprovedForAll(owner, operator address) bool {
191 return nft.IsApprovedForAll(owner, operator)
192}
193
194// Mint creates new NFT and transfers to address.
195//
196// Parameters:
197// - to: recipient address
198// - tid: token ID
199//
200// Returns minted token ID.
201// Only callable by position contract.
202func Mint(cur realm, to address, tid grc721.TokenID) grc721.TokenID {
203 caller := cur.Previous().Address()
204 access.AssertIsPosition(caller)
205
206 positionAddr := access.MustGetAddress(prabc.ROLE_POSITION.String())
207 checkErr(nft.Mint(positionAddr, tid))
208
209 // Store only the gradient parameters instead of full base64 SVG to reduce storage costs.
210 // Parameters are converted to full SVG on read via TokenURI().
211 imageParams := genImageParamsString(generateRandInstance())
212 checkErr(setTokenURI(0, cur, tid, grc721.TokenURI(imageParams)))
213
214 checkErr(nft.TransferFrom(positionAddr, positionAddr, to, tid))
215
216 return tid
217}
218
219// Exists checks if token ID exists.
220func Exists(tid grc721.TokenID) bool {
221 _, err := nft.OwnerOf(tid)
222 return err == nil
223}
224
225// Burn removes a specific token ID.
226//
227// Parameters:
228// - tid: token ID to burn
229//
230// Only callable by position.
231func Burn(cur realm, tid grc721.TokenID) {
232 caller := cur.Previous().Address()
233 access.AssertIsPosition(caller)
234
235 checkErr(nft.Burn(tid))
236}
237
238// Render returns the HTML representation of the NFT.
239func Render(path string) string {
240 if path == "" {
241 return nft.RenderHome()
242 }
243 return "404\n"
244}
245
246// setTokenURI sets the metadata URI for a specific token ID.
247func setTokenURI(_ int, rlm realm, tid grc721.TokenID, tURI grc721.TokenURI) error {
248 if !rlm.IsCurrent() {
249 return errors.New(errSpoofedRealm)
250 }
251
252 previousRealm := rlm.Previous()
253 previousAddr := previousRealm.Address()
254
255 _, err := nft.SetTokenURI(previousAddr, tid, tURI)
256 if err != nil {
257 return makeErrorWithDetails(err.Error(), ufmt.Sprintf("token id (%s)", tid))
258 }
259 tokenURI, err := TokenURI(tid)
260 if err != nil {
261 return makeErrorWithDetails(err.Error(), ufmt.Sprintf("token id (%s)", tid))
262 }
263
264 chain.Emit(
265 "SetTokenURI",
266 "prevAddr", previousAddr.String(),
267 "prevRealm", previousRealm.PkgPath(),
268 "tokenId", string(tid),
269 "tokenURI", tokenURI,
270 )
271
272 return nil
273}