Skip to content

Commit 5098e51

Browse files
committed
Sim evm stablecoins
1 parent 276f717 commit 5098e51

3 files changed

Lines changed: 150 additions & 0 deletions

File tree

cmd/sim/evm/evm.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func NewEvmCmd() *cobra.Command {
4141
cmd.AddCommand(NewSupportedChainsCmd())
4242
cmd.AddCommand(NewBalancesCmd())
4343
cmd.AddCommand(NewBalanceCmd())
44+
cmd.AddCommand(NewStablecoinsCmd())
4445

4546
return cmd
4647
}

cmd/sim/evm/stablecoins.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package evm
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/url"
7+
8+
"github.com/spf13/cobra"
9+
10+
"github.com/duneanalytics/cli/output"
11+
)
12+
13+
// NewStablecoinsCmd returns the `sim evm stablecoins` command.
14+
func NewStablecoinsCmd() *cobra.Command {
15+
cmd := &cobra.Command{
16+
Use: "stablecoins <address>",
17+
Short: "Get stablecoin balances for a wallet address",
18+
Long: "Return stablecoin balances for the given wallet address across supported\n" +
19+
"EVM chains, including USD valuations.\n\n" +
20+
"Examples:\n" +
21+
" dune sim evm stablecoins 0xd8da6bf26964af9d7eed9e03e53415d37aa96045\n" +
22+
" dune sim evm stablecoins 0xd8da... --chain-ids 1,8453\n" +
23+
" dune sim evm stablecoins 0xd8da... -o json",
24+
Args: cobra.ExactArgs(1),
25+
RunE: runStablecoins,
26+
}
27+
28+
cmd.Flags().String("chain-ids", "", "Comma-separated chain IDs or tags (default: all default chains)")
29+
cmd.Flags().String("filters", "", "Token filter: erc20 or native")
30+
cmd.Flags().String("metadata", "", "Extra metadata fields: logo,url,pools")
31+
cmd.Flags().Bool("exclude-spam", false, "Exclude tokens with <100 USD liquidity")
32+
cmd.Flags().String("historical-prices", "", "Hour offsets for historical prices (e.g. 720,168,24)")
33+
cmd.Flags().Int("limit", 0, "Max results (1-1000)")
34+
cmd.Flags().String("offset", "", "Pagination cursor from previous response")
35+
output.AddFormatFlag(cmd, "text")
36+
37+
return cmd
38+
}
39+
40+
func runStablecoins(cmd *cobra.Command, args []string) error {
41+
client, err := requireSimClient(cmd)
42+
if err != nil {
43+
return err
44+
}
45+
46+
address := args[0]
47+
params := url.Values{}
48+
49+
if v, _ := cmd.Flags().GetString("chain-ids"); v != "" {
50+
params.Set("chain_ids", v)
51+
}
52+
if v, _ := cmd.Flags().GetString("filters"); v != "" {
53+
params.Set("filters", v)
54+
}
55+
if v, _ := cmd.Flags().GetString("metadata"); v != "" {
56+
params.Set("metadata", v)
57+
}
58+
if v, _ := cmd.Flags().GetBool("exclude-spam"); v {
59+
params.Set("exclude_spam_tokens", "true")
60+
}
61+
if v, _ := cmd.Flags().GetString("historical-prices"); v != "" {
62+
params.Set("historical_prices", v)
63+
}
64+
if v, _ := cmd.Flags().GetInt("limit"); v > 0 {
65+
params.Set("limit", fmt.Sprintf("%d", v))
66+
}
67+
if v, _ := cmd.Flags().GetString("offset"); v != "" {
68+
params.Set("offset", v)
69+
}
70+
71+
data, err := client.Get(cmd.Context(), "/v1/evm/balances/"+address+"/stablecoins", params)
72+
if err != nil {
73+
return err
74+
}
75+
76+
w := cmd.OutOrStdout()
77+
switch output.FormatFromCmd(cmd) {
78+
case output.FormatJSON:
79+
var raw json.RawMessage = data
80+
return output.PrintJSON(w, raw)
81+
default:
82+
var resp balancesResponse
83+
if err := json.Unmarshal(data, &resp); err != nil {
84+
return fmt.Errorf("parsing response: %w", err)
85+
}
86+
87+
printWarnings(cmd, resp.Warnings)
88+
89+
columns := []string{"CHAIN", "SYMBOL", "AMOUNT", "PRICE_USD", "VALUE_USD"}
90+
rows := make([][]string, len(resp.Balances))
91+
for i, b := range resp.Balances {
92+
rows[i] = []string{
93+
b.Chain,
94+
b.Symbol,
95+
formatAmount(b.Amount, b.Decimals),
96+
formatUSD(b.PriceUSD),
97+
formatUSD(b.ValueUSD),
98+
}
99+
}
100+
output.PrintTable(w, columns, rows)
101+
102+
if resp.NextOffset != "" {
103+
fmt.Fprintf(w, "\nNext offset: %s\n", resp.NextOffset)
104+
}
105+
return nil
106+
}
107+
}

cmd/sim/evm/stablecoins_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package evm_test
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestEvmStablecoins_Text(t *testing.T) {
13+
key := simAPIKey(t)
14+
15+
root := newSimTestRoot()
16+
var buf bytes.Buffer
17+
root.SetOut(&buf)
18+
root.SetArgs([]string{"sim", "--sim-api-key", key, "evm", "stablecoins", evmTestAddress, "--chain-ids", "1"})
19+
20+
require.NoError(t, root.Execute())
21+
22+
out := buf.String()
23+
assert.Contains(t, out, "CHAIN")
24+
assert.Contains(t, out, "SYMBOL")
25+
assert.Contains(t, out, "VALUE_USD")
26+
}
27+
28+
func TestEvmStablecoins_JSON(t *testing.T) {
29+
key := simAPIKey(t)
30+
31+
root := newSimTestRoot()
32+
var buf bytes.Buffer
33+
root.SetOut(&buf)
34+
root.SetArgs([]string{"sim", "--sim-api-key", key, "evm", "stablecoins", evmTestAddress, "--chain-ids", "1", "-o", "json"})
35+
36+
require.NoError(t, root.Execute())
37+
38+
var resp map[string]interface{}
39+
require.NoError(t, json.Unmarshal(buf.Bytes(), &resp))
40+
assert.Contains(t, resp, "wallet_address")
41+
assert.Contains(t, resp, "balances")
42+
}

0 commit comments

Comments
 (0)