token.gno
3.45 Kb · 161 lines
1package enumerable
2
3import (
4 "gno.land/p/nt/grc721/v0"
5)
6
7const Kind = "enumerable"
8
9// NewEnumerable attaches an enumerable extension and registers it as a core
10// Extension. Attach it before the first mint so every token is indexed.
11func NewEnumerable(coreLedger *grc721.PrivateLedger) (*Enumerable, *Ledger) {
12 if coreLedger == nil {
13 panic("enumerable: nil core ledger")
14 }
15
16 st := &storage{}
17 core := coreLedger.ReadToken()
18 led := &Ledger{core: core, st: st}
19 coreLedger.RegisterExtension(led)
20
21 return &Enumerable{core: core, st: st}, led
22}
23
24func (e *Enumerable) ExtensionKind() string {
25 return Kind
26}
27
28func (e *Enumerable) TokenID() string {
29 return e.core.ID()
30}
31
32func (e *Enumerable) TotalSupply() int64 {
33 return int64(len(e.st.allTokens))
34}
35
36func (e *Enumerable) TokenByIndex(index int64) (grc721.TokenID, error) {
37 if index < 0 || index >= int64(len(e.st.allTokens)) {
38 return "", ErrIndexOutOfRange
39 }
40
41 return e.st.allTokens[index], nil
42}
43
44func (e *Enumerable) TokenOfOwnerByIndex(owner address, index int64) (grc721.TokenID, error) {
45 list := e.ownedList(owner)
46 if list == nil || index < 0 || index >= int64(len(list.ids)) {
47 return "", ErrIndexOutOfRange
48 }
49
50 return list.ids[index], nil
51}
52
53func (e *Enumerable) ownedList(owner address) *tokenList {
54 v := e.st.owned.Get(owner.String())
55 if v == nil {
56 return nil
57 }
58
59 return v.(*tokenList)
60}
61
62func (led *Ledger) ExtensionKind() string { return Kind }
63
64func (led *Ledger) OnMint(to address, tid grc721.TokenID) {
65 led.addToAll(tid)
66 led.addToOwner(to, tid)
67}
68
69func (led *Ledger) OnTransfer(from, to address, tid grc721.TokenID) {
70 led.removeFromOwner(from, tid)
71 led.addToOwner(to, tid)
72}
73
74func (led *Ledger) OnBurn(tid grc721.TokenID) {
75 if v := led.st.owner.Get(tid.String()); v != nil {
76 led.removeFromOwner(v.(address), tid)
77 }
78
79 led.removeFromAll(tid)
80}
81
82func (led *Ledger) addToAll(tid grc721.TokenID) {
83 led.st.allIndex.Set(tid.String(), len(led.st.allTokens))
84 led.st.allTokens = append(led.st.allTokens, tid)
85}
86
87// Swap-and-pop: move the last id into the freed slot, then truncate.
88func (led *Ledger) removeFromAll(tid grc721.TokenID) {
89 tidStr := tid.String()
90
91 v := led.st.allIndex.Get(tidStr)
92 if v == nil {
93 return
94 }
95
96 idx := v.(int)
97
98 last := len(led.st.allTokens) - 1
99 if idx != last {
100 moved := led.st.allTokens[last]
101 led.st.allTokens[idx] = moved
102 led.st.allIndex.Set(moved.String(), idx)
103 }
104
105 led.st.allTokens = led.st.allTokens[:last]
106
107 led.st.allIndex.Remove(tidStr)
108}
109
110func (led *Ledger) addToOwner(owner address, tid grc721.TokenID) {
111 ownerStr := owner.String()
112
113 var list *tokenList
114 if v := led.st.owned.Get(ownerStr); v != nil {
115 list = v.(*tokenList)
116 } else {
117 list = &tokenList{}
118 led.st.owned.Set(ownerStr, list)
119 }
120
121 led.st.ownedIndex.Set(tid.String(), len(list.ids))
122 list.ids = append(list.ids, tid)
123 led.st.owner.Set(tid.String(), owner)
124}
125
126// Swap-and-pop within the owner list; drops the owner entry when it empties.
127func (led *Ledger) removeFromOwner(owner address, tid grc721.TokenID) {
128 ownerStr := owner.String()
129
130 v := led.st.owned.Get(ownerStr)
131 if v == nil {
132 return
133 }
134
135 list := v.(*tokenList)
136 tidStr := tid.String()
137
138 iv := led.st.ownedIndex.Get(tidStr)
139 if iv == nil {
140 return
141 }
142
143 idx := iv.(int)
144
145 last := len(list.ids) - 1
146 if idx != last {
147 moved := list.ids[last]
148 list.ids[idx] = moved
149 led.st.ownedIndex.Set(moved.String(), idx)
150 }
151
152 list.ids = list.ids[:last]
153
154 led.st.ownedIndex.Remove(tidStr)
155
156 if len(list.ids) == 0 {
157 led.st.owned.Remove(ownerStr)
158 }
159
160 led.st.owner.Remove(tidStr)
161}