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

item.gno

6.40 Kb · 251 lines
  1package item
  2
  3import (
  4	"chain/runtime/unsafe"
  5	"strconv"
  6
  7	"gno.land/p/akkadia/beta/accesscontrol"
  8	"gno.land/p/akkadia/beta/grc1155"
  9	"gno.land/p/nt/bptree/v0"
 10	"gno.land/r/akkadia/beta/admin"
 11)
 12
 13const (
 14	maxInt64 = int64(9223372036854775807)
 15)
 16
 17var (
 18	itemToken     = grc1155.NewBasicGRC1155Token("")
 19	itemSupplies  *bptree.BPTree
 20	ownerTokenIDs *bptree.BPTree
 21)
 22
 23func init() { resetItemState() }
 24
 25func resetItemState() {
 26	itemToken = grc1155.NewBasicGRC1155Token("")
 27	itemSupplies = bptree.NewBPTree32()
 28	ownerTokenIDs = bptree.NewBPTree32()
 29	minterRealms = bptree.NewBPTree32()
 30	listLimit = 100
 31}
 32
 33func SafeMint(cur realm, recipient address, tokenID string, amount int64) {
 34	executor := accesscontrol.MustGetAdminOrOperatorCaller(0, cur, admin.IsAdmin, admin.IsOperator)
 35	assertItemNoCoins()
 36	mintItem(cross(cur), executor, recipient, tokenID, amount)
 37}
 38
 39func SafeMintFromRealm(cur realm, recipient address, tokenID string, amount int64) {
 40	executor := mustGetMinterRealmCaller(0, cur)
 41	if unsafe.OriginCaller() != recipient {
 42		panic("item recipient must be origin caller")
 43	}
 44	mintItem(cross(cur), executor, recipient, tokenID, amount)
 45}
 46
 47func Uri() string { return itemToken.Uri() }
 48func IsApprovedForAll(owner, operator address) bool {
 49	return itemToken.IsApprovedForAll(owner, operator)
 50}
 51
 52func SetApprovalForAll(cur realm, operator address, approved bool) {
 53	caller := accesscontrol.MustGetUserCaller(0, cur)
 54	assertItemNoCoins()
 55	if err := itemToken.SetApprovalForAll(caller, operator, approved); err != nil {
 56		panic("item operator approval failed: " + err.Error())
 57	}
 58}
 59
 60func SafeTransferFrom(cur realm, from, to address, tokenID string, amount int64) {
 61	caller := accesscontrol.MustGetUserCaller(0, cur)
 62	assertItemNoCoins()
 63	assertItemTokenID(tokenID)
 64	assertItemAmount(amount, "transfer")
 65	if err := itemToken.SafeTransferFrom(caller, from, to, grc1155.TokenID(tokenID), amount); err != nil {
 66		panic("item transfer failed: " + err.Error())
 67	}
 68	if from != to {
 69		addOwnerTokenID(to, tokenID)
 70		if BalanceOf(from, tokenID) == 0 {
 71			removeOwnerTokenID(from, tokenID)
 72		}
 73	}
 74}
 75
 76func BalanceOf(owner address, tokenID string) int64 {
 77	if !owner.IsValid() {
 78		panic("invalid item owner")
 79	}
 80	assertItemTokenID(tokenID)
 81	balance, err := itemToken.BalanceOf(owner, grc1155.TokenID(tokenID))
 82	if err != nil {
 83		panic("item balance query failed: " + err.Error())
 84	}
 85	return balance
 86}
 87
 88func TotalSupply(tokenID string) int64 {
 89	assertItemTokenID(tokenID)
 90	value := itemSupplies.Get(tokenID)
 91	if value == nil {
 92		return 0
 93	}
 94	return value.(int64)
 95}
 96
 97func GetOwnedCount(owner address) int {
 98	if !owner.IsValid() {
 99		panic("invalid item owner")
100	}
101	ids, found := getOwnerTokenIDs(owner)
102	if !found {
103		return 0
104	}
105	return ids.Size()
106}
107
108func ListByOwner(owner address, page, count int) []map[string]string {
109	if !owner.IsValid() {
110		panic("invalid item owner")
111	}
112	assertItemListPageCount(page, count)
113	ids, found := getOwnerTokenIDs(owner)
114	if !found {
115		return []map[string]string{}
116	}
117	result := []map[string]string{}
118	ids.IterateByOffset((page-1)*count, count, func(tokenID string, _ any) bool {
119		result = append(result, map[string]string{
120			"tokenId": tokenID,
121			"amount":  strconv.FormatInt(BalanceOf(owner, tokenID), 10),
122		})
123		return false
124	})
125	return result
126}
127
128func Burn(cur realm, from address, tokenID string, amount int64) {
129	caller := accesscontrol.MustGetUserCaller(0, cur)
130	assertItemNoCoins()
131	assertItemTokenID(tokenID)
132	if caller != from && !itemToken.IsApprovedForAll(from, caller) {
133		panic("item owner or approved operator required")
134	}
135	assertItemAmount(amount, "burn")
136	burnItem(cross(cur), caller, from, tokenID, amount)
137}
138
139func BurnFromRealm(cur realm, from address, tokenID string, amount int64) {
140	caller := mustGetMinterRealmCaller(0, cur)
141	if unsafe.OriginCaller() != from {
142		panic("item owner must be origin caller")
143	}
144	assertItemTokenID(tokenID)
145	assertItemAmount(amount, "burn")
146	burnItem(cross(cur), caller, from, tokenID, amount)
147}
148
149func mintItem(cur realm, executor, recipient address, tokenID string, amount int64) {
150	assertItemTokenID(tokenID)
151	if !recipient.IsValid() || recipient == cur.Address() {
152		panic("invalid item recipient")
153	}
154	if amount <= 0 {
155		panic("item amount must be positive")
156	}
157	supply := TotalSupply(tokenID)
158	if supply > maxInt64-amount {
159		panic("item amount overflow")
160	}
161	if err := itemToken.SafeMint(executor, recipient, grc1155.TokenID(tokenID), amount); err != nil {
162		panic("item mint failed: " + err.Error())
163	}
164	itemSupplies.Set(tokenID, supply+amount)
165	addOwnerTokenID(recipient, tokenID)
166}
167
168func burnItem(cur realm, operator, from address, tokenID string, amount int64) {
169	supply := TotalSupply(tokenID)
170	if supply < amount {
171		panic("item burn amount exceeds supply")
172	}
173	if err := itemToken.Burn(operator, from, grc1155.TokenID(tokenID), amount); err != nil {
174		panic("item burn failed: " + err.Error())
175	}
176	itemSupplies.Set(tokenID, supply-amount)
177	if BalanceOf(from, tokenID) == 0 {
178		removeOwnerTokenID(from, tokenID)
179	}
180}
181
182func mustGetMinterRealmCaller(_ int, rlm realm) address {
183	accesscontrol.AssertCurrentRealm(0, rlm)
184	prev := rlm.Previous()
185	if prev.IsUserCall() {
186		panic("user calls not allowed: function must be called by a realm")
187	}
188	caller := prev.Address()
189	if !IsMinterRealm(caller) {
190		panic("minter realm required")
191	}
192	return caller
193}
194
195func assertItemNoCoins() {
196	if len(unsafe.OriginSend()) != 0 {
197		panic("attached coins not allowed")
198	}
199}
200
201func assertItemTokenID(tokenID string) {
202	if tokenID == "" {
203		panic("token id is required")
204	}
205}
206
207func assertItemAmount(amount int64, action string) {
208	if amount <= 0 {
209		panic("item " + action + " amount must be positive")
210	}
211}
212
213func assertItemListPageCount(page, count int) {
214	if page < 1 {
215		panic("page must be at least 1")
216	}
217	if count < 1 {
218		panic("count must be at least 1")
219	}
220	if count > listLimit {
221		panic("count exceeds listLimit (max: " + strconv.Itoa(listLimit) + ")")
222	}
223}
224
225func addOwnerTokenID(owner address, tokenID string) {
226	value := ownerTokenIDs.Get(owner.String())
227	var ids *bptree.BPTree
228	if value != nil {
229		ids = value.(*bptree.BPTree)
230	} else {
231		ids = bptree.NewBPTree32()
232		ownerTokenIDs.Set(owner.String(), ids)
233	}
234	ids.Set(tokenID, true)
235}
236
237func removeOwnerTokenID(owner address, tokenID string) {
238	ids, found := getOwnerTokenIDs(owner)
239	if !found {
240		return
241	}
242	ids.Remove(tokenID)
243}
244
245func getOwnerTokenIDs(owner address) (*bptree.BPTree, bool) {
246	value := ownerTokenIDs.Get(owner.String())
247	if value == nil {
248		return nil, false
249	}
250	return value.(*bptree.BPTree), true
251}