Search Apps Documentation Source Content File Folder Download Copy Actions Download

grc721_basic.gno

7.77 Kb · 320 lines
  1package gingernft
  2import (
  3	"chain"
  4	"chain/runtime"
  5	"math/overflow"
  6	"strconv"
  7	"gno.land/p/nt/avl/v0"
  8	"gno.land/p/nt/ufmt/v0"
  9)
 10type BasicNFT struct {
 11	name              string
 12	symbol            string
 13	owners            avl.Tree
 14	balances          avl.Tree
 15	tokenApprovals    avl.Tree
 16	tokenURIs         avl.Tree
 17	operatorApprovals avl.Tree
 18}
 19func NewBasicNFT(name, symbol string) *BasicNFT {
 20	return &BasicNFT{
 21		name:   name,
 22		symbol: symbol,
 23		owners:            avl.Tree{},
 24		balances:          avl.Tree{},
 25		tokenApprovals:    avl.Tree{},
 26		tokenURIs:         avl.Tree{},
 27		operatorApprovals: avl.Tree{},
 28	}
 29}
 30func (s *BasicNFT) Name() string      { return s.name }
 31func (s *BasicNFT) Symbol() string    { return s.symbol }
 32func (s *BasicNFT) TokenCount() int64 { return int64(s.owners.Size()) }
 33func (s *BasicNFT) BalanceOf(addr address) (int64, error) {
 34	if err := isValidAddress(addr); err != nil {
 35		return 0, err
 36	}
 37	balance, found := s.balances.Get(addr.String())
 38	if !found {
 39		return 0, nil
 40	}
 41	return balance.(int64), nil
 42}
 43func (s *BasicNFT) OwnerOf(tid TokenID) (address, error) {
 44	owner, found := s.owners.Get(string(tid))
 45	if !found {
 46		return "", ErrInvalidTokenId
 47	}
 48	return owner.(address), nil
 49}
 50func (s *BasicNFT) TokenURI(tid TokenID) (string, error) {
 51	uri, found := s.tokenURIs.Get(tid.String())
 52	if !found {
 53		return "", ErrInvalidTokenId
 54	}
 55	return uri.(string), nil
 56}
 57func (s *BasicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {
 58	if !s.exists(tid) {
 59		return false, ErrInvalidTokenId
 60	}
 61	owner, err := s.OwnerOf(tid)
 62	if err != nil {
 63		return false, err
 64	}
 65	caller := runtime.PreviousRealm().Address()
 66	if caller != owner {
 67		return false, ErrCallerIsNotOwner
 68	}
 69	s.tokenURIs.Set(tid.String(), tURI.String())
 70	chain.Emit(
 71		TokenURIUpdateEvent,
 72		"token", s.symbol,
 73		"tokenId", tid.String(),
 74	)
 75	return true, nil
 76}
 77func (s *BasicNFT) IsApprovedForAll(owner, operator address) bool {
 78	key := owner.String() + ":" + operator.String()
 79	approved, found := s.operatorApprovals.Get(key)
 80	if !found {
 81		return false
 82	}
 83	return approved.(bool)
 84}
 85func (s *BasicNFT) Approve(to address, tid TokenID) error {
 86	if err := isValidAddress(to); err != nil {
 87		return err
 88	}
 89	owner, err := s.OwnerOf(tid)
 90	if err != nil {
 91		return err
 92	}
 93	if owner == to {
 94		return ErrApprovalToCurrentOwner
 95	}
 96	caller := runtime.PreviousRealm().Address()
 97	if caller != owner && !s.IsApprovedForAll(owner, caller) {
 98		return ErrCallerIsNotOwnerOrApproved
 99	}
100	tidStr := tid.String()
101	s.tokenApprovals.Set(tidStr, to)
102	chain.Emit(
103		ApprovalEvent,
104		"token", s.symbol,
105		"owner", owner.String(),
106		"to", to.String(),
107		"tokenId", tidStr,
108	)
109	return nil
110}
111func (s *BasicNFT) GetApproved(tid TokenID) (address, error) {
112	addr, found := s.tokenApprovals.Get(tid.String())
113	if !found {
114		return zeroAddress, ErrTokenIdNotHasApproved
115	}
116	return addr.(address), nil
117}
118func (s *BasicNFT) SetApprovalForAll(operator address, approved bool) error {
119	if err := isValidAddress(operator); err != nil {
120		return ErrInvalidAddress
121	}
122	caller := runtime.PreviousRealm().Address()
123	return s.setApprovalForAll(caller, operator, approved)
124}
125func (s *BasicNFT) SafeTransferFrom(from, to address, tid TokenID) error {
126	caller := runtime.PreviousRealm().Address()
127	if !s.isApprovedOrOwner(caller, tid) {
128		return ErrCallerIsNotOwnerOrApproved
129	}
130	err := s.transfer(from, to, tid)
131	if err != nil {
132		return err
133	}
134	if !s.checkOnGRC721Received(from, to, tid) {
135		return ErrTransferToNonGRC721Receiver
136	}
137	return nil
138}
139func (s *BasicNFT) TransferFrom(from, to address, tid TokenID) error {
140	caller := runtime.PreviousRealm().Address()
141	if !s.isApprovedOrOwner(caller, tid) {
142		return ErrCallerIsNotOwnerOrApproved
143	}
144	err := s.transfer(from, to, tid)
145	if err != nil {
146		return err
147	}
148	return nil
149}
150func (s *BasicNFT) Mint(to address, tid TokenID) error {
151	return s.mint(to, tid)
152}
153func (s *BasicNFT) SafeMint(to address, tid TokenID) error {
154	err := s.mint(to, tid)
155	if err != nil {
156		return err
157	}
158	if !s.checkOnGRC721Received(zeroAddress, to, tid) {
159		return ErrTransferToNonGRC721Receiver
160	}
161	return nil
162}
163func (s *BasicNFT) Burn(tid TokenID) error {
164	owner, err := s.OwnerOf(tid)
165	if err != nil {
166		return err
167	}
168	s.beforeTokenTransfer(owner, zeroAddress, tid, 1)
169	tidStr := tid.String()
170	s.tokenApprovals.Remove(tidStr)
171	balance, err := s.BalanceOf(owner)
172	if err != nil {
173		return err
174	}
175	balance = overflow.Sub64p(balance, 1)
176	ownerStr := owner.String()
177	s.balances.Set(ownerStr, balance)
178	s.owners.Remove(tidStr)
179	chain.Emit(
180		BurnEvent,
181		"token", s.symbol,
182		"from", ownerStr,
183		"tokenId", tidStr,
184	)
185	s.afterTokenTransfer(owner, zeroAddress, tid, 1)
186	return nil
187}
188func (s *BasicNFT) setApprovalForAll(owner, operator address, approved bool) error {
189	if owner == operator {
190		return ErrApprovalToCurrentOwner
191	}
192	key := owner.String() + ":" + operator.String()
193	s.operatorApprovals.Set(key, approved)
194	chain.Emit(
195		ApprovalForAllEvent,
196		"token", s.symbol,
197		"owner", owner.String(),
198		"to", operator.String(),
199		"approved", strconv.FormatBool(approved),
200	)
201	return nil
202}
203func (s *BasicNFT) transfer(from, to address, tid TokenID) error {
204	if err := isValidAddress(from); err != nil {
205		return ErrInvalidAddress
206	}
207	if err := isValidAddress(to); err != nil {
208		return ErrInvalidAddress
209	}
210	if from == to {
211		return ErrCannotTransferToSelf
212	}
213	owner, err := s.OwnerOf(tid)
214	if err != nil {
215		return err
216	}
217	if owner != from {
218		return ErrTransferFromIncorrectOwner
219	}
220	s.beforeTokenTransfer(from, to, tid, 1)
221	owner, err = s.OwnerOf(tid)
222	if err != nil {
223		return err
224	}
225	if owner != from {
226		return ErrTransferFromIncorrectOwner
227	}
228	tidStr := tid.String()
229	s.tokenApprovals.Remove(tidStr)
230	fromBalance, err := s.BalanceOf(from)
231	if err != nil {
232		return err
233	}
234	toBalance, err := s.BalanceOf(to)
235	if err != nil {
236		return err
237	}
238	fromBalance = overflow.Sub64p(fromBalance, 1)
239	toBalance = overflow.Add64p(toBalance, 1)
240	fromStr := from.String()
241	toStr := to.String()
242	s.balances.Set(fromStr, fromBalance)
243	s.balances.Set(toStr, toBalance)
244	s.owners.Set(tidStr, to)
245	chain.Emit(
246		TransferEvent,
247		"token", s.symbol,
248		"from", fromStr,
249		"to", toStr,
250		"tokenId", tidStr,
251	)
252	s.afterTokenTransfer(from, to, tid, 1)
253	return nil
254}
255func (s *BasicNFT) mint(to address, tid TokenID) error {
256	if err := isValidAddress(to); err != nil {
257		return err
258	}
259	if s.exists(tid) {
260		return ErrTokenIdAlreadyExists
261	}
262	s.beforeTokenTransfer(zeroAddress, to, tid, 1)
263	if s.exists(tid) {
264		return ErrTokenIdAlreadyExists
265	}
266	toBalance, err := s.BalanceOf(to)
267	if err != nil {
268		return err
269	}
270	toBalance = overflow.Add64p(toBalance, 1)
271	toStr := to.String()
272	tidStr := tid.String()
273	s.balances.Set(toStr, toBalance)
274	s.owners.Set(tidStr, to)
275	chain.Emit(
276		MintEvent,
277		"token", s.symbol,
278		"to", toStr,
279		"tokenId", tidStr,
280	)
281	s.afterTokenTransfer(zeroAddress, to, tid, 1)
282	return nil
283}
284func (s *BasicNFT) isApprovedOrOwner(addr address, tid TokenID) bool {
285	owner, found := s.owners.Get(tid.String())
286	if !found {
287		return false
288	}
289	ownerAddr := owner.(address)
290	if addr == ownerAddr || s.IsApprovedForAll(ownerAddr, addr) {
291		return true
292	}
293	approved, err := s.GetApproved(tid)
294	if err != nil {
295		return false
296	}
297	return approved == addr
298}
299func (s *BasicNFT) exists(tid TokenID) bool {
300	_, found := s.owners.Get(tid.String())
301	return found
302}
303func (s *BasicNFT) beforeTokenTransfer(from, to address, firstTokenId TokenID, batchSize int64) {
304}
305func (s *BasicNFT) afterTokenTransfer(from, to address, firstTokenId TokenID, batchSize int64) {
306}
307func (s *BasicNFT) checkOnGRC721Received(from, to address, tid TokenID) bool {
308	return true
309}
310func (s *BasicNFT) RenderHome() (str string) {
311	str += ufmt.Sprintf("# %s ($%s)\n\n", s.name, s.symbol)
312	str += ufmt.Sprintf("* **Total supply**: %d\n", s.TokenCount())
313	str += ufmt.Sprintf("* **Known accounts**: %d\n", s.balances.Size())
314	return
315}
316func (n *BasicNFT) Getter() NFTGetter {
317	return func() IGRC721 {
318		return n
319	}
320}