1package enumerable23import (4 "gno.land/p/nt/grc721/v0"5)67const Kind = "enumerable"89// NewEnumerable attaches an enumerable extension and registers it as a core10// 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 }1516 st := &storage{}17 core := coreLedger.ReadToken()18 led := &Ledger{core: core, st: st}19 coreLedger.RegisterExtension(led)2021 return &Enumerable{core: core, st: st}, led22}2324func (e *Enumerable) ExtensionKind() string {25 return Kind26}2728func (e *Enumerable) TokenID() string {29 return e.core.ID()30}3132func (e *Enumerable) TotalSupply() int64 {33 return int64(len(e.st.allTokens))34}3536func (e *Enumerable) TokenByIndex(index int64) (grc721.TokenID, error) {37 if index < 0 || index >= int64(len(e.st.allTokens)) {38 return "", ErrIndexOutOfRange39 }4041 return e.st.allTokens[index], nil42}4344func (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 "", ErrIndexOutOfRange48 }4950 return list.ids[index], nil51}5253func (e *Enumerable) ownedList(owner address) *tokenList {54 v := e.st.owned.Get(owner.String())55 if v == nil {56 return nil57 }5859 return v.(*tokenList)60}6162func (led *Ledger) ExtensionKind() string { return Kind }6364func (led *Ledger) OnMint(to address, tid grc721.TokenID) {65 led.addToAll(tid)66 led.addToOwner(to, tid)67}6869func (led *Ledger) OnTransfer(from, to address, tid grc721.TokenID) {70 led.removeFromOwner(from, tid)71 led.addToOwner(to, tid)72}7374func (led *Ledger) OnBurn(tid grc721.TokenID) {75 if v := led.st.owner.Get(tid.String()); v != nil {76 led.removeFromOwner(v.(address), tid)77 }7879 led.removeFromAll(tid)80}8182func (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}8687// Swap-and-pop: move the last id into the freed slot, then truncate.88func (led *Ledger) removeFromAll(tid grc721.TokenID) {89 tidStr := tid.String()9091 v := led.st.allIndex.Get(tidStr)92 if v == nil {93 return94 }9596 idx := v.(int)9798 last := len(led.st.allTokens) - 199 if idx != last {100 moved := led.st.allTokens[last]101 led.st.allTokens[idx] = moved102 led.st.allIndex.Set(moved.String(), idx)103 }104105 led.st.allTokens = led.st.allTokens[:last]106107 led.st.allIndex.Remove(tidStr)108}109110func (led *Ledger) addToOwner(owner address, tid grc721.TokenID) {111 ownerStr := owner.String()112113 var list *tokenList114 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 }120121 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}125126// 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()129130 v := led.st.owned.Get(ownerStr)131 if v == nil {132 return133 }134135 list := v.(*tokenList)136 tidStr := tid.String()137138 iv := led.st.ownedIndex.Get(tidStr)139 if iv == nil {140 return141 }142143 idx := iv.(int)144145 last := len(list.ids) - 1146 if idx != last {147 moved := list.ids[last]148 list.ids[idx] = moved149 led.st.ownedIndex.Set(moved.String(), idx)150 }151152 list.ids = list.ids[:last]153154 led.st.ownedIndex.Remove(tidStr)155156 if len(list.ids) == 0 {157 led.st.owned.Remove(ownerStr)158 }159160 led.st.owner.Remove(tidStr)161}162Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.