Search Apps Documentation Source Content File Folder Download Copy

basic_nft.gno

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