Search Apps Documentation Source Content File Folder Download Copy Actions Download 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}