Skip to content

Commit c65dd10

Browse files
committed
pr feedback
1 parent 225485f commit c65dd10

5 files changed

Lines changed: 219 additions & 3 deletions

File tree

cmd/sim/client.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ func NewSimClient(apiKey string) *SimClient {
3030
}
3131
}
3232

33+
// NewBareSimClient creates a Sim API client without authentication.
34+
// Use this for public endpoints that don't require an API key.
35+
func NewBareSimClient() *SimClient {
36+
return &SimClient{
37+
baseURL: defaultBaseURL,
38+
httpClient: &http.Client{
39+
Timeout: 30 * time.Second,
40+
},
41+
}
42+
}
43+
3344
// Get performs a GET request to the Sim API and returns the raw JSON response body.
3445
// The path should include the leading slash (e.g. "/v1/evm/supported-chains").
3546
// Query parameters are appended from params.
@@ -46,7 +57,9 @@ func (c *SimClient) Get(ctx context.Context, path string, params url.Values) ([]
4657
if err != nil {
4758
return nil, fmt.Errorf("creating request: %w", err)
4859
}
49-
req.Header.Set("X-Sim-Api-Key", c.apiKey)
60+
if c.apiKey != "" {
61+
req.Header.Set("X-Sim-Api-Key", c.apiKey)
62+
}
5063
req.Header.Set("Accept", "application/json")
5164

5265
resp, err := c.httpClient.Do(req)

cmd/sim/evm/evm.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,33 @@
11
package evm
22

33
import (
4+
"context"
5+
"net/url"
6+
7+
"github.com/duneanalytics/cli/cmdutil"
48
"github.com/spf13/cobra"
59
)
610

11+
// SimClient is the interface that evm commands use to talk to the Sim API.
12+
// It is satisfied by *sim.SimClient (stored in the command context by
13+
// the sim parent command's PersistentPreRunE).
14+
type SimClient interface {
15+
Get(ctx context.Context, path string, params url.Values) ([]byte, error)
16+
}
17+
18+
// SimClientFromCmd extracts the SimClient from the command context.
19+
func SimClientFromCmd(cmd *cobra.Command) SimClient {
20+
v := cmdutil.SimClientFromCmd(cmd)
21+
if v == nil {
22+
return nil
23+
}
24+
c, ok := v.(SimClient)
25+
if !ok {
26+
return nil
27+
}
28+
return c
29+
}
30+
731
// NewEvmCmd returns the `sim evm` parent command.
832
func NewEvmCmd() *cobra.Command {
933
cmd := &cobra.Command{
@@ -14,7 +38,7 @@ func NewEvmCmd() *cobra.Command {
1438
"and DeFi positions.",
1539
}
1640

17-
// Subcommands will be added here as they are implemented.
41+
cmd.AddCommand(NewSupportedChainsCmd())
1842

1943
return cmd
2044
}

cmd/sim/evm/supported_chains.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package evm
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/spf13/cobra"
9+
10+
"github.com/duneanalytics/cli/output"
11+
)
12+
13+
// NewSupportedChainsCmd returns the `sim evm supported-chains` command.
14+
func NewSupportedChainsCmd() *cobra.Command {
15+
cmd := &cobra.Command{
16+
Use: "supported-chains",
17+
Short: "List supported EVM chains and their endpoint availability",
18+
Long: "Display all EVM chains supported by the Sim API and which endpoints\n" +
19+
"(balances, activity, transactions, etc.) are available for each chain.\n\n" +
20+
"This endpoint is public and does not require a Sim API key.\n\n" +
21+
"Examples:\n" +
22+
" dune sim evm supported-chains\n" +
23+
" dune sim evm supported-chains -o json",
24+
Annotations: map[string]string{"skipSimAuth": "true"},
25+
RunE: runSupportedChains,
26+
}
27+
28+
output.AddFormatFlag(cmd, "text")
29+
30+
return cmd
31+
}
32+
33+
type supportedChainsResponse struct {
34+
Chains []chainEntry `json:"chains"`
35+
}
36+
37+
type chainEntry struct {
38+
Name string `json:"name"`
39+
ChainID json.Number `json:"chain_id"`
40+
Tags []string `json:"tags"`
41+
Balances endpointSupport `json:"balances"`
42+
Activity endpointSupport `json:"activity"`
43+
Transactions endpointSupport `json:"transactions"`
44+
TokenInfo endpointSupport `json:"token_info"`
45+
TokenHolders endpointSupport `json:"token_holders"`
46+
Collectibles endpointSupport `json:"collectibles"`
47+
DefiPositions endpointSupport `json:"defi_positions"`
48+
}
49+
50+
type endpointSupport struct {
51+
Supported bool `json:"supported"`
52+
}
53+
54+
func runSupportedChains(cmd *cobra.Command, _ []string) error {
55+
client := SimClientFromCmd(cmd)
56+
if client == nil {
57+
return fmt.Errorf("sim client not initialized")
58+
}
59+
60+
data, err := client.Get(cmd.Context(), "/v1/evm/supported-chains", nil)
61+
if err != nil {
62+
return err
63+
}
64+
65+
w := cmd.OutOrStdout()
66+
switch output.FormatFromCmd(cmd) {
67+
case output.FormatJSON:
68+
var raw json.RawMessage = data
69+
return output.PrintJSON(w, raw)
70+
default:
71+
var resp supportedChainsResponse
72+
if err := json.Unmarshal(data, &resp); err != nil {
73+
return fmt.Errorf("parsing response: %w", err)
74+
}
75+
76+
columns := []string{
77+
"NAME", "CHAIN_ID", "TAGS",
78+
"BALANCES", "ACTIVITY", "TXS",
79+
"TOKEN_INFO", "HOLDERS", "COLLECTIBLES", "DEFI",
80+
}
81+
rows := make([][]string, len(resp.Chains))
82+
for i, c := range resp.Chains {
83+
rows[i] = []string{
84+
c.Name,
85+
c.ChainID.String(),
86+
strings.Join(c.Tags, ","),
87+
boolYN(c.Balances.Supported),
88+
boolYN(c.Activity.Supported),
89+
boolYN(c.Transactions.Supported),
90+
boolYN(c.TokenInfo.Supported),
91+
boolYN(c.TokenHolders.Supported),
92+
boolYN(c.Collectibles.Supported),
93+
boolYN(c.DefiPositions.Supported),
94+
}
95+
}
96+
output.PrintTable(w, columns, rows)
97+
return nil
98+
}
99+
}
100+
101+
func boolYN(b bool) string {
102+
if b {
103+
return "Y"
104+
}
105+
return "N"
106+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package evm_test
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"testing"
8+
9+
"github.com/duneanalytics/cli/cmd/sim"
10+
"github.com/duneanalytics/cli/cmd/sim/evm"
11+
"github.com/duneanalytics/cli/cmdutil"
12+
"github.com/spf13/cobra"
13+
"github.com/stretchr/testify/assert"
14+
"github.com/stretchr/testify/require"
15+
)
16+
17+
// newEvmTestRoot builds a minimal command tree: dune -> evm -> <subcommands>.
18+
// A bare (unauthenticated) SimClient is stored in context so public-endpoint
19+
// commands can use the shared HTTP infrastructure.
20+
func newEvmTestRoot() *cobra.Command {
21+
root := &cobra.Command{Use: "dune"}
22+
root.SetContext(context.Background())
23+
24+
evmCmd := evm.NewEvmCmd()
25+
root.AddCommand(evmCmd)
26+
27+
// Inject a bare client like simPreRun does for skipSimAuth commands.
28+
cmdutil.SetSimClient(root, sim.NewBareSimClient())
29+
30+
return root
31+
}
32+
33+
// supported-chains is a public endpoint — no API key required.
34+
35+
func TestSupportedChains_Text(t *testing.T) {
36+
root := newEvmTestRoot()
37+
var buf bytes.Buffer
38+
root.SetOut(&buf)
39+
root.SetArgs([]string{"evm", "supported-chains"})
40+
41+
require.NoError(t, root.Execute())
42+
43+
out := buf.String()
44+
assert.Contains(t, out, "NAME")
45+
assert.Contains(t, out, "CHAIN_ID")
46+
assert.Contains(t, out, "BALANCES")
47+
assert.Contains(t, out, "ethereum")
48+
}
49+
50+
func TestSupportedChains_JSON(t *testing.T) {
51+
root := newEvmTestRoot()
52+
var buf bytes.Buffer
53+
root.SetOut(&buf)
54+
root.SetArgs([]string{"evm", "supported-chains", "-o", "json"})
55+
56+
require.NoError(t, root.Execute())
57+
58+
var resp map[string]interface{}
59+
require.NoError(t, json.Unmarshal(buf.Bytes(), &resp))
60+
assert.Contains(t, resp, "chains")
61+
62+
chains, ok := resp["chains"].([]interface{})
63+
require.True(t, ok, "chains should be an array")
64+
require.NotEmpty(t, chains, "should have at least one chain")
65+
66+
first, ok := chains[0].(map[string]interface{})
67+
require.True(t, ok)
68+
assert.Contains(t, first, "name")
69+
assert.Contains(t, first, "chain_id")
70+
}

cmd/sim/sim.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@ func simPreRun(cmd *cobra.Command, _ []string) error {
5050
// correct duration for telemetry.
5151
cmdutil.SetStartTime(cmd, time.Now())
5252

53-
// Allow commands like `sim auth` to skip sim client creation.
53+
// Commands like `sim evm supported-chains` that hit public endpoints
54+
// don't require an API key. Provide a bare (unauthenticated) client so
55+
// they can still use the shared HTTP infrastructure and error handling.
5456
if cmd.Annotations["skipSimAuth"] == "true" {
57+
cmdutil.SetSimClient(cmd, NewBareSimClient())
5558
return nil
5659
}
5760

0 commit comments

Comments
 (0)