Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 93 additions & 69 deletions examples/gno.land/r/gnoland/coins/coins.gno
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (
"gno.land/p/leon/ctg"
"gno.land/p/moul/md"
"gno.land/p/moul/mdtable"
"gno.land/p/moul/realmpath"

"gno.land/r/sys/users"
)
Expand All @@ -42,8 +41,8 @@ func init() {
res.Write(renderHomepage())
})

router.HandleFunc("balances/{address}", func(res *mux.ResponseWriter, req *mux.Request) {
res.Write(renderAllBalances(req.RawPath, req.GetVar("address")))
router.HandleFunc("balances", func(res *mux.ResponseWriter, req *mux.Request) {
res.Write(renderBalances(req))
})

router.HandleFunc("convert/{address}", func(res *mux.ResponseWriter, req *mux.Request) {
Expand All @@ -69,78 +68,70 @@ func Render(path string) string {
func renderHomepage() string {
return strings.Replace(`# Gno.land Coins Explorer

This is a simple, readonly realm that allows users to browse native coin balances.
Here are a few examples on how to use it:
This is a simple, readonly realm that allows users to browse native coin balances. Check your coin balance below!

- ~/r/gnoland/coins:balances/<address>~ - show full list of coin balances of an address
- [Example](/r/gnoland/coins:balances/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5)
- ~/r/gnoland/coins:balances/<address>?coin=ugnot~ - shows the balance of an address for a specific coin
- [Example](/r/gnoland/coins:balances/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5?coin=ugnot)
- ~/r/gnoland/coins:convert/<cosmos_address>~ - convert Cosmos address to Gno address
<gno-form path="balances">
<gno-input name="address" type="text" placeholder="Valid bech32 address (e.g. g1..., cosmos1..., osmo1...)" />
<gno-input name="coin" type="text" placeholder="Coin (e.g. ugnot)"" />
</gno-form>

Here are a few more ways to use this app:

- ~/r/gnoland/coins:balances?address=g1...~ - show full list of coin balances of an address
- [Example](/r/gnoland/coins:balances?address=g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5)
- ~/r/gnoland/coins:balances?address=g1...&coin=ugnot~ - shows the balance of an address for a specific coin
- [Example](/r/gnoland/coins:balances?address=g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5&coin=ugnot)
- ~/r/gnoland/coins:convert/<bech32_addr>~ - convert a bech32 address to a Gno address
- [Example](/r/gnoland/coins:convert/cosmos1jg8mtutu9khhfwc4nxmuhcpftf0pajdh6svrgs)
- ~/r/gnoland/coins:supply/<denom>~ - shows the total supply of denom
- Coming soon!

`, "~", "`", -1)
}

func renderConvertedAddress(addr string) string {
out := "# Address converter\n\n"
func renderBalances(req *mux.Request) string {
out := "# Balances\n\n"

gnoAddress, err := ctg.ConvertCosmosToGno(addr)
if err != nil {
out += err.Error()
input := req.Query.Get("address")
coin := req.Query.Get("coin")

if input == "" && coin == "" {
out += "Please input a valid address and coin denomination.\n\n"
return out
}

user, _ := users.ResolveAny(gnoAddress.String())
name := "`" + gnoAddress.String() + "`"
if user != nil {
name = user.RenderLink("")
if input == "" {
out += "Please input a valid bech32 address.\n\n"
return out
}

out += ufmt.Sprintf("`%s` on Cosmos matches %s on gno.land.\n\n", addr, name)
out += "[View `ugnot` balance for this address](/r/gnoland/coins:balances/" + gnoAddress.String() + "?coin=ugnot)\n\n"
out += "[View full balance list for this address](/r/gnoland/coins:balances/" + gnoAddress.String() + ")"
return out
}
originalInput := input
var wasConverted bool

func renderSingleCoinBalance(banker std.Banker, denom string, addr string) string {
out := "# Single coin balance\n\n"
if !std.Address(addr).IsValid() {
out += "Invalid address."
return out
// Try to validate or convert
if !std.Address(input).IsValid() {
addr, err := ctg.ConvertAnyToGno(input)
if err != nil {
return out + ufmt.Sprintf("Tried converting `%s` to a Gno address but failed. Please try with a valid bech32 address.\n\n", input)
}
input = addr.String()
wasConverted = true
}

user, _ := users.ResolveAny(addr)
name := "`" + addr + "`"
if user != nil {
name = user.RenderLink("")
if wasConverted {
out += ufmt.Sprintf("> [!NOTE]\n> Automatically converted `%s` to its Gno equivalent.\n\n", originalInput)
}

out += ufmt.Sprintf("%s has `%d%s` at block #%d\n\n",
name, banker.GetCoins(std.Address(addr)).AmountOf(denom), denom, std.ChainHeight())

out += "[View full balance list for this address](/r/gnoland/coins:balances/" + addr + ")"

return out
}
banker := std.NewBanker(std.BankerTypeReadonly)
balances := banker.GetCoins(std.Address(input))

func renderAllBalances(rawpath, input string) string {
out := "# Balances\n\n"
if len(balances) == 0 {
out += "This address currently has no coins."
return out
}

if strings.HasPrefix(input, "cosmos") {
addr, err := ctg.ConvertCosmosToGno(input)
if err != nil {
out += "Tried converting a Cosmos address to a Gno address but failed. Please double-scheck your input."
return out
}
out += ufmt.Sprintf("> [!NOTE]\n> Automatically converted `%s` to its Gno equivalent.\n\n", input)
input = addr.String()
} else {
if !std.Address(input).IsValid() {
out += "Invalid address."
return out
}
if coin != "" {
return renderSingleCoinBalance(coin, input, originalInput, wasConverted)
}

user, _ := users.ResolveAny(input)
Expand All @@ -149,19 +140,9 @@ func renderAllBalances(rawpath, input string) string {
name = user.RenderLink("")
}

banker := std.NewBanker(std.BankerTypeReadonly)
out += ufmt.Sprintf("This page shows full coin balances of %s at block #%d\n\n",
name, std.ChainHeight())

req := realmpath.Parse(rawpath)

coin := req.Query.Get("coin")
if coin != "" {
return renderSingleCoinBalance(banker, coin, input)
}

balances := banker.GetCoins(std.Address(input))

// Determine sorting
if getSortField(req) == "balance" {
coinsort.SortByBalance(balances)
Expand All @@ -188,21 +169,64 @@ func renderAllBalances(rawpath, input string) string {
return out
}

func renderSingleCoinBalance(denom, addr, origInput string, wasConverted bool) string {
out := "# Coin balance\n\n"
banker := std.NewBanker(std.BankerTypeReadonly)

if wasConverted {
out += ufmt.Sprintf("> [!NOTE]\n> Automatically converted `%s` to its Gno equivalent.\n\n", origInput)
}

user, _ := users.ResolveAny(addr)
name := "`" + addr + "`"
if user != nil {
name = user.RenderLink("")
}

out += ufmt.Sprintf("%s has `%d%s` at block #%d\n\n",
name, banker.GetCoins(std.Address(addr)).AmountOf(denom), denom, std.ChainHeight())

out += "[View full balance list for this address](/r/gnoland/coins:balances?address=" + addr + ")"

return out
}

func renderConvertedAddress(addr string) string {
out := "# Address converter\n\n"

gnoAddress, err := ctg.ConvertAnyToGno(addr)
if err != nil {
out += err.Error()
return out
}

user, _ := users.ResolveAny(gnoAddress.String())
name := "`" + gnoAddress.String() + "`"
if user != nil {
name = user.RenderLink("")
}

out += ufmt.Sprintf("`%s` on Cosmos matches %s on gno.land.\n\n", addr, name)
out += "[[View `ugnot` balance for this address]](/r/gnoland/coins:balances?address=" + gnoAddress.String() + "&coin=ugnot) - "
out += "[[View full balance list for this address]](/r/gnoland/coins:balances?address=" + gnoAddress.String() + ")"
return out
}

// Helper functions for sorting and pagination
func getSortField(req *realmpath.Request) string {
func getSortField(req *mux.Request) string {
field := req.Query.Get("sort")
switch field {
case "denom", "balance": // XXX: add Coins.SortBy{denom,bal} methods
case "denom", "balance":
return field
}
return "denom"
}

func isSortReversed(req *realmpath.Request) bool {
func isSortReversed(req *mux.Request) bool {
return req.Query.Get("order") != "asc"
}

func renderSortLink(req *realmpath.Request, field, label string) string {
func renderSortLink(req *mux.Request, field, label string) string {
currentField := getSortField(req)
currentOrder := req.Query.Get("order")

Expand Down
35 changes: 30 additions & 5 deletions examples/gno.land/r/gnoland/coins/coins_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ func TestBalanceChecker(t *testing.T) {
testing.IssueCoins(addr2, std.NewCoins(std.NewCoin(denom2, 12345)))

gnoAddr, _ := ctg.ConvertCosmosToGno("cosmos1s2v4tdskccx2p3yyvzem4mw5nn5fprwcku77hr")
osmoAddr := "osmo1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3aq6l09"
gnoAddr1, _ := ctg.ConvertAnyToGno(osmoAddr)

testing.IssueCoins(gnoAddr1, std.NewCoins(std.NewCoin(denom2, 12345)))

tests := []struct {
name string
Expand All @@ -43,33 +47,54 @@ func TestBalanceChecker(t *testing.T) {
// path: denom,
// expected: "Balance: 1500000testtoken",
// },

{
name: "addr1's coin balance",
path: ufmt.Sprintf("balances/%s?coin=%s", addr1.String(), denom1),
path: ufmt.Sprintf("balances?address=%s&coin=%s", addr1.String(), denom1),
contains: ufmt.Sprintf("`%s` has `%d%s`", addr1.String(), 1000000, denom1),
},
{
name: "addr2's full balances",
path: ufmt.Sprintf("balances/%s", addr2.String()),
path: ufmt.Sprintf("balances?address=%s", addr2.String()),
contains: ufmt.Sprintf("This page shows full coin balances of `%s` at block", addr2.String()),
},
{
name: "addr2's full balances",
path: ufmt.Sprintf("balances/%s", addr2.String()),
path: ufmt.Sprintf("balances?address=%s", addr2.String()),
contains: `| testtoken1 | 501 |
| testtoken2 | 12345 |`,
},
{
name: "addr2's coin balance",
path: ufmt.Sprintf("balances/%s?coin=%s", addr2.String(), denom1),
path: ufmt.Sprintf("balances?address=%s&coin=%s", addr2.String(), denom1),
contains: ufmt.Sprintf("`%s` has `%d%s`", addr2.String(), 501, denom1),
},
{
name: "cosmos addr conversion",
path: "convert/cosmos1s2v4tdskccx2p3yyvzem4mw5nn5fprwcku77hr",
contains: ufmt.Sprintf("`cosmos1s2v4tdskccx2p3yyvzem4mw5nn5fprwcku77hr` on Cosmos matches `%s`", gnoAddr),
},
{
name: "balances bech32 auto convert",
path: ufmt.Sprintf("balances?address=%s&coin=%s", osmoAddr, denom1),
contains: "Automatically converted `osmo1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3aq6l09`",
},
{
name: "single coin balance bech32 auto convert",
path: ufmt.Sprintf("balances?address=%s", osmoAddr),
contains: "Automatically converted `osmo1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3aq6l09`",
},
{
name: "no addr",
path: "balances?address=",
contains: "Please input a valid address",
wantPanic: false,
},
{
name: "no addr",
path: "balances?address=&coin=",
contains: "Please input a valid address and coin denomination.",
wantPanic: false,
},
{
name: "invalid path",
path: "invalid",
Expand Down
Loading