package tba import ( "strconv" "strings" "chain" "chain/banker" "chain/runtime/unsafe" "gno.land/p/test1/grc721" "gno.land/p/nt/avl/v0" ) const selfPath = "gno.land/r/zeycan1/tba" type tokenMeta struct { name string image string } var ( nft *grc721.BasicNFT admin address nextID int64 = 1 allTokens []grc721.TokenID metaByID avl.Tree // tokenID string -> *tokenMeta ) func init() { admin = address(unsafe.PreviousRealm().Address()) } func Setup(cur realm) { if nft != nil { panic("already initialized") } caller := cur.Previous().Address() if caller != admin { panic("only admin can initialize") } nft = grc721.NewBasicNFT(0, cur, "Token Bound Account", "TBA") } func Mint(cur realm, to address, name string, image string) grc721.TokenID { caller := cur.Previous().Address() if caller != admin { panic("only admin can mint") } tid := grc721.TokenID(strconv.FormatInt(nextID, 10)) nextID++ if err := nft.Mint(to, tid); err != nil { panic(err) } allTokens = append(allTokens, tid) metaByID.Set(string(tid), &tokenMeta{name: name, image: image}) return tid } func vaultSubpath(tid grc721.TokenID) string { return "vault/" + string(tid) } func VaultAddress(tid grc721.TokenID) address { return chain.DerivePkgSubAddr(selfPath, vaultSubpath(tid)) } func Balance(tid grc721.TokenID) int64 { coins := banker.NewReadonlyBanker().GetCoins(VaultAddress(tid)) return coins.AmountOf("ugnot") } func Withdraw(cur realm, tid grc721.TokenID, to address, amount int64) { caller := cur.Previous().Address() owner, err := nft.OwnerOf(tid) if err != nil { panic(err) } if caller != owner { panic("caller is not the current owner of this token") } sub := cur.Sub(vaultSubpath(tid)) b := banker.NewBanker(banker.BankerTypeRealmSend, sub) b.SendCoins(sub.Address(), to, chain.NewCoins(chain.NewCoin("ugnot", amount))) } func OwnerOf(tid grc721.TokenID) (address, error) { return nft.OwnerOf(tid) } func BalanceOfOwner(owner address) (int64, error) { return nft.BalanceOf(owner) } func TransferFrom(cur realm, from, to address, tid grc721.TokenID) error { caller := cur.Previous().Address() return nft.TransferFrom(caller, from, to, tid) } func AllTokens() []grc721.TokenID { return allTokens } func TokensOf(owner address) []grc721.TokenID { var out []grc721.TokenID for _, tid := range allTokens { o, err := nft.OwnerOf(tid) if err == nil && o == owner { out = append(out, tid) } } return out } func TokenInfo(tid grc721.TokenID) (name string, image string, owner address, vault address, balance int64, err error) { owner, err = nft.OwnerOf(tid) if err != nil { return "", "", "", "", 0, err } vault = VaultAddress(tid) balance = Balance(tid) if v := metaByID.Get(string(tid)); v != nil { m := v.(*tokenMeta) name = m.name image = m.image } return name, image, owner, vault, balance, nil } func Render(path string) string { if nft == nil { return "# Token Bound Accounts\n\nNot initialized yet. Call Setup() first.\n" } return nft.RenderHome() } // --- Frontend-friendly CSV variants (avoid slice-ref parsing issues) --- func AllTokensCSV() string { ids := make([]string, len(allTokens)) for i, t := range allTokens { ids[i] = string(t) } return strings.Join(ids, ",") } func TokensOfCSV(owner address) string { toks := TokensOf(owner) ids := make([]string, len(toks)) for i, t := range toks { ids[i] = string(t) } return strings.Join(ids, ",") }