package block import ( "chain" "chain/banker" "chain/runtime" "strconv" "gno.land/p/demo/tokens/grc1155" "gno.land/p/nt/avl" "gno.land/p/nt/ufmt" "gno.land/r/akkadia/admin" ) const ( MintEvent = "Mint" ) var ( token = grc1155.NewBasicGRC1155Token("") supply = avl.NewTree() // tid -> uint32 ) func BalanceOf(user address, tokenID grc1155.TokenID) int64 { balance, err := token.BalanceOf(user, tokenID) if err != nil { panic("balanceOf failed: " + err.Error()) } return balance } func BalanceOfBatch(ul []address, tokenIDs []grc1155.TokenID) []int64 { balanceBatch, err := token.BalanceOfBatch(ul, tokenIDs) if err != nil { panic("balanceOfBatch failed: " + err.Error()) } return balanceBatch } func IsApprovedForAll(owner, operator address) bool { return token.IsApprovedForAll(owner, operator) } func SetApprovalForAll(cur realm, operator address, approved bool) { err := token.SetApprovalForAll(operator, approved) if err != nil { panic("setApprovalForAll failed: " + err.Error()) } } func TransferFrom(cur realm, from, to address, tokenID grc1155.TokenID, amount int64) { err := token.SafeTransferFrom(from, to, tokenID, amount) if err != nil { panic("transferFrom failed: " + err.Error()) } // Update user token indexes addUserToken(to, tokenID) removeUserToken(from, tokenID) } func BatchTransferFrom(cur realm, from, to address, tokenIDs []grc1155.TokenID, amounts []int64) { err := token.SafeBatchTransferFrom(from, to, tokenIDs, amounts) if err != nil { panic("batchTransferFrom failed: " + err.Error()) } // Update user token indexes in batch (efficient) addUserTokenBatch(to, tokenIDs) removeUserTokenBatch(from, tokenIDs) } // Mint mints block tokens with payment (anyone can mint) func Mint(cur realm, to address, blockID uint32, amount int64) { if amount < 1 { panic("amount must be positive") } caller := runtime.PreviousRealm().Address() // Get block metadata blockIDStr := blockIDToString(blockID) blockIDKey := blockIDToKey(blockID) blockVal, found := blocks.Get(blockIDKey) if !found { panic("block not found: " + blockIDStr) } block := blockVal.(map[string]string) // Get mint price mintPriceStr := block["mintPrice"] mintPrice, err := strconv.ParseInt(mintPriceStr, 10, 64) if err != nil { panic("mint price is not a number") } // Get max supply maxSupplyStr := block["maxSupply"] maxSupply, err := strconv.ParseInt(maxSupplyStr, 10, 64) if err != nil { panic("max supply is not a number") } // -1 blocks are not mintable if maxSupply == -1 { panic("block not mintable") } // Check current supply currentSupply, found := supply.Get(blockIDStr) if !found { if amount > maxSupply { panic("supply exceeded") } supply.Set(blockIDStr, amount) } else { if currentSupply.(int64) + amount > maxSupply { panic("supply exceeded") } supply.Set(blockIDStr, currentSupply.(int64) + amount) } // Check payment sent := banker.OriginSend() totalPrice := mintPrice * amount sentAmount := sent.AmountOf("ugnot") if sentAmount < totalPrice { panic(ufmt.Sprintf("insufficient payment: required %d ugnot, got %d", totalPrice, sentAmount)) } // Refund excess if sentAmount > totalPrice { banker_ := banker.NewBanker(banker.BankerTypeRealmSend) realmAddr := runtime.CurrentRealm().Address() refund := sentAmount - totalPrice refundCoins := chain.NewCoins(chain.NewCoin("ugnot", refund)) banker_.SendCoins(realmAddr, caller, refundCoins) } // Get creator address creatorStr, found := block["creator"] if !found { panic("creator not found in block: " + blockIDStr) } creator := parseAddress(creatorStr) // Calculate shares bps := GetCreatorBPS() var creatorShare, operatorShare int64 if totalPrice > 0 { creatorShare = (totalPrice * int64(bps)) / 10000 operatorShare = totalPrice - creatorShare } // Distribute mint revenue to creator and operator if totalPrice > 0 { // Send funds bnk := banker.NewBanker(banker.BankerTypeRealmSend) realmAddr := runtime.CurrentRealm().Address() if creatorShare > 0 { coins := chain.Coins{chain.Coin{"ugnot", creatorShare}} bnk.SendCoins(realmAddr, creator, coins) } if operatorShare > 0 { coins := chain.Coins{chain.Coin{"ugnot", operatorShare}} bnk.SendCoins(realmAddr, admin.GetFeeCollector(), coins) } } // Mint tokens tokenID := blockIDToTokenID(blockID) err = token.SafeMint(to, tokenID, amount) if err != nil { panic("mint failed: " + err.Error()) } // Update user token index addUserToken(to, tokenID) // Emit mint revenue event chain.Emit(MintEvent, "id", blockIDStr, "amount", strconv.FormatInt(amount, 10), "totalPrice", strconv.FormatInt(totalPrice, 10), "creator", creator.String(), "creatorShare", strconv.FormatInt(creatorShare, 10), "feeCollector", admin.GetFeeCollector().String(), "feeCollectorShare", strconv.FormatInt(operatorShare, 10), ) } // burn - internal helper for burning tokens func burn(from address, tokeID grc1155.TokenID, amount int64) { err := token.Burn(from, tokeID, amount) if err != nil { panic("burn failed: " + err.Error()) } // Update user token index (removes if balance becomes 0) removeUserToken(from, tokeID) } func Burn(cur realm, from address, tokeID grc1155.TokenID, amount int64) { caller := runtime.PreviousRealm().Address() if caller != from { assertIsAdmin(caller) } burn(from, tokeID, amount) } func BurnBatch(cur realm, from address, tokeIDs []grc1155.TokenID, amounts []int64) { caller := runtime.PreviousRealm().Address() if caller != from { assertIsAdmin(caller) } // Burn tokens in one operation err := token.BatchBurn(from, tokeIDs, amounts) if err != nil { panic("batchBurn failed: " + err.Error()) } // Update user token index in batch (efficient) removeUserTokenBatch(from, tokeIDs) } func ListSupplies(tokeIDs ...grc1155.TokenID) []map[string]string { result := []map[string]string{} if len(tokeIDs) == 0 { return result } for _, tokeID := range tokeIDs { currentSupply, found := supply.Get(string(tokeID)) if !found { continue } result = append(result, map[string]string{ "id": string(tokeID), "supply": ufmt.Sprintf("%d", currentSupply.(int64)), }) } return result }