Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}