Search Apps Documentation Source Content File Folder Download Copy

basic_nft.gno

8.51 Kb ยท 378 lines
  1package grc721
  2
  3import (
  4	"std"
  5
  6	"gno.land/p/demo/avl"
  7	"gno.land/p/demo/ufmt"
  8)
  9
 10type basicNFT struct {
 11	name              string
 12	symbol            string
 13	owners            avl.Tree // tokenId -> OwnerAddress
 14	balances          avl.Tree // OwnerAddress -> TokenCount
 15	tokenApprovals    avl.Tree // TokenId -> ApprovedAddress
 16	tokenURIs         avl.Tree // TokenId -> URIs
 17	operatorApprovals avl.Tree // "OwnerAddress:OperatorAddress" -> bool
 18}
 19
 20// Returns new basic NFT
 21func NewBasicNFT(name string, symbol string) *basicNFT {
 22	return &basicNFT{
 23		name:   name,
 24		symbol: symbol,
 25
 26		owners:            avl.Tree{},
 27		balances:          avl.Tree{},
 28		tokenApprovals:    avl.Tree{},
 29		tokenURIs:         avl.Tree{},
 30		operatorApprovals: avl.Tree{},
 31	}
 32}
 33
 34func (s *basicNFT) Name() string       { return s.name }
 35func (s *basicNFT) Symbol() string     { return s.symbol }
 36func (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) }
 37
 38// BalanceOf returns balance of input address
 39func (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) {
 40	if err := isValidAddress(addr); err != nil {
 41		return 0, err
 42	}
 43
 44	balance, found := s.balances.Get(addr.String())
 45	if !found {
 46		return 0, nil
 47	}
 48
 49	return balance.(uint64), nil
 50}
 51
 52// OwnerOf returns owner of input token id
 53func (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {
 54	owner, found := s.owners.Get(string(tid))
 55	if !found {
 56		return "", ErrInvalidTokenId
 57	}
 58
 59	return owner.(std.Address), nil
 60}
 61
 62// TokenURI returns the URI of input token id
 63func (s *basicNFT) TokenURI(tid TokenID) (string, error) {
 64	uri, found := s.tokenURIs.Get(string(tid))
 65	if !found {
 66		return "", ErrInvalidTokenId
 67	}
 68
 69	return uri.(string), nil
 70}
 71
 72func (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {
 73	// check for invalid TokenID
 74	if !s.exists(tid) {
 75		return false, ErrInvalidTokenId
 76	}
 77
 78	// check for the right owner
 79	owner, err := s.OwnerOf(tid)
 80	if err != nil {
 81		return false, err
 82	}
 83	caller := std.PrevRealm().Addr()
 84	if caller != owner {
 85		return false, ErrCallerIsNotOwner
 86	}
 87	s.tokenURIs.Set(string(tid), string(tURI))
 88	return true, nil
 89}
 90
 91// IsApprovedForAll returns true if operator is approved for all by the owner.
 92// Otherwise, returns false
 93func (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {
 94	key := owner.String() + ":" + operator.String()
 95	_, found := s.operatorApprovals.Get(key)
 96	if !found {
 97		return false
 98	}
 99
100	return true
101}
102
103// Approve approves the input address for particular token
104func (s *basicNFT) Approve(to std.Address, tid TokenID) error {
105	if err := isValidAddress(to); err != nil {
106		return err
107	}
108
109	owner, err := s.OwnerOf(tid)
110	if err != nil {
111		return err
112	}
113	if owner == to {
114		return ErrApprovalToCurrentOwner
115	}
116
117	caller := std.PrevRealm().Addr()
118	if caller != owner && !s.IsApprovedForAll(owner, caller) {
119		return ErrCallerIsNotOwnerOrApproved
120	}
121
122	s.tokenApprovals.Set(string(tid), to.String())
123	event := ApprovalEvent{owner, to, tid}
124	emit(&event)
125
126	return nil
127}
128
129// GetApproved return the approved address for token
130func (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {
131	addr, found := s.tokenApprovals.Get(string(tid))
132	if !found {
133		return zeroAddress, ErrTokenIdNotHasApproved
134	}
135
136	return std.Address(addr.(string)), nil
137}
138
139// SetApprovalForAll can approve the operator to operate on all tokens
140func (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {
141	if err := isValidAddress(operator); err != nil {
142		return ErrInvalidAddress
143	}
144
145	caller := std.PrevRealm().Addr()
146	return s.setApprovalForAll(caller, operator, approved)
147}
148
149// Safely transfers `tokenId` token from `from` to `to`, checking that
150// contract recipients are aware of the GRC721 protocol to prevent
151// tokens from being forever locked.
152func (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {
153	caller := std.PrevRealm().Addr()
154	if !s.isApprovedOrOwner(caller, tid) {
155		return ErrCallerIsNotOwnerOrApproved
156	}
157
158	err := s.transfer(from, to, tid)
159	if err != nil {
160		return err
161	}
162
163	if !s.checkOnGRC721Received(from, to, tid) {
164		return ErrTransferToNonGRC721Receiver
165	}
166
167	return nil
168}
169
170// Transfers `tokenId` token from `from` to `to`.
171func (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {
172	caller := std.PrevRealm().Addr()
173	if !s.isApprovedOrOwner(caller, tid) {
174		return ErrCallerIsNotOwnerOrApproved
175	}
176
177	err := s.transfer(from, to, tid)
178	if err != nil {
179		return err
180	}
181
182	return nil
183}
184
185// Mints `tokenId` and transfers it to `to`.
186func (s *basicNFT) Mint(to std.Address, tid TokenID) error {
187	return s.mint(to, tid)
188}
189
190// Mints `tokenId` and transfers it to `to`. Also checks that
191// contract recipients are using GRC721 protocol
192func (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {
193	err := s.mint(to, tid)
194	if err != nil {
195		return err
196	}
197
198	if !s.checkOnGRC721Received(zeroAddress, to, tid) {
199		return ErrTransferToNonGRC721Receiver
200	}
201
202	return nil
203}
204
205func (s *basicNFT) Burn(tid TokenID) error {
206	owner, err := s.OwnerOf(tid)
207	if err != nil {
208		return err
209	}
210
211	s.beforeTokenTransfer(owner, zeroAddress, tid, 1)
212
213	s.tokenApprovals.Remove(string(tid))
214	balance, err := s.BalanceOf(owner)
215	if err != nil {
216		return err
217	}
218	balance -= 1
219	s.balances.Set(owner.String(), balance)
220	s.owners.Remove(string(tid))
221
222	event := TransferEvent{owner, zeroAddress, tid}
223	emit(&event)
224
225	s.afterTokenTransfer(owner, zeroAddress, tid, 1)
226
227	return nil
228}
229
230/* Helper methods */
231
232// Helper for SetApprovalForAll()
233func (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {
234	if owner == operator {
235		return ErrApprovalToCurrentOwner
236	}
237
238	key := owner.String() + ":" + operator.String()
239	s.operatorApprovals.Set(key, approved)
240
241	event := ApprovalForAllEvent{owner, operator, approved}
242	emit(&event)
243
244	return nil
245}
246
247// Helper for TransferFrom() and SafeTransferFrom()
248func (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {
249	if err := isValidAddress(from); err != nil {
250		return ErrInvalidAddress
251	}
252	if err := isValidAddress(to); err != nil {
253		return ErrInvalidAddress
254	}
255
256	if from == to {
257		return ErrCannotTransferToSelf
258	}
259
260	owner, err := s.OwnerOf(tid)
261	if err != nil {
262		return err
263	}
264	if owner != from {
265		return ErrTransferFromIncorrectOwner
266	}
267
268	s.beforeTokenTransfer(from, to, tid, 1)
269
270	// Check that tokenId was not transferred by `beforeTokenTransfer`
271	owner, err = s.OwnerOf(tid)
272	if err != nil {
273		return err
274	}
275	if owner != from {
276		return ErrTransferFromIncorrectOwner
277	}
278
279	s.tokenApprovals.Remove(string(tid))
280	fromBalance, err := s.BalanceOf(from)
281	if err != nil {
282		return err
283	}
284	toBalance, err := s.BalanceOf(to)
285	if err != nil {
286		return err
287	}
288	fromBalance -= 1
289	toBalance += 1
290	s.balances.Set(from.String(), fromBalance)
291	s.balances.Set(to.String(), toBalance)
292	s.owners.Set(string(tid), to)
293
294	event := TransferEvent{from, to, tid}
295	emit(&event)
296
297	s.afterTokenTransfer(from, to, tid, 1)
298
299	return nil
300}
301
302// Helper for Mint() and SafeMint()
303func (s *basicNFT) mint(to std.Address, tid TokenID) error {
304	if err := isValidAddress(to); err != nil {
305		return err
306	}
307
308	if s.exists(tid) {
309		return ErrTokenIdAlreadyExists
310	}
311
312	s.beforeTokenTransfer(zeroAddress, to, tid, 1)
313
314	// Check that tokenId was not minted by `beforeTokenTransfer`
315	if s.exists(tid) {
316		return ErrTokenIdAlreadyExists
317	}
318
319	toBalance, err := s.BalanceOf(to)
320	if err != nil {
321		return err
322	}
323	toBalance += 1
324	s.balances.Set(to.String(), toBalance)
325	s.owners.Set(string(tid), to)
326
327	event := TransferEvent{zeroAddress, to, tid}
328	emit(&event)
329
330	s.afterTokenTransfer(zeroAddress, to, tid, 1)
331
332	return nil
333}
334
335func (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {
336	owner, found := s.owners.Get(string(tid))
337	if !found {
338		return false
339	}
340
341	if addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {
342		return true
343	}
344
345	_, err := s.GetApproved(tid)
346	if err != nil {
347		return false
348	}
349
350	return true
351}
352
353// Checks if token id already exists
354func (s *basicNFT) exists(tid TokenID) bool {
355	_, found := s.owners.Get(string(tid))
356	return found
357}
358
359func (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {
360	// TODO: Implementation
361}
362
363func (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {
364	// TODO: Implementation
365}
366
367func (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {
368	// TODO: Implementation
369	return true
370}
371
372func (s *basicNFT) RenderHome() (str string) {
373	str += ufmt.Sprintf("# %s ($%s)\n\n", s.name, s.symbol)
374	str += ufmt.Sprintf("* **Total supply**: %d\n", s.TokenCount())
375	str += ufmt.Sprintf("* **Known accounts**: %d\n", s.balances.Size())
376
377	return
378}