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

basic_nft.gno

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