Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}