From 70c87a1ddcd74efbf870057b101aa024c65765a2 Mon Sep 17 00:00:00 2001 From: Ivan Pusic <450140+ivpusic@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:21:12 +0100 Subject: [PATCH] cleanup --- cmd/sim/evm/balances.go | 37 ++++++++++++++++++++++------- cmd/sim/evm/evm.go | 1 + cmd/sim/evm/stablecoins.go | 29 +++++++++++++++++++++++ cmd/sim/evm/stablecoins_test.go | 42 +++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 cmd/sim/evm/stablecoins.go create mode 100644 cmd/sim/evm/stablecoins_test.go diff --git a/cmd/sim/evm/balances.go b/cmd/sim/evm/balances.go index de791e8..922001d 100644 --- a/cmd/sim/evm/balances.go +++ b/cmd/sim/evm/balances.go @@ -26,15 +26,8 @@ func NewBalancesCmd() *cobra.Command { RunE: runBalances, } - cmd.Flags().String("chain-ids", "", "Comma-separated chain IDs or tags (default: all default chains)") - cmd.Flags().String("filters", "", "Token filter: erc20 or native") + addBalanceFlags(cmd) cmd.Flags().String("asset-class", "", "Asset class filter: stablecoin") - cmd.Flags().String("metadata", "", "Extra metadata fields: logo,url,pools") - cmd.Flags().Bool("exclude-spam", false, "Exclude tokens with <100 USD liquidity") - cmd.Flags().String("historical-prices", "", "Hour offsets for historical prices (e.g. 720,168,24)") - cmd.Flags().Int("limit", 0, "Max results (1-1000)") - cmd.Flags().String("offset", "", "Pagination cursor from previous response") - output.AddFormatFlag(cmd, "text") return cmd } @@ -70,6 +63,30 @@ type warningEntry struct { } func runBalances(cmd *cobra.Command, args []string) error { + return runBalancesEndpoint(cmd, args, "/v1/evm/balances/", "") +} + +// addBalanceFlags registers the common flags shared by the balances and +// stablecoins commands. +func addBalanceFlags(cmd *cobra.Command) { + cmd.Flags().String("chain-ids", "", "Comma-separated chain IDs or tags (default: all default chains)") + cmd.Flags().String("filters", "", "Token filter: erc20 or native") + cmd.Flags().String("metadata", "", "Extra metadata fields: logo,url,pools") + cmd.Flags().Bool("exclude-spam", false, "Exclude tokens with <100 USD liquidity") + cmd.Flags().String("historical-prices", "", "Hour offsets for historical prices (e.g. 720,168,24)") + cmd.Flags().Int("limit", 0, "Max results (1-1000)") + cmd.Flags().String("offset", "", "Pagination cursor from previous response") + output.AddFormatFlag(cmd, "text") +} + +// runBalancesEndpoint is the shared run implementation for the balances and +// stablecoins commands. The final API path is built as: +// +// pathPrefix + address + pathSuffix +// +// For example "/v1/evm/balances/" + addr + "" for balances, +// or "/v1/evm/balances/" + addr + "/stablecoins" for stablecoins. +func runBalancesEndpoint(cmd *cobra.Command, args []string, pathPrefix, pathSuffix string) error { client := SimClientFromCmd(cmd) if client == nil { return fmt.Errorf("sim client not initialized") @@ -84,6 +101,8 @@ func runBalances(cmd *cobra.Command, args []string) error { if v, _ := cmd.Flags().GetString("filters"); v != "" { params.Set("filters", v) } + // asset-class is only registered on the balances command; silently ignored + // when the flag is absent. if v, _ := cmd.Flags().GetString("asset-class"); v != "" { params.Set("asset_class", v) } @@ -103,7 +122,7 @@ func runBalances(cmd *cobra.Command, args []string) error { params.Set("offset", v) } - data, err := client.Get(cmd.Context(), "/v1/evm/balances/"+address, params) + data, err := client.Get(cmd.Context(), pathPrefix+address+pathSuffix, params) if err != nil { return err } diff --git a/cmd/sim/evm/evm.go b/cmd/sim/evm/evm.go index 9f6d088..2d797ab 100644 --- a/cmd/sim/evm/evm.go +++ b/cmd/sim/evm/evm.go @@ -41,6 +41,7 @@ func NewEvmCmd() *cobra.Command { cmd.AddCommand(NewSupportedChainsCmd()) cmd.AddCommand(NewBalancesCmd()) cmd.AddCommand(NewBalanceCmd()) + cmd.AddCommand(NewStablecoinsCmd()) return cmd } diff --git a/cmd/sim/evm/stablecoins.go b/cmd/sim/evm/stablecoins.go new file mode 100644 index 0000000..9b6dc34 --- /dev/null +++ b/cmd/sim/evm/stablecoins.go @@ -0,0 +1,29 @@ +package evm + +import ( + "github.com/spf13/cobra" +) + +// NewStablecoinsCmd returns the `sim evm stablecoins` command. +func NewStablecoinsCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "stablecoins
", + Short: "Get stablecoin balances for a wallet address", + Long: "Return stablecoin balances for the given wallet address across supported\n" + + "EVM chains, including USD valuations.\n\n" + + "Examples:\n" + + " dune sim evm stablecoins 0xd8da6bf26964af9d7eed9e03e53415d37aa96045\n" + + " dune sim evm stablecoins 0xd8da... --chain-ids 1,8453\n" + + " dune sim evm stablecoins 0xd8da... -o json", + Args: cobra.ExactArgs(1), + RunE: runStablecoins, + } + + addBalanceFlags(cmd) + + return cmd +} + +func runStablecoins(cmd *cobra.Command, args []string) error { + return runBalancesEndpoint(cmd, args, "/v1/evm/balances/", "/stablecoins") +} diff --git a/cmd/sim/evm/stablecoins_test.go b/cmd/sim/evm/stablecoins_test.go new file mode 100644 index 0000000..7dd4888 --- /dev/null +++ b/cmd/sim/evm/stablecoins_test.go @@ -0,0 +1,42 @@ +package evm_test + +import ( + "bytes" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEvmStablecoins_Text(t *testing.T) { + key := simAPIKey(t) + + root := newSimTestRoot() + var buf bytes.Buffer + root.SetOut(&buf) + root.SetArgs([]string{"sim", "--sim-api-key", key, "evm", "stablecoins", evmTestAddress, "--chain-ids", "1"}) + + require.NoError(t, root.Execute()) + + out := buf.String() + assert.Contains(t, out, "CHAIN") + assert.Contains(t, out, "SYMBOL") + assert.Contains(t, out, "VALUE_USD") +} + +func TestEvmStablecoins_JSON(t *testing.T) { + key := simAPIKey(t) + + root := newSimTestRoot() + var buf bytes.Buffer + root.SetOut(&buf) + root.SetArgs([]string{"sim", "--sim-api-key", key, "evm", "stablecoins", evmTestAddress, "--chain-ids", "1", "-o", "json"}) + + require.NoError(t, root.Execute()) + + var resp map[string]interface{} + require.NoError(t, json.Unmarshal(buf.Bytes(), &resp)) + assert.Contains(t, resp, "wallet_address") + assert.Contains(t, resp, "balances") +}