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.

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}