package chunk import ( "chain/runtime" "strconv" "strings" "gno.land/p/demo/tokens/grc721" "gno.land/p/nt/avl" "gno.land/p/nt/ufmt" ) const metadataSeparator = "|" var ( nft = grc721.NewBasicNFT("Akkadia Chunk", "AKC") ownerIndex = avl.NewTree() // owner -> []uint64 (encoded chunkKeys) metadata = avl.NewTree() // worldID -> map[uint64]string (encodedKey -> "worldType|hash") ) // encodeChunkKey packs worldID, x, y into uint64 // Layout: worldID (20 bits) | x (22 bits) | y (22 bits) func encodeChunkKey(worldID uint32, x, y int32) uint64 { return (uint64(worldID) << 44) | (uint64(uint32(x)&0x3FFFFF) << 22) | uint64(uint32(y)&0x3FFFFF) } // decodeChunkKey unpacks uint64 into worldID, x, y func decodeChunkKey(encoded uint64) (uint32, int32, int32) { worldID := uint32(encoded >> 44) x := int32((encoded >> 22) & 0x3FFFFF) y := int32(encoded & 0x3FFFFF) // Sign extend if negative (bit 21 set) if x&0x200000 != 0 { x |= ^0x3FFFFF } if y&0x200000 != 0 { y |= ^0x3FFFFF } return worldID, x, y } // ==================== GRC-721 ==================== func Name() string { return nft.Name() } func Symbol() string { return nft.Symbol() } func TokenCount() int64 { return nft.TokenCount() } func BalanceOf(user address) int64 { balance, err := nft.BalanceOf(user) if err != nil { panic("balanceOf failed: " + err.Error()) } return balance } func OwnerOf(tokenID grc721.TokenID) address { owner, err := nft.OwnerOf(tokenID) if err != nil { panic("ownerOf failed: " + err.Error()) } return owner } func IsApprovedForAll(owner, user address) bool { return nft.IsApprovedForAll(owner, user) } func GetApproved(tokenID grc721.TokenID) address { addr, err := nft.GetApproved(tokenID) if err != nil { panic("getApproved failed: " + err.Error()) } return addr } func Approve(cur realm, user address, tokenID grc721.TokenID) { err := nft.Approve(user, tokenID) if err != nil { panic("approve failed: " + err.Error()) } } func SetApprovalForAll(cur realm, user address, approved bool) { err := nft.SetApprovalForAll(user, approved) if err != nil { panic("setApprovalForAll failed: " + err.Error()) } } func TransferFrom(cur realm, from, to address, tokenID grc721.TokenID) { err := nft.TransferFrom(from, to, tokenID) if err != nil { panic("transferFrom failed: " + err.Error()) } } func Mint(cur realm, to address, worldType string, worldID uint32, x string, y string, chunkHashKey string, chunkVerifier string) grc721.TokenID { caller := runtime.PreviousRealm().Address() assertIsAdmin(caller) mustGetWorld(worldID) worldIDStr := ufmt.Sprintf("%d", worldID) chunkKey := worldIDStr + ":" + x + "_" + y tokenID := grc721.TokenID(chunkKey) // Parse x, y for owner index xInt, errX := strconv.Atoi(x) if errX != nil { panic("invalid x: " + x) } yInt, errY := strconv.Atoi(y) if errY != nil { panic("invalid y: " + y) } err := nft.Mint(to, tokenID) if err != nil { panic("mint failed: " + err.Error()) } addToOwnerIndex(to, worldID, int32(xInt), int32(yInt)) createMetadata(tokenID, worldType, worldIDStr, x, y, chunkHashKey) if chunkVerifier != "" { setChunkVerifierInternal(worldID, chunkKey, chunkVerifier) } return tokenID } func Burn(cur realm, tokenID grc721.TokenID) { panic("burn not supported for chunk tokens") } // ==================== Owner Index ==================== func ListTokensByOwner(owner address, page, count int) []string { if page < 1 { panic("page must be at least 1") } if count < 1 { panic("count must be at least 1") } if count > listLimit { panic("count exceeds listLimit") } value, exists := ownerIndex.Get(owner.String()) if !exists { return []string{} } chunks := value.([]uint64) // Pagination start := (page - 1) * count if start >= len(chunks) { return []string{} } end := start + count if end > len(chunks) { end = len(chunks) } // Convert to tokenID strings result := make([]string, 0, end-start) for _, encoded := range chunks[start:end] { worldID, x, y := decodeChunkKey(encoded) tokenID := ufmt.Sprintf("%d:%d_%d", worldID, x, y) result = append(result, tokenID) } return result } func addToOwnerIndex(owner address, worldID uint32, x, y int32) { encoded := encodeChunkKey(worldID, x, y) ownerKey := owner.String() value, exists := ownerIndex.Get(ownerKey) if !exists { ownerIndex.Set(ownerKey, []uint64{encoded}) return } chunks := value.([]uint64) chunks = append(chunks, encoded) ownerIndex.Set(ownerKey, chunks) } func removeFromOwnerIndex(owner address, worldID uint32, x, y int32) { encoded := encodeChunkKey(worldID, x, y) ownerKey := owner.String() value, exists := ownerIndex.Get(ownerKey) if !exists { return } chunks := value.([]uint64) for i, c := range chunks { if c == encoded { chunks = append(chunks[:i], chunks[i+1:]...) ownerIndex.Set(ownerKey, chunks) return } } } func ListOwners(tokenIDs ...grc721.TokenID) []map[string]string { result := []map[string]string{} if len(tokenIDs) == 0 { return result } if len(tokenIDs) > listLimit { panic("tokenIDs exceeds listLimit") } for _, tokenID := range tokenIDs { owner, err := nft.OwnerOf(tokenID) if err != nil { continue } result = append(result, map[string]string{"id": string(tokenID), "owner": string(owner)}) } return result } // ==================== Metadata ==================== // parseTokenID splits tokenID "worldID:x_y" into worldID and chunkKey (x_y) func parseTokenID(tokenID grc721.TokenID) (string, string) { tokenIDStr := string(tokenID) parts := strings.SplitN(tokenIDStr, ":", 2) if len(parts) != 2 { panic("invalid tokenID format: " + tokenIDStr) } return parts[0], parts[1] } // parseChunkKey splits chunkKey "x_y" into x and y func parseChunkKey(chunkKey string) (string, string) { parts := strings.SplitN(chunkKey, "_", 2) if len(parts) != 2 { panic("invalid chunkKey format: " + chunkKey) } return parts[0], parts[1] } // getWorldMetadataMap returns the metadata map for a world, creating if needed func getWorldMetadataMap(worldID string, create bool) map[uint64]string { if v, exists := metadata.Get(worldID); exists { return v.(map[uint64]string) } if !create { return nil } m := make(map[uint64]string) metadata.Set(worldID, m) return m } // buildFullMetadata reconstructs full metadata from encoded key and stored URI path func buildFullMetadata(worldID uint32, encodedKey uint64, uri string) map[string]string { _, x, y := decodeChunkKey(encodedKey) parts := strings.SplitN(uri, metadataSeparator, 2) worldType := parts[0] hash := "" if len(parts) > 1 { hash = parts[1] } worldIDStr := ufmt.Sprintf("%d", worldID) tokenIDStr := ufmt.Sprintf("%d:%d_%d", worldID, x, y) result := make(map[string]string) result["id"] = tokenIDStr result["worldId"] = worldIDStr result["x"] = ufmt.Sprintf("%d", x) result["y"] = ufmt.Sprintf("%d", y) result["worldType"] = worldType result["hash"] = hash return result } func createMetadata(tokenID grc721.TokenID, worldType string, worldID string, x string, y string, hash string) { worldMap := getWorldMetadataMap(worldID, true) // Parse coordinates for encoded key xInt, _ := strconv.Atoi(x) yInt, _ := strconv.Atoi(y) worldIDInt, _ := strconv.Atoi(worldID) encodedKey := encodeChunkKey(uint32(worldIDInt), int32(xInt), int32(yInt)) if _, exists := worldMap[encodedKey]; exists { panic("metadata already exists") } uri := worldType + metadataSeparator + hash worldMap[encodedKey] = uri } func GetMetadata(tokenID grc721.TokenID) map[string]string { worldIDStr, chunkKey := parseTokenID(tokenID) worldMap := getWorldMetadataMap(worldIDStr, false) if worldMap == nil { return nil } // Parse coordinates for encoded key xStr, yStr := parseChunkKey(chunkKey) xInt, _ := strconv.Atoi(xStr) yInt, _ := strconv.Atoi(yStr) worldIDInt, _ := strconv.Atoi(worldIDStr) encodedKey := encodeChunkKey(uint32(worldIDInt), int32(xInt), int32(yInt)) uri, exists := worldMap[encodedKey] if !exists { return nil } return buildFullMetadata(uint32(worldIDInt), encodedKey, uri) } func GetWorldMetadataSize(worldID uint32) int { worldIDStr := ufmt.Sprintf("%d", worldID) worldMap := getWorldMetadataMap(worldIDStr, false) if worldMap == nil { return 0 } return len(worldMap) } func ListMetadataByWorld(worldID uint32, page, count int) []map[string]string { if page < 1 { panic("page must be at least 1") } if count < 1 { panic("count must be at least 1") } if count > listLimit { panic("count exceeds listLimit") } worldIDStr := ufmt.Sprintf("%d", worldID) worldMap := getWorldMetadataMap(worldIDStr, false) if worldMap == nil { return []map[string]string{} } offset := (page - 1) * count idx := 0 result := make([]map[string]string, 0, count) for encodedKey, uri := range worldMap { if idx < offset { idx++ continue } result = append(result, buildFullMetadata(worldID, encodedKey, uri)) if len(result) >= count { break } idx++ } return result } func GetOwnerTokenSize(owner address) int { value, exists := ownerIndex.Get(owner.String()) if !exists { return 0 } return len(value.([]uint64)) } func ListMetadataByOwner(owner address, page, count int) []map[string]string { if page < 1 { panic("page must be at least 1") } if count < 1 { panic("count must be at least 1") } if count > listLimit { panic("count exceeds listLimit") } value, exists := ownerIndex.Get(owner.String()) if !exists { return []map[string]string{} } chunks := value.([]uint64) // Pagination start := (page - 1) * count if start >= len(chunks) { return []map[string]string{} } end := start + count if end > len(chunks) { end = len(chunks) } // Get metadata for paginated slice result := make([]map[string]string, 0, end-start) for _, encoded := range chunks[start:end] { worldID, x, y := decodeChunkKey(encoded) tokenID := grc721.TokenID(ufmt.Sprintf("%d:%d_%d", worldID, x, y)) data := GetMetadata(tokenID) if data != nil { result = append(result, data) } } return result }