Kind Realm (renderable)
Name equipment
Namespace akkadia / beta
Exported functions 26
Module gno.land/r/akkadia/beta/equipment
gno 0.9 equipment.gno gno
⧉
1 package equipment 2 3 import ( 4 "chain/runtime/unsafe" 5 "net/url" 6 "strconv" 7 "time" 8 Approve (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, operator string, tokenID uint64)
BalanceOf (owner string) int64
Burn (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, tokenID uint64)
BurnFromRealm (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, tokenID uint64)
GetApproved (tokenID uint64) string
GetLatestTokenID () uint64
GetListLimit () int
GetMetadata (tokenID uint64) map[string]string
GetOwnedCount (owner string) int
Has (tokenID uint64) bool
IsApprovedForAll (owner string, operator string) bool
IsMinterRealm (addr string) bool
ListByOwner (owner string, page int, count int) []map[string]string
Mint (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, recipient string, definitionID uint32, attrKeys string, attrValues string) uint64
MintFromRealm (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, recipient string, definitionID uint32, attrKeys string, attrValues string) uint64
Name () string
OwnerOf (tokenID uint64) string
Render (iURL string) string
SetApprovalForAll (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, operator string, approved bool)
SetListLimit (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, limit int)
SetMetadata (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, tokenID uint64, attrKeys string, attrValues string)
SetMetadataFromRealm (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, tokenID uint64, attrKeys string, attrValues string)
SetMinterRealm (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, pkgPath string)
Symbol () string
TransferFrom (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, from string, to string, tokenID uint64)
UnsetMinterRealm (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, pkgPath string)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.
9 "gno.land/p/akkadia/beta/accesscontrol"
10 "gno.land/p/akkadia/beta/grc721"
11 "gno.land/p/nt/bptree/v0"
12 "gno.land/r/akkadia/beta/admin"
13 )
14
15 const (
16 definitionIDKey = "definitionId"
17 tokenIDKey = "tokenId"
18 createdAtKey = "created_at"
19 maxEquipmentTokenID uint64 = 18446744073709551615
20 )
21
22 var (
23 equipmentToken = grc721.NewBasicNFT( "Akkadia Equipment" , "AEQP" )
24 equipmentMetadata *bptree.BPTree
25 ownerTokenIDs *bptree.BPTree
26 latestTokenID uint64
27 )
28
29 func init() { resetEquipmentState() }
30
31 func resetEquipmentState() {
32 equipmentToken = grc721.NewBasicNFT( "Akkadia Equipment" , "AEQP" )
33 equipmentMetadata = bptree.NewBPTree32()
34 ownerTokenIDs = bptree.NewBPTree32()
35 minterRealms = bptree.NewBPTree32()
36 listLimit = 100
37 latestTokenID = 0
38 }
39
40 func Mint(cur realm, recipient address, definitionID uint32 , attrKeys, attrValues string ) uint64 {
41 accesscontrol.MustGetAdminOrOperatorCaller( 0 , cur, admin.IsAdmin, admin.IsOperator)
42 assertEquipmentNoCoins()
43 return mintEquipment(cross(cur), recipient, definitionID, attrKeys, attrValues)
44 }
45
46 func MintFromRealm(cur realm, recipient address, definitionID uint32 , attrKeys, attrValues string ) uint64 {
47 mustGetMinterRealmCaller( 0 , cur)
48 if unsafe.OriginCaller() != recipient {
49 panic( "equipment recipient must be origin caller" )
50 }
51 return mintEquipment(cross(cur), recipient, definitionID, attrKeys, attrValues)
52 }
53
54 func GetMetadata(tokenID uint64 ) map [ string ] string {
55 return copyStringMap(mustGetEquipmentMetadata(tokenID))
56 }
57
58 func SetMetadata(cur realm, tokenID uint64 , attrKeys, attrValues string ) {
59 accesscontrol.MustGetAdminOrOperatorCaller( 0 , cur, admin.IsAdmin, admin.IsOperator)
60 assertEquipmentNoCoins()
61 setEquipmentMetadata(tokenID, attrKeys, attrValues)
62 }
63
64 func SetMetadataFromRealm(cur realm, tokenID uint64 , attrKeys, attrValues string ) {
65 mustGetMinterRealmCaller( 0 , cur)
66 if unsafe.OriginCaller() != OwnerOf(tokenID) {
67 panic( "equipment owner must be origin caller" )
68 }
69 setEquipmentMetadata(tokenID, attrKeys, attrValues)
70 }
71
72 func GetOwnedCount(owner address) int {
73 if !owner.IsValid() {
74 panic( "invalid equipment owner" )
75 }
76 ids, found := getOwnerTokenIDs(owner)
77 if !found {
78 return 0
79 }
80 return ids.Size()
81 }
82
83 func ListByOwner(owner address, page, count int ) [] map [ string ] string {
84 if !owner.IsValid() {
85 panic( "invalid equipment owner" )
86 }
87 assertEquipmentListPageCount(page, count)
88 ids, found := getOwnerTokenIDs(owner)
89 if !found {
90 return [] map [ string ] string {}
91 }
92 result := [] map [ string ] string {}
93 ids.IterateByOffset((page- 1 )*count, count, func (key string , _ any ) bool {
94 tokenID := parseUint64Key(key)
95 row := GetMetadata(tokenID)
96 row[tokenIDKey] = strconv.FormatUint(tokenID, 10 )
97 result = append(result, row)
98 return false
99 })
100 return result
101 }
102
103 func Name() string { return equipmentToken.Name() }
104 func Symbol() string { return equipmentToken.Symbol() }
105
106 func OwnerOf(tokenID uint64 ) address {
107 owner, err := equipmentToken.OwnerOf(equipmentTokenID(tokenID))
108 if err != nil {
109 panic( "equipment owner query failed: " + err.Error())
110 }
111 return owner
112 }
113
114 func Has(tokenID uint64 ) bool {
115 _, err := equipmentToken.OwnerOf(equipmentTokenID(tokenID))
116 return err == nil
117 }
118
119 func GetLatestTokenID() uint64 {
120 return latestTokenID
121 }
122
123 func BalanceOf(owner address) int64 {
124 if !owner.IsValid() {
125 panic( "invalid equipment owner" )
126 }
127 balance, err := equipmentToken.BalanceOf(owner)
128 if err != nil {
129 panic( "equipment balance query failed: " + err.Error())
130 }
131 return balance
132 }
133
134 func GetApproved(tokenID uint64 ) address {
135 approved, err := equipmentToken.GetApproved(equipmentTokenID(tokenID))
136 if err != nil {
137 panic( "equipment approval query failed: " + err.Error())
138 }
139 return approved
140 }
141
142 func IsApprovedForAll(owner, operator address) bool {
143 return equipmentToken.IsApprovedForAll(owner, operator)
144 }
145
146 func Approve(cur realm, operator address, tokenID uint64 ) {
147 caller := accesscontrol.MustGetUserCaller( 0 , cur)
148 assertEquipmentNoCoins()
149 if err := equipmentToken.Approve(caller, operator, equipmentTokenID(tokenID)); err != nil {
150 panic( "equipment approval failed: " + err.Error())
151 }
152 }
153
154 func SetApprovalForAll(cur realm, operator address, approved bool ) {
155 caller := accesscontrol.MustGetUserCaller( 0 , cur)
156 assertEquipmentNoCoins()
157 if err := equipmentToken.SetApprovalForAll(caller, operator, approved); err != nil {
158 panic( "equipment operator approval failed: " + err.Error())
159 }
160 }
161
162 func TransferFrom(cur realm, from, to address, tokenID uint64 ) {
163 caller := accesscontrol.MustGetUserCaller( 0 , cur)
164 assertEquipmentNoCoins()
165 if err := equipmentToken.TransferFrom(caller, from, to, equipmentTokenID(tokenID)); err != nil {
166 panic( "equipment transfer failed: " + err.Error())
167 }
168 if from != to {
169 removeOwnerTokenID(from, tokenID)
170 addOwnerTokenID(to, tokenID)
171 }
172 }
173
174 func Burn(cur realm, tokenID uint64 ) {
175 caller := accesscontrol.MustGetUserCaller( 0 , cur)
176 assertEquipmentNoCoins()
177 owner := OwnerOf(tokenID)
178 approved, err := equipmentToken.GetApproved(equipmentTokenID(tokenID))
179 if caller != owner && !equipmentToken.IsApprovedForAll(owner, caller) && (err != nil || approved != caller) {
180 panic( "equipment owner or approved operator required" )
181 }
182 burnEquipment(tokenID, owner)
183 }
184
185 func BurnFromRealm(cur realm, tokenID uint64 ) {
186 mustGetMinterRealmCaller( 0 , cur)
187 owner := OwnerOf(tokenID)
188 if unsafe.OriginCaller() != owner {
189 panic( "equipment owner must be origin caller" )
190 }
191 burnEquipment(tokenID, owner)
192 }
193
194 func mintEquipment(cur realm, recipient address, definitionID uint32 , attrKeys, attrValues string ) uint64 {
195 if !recipient.IsValid() || recipient == cur.Address() {
196 panic( "invalid equipment recipient" )
197 }
198 attrs := parseEquipmentAttributes(attrKeys, attrValues)
199 tokenID := allocateTokenID()
200 if err := equipmentToken.Mint(recipient, equipmentTokenID(tokenID)); err != nil {
201 panic( "equipment mint failed: " + err.Error())
202 }
203 attrs[definitionIDKey] = strconv.FormatUint( uint64 (definitionID), 10 )
204 attrs[createdAtKey] = strconv.FormatInt(time.Now().Unix(), 10 )
205 equipmentMetadata.Set(uint64Key(tokenID), attrs)
206 addOwnerTokenID(recipient, tokenID)
207 return tokenID
208 }
209
210 func allocateTokenID() uint64 {
211 if latestTokenID == maxEquipmentTokenID {
212 panic( "equipment token id exhausted" )
213 }
214 latestTokenID++
215 return latestTokenID
216 }
217
218 func mustGetEquipmentMetadata(tokenID uint64 ) map [ string ] string {
219 value := equipmentMetadata.Get(uint64Key(tokenID))
220 if value == nil {
221 panic( "equipment metadata not found: " + strconv.FormatUint(tokenID, 10 ))
222 }
223 return value.( map [ string ] string )
224 }
225
226 func setEquipmentMetadata(tokenID uint64 , attrKeys, attrValues string ) {
227 current := mustGetEquipmentMetadata(tokenID)
228 attrs := parseEquipmentAttributes(attrKeys, attrValues)
229 attrs[definitionIDKey] = current[definitionIDKey]
230 attrs[createdAtKey] = current[createdAtKey]
231 equipmentMetadata.Set(uint64Key(tokenID), attrs)
232 }
233
234 func parseEquipmentAttributes(attrKeys, attrValues string ) map [ string ] string {
235 attrs := parseAttributesCSV(attrKeys, attrValues)
236 if _, found := attrs[definitionIDKey]; found {
237 panic( "reserved key: " + definitionIDKey)
238 }
239 if _, found := attrs[tokenIDKey]; found {
240 panic( "reserved key: " + tokenIDKey)
241 }
242 if _, found := attrs[createdAtKey]; found {
243 panic( "reserved key: " + createdAtKey)
244 }
245 return attrs
246 }
247
248 func burnEquipment(tokenID uint64 , owner address) {
249 if err := equipmentToken.Burn(equipmentTokenID(tokenID)); err != nil {
250 panic( "equipment burn failed: " + err.Error())
251 }
252 if _, removed := equipmentMetadata.Remove(uint64Key(tokenID)); !removed {
253 panic( "equipment metadata not found: " + strconv.FormatUint(tokenID, 10 ))
254 }
255 removeOwnerTokenID(owner, tokenID)
256 }
257
258 func mustGetMinterRealmCaller(_ int , rlm realm) address {
259 accesscontrol.AssertCurrentRealm( 0 , rlm)
260 prev := rlm.Previous()
261 if prev.IsUserCall() {
262 panic( "user calls not allowed: function must be called by a realm" )
263 }
264 caller := prev.Address()
265 if !IsMinterRealm(caller) {
266 panic( "minter realm required" )
267 }
268 return caller
269 }
270
271 func assertEquipmentNoCoins() {
272 if len(unsafe.OriginSend()) != 0 {
273 panic( "attached coins not allowed" )
274 }
275 }
276
277 func assertEquipmentListPageCount(page, count int ) {
278 if page < 1 {
279 panic( "page must be at least 1" )
280 }
281 if count < 1 {
282 panic( "count must be at least 1" )
283 }
284 if count > listLimit {
285 panic( "count exceeds listLimit (max: " + strconv.Itoa(listLimit) + ")" )
286 }
287 }
288
289 func addOwnerTokenID(owner address, tokenID uint64 ) {
290 value := ownerTokenIDs.Get(owner.String())
291 var ids *bptree.BPTree
292 if value != nil {
293 ids = value.(*bptree.BPTree)
294 } else {
295 ids = bptree.NewBPTree32()
296 ownerTokenIDs.Set(owner.String(), ids)
297 }
298 ids.Set(uint64Key(tokenID), true )
299 }
300
301 func removeOwnerTokenID(owner address, tokenID uint64 ) {
302 ids, found := getOwnerTokenIDs(owner)
303 if !found {
304 return
305 }
306 ids.Remove(uint64Key(tokenID))
307 }
308
309 func getOwnerTokenIDs(owner address) (*bptree.BPTree, bool ) {
310 value := ownerTokenIDs.Get(owner.String())
311 if value == nil {
312 return nil , false
313 }
314 return value.(*bptree.BPTree), true
315 }
316
317 func equipmentTokenID(tokenID uint64 ) grc721.TokenID {
318 return grc721.TokenID(strconv.FormatUint(tokenID, 10 ))
319 }
320
321 func uint64Key(id uint64 ) string {
322 s := strconv.FormatUint(id, 10 )
323 if len(s) >= 20 {
324 return s
325 }
326 return "00000000000000000000" [len(s):] + s
327 }
328
329 func parseUint64Key(key string ) uint64 {
330 id, err := strconv.ParseUint(key, 10 , 64 )
331 if err != nil {
332 panic( "invalid uint64 key: " + key)
333 }
334 return id
335 }
336
337 func copyStringMap(source map [ string ] string ) map [ string ] string {
338 result := map [ string ] string {}
339 for key, value := range source {
340 result[key] = value
341 }
342 return result
343 }
344
345 // parseAttributesCSV accepts comma-separated keys and URL-encoded values.
346 // Empty keys and values produce an empty attribute map so mint can store
347 // definitionId and created_at alone.
348 func parseAttributesCSV(keys, values string ) map [ string ] string {
349 if keys == "" && values == "" {
350 return map [ string ] string {}
351 }
352
353 result := map [ string ] string {}
354 keyStart, valStart := 0 , 0
355 keyIdx, valIdx := 0 , 0
356 for {
357 for keyIdx < len(keys) && keys[keyIdx] != ',' {
358 keyIdx++
359 }
360 for valIdx < len(values) && values[valIdx] != ',' {
361 valIdx++
362 }
363
364 key := keys[keyStart:keyIdx]
365 if key == "" {
366 panic( "attributes key must be not empty" )
367 }
368 if _, found := result[key]; found {
369 panic( "attributes key duplicated: " + key)
370 }
371
372 value, err := url.PathUnescape(values[valStart:valIdx])
373 if err != nil {
374 panic( "attributes value URL decode failed: " + key)
375 }
376 result[key] = value
377
378 keyEnd := keyIdx >= len(keys)
379 valEnd := valIdx >= len(values)
380 if keyEnd != valEnd {
381 panic( "attributes keys and values count mismatch" )
382 }
383 if keyEnd {
384 break
385 }
386
387 keyIdx++
388 valIdx++
389 keyStart = keyIdx
390 valStart = valIdx
391 }
392
393 return result
394 }
395