diff --git a/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token.gno b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token.gno index f152ee90e79..6043132e1dc 100644 --- a/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token.gno +++ b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token.gno @@ -345,3 +345,9 @@ func (s *basicGRC1155Token) RenderHome() (str string) { return } + +func (mt *basicGRC1155Token) Getter() MultiTokenGetter { + return func() IGRC1155 { + return mt + } +} diff --git a/examples/gno.land/p/demo/grc/grc1155/igrc1155.gno b/examples/gno.land/p/demo/grc/grc1155/igrc1155.gno index 5d524e36773..0e7b947cd29 100644 --- a/examples/gno.land/p/demo/grc/grc1155/igrc1155.gno +++ b/examples/gno.land/p/demo/grc/grc1155/igrc1155.gno @@ -38,3 +38,5 @@ type ApprovalForAllEvent struct { type UpdateURIEvent struct { URI string } + +type MultiTokenGetter func() IGRC1155 diff --git a/examples/gno.land/p/demo/grc/grc721/basic_nft.gno b/examples/gno.land/p/demo/grc/grc721/basic_nft.gno index 0505aaa1c26..bd754ef62fe 100644 --- a/examples/gno.land/p/demo/grc/grc721/basic_nft.gno +++ b/examples/gno.land/p/demo/grc/grc721/basic_nft.gno @@ -395,3 +395,10 @@ func (s *basicNFT) RenderHome() (str string) { return } + +// Then add the Getter method to your NFT types +func (n *basicNFT) Getter() NFTGetter { + return func() IGRC721 { + return n + } +} diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721.gno b/examples/gno.land/p/demo/grc/grc721/igrc721.gno index 6c26c953d51..054dc322f31 100644 --- a/examples/gno.land/p/demo/grc/grc721/igrc721.gno +++ b/examples/gno.land/p/demo/grc/grc721/igrc721.gno @@ -26,3 +26,5 @@ const ( ApprovalEvent = "Approval" ApprovalForAllEvent = "ApprovalForAll" ) + +type NFTGetter func() IGRC721 diff --git a/examples/gno.land/r/demo/grc20reg/grc20reg.gno b/examples/gno.land/r/demo/grc20reg/grc20reg.gno index ff46ec94860..70d8987df86 100644 --- a/examples/gno.land/r/demo/grc20reg/grc20reg.gno +++ b/examples/gno.land/r/demo/grc20reg/grc20reg.gno @@ -4,6 +4,7 @@ import ( "std" "gno.land/p/demo/avl" + "gno.land/p/demo/avl/rotree" "gno.land/p/demo/fqname" "gno.land/p/demo/grc/grc20" "gno.land/p/demo/ufmt" @@ -22,6 +23,22 @@ func Register(tokenGetter grc20.TokenGetter, slug string) { ) } +func RegisterWithTokenhub(tokenGetter grc20.TokenGetter, key string) { + prevRealmPath := std.PrevRealm().PkgPath() + if prevRealmPath != "gno.land/r/matijamajranovic/tokenhub" { + return + } + + registry.Set(key, tokenGetter) + + rlmPath, slug := fqname.Parse(key) + std.Emit( + registerEvent, + "pkgpath", rlmPath, + "slug", slug, + ) +} + func Get(key string) grc20.TokenGetter { tokenGetter, ok := registry.Get(key) if !ok { @@ -74,3 +91,7 @@ func Render(path string) string { } const registerEvent = "register" + +func GetRegistry() *rotree.ReadOnlyTree { + return rotree.Wrap(registry, nil) +} diff --git a/examples/gno.land/r/matijamarjanovic/tokenhub/errors.gno b/examples/gno.land/r/matijamarjanovic/tokenhub/errors.gno new file mode 100644 index 00000000000..f4030a06cf7 --- /dev/null +++ b/examples/gno.land/r/matijamarjanovic/tokenhub/errors.gno @@ -0,0 +1,15 @@ +package tokenhub + +import ( + "errors" +) + +var ( + ErrNFTAlreadyRegistered = errors.New("NFT already registered") + ErrNFTNotFound = errors.New("NFT not found") + ErrMTAlreadyRegistered = errors.New("multi-token already registered") + ErrMTNotFound = errors.New("multi-token not found") + ErrMTInfoNotFound = errors.New("multi-token info not found") + ErrNFTtokIDNotExists = errors.New("NFT token ID does not exists") + ErrNFTNotMetadata = errors.New("NFT must implement IGRC721CollectionMetadata") +) diff --git a/examples/gno.land/r/matijamarjanovic/tokenhub/getters.gno b/examples/gno.land/r/matijamarjanovic/tokenhub/getters.gno new file mode 100644 index 00000000000..268ac818355 --- /dev/null +++ b/examples/gno.land/r/matijamarjanovic/tokenhub/getters.gno @@ -0,0 +1,204 @@ +package tokenhub + +import ( + "std" + "strings" + + "gno.land/p/demo/grc/grc1155" + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/grc/grc721" + "gno.land/p/demo/ufmt" + "gno.land/r/demo/grc20reg" + "gno.land/r/demo/users" +) + +// GetUserTokenBalances returns a string of all the grc20 tokens the user owns +func GetUserTokenBalances(userNameOrAddress string) string { + return getTokenBalances(userNameOrAddress, false) +} + +// GetUserTokenBalancesNonZero returns a string of all the grc20 tokens the user owns, but only the ones that have a balance greater than 0 +func GetUserTokenBalancesNonZero(userNameOrAddress string) string { + return getTokenBalances(userNameOrAddress, true) +} + +// GetUserNFTBalances returns a string of all the NFTs the user owns +func GetUserNFTBalances(userNameOrAddress string) string { + return getNFTBalances(userNameOrAddress) +} + +// GetUserMultiTokenBalances returns a string of all the multi-tokens the user owns +func GetUserMultiTokenBalances(userNameOrAddress string) string { + return getMultiTokenBalances(userNameOrAddress, false) +} + +// GetUserMultiTokenBalancesNonZero returns a string of all the multi-tokens the user owns, but only the ones that have a balance greater than 0 +func GetUserMultiTokenBalancesNonZero(userNameOrAddress string) string { + return getMultiTokenBalances(userNameOrAddress, true) +} + +// GetToken returns a token instance for a given key +func GetToken(key string) *grc20.Token { + getter := grc20reg.Get(key) + return getter() +} + +// MustGetToken returns a token instance for a given key, panics if the token is not found +func MustGetToken(key string) *grc20.Token { + getter := grc20reg.MustGet(key) + if getter == nil { + panic("unknown token: " + key) + } + return getter() +} + +// GetNFT returns an NFT instance for a given key +func GetNFT(key string) grc721.IGRC721 { + nftGetter, ok := registeredNFTs.Get(key) + if !ok { + return nil + } + return (nftGetter.(grc721.NFTGetter))() +} + +// MustGetNFT returns an NFT instance for a given key, panics if the NFT is not found +func MustGetNFT(key string) grc721.IGRC721 { + nftGetter := GetNFT(key) + if nftGetter == nil { + panic("unknown NFT: " + key) + } + return nftGetter +} + +// GetMultiToken returns a multi-token instance for a given key +func GetMultiToken(key string) grc1155.IGRC1155 { + info, ok := registeredMTs.Get(key) + if !ok { + return nil + } + mt := info.(GRC1155TokenInfo).Collection + return mt() +} + +// MustGetMultiToken returns a multi-token instance for a given key, panics if the multi-token is not found +func MustGetMultiToken(key string) grc1155.IGRC1155 { + info := GetMultiToken(key) + if info == nil { + panic("unknown multi-token: " + key) + } + return info +} + +// GetAllNFTs returns a string of all the NFTs registered +func GetAllNFTs() string { + var out string + registeredNFTs.Iterate("", "", func(key string, value interface{}) bool { + out += ufmt.Sprintf("NFT:%s,", key) + return false + }) + return out +} + +// GetAllTokens returns a string of all the tokens registered +func GetAllTokens() string { + var out string + grc20reg.GetRegistry().Iterate("", "", func(key string, value interface{}) bool { + out += "Token:" + key + "," + return false + }) + return out +} + +// GetAllMultiTokens returns a string of all the multi-tokens registered +func GetAllMultiTokens() string { + var out string + registeredMTs.Iterate("", "", func(key string, value interface{}) bool { + out += "MultiToken:" + key + "," + return false + }) + return out +} + +// GetAllRegistered returns a string of all the registered tokens, NFTs and multi-tokens +func GetAllRegistered() string { + return GetAllNFTs() + GetAllTokens() + GetAllMultiTokens() +} + +// getNFTBalances returns a string of all the NFTs the user owns +func getNFTBalances(input string) string { + addr := getAddressForUsername(input) + if !addr.IsValid() { + panic("invalid address or username: " + input) + } + var out string + + registeredNFTs.Iterate("", "", func(key string, value interface{}) bool { + nftGetter := value.(grc721.NFTGetter) + nft := nftGetter() + key_parts := strings.Split(key, ".") + owner, err := nft.OwnerOf(grc721.TokenID(key_parts[len(key_parts)-1])) + if err == nil && addr == owner { // show only the nfts owner owns + out += "NFT:" + key + "," + } + return false + }) + + return out +} + +// getTokenBalances returns a string of all the tokens the user owns +func getTokenBalances(input string, nonZero bool) string { + addr := getAddressForUsername(input) + if !addr.IsValid() { + panic("invalid address or username: " + input) + } + var out string + grc20reg.GetRegistry().Iterate("", "", func(key string, value interface{}) bool { + tokenGetter := value.(grc20.TokenGetter) + token := tokenGetter() + balance := token.BalanceOf(addr) + if !nonZero || balance > 0 { + out += ufmt.Sprintf("Token:%s:%d,", key, balance) + } + return false + }) + + return out +} + +// getMultiTokenBalances returns a string of all the multi-tokens the user owns +func getMultiTokenBalances(input string, nonZero bool) string { + addr := getAddressForUsername(input) + if !addr.IsValid() { + panic("invalid address or username: " + input) + } + var out string + + registeredMTs.Iterate("", "", func(key string, value interface{}) bool { + info := value.(GRC1155TokenInfo) + mt := info.Collection() + balance, err := mt.BalanceOf(addr, grc1155.TokenID(info.TokenID)) + if err == nil { + if !nonZero || balance > 0 { + out += ufmt.Sprintf("MultiToken:%s:%d,", key, balance) + } + } + return false + }) + + return out +} + +// getAddressForUsername returns an address for a given username or address +func getAddressForUsername(addrOrName string) std.Address { + addr := std.Address(addrOrName) + if addr.IsValid() { + return addr + } + + if user := users.GetUserByName(addrOrName); user != nil { + return user.Address + } + + return "" +} diff --git a/examples/gno.land/r/matijamarjanovic/tokenhub/gno.mod b/examples/gno.land/r/matijamarjanovic/tokenhub/gno.mod new file mode 100644 index 00000000000..434d0a9bdb1 --- /dev/null +++ b/examples/gno.land/r/matijamarjanovic/tokenhub/gno.mod @@ -0,0 +1 @@ +module gno.land/r/matijamajranovic/tokenhub diff --git a/examples/gno.land/r/matijamarjanovic/tokenhub/render.gno b/examples/gno.land/r/matijamarjanovic/tokenhub/render.gno new file mode 100644 index 00000000000..d76e3f4f41f --- /dev/null +++ b/examples/gno.land/r/matijamarjanovic/tokenhub/render.gno @@ -0,0 +1,170 @@ +package tokenhub + +import ( + "strings" + + "gno.land/p/demo/avl/pager" + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/grc/grc721" + "gno.land/p/demo/ufmt" + "gno.land/p/moul/md" + "gno.land/r/demo/grc20reg" +) + +const ( + token = "token" // grc20 + nft = "nft" // grc721 + mt = "mt" // grc1155 +) + +func Render(path string) string { + var out string + + switch { + case path == "": + out = renderHome() + + case strings.HasPrefix(path, token): + out = renderToken(path) + + case strings.HasPrefix(path, nft): + out = renderNFT(path) + + case strings.HasPrefix(path, mt): + out = renderMT(path) + + default: + out = md.H1("404 Not Found") + out += md.Paragraph("The requested page does not exist.") + out += "[Back to home](/r/matijamajranovic/tokenhub)" + } + + return out +} + +func renderHome() string { + out := md.H1("Token Hub") + out += md.Paragraph("A central registry for GRC721 NFTs, GRC20 tokens, and GRC1155 multi-tokens on gno.land") + + links := []string{ + "[GRC20 Tokens](/r/matijamajranovic/tokenhub:tokens)", + "[GRC721 NFTs](/r/matijamajranovic/tokenhub:nfts)", + "[GRC1155 Multi-Tokens](/r/matijamajranovic/tokenhub:mts)", + } + out += md.BulletList(links) + + return out +} + +func renderToken(path string) string { + out := md.H1("GRC20 Tokens") + var tokenItems []string + + tokenPager := pager.NewPager(grc20reg.GetRegistry(), pageSize, false) + + page := tokenPager.MustGetPageByPath(path) + + for _, item := range page.Items { + tokenGetter := item.Value.(grc20.TokenGetter) + token := tokenGetter() + tokenItems = append(tokenItems, ufmt.Sprintf("%s (%s) [%s]", + md.Bold(token.GetName()), + md.InlineCode(token.GetSymbol()), + md.InlineCode(item.Key))) + } + + if len(tokenItems) > 0 { + out += md.BulletList(tokenItems) + out += "\n" + + if picker := page.Picker(); picker != "" { + out += md.HorizontalRule() + out += md.Paragraph(picker) + } else { + out += md.HorizontalRule() + } + } else { + out += md.Italic("No tokens registered yet") + out += "\n" + } + out += renderFooter() + return out +} + +func renderNFT(path string) string { + out := md.H1("GRC721 NFTs") + + var nftItems []string + nftPager := pager.NewPager(registeredNFTs, pageSize, false) + page := nftPager.MustGetPageByPath(path) + + for _, item := range page.Items { + nftGetter := item.Value.(grc721.NFTGetter) + nft := nftGetter() + metadata, ok := nft.(grc721.IGRC721CollectionMetadata) + if !ok { + continue + } + + nftItems = append(nftItems, ufmt.Sprintf("%s (%s) [%s]", + md.Bold(metadata.Name()), + md.InlineCode(metadata.Symbol()), + md.InlineCode(item.Key))) + } + + if len(nftItems) > 0 { + out += md.BulletList(nftItems) + out += "\n" + + if picker := page.Picker(); picker != "" { + out += md.HorizontalRule() + out += md.Paragraph(picker) + } else { + out += md.HorizontalRule() + } + } else { + out += md.Italic("No NFTs registered yet") + out += "\n" + } + out += renderFooter() + return out +} + +func renderMT(path string) string { + out := md.H1("GRC1155 Multi-Tokens") + var mtItems []string + + mtPager := pager.NewPager(registeredMTs, pageSize, false) + + page := mtPager.MustGetPageByPath(path) + + for _, item := range page.Items { + info := item.Value.(GRC1155TokenInfo) + mtItems = append(mtItems, ufmt.Sprintf("%s: %s [%s]", + md.Bold("TokenID"), + md.InlineCode(info.TokenID), + md.InlineCode(item.Key))) + } + + if len(mtItems) > 0 { + out += md.BulletList(mtItems) + out += "\n" + + if picker := page.Picker(); picker != "" { + out += md.HorizontalRule() + out += md.Paragraph(picker) + } else { + out += md.HorizontalRule() + } + } else { + out += md.Italic("No multi-tokens registered yet") + out += "\n" + } + out += renderFooter() + return out +} +func renderFooter() string { + out := "\n" + out += md.Link("Back to home", "http://localhost:8888/r/matijamajranovic/tokenhub") + return out +} diff --git a/examples/gno.land/r/matijamarjanovic/tokenhub/tokenhub.gno b/examples/gno.land/r/matijamarjanovic/tokenhub/tokenhub.gno new file mode 100644 index 00000000000..c11627a010a --- /dev/null +++ b/examples/gno.land/r/matijamarjanovic/tokenhub/tokenhub.gno @@ -0,0 +1,86 @@ +package tokenhub + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/fqname" + "gno.land/p/demo/grc/grc1155" + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/grc/grc721" + "gno.land/r/demo/grc20reg" + "gno.land/r/leon/hof" +) + +type GRC1155TokenInfo struct { + Collection grc1155.MultiTokenGetter + TokenID string +} + +var ( + registeredTokens = avl.NewTree() // rlmPath[.slug] -> grc20.TokenGetter + registeredNFTs = avl.NewTree() // rlmPath[.slug] -> grc721.NFTGetter + registeredMTs = avl.NewTree() // rlmPath[.slug] -> GRC1155TokenInfo +) + +const pageSize = 10 + +func init() { + hof.Register() +} + +// RegisterToken is a function that uses gno.land/r/demo/grc20reg to register a token +// It uses the slug to construct a key and then registers the token in the registry +// The logic is the same as in grc20reg, but it's done here so the key path is callers pkgpath and not of this realm +// After doing so, the token hub realm uses grc20reg's registry as a read-only avl.Tree +func RegisterToken(tokenGetter grc20.TokenGetter, slug string) { + rlmPath := std.PrevRealm().PkgPath() + key := fqname.Construct(rlmPath, slug) + grc20reg.RegisterWithTokenhub(tokenGetter, key) +} + +// RegisterNFT is a function that registers an NFT in an avl.Tree +func RegisterNFT(nftGetter grc721.NFTGetter, collection string, tokenId string) error { + nft := nftGetter() + _, ok := nft.(grc721.IGRC721CollectionMetadata) + if !ok { + return ErrNFTNotMetadata + } + + nftOwner, err := nft.OwnerOf(grc721.TokenID(tokenId)) + + if err != nil { + return err + } + if !nftOwner.IsValid() { + return ErrNFTtokIDNotExists + } + + rlmPath := std.PrevRealm().PkgPath() + key := rlmPath + "." + collection + "." + tokenId + + if registeredNFTs.Has(key) { + return ErrNFTAlreadyRegistered + } + + registeredNFTs.Set(key, nftGetter) + return nil +} + +// RegisterMultiToken is a function that registers a multi-token in an avl.Tree +// The avl.Tree value is a struct defined in this realm. It contains not only the getter (like other token types) but also the tokenID +func RegisterMultiToken(mtGetter grc1155.MultiTokenGetter, tokenID string) error { + rlmPath := std.PrevRealm().PkgPath() + + key := rlmPath + "." + tokenID + + if registeredMTs.Has(key) { + return ErrMTAlreadyRegistered + } + + registeredMTs.Set(key, GRC1155TokenInfo{ + Collection: mtGetter, + TokenID: tokenID, + }) + return nil +} diff --git a/examples/gno.land/r/matijamarjanovic/tokenhub/tokenhub_test.gno b/examples/gno.land/r/matijamarjanovic/tokenhub/tokenhub_test.gno new file mode 100644 index 00000000000..83454e7eaf9 --- /dev/null +++ b/examples/gno.land/r/matijamarjanovic/tokenhub/tokenhub_test.gno @@ -0,0 +1,194 @@ +package tokenhub + +import ( + "std" + "strings" + "testing" + + "gno.land/p/demo/grc/grc1155" + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/grc/grc721" + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +func TestTokenRegistration(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm("gno.land/r/matijamarjanovic/home")) + + token, _ := grc20.NewToken("Test Token", "TEST", 6) + RegisterToken(token.Getter(), "test_token") + + retrievedToken := GetToken("gno.land/r/matijamarjanovic/home.test_token") + urequire.True(t, retrievedToken != nil, "Should retrieve registered token") + + uassert.Equal(t, "Test Token", retrievedToken.GetName(), "Token name should match") + uassert.Equal(t, "TEST", retrievedToken.GetSymbol(), "Token symbol should match") +} + +func TestNFTRegistration(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm("gno.land/r/matijamarjanovic/home")) + + nft := grc721.NewBasicNFT("Test NFT", "TNFT") + nft.Mint(std.CurrentRealm().Addr(), grc721.TokenID("1")) + err := RegisterNFT(nft.Getter(), "test_nft", "1") + urequire.NoError(t, err, "Should register NFT without error") + + retrievedNFT := GetNFT("gno.land/r/matijamarjanovic/home.test_nft.1") + urequire.True(t, retrievedNFT != nil, "Should retrieve registered NFT") + + metadata, ok := retrievedNFT.(grc721.IGRC721CollectionMetadata) + urequire.True(t, ok, "NFT should implement IGRC721CollectionMetadata") + uassert.Equal(t, "Test NFT", metadata.Name(), "NFT name should match") + uassert.Equal(t, "TNFT", metadata.Symbol(), "NFT symbol should match") +} + +func TestGRC1155Registration(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm("gno.land/r/matijamarjanovic/home")) + + mt := grc1155.NewBasicGRC1155Token("test-uri") + err := RegisterMultiToken(mt.Getter(), "1") + urequire.NoError(t, err, "Should register multi-token without error") + + multiToken := GetMultiToken("gno.land/r/matijamarjanovic/home.1") + urequire.True(t, multiToken != nil, "Should retrieve multi-token") + _, ok := multiToken.(grc1155.IGRC1155) + urequire.True(t, ok, "Retrieved multi-token should implement IGRC1155") + + err = RegisterMultiToken(mt.Getter(), "1") + uassert.True(t, err != nil, "Should not allow duplicate registration") +} + +func TestBalanceRetrieval(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm("gno.land/r/matijamarjanovic/home")) + + token, ledger := grc20.NewToken("Test Token", "TEST", 6) + RegisterToken(token.Getter(), "test_tokenn") + ledger.Mint(std.CurrentRealm().Addr(), 1000) + + mt := grc1155.NewBasicGRC1155Token("test-uri") + RegisterMultiToken(mt.Getter(), "11") + mt.SafeMint(std.CurrentRealm().Addr(), grc1155.TokenID("11"), 5) + + balances := GetUserTokenBalances(std.CurrentRealm().Addr().String()) + uassert.True(t, strings.Contains(balances, "Token:gno.land/r/matijamarjanovic/home.test_tokenn:1000"), "Should show correct GRC20 balance") + + balances = GetUserNFTBalances(std.CurrentRealm().Addr().String()) + uassert.True(t, strings.Contains(balances, "NFT:gno.land/r/matijamarjanovic/home.test_nft.1"), "Should show correct NFT balance") //already minted in test register + + balances = GetUserMultiTokenBalances(std.CurrentRealm().Addr().String()) + uassert.True(t, strings.Contains(balances, "MultiToken:gno.land/r/matijamarjanovic/home.11:5"), "Should show multi-token balance") + + nonZeroBalances := GetUserTokenBalancesNonZero(std.CurrentRealm().Addr().String()) + uassert.True(t, strings.Contains(nonZeroBalances, "Token:gno.land/r/matijamarjanovic/home.test_tokenn:1000"), "Should show non-zero GRC20 balance") +} + +func TestErrorCases(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm("gno.land/r/matijamarjanovic/home")) + + nft := grc721.NewBasicNFT("Test NFT", "TNFT") + err := RegisterNFT(nft.Getter(), "test_nft", "1") + uassert.True(t, err != nil, "Should not allow duplicate registration") + + err = RegisterNFT(nft.Getter(), "test_nft", "1") + uassert.True(t, err != nil, "Should not allow duplicate registration") + + mt := grc1155.NewBasicGRC1155Token("test-uri") + err = RegisterMultiToken(mt.Getter(), "1") + uassert.True(t, err != nil, "Should not allow duplicate registration") + + err = RegisterMultiToken(mt.Getter(), "1") + uassert.True(t, err != nil, "Should not allow duplicate registration") +} + +func TestTokenListingFunctions(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm("gno.land/r/matijamarjanovic/home")) + + grc20Token, _ := grc20.NewToken("Test Token", "TEST", 6) + RegisterToken(grc20Token.Getter(), "listing_token") + + nftToken := grc721.NewBasicNFT("Listing NFT", "LNFT") + nftToken.Mint(std.Address("g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y"), grc721.TokenID("1")) + RegisterNFT(nftToken.Getter(), "listing_nft", "1") + + multiToken := grc1155.NewBasicGRC1155Token("test-uri") + RegisterMultiToken(multiToken.Getter(), "listing_mt") + + nftList := GetAllNFTs() + uassert.True(t, strings.Contains(nftList, "NFT:gno.land/r/matijamarjanovic/home.listing_nft.1"), + "GetAllNFTs should list registered NFT") + + grc20List := GetAllTokens() + uassert.True(t, strings.Contains(grc20List, "Token:gno.land/r/matijamarjanovic/home.listing_token"), + "GetAllGRC20Tokens should list registered token") + + grc1155List := GetAllMultiTokens() + uassert.True(t, strings.Contains(grc1155List, "MultiToken:gno.land/r/matijamarjanovic/home.listing_mt"), + "GetAllMultiTokens should list registered multi-token") + + completeList := GetAllRegistered() + uassert.True(t, strings.Contains(completeList, "NFT:gno.land/r/matijamarjanovic/home.listing_nft.1"), + "GetAllTokens should list NFTs") + uassert.True(t, strings.Contains(completeList, "Token:gno.land/r/matijamarjanovic/home.listing_token"), + "GetAllTokens should list GRC20 tokens") + uassert.True(t, strings.Contains(completeList, "MultiToken:gno.land/r/matijamarjanovic/home.listing_mt"), + "GetAllTokens should list multi-tokens") +} + +func TestMustGetFunctions(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm("gno.land/r/matijamarjanovic/home")) + + token, _ := grc20.NewToken("Must Token", "MUST", 6) + RegisterToken(token.Getter(), "must_token") + + retrievedToken := MustGetToken("gno.land/r/matijamarjanovic/home.must_token") + uassert.Equal(t, "Must Token", retrievedToken.GetName(), "Token name should match") + + defer func() { + r := recover() + uassert.True(t, r != nil, "MustGetToken should panic for non-existent token") + uassert.True(t, strings.Contains(r.(string), "unknown token"), "Panic message should mention unknown token") + }() + MustGetToken("non_existent_token") + + nft := grc721.NewBasicNFT("Must NFT", "MNFT") + nft.Mint(std.CurrentRealm().Addr(), grc721.TokenID("1")) + RegisterNFT(nft.Getter(), "must_nft", "1") + + retrievedNFT := MustGetNFT("gno.land/r/matijamarjanovic/home.must_nft.1") + metadata, ok := retrievedNFT.(grc721.IGRC721CollectionMetadata) + urequire.True(t, ok, "NFT should implement IGRC721CollectionMetadata") + uassert.Equal(t, "Must NFT", metadata.Name(), "NFT name should match") + + defer func() { + r := recover() + uassert.True(t, r != nil, "MustGetNFT should panic for non-existent NFT") + uassert.True(t, strings.Contains(r.(string), "unknown NFT"), "Panic message should mention unknown NFT") + }() + MustGetNFT("non_existent_nft") + + mt := grc1155.NewBasicGRC1155Token("must-uri") + RegisterMultiToken(mt.Getter(), "must_mt") + + retrievedMT := MustGetMultiToken("gno.land/r/matijamarjanovic/home.must_mt") + _, ok = retrievedMT.(grc1155.IGRC1155) + urequire.True(t, ok, "Retrieved multi-token should implement IGRC1155") + + defer func() { + r := recover() + uassert.True(t, r != nil, "MustGetMultiToken should panic for non-existent multi-token") + uassert.True(t, strings.Contains(r.(string), "unknown multi-token"), "Panic message should mention unknown multi-token") + }() + MustGetMultiToken("non_existent_mt") +} + +func TestGetAddressForUsername(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm("gno.land/r/matijamarjanovic/home")) + + validAddr := "g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y" + addr := getAddressForUsername(validAddr) + uassert.Equal(t, validAddr, addr.String(), "Should return same address for valid address input") + + invalidInput := "invalid_input" + addr = getAddressForUsername(invalidInput) + uassert.Equal(t, "", addr.String(), "Should return empty address for invalid input") +}