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, ",")
}