package acr import ( "chain" "chain/runtime" "strconv" "gno.land/p/demo/tokens/grc20" "gno.land/p/nt/avl" "gno.land/p/nt/ufmt" ) const ( MintEvent = "Mint" ) var ( token *grc20.Token ledger *grc20.PrivateLedger // Index for top users by balance: "paddedBalance" -> map[address]bool balanceIndex = avl.NewTree() // Index for top users by total minted amount: "paddedMint" -> map[address]bool mintIndex = avl.NewTree() // Track total minted amount per user userMintTotal = avl.NewTree() // address -> int64 // Track processed mint requests for deduplication processedRequests = avl.NewTree() // requestID -> bool ) func init() { token, ledger = grc20.NewToken("Akkadia Community Rune", "ACR", 6) } // ==================== GRC-20 Standard ==================== func GetName() string { return token.GetName() } func GetSymbol() string { return token.GetSymbol() } func GetDecimals() int { return token.GetDecimals() } func TotalSupply() int64 { return token.TotalSupply() } func BalanceOf(account address) int64 { return token.BalanceOf(account) } func Transfer(cur realm, to address, amount int64) { caller := runtime.PreviousRealm().Address() oldFromBalance := token.BalanceOf(caller) oldToBalance := token.BalanceOf(to) teller := token.CallerTeller() err := teller.Transfer(to, amount) if err != nil { panic("transfer failed: " + err.Error()) } // Update balance index updateBalanceIndex(caller, oldFromBalance, token.BalanceOf(caller)) updateBalanceIndex(to, oldToBalance, token.BalanceOf(to)) } func Allowance(owner, spender address) int64 { return token.Allowance(owner, spender) } func Approve(cur realm, spender address, amount int64) { teller := token.CallerTeller() err := teller.Approve(spender, amount) if err != nil { panic("approve failed: " + err.Error()) } } func TransferFrom(cur realm, from, to address, amount int64) { oldFromBalance := token.BalanceOf(from) oldToBalance := token.BalanceOf(to) teller := token.CallerTeller() err := teller.TransferFrom(from, to, amount) if err != nil { panic("transferFrom failed: " + err.Error()) } // Update balance index updateBalanceIndex(from, oldFromBalance, token.BalanceOf(from)) updateBalanceIndex(to, oldToBalance, token.BalanceOf(to)) } // ==================== Admin Functions ==================== // Mint mints ACR tokens to a user func Mint(cur realm, requestID string, to address, amount int64) { caller := runtime.PreviousRealm().Address() assertIsAdminOrOperator(caller) if requestID == "" { panic("requestID is required") } if processedRequests.Has(requestID) { panic("requestID already processed") } if amount <= 0 { panic("amount must be positive") } oldBalance := token.BalanceOf(to) err := ledger.Mint(to, amount) if err != nil { panic("mint failed: " + err.Error()) } // Mark request as processed processedRequests.Set(requestID, true) // Update balance index updateBalanceIndex(to, oldBalance, token.BalanceOf(to)) // Update mint index updateMintIndex(to, amount) chain.Emit( MintEvent, "requestID", requestID, "to", to.String(), "amount", strconv.FormatInt(amount, 10), ) } // IsRequestProcessed checks if a requestID has been processed func IsRequestProcessed(requestID string) bool { return processedRequests.Has(requestID) } // ListRequestProcessed checks multiple requestIDs and returns their processed status func ListRequestProcessed(requestIDs ...string) map[string]bool { if len(requestIDs) > listLimit { panic("requestIDs exceeds listLimit") } result := make(map[string]bool) for _, id := range requestIDs { result[id] = processedRequests.Has(id) } return result } // Burn burns ACR tokens from caller func Burn(cur realm, amount int64) { caller := runtime.PreviousRealm().Address() if amount <= 0 { panic("amount must be positive") } oldBalance := token.BalanceOf(caller) err := ledger.Burn(caller, amount) if err != nil { panic("burn failed: " + err.Error()) } // Update balance index updateBalanceIndex(caller, oldBalance, token.BalanceOf(caller)) } // ==================== Balance Index ==================== func balanceToKey(balance int64) string { // Pad to 20 digits for proper AVL tree sorting return ufmt.Sprintf("%020d", balance) } func addToBalanceIndex(addr address, balance int64) { key := balanceToKey(balance) value, found := balanceIndex.Get(key) var users map[address]bool if !found { users = map[address]bool{} balanceIndex.Set(key, users) } else { users = value.(map[address]bool) } users[addr] = true } func removeFromBalanceIndex(addr address, balance int64) { key := balanceToKey(balance) value, found := balanceIndex.Get(key) if !found { return } users := value.(map[address]bool) delete(users, addr) if len(users) == 0 { balanceIndex.Remove(key) } } func updateBalanceIndex(addr address, oldBalance, newBalance int64) { // Remove from old balance if oldBalance > 0 { removeFromBalanceIndex(addr, oldBalance) } // Add to new balance if newBalance > 0 { addToBalanceIndex(addr, newBalance) } } // ListTopUsersByBalance returns top users sorted by balance (descending) with pagination func ListTopUsersByBalance(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") } var result []map[string]string offset := (page - 1) * count skipped := 0 collected := 0 // ReverseIterate gives us high-to-low balance order balanceIndex.ReverseIterate("", "", func(key string, value any) bool { if collected >= count { return true // stop iteration } users := value.(map[address]bool) for addr := range users { // Skip until we reach offset if skipped < offset { skipped++ continue } if collected >= count { return true } balance := token.BalanceOf(addr) result = append(result, map[string]string{ "user": addr.String(), "balance": ufmt.Sprintf("%d", balance), }) collected++ } return false }) return result } // ==================== Mint Index ==================== func MintedOf(account address) int64 { value, found := userMintTotal.Get(account.String()) if !found { return 0 } return value.(int64) } func addToMintIndex(addr address, mintTotal int64) { key := balanceToKey(mintTotal) value, found := mintIndex.Get(key) var users map[address]bool if !found { users = map[address]bool{} mintIndex.Set(key, users) } else { users = value.(map[address]bool) } users[addr] = true } func removeFromMintIndex(addr address, mintTotal int64) { key := balanceToKey(mintTotal) value, found := mintIndex.Get(key) if !found { return } users := value.(map[address]bool) delete(users, addr) if len(users) == 0 { mintIndex.Remove(key) } } func updateMintIndex(addr address, amount int64) { oldMintTotal := MintedOf(addr) newMintTotal := oldMintTotal + amount // Update user mint total userMintTotal.Set(addr.String(), newMintTotal) // Remove from old mint total if oldMintTotal > 0 { removeFromMintIndex(addr, oldMintTotal) } // Add to new mint total addToMintIndex(addr, newMintTotal) } // ListTopUsersByMinting returns top users sorted by total minted amount (descending) with pagination func ListTopUsersByMinting(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") } var result []map[string]string offset := (page - 1) * count skipped := 0 collected := 0 // ReverseIterate gives us high-to-low mint order mintIndex.ReverseIterate("", "", func(key string, value any) bool { if collected >= count { return true // stop iteration } users := value.(map[address]bool) for addr := range users { // Skip until we reach offset if skipped < offset { skipped++ continue } if collected >= count { return true } minted := MintedOf(addr) result = append(result, map[string]string{ "user": addr.String(), "minted": ufmt.Sprintf("%d", minted), }) collected++ } return false }) return result }