Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}