gnft.gno
9.12 Kb · 326 lines
1package gnft
2
3import (
4 "chain"
5 "errors"
6
7 "gno.land/p/nt/grc721/metadata/v0"
8 "gno.land/p/nt/grc721/v0"
9 "gno.land/p/nt/seqid/v0"
10 "gno.land/p/nt/ufmt/v0"
11 "gno.land/r/gnoswap/access/v1"
12
13 prabc "gno.land/p/gnoswap/rbac/v1"
14 _ "gno.land/r/gnoswap/rbac/v1"
15)
16
17var (
18 token *grc721.Token
19 ledger *grc721.PrivateLedger
20 meta *metadata.Metadata
21 metaLedger *metadata.Ledger
22)
23
24func init(cur realm) {
25 token, ledger = grc721.NewToken("GNOSWAP NFT", "GNFT", seqid.ID(0), cur)
26 meta, metaLedger = metadata.NewMetadata(ledger)
27}
28
29// Name returns the NFT collection name.
30//
31// Returns:
32// - name: collection name
33func Name() string {
34 return token.GetName()
35}
36
37// Symbol returns the NFT collection symbol.
38//
39// Returns:
40// - symbol: collection symbol
41func Symbol() string {
42 return token.GetSymbol()
43}
44
45// TotalSupply returns the total number of NFTs minted.
46//
47// Returns:
48// - supply: number of NFTs minted by the collection
49func TotalSupply() int64 {
50 return token.TotalSupply()
51}
52
53// TokenURI returns the metadata URI for the specified token ID.
54// Parameter-format values (x1,y1,x2,y2,color1,color2) are rendered as a
55// base64-encoded SVG data URI; other non-empty stored URIs are returned unchanged.
56//
57// Parameters:
58// - tid: token ID whose metadata URI is requested
59//
60// Returns:
61// - uri: stored URI, or a generated SVG data URI for parameter-format metadata
62// - error: metadata lookup error when tid has no retrievable metadata
63func TokenURI(tid grc721.TokenID) (string, error) {
64 stored, err := meta.TokenURI(tid)
65 if err != nil {
66 return "", err
67 }
68
69 params, err := parseImageParams(stored)
70 if err == nil {
71 return params.generateImageURI(tid), nil
72 }
73
74 return stored, nil
75}
76
77// BalanceOf returns the number of NFTs owned by the specified address.
78//
79// Parameters:
80// - owner: address whose NFT balance is queried
81//
82// Returns:
83// - balance: number of NFTs owned by owner
84// - error: token-ledger balance lookup error; an invalid owner address
85// panics before the lookup
86func BalanceOf(owner address) (int64, error) {
87 assertIsValidAddress(owner)
88 return token.BalanceOf(owner)
89}
90
91// OwnerOf returns the owner address for the specified token ID.
92//
93// Parameters:
94// - tid: token ID whose owner is requested
95//
96// Returns:
97// - owner: current owner address
98// - error: token-ledger error when tid does not exist or cannot be read
99func OwnerOf(tid grc721.TokenID) (address, error) {
100 return token.OwnerOf(tid)
101}
102
103// MustOwnerOf returns the owner address for the specified token ID.
104// It panics if the token ID is invalid.
105//
106// Parameters:
107// - tid: token ID whose owner is requested
108//
109// Returns:
110// - owner: current owner address; panics when tid is invalid or absent
111func MustOwnerOf(tid grc721.TokenID) address {
112 ownerAddr, err := token.OwnerOf(tid)
113 checkErr(err)
114 return ownerAddr
115}
116
117// SetTokenURI sets the metadata URI for the specified token.
118//
119// Parameters:
120// - cur: Current realm context; callers use cross(cur) when crossing into this realm.
121// - tid: token ID whose metadata URI is replaced
122// - tURI: non-empty metadata URI or parameter-format image description
123//
124// Returns:
125// - updated: true after the URI is stored and re-read successfully
126// - error: nil on success; validation and storage failures panic before a
127// non-nil error can be returned
128//
129// Only callable by position contract.
130func SetTokenURI(cur realm, tid grc721.TokenID, tURI string) (bool, error) {
131 caller := cur.Previous().Address()
132 access.AssertIsPosition(caller)
133
134 assertIsNonEmptyTokenURI(tURI)
135 assertIsValidTokenURI(tid)
136
137 checkErr(setTokenURI(0, cur, tid, tURI))
138
139 return true, nil
140}
141
142// TransferFrom transfers a token from one address to another.
143//
144// Parameters:
145// - cur: Current realm context; callers use cross(cur) when crossing into this realm.
146// - from: current owner address
147// - to: recipient address
148// - tid: token ID to transfer
149//
150// Returns:
151// - error: nil after a successful transfer; invalid addresses, authorization,
152// ownership, or ledger failures panic through checkTransferErr
153//
154// Permission model:
155// - Tokens held by the staker contract (i.e. currently staked) can only be
156// moved by the staker itself; the underlying staked LP position is
157// non-transferable.
158// - Otherwise, ownership and approval are enforced by the GRC721 layer
159// (owner / approved-for-token / approved-for-all).
160func TransferFrom(cur realm, from, to address, tid grc721.TokenID) error {
161 assertFromIsValidAddress(from)
162 assertToIsValidAddress(to)
163 caller := cur.Previous().Address()
164 assertIsAllowedTransfer(caller, tid)
165
166 err := ledger.TransferFrom(caller, from, to, tid)
167 checkTransferErr(err, caller, from, to, tid)
168 return nil
169}
170
171// Approve grants permission to transfer a specific token ID to another address.
172//
173// Parameters:
174// - cur: Current realm context; callers use cross(cur) when crossing into this realm.
175// - approved: address to approve
176// - tid: token ID to approve for transfer
177//
178// Returns:
179// - error: nil after approval is stored; invalid addresses, ownership, or
180// ledger failures panic through checkApproveErr
181func Approve(cur realm, approved address, tid grc721.TokenID) error {
182 assertIsValidAddress(approved)
183
184 caller := cur.Previous().Address()
185 err := ledger.Approve(caller, approved, tid)
186 checkApproveErr(err, caller, approved, tid)
187 return nil
188}
189
190// SetApprovalForAll enables/disables operator approval for all tokens.
191//
192// Parameters:
193// - cur: Current realm context; callers use cross(cur) when crossing into this realm.
194// - operator: address to set approval for
195// - approved: true to approve, false to revoke
196//
197// Returns:
198// - error: nil after the operator approval is stored; ledger failures panic
199// before a non-nil error can be returned
200func SetApprovalForAll(cur realm, operator address, approved bool) error {
201 assertIsValidAddress(operator)
202
203 checkErr(ledger.SetApprovalForAll(cur.Previous().Address(), operator, approved))
204 return nil
205}
206
207// GetApproved returns approved address for token ID.
208//
209// Parameters:
210// - tid: token ID whose per-token approval is requested
211//
212// Returns:
213// - approved: address approved for tid
214// - error: token-ledger error if tid does not exist or approval cannot be read
215func GetApproved(tid grc721.TokenID) (address, error) {
216 return token.GetApproved(tid)
217}
218
219// IsApprovedForAll checks if operator can manage all owner's tokens.
220//
221// Parameters:
222// - owner: token owner address
223// - operator: operator address to check
224//
225// Returns:
226// - approved: true when operator is approved for every token owned by owner
227func IsApprovedForAll(owner, operator address) bool {
228 return token.IsApprovedForAll(owner, operator)
229}
230
231// Mint creates new NFT and transfers it to to.
232//
233// Parameters:
234// - cur: Current realm context; callers use cross(cur) when crossing into this realm.
235// - to: recipient address
236// - tid: token ID to mint
237//
238// Returns:
239// - tokenID: minted token ID
240//
241// Only callable by position contract.
242func Mint(cur realm, to address, tid grc721.TokenID) grc721.TokenID {
243 caller := cur.Previous().Address()
244 access.AssertIsPosition(caller)
245
246 positionAddr := access.MustGetAddress(prabc.ROLE_POSITION.String())
247 checkErr(ledger.Mint(positionAddr, tid))
248
249 // Store only the gradient parameters instead of full base64 SVG to reduce storage costs.
250 // Parameters are converted to full SVG on read via TokenURI().
251 imageParams := genImageParamsString(generateRandInstance())
252 checkErr(setTokenURI(0, cur, tid, imageParams))
253
254 checkErr(ledger.TransferFrom(positionAddr, positionAddr, to, tid))
255
256 return tid
257}
258
259// Exists checks if token ID exists.
260//
261// Parameters:
262// - tid: token ID to check
263//
264// Returns:
265// - exists: true when the ledger can resolve an owner for tid; false otherwise
266func Exists(tid grc721.TokenID) bool {
267 _, err := token.OwnerOf(tid)
268 return err == nil
269}
270
271// Burn removes a specific token ID.
272//
273// Parameters:
274// - cur: Current realm context; callers use cross(cur) when crossing into this realm.
275// - tid: token ID to burn
276//
277// Only callable by position.
278func Burn(cur realm, tid grc721.TokenID) {
279 caller := cur.Previous().Address()
280 access.AssertIsPosition(caller)
281
282 checkErr(ledger.Burn(tid))
283}
284
285// Render returns the HTML representation of the NFT.
286//
287// Parameters:
288// - path: render path; the empty path selects the collection home page
289//
290// Returns:
291// - html: collection HTML for the home path, or "404\n" for unsupported paths
292func Render(path string) string {
293 if path == "" {
294 return token.RenderHome()
295 }
296 return "404\n"
297}
298
299// setTokenURI sets the metadata URI for a specific token ID.
300func setTokenURI(_ int, rlm realm, tid grc721.TokenID, tURI string) error {
301 if !rlm.IsCurrent() {
302 return errors.New(errSpoofedRealm)
303 }
304
305 previousRealm := rlm.Previous()
306 previousAddr := previousRealm.Address()
307
308 err := metaLedger.SetTokenURI(tid, tURI)
309 if err != nil {
310 return makeErrorWithDetails(err.Error(), ufmt.Sprintf("token id (%s)", tid))
311 }
312 tokenURI, err := TokenURI(tid)
313 if err != nil {
314 return makeErrorWithDetails(err.Error(), ufmt.Sprintf("token id (%s)", tid))
315 }
316
317 chain.Emit(
318 "SetTokenURI",
319 "prevAddr", previousAddr.String(),
320 "prevRealm", previousRealm.PkgPath(),
321 "tokenId", string(tid),
322 "tokenURI", tokenURI,
323 )
324
325 return nil
326}