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
4 changes: 3 additions & 1 deletion cmd/sim/evm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ func NewEvmCmd() *cobra.Command {
" collectibles - ERC721 and ERC1155 NFT holdings with spam filtering\n" +
" token-info - Token metadata, pricing, supply, and market cap\n" +
" token-holders - Top holders of an ERC20 token ranked by balance\n" +
" defi-positions - DeFi positions across lending, AMM, and vault protocols (beta)\n\n" +
" defi-positions - DeFi positions across lending, AMM, and vault protocols (beta)\n" +
" supported-protocols - DeFi protocol families and chains covered by defi-positions\n\n" +
"Most commands support --chain-ids to restrict results to specific networks.\n" +
"Run 'dune sim evm supported-chains' to discover valid chain IDs, tags, and\n" +
"which endpoints are available per chain.\n\n" +
Expand All @@ -63,6 +64,7 @@ func NewEvmCmd() *cobra.Command {
cmd.AddCommand(NewTokenInfoCmd())
cmd.AddCommand(NewTokenHoldersCmd())
cmd.AddCommand(NewDefiPositionsCmd())
cmd.AddCommand(NewSupportedProtocolsCmd())

return cmd
}
115 changes: 115 additions & 0 deletions cmd/sim/evm/supported_protocols.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package evm

import (
"encoding/json"
"fmt"
"net/url"
"strings"

"github.com/spf13/cobra"

"github.com/duneanalytics/cli/output"
)

// Chain-status values returned by the API are lowercase strings.
const protocolStatusPreview = "preview"

// NewSupportedProtocolsCmd returns the `sim evm supported-protocols` command.
func NewSupportedProtocolsCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "supported-protocols",
Short: "List DeFi protocol families and chains supported by defi-positions",
Long: "Display DeFi protocol families covered by the Sim defi-positions\n" +
"endpoint, the chains each family is available on, and the sub-protocols\n" +
"(forks) recognized under each family. Each chain entry has a status of\n" +
"Stable or Preview.\n\n" +
"Use this to discover which protocols and chains 'dune sim evm defi-positions'\n" +
"can return data for.\n\n" +
"Examples:\n" +
" dune sim evm supported-protocols\n" +
" dune sim evm supported-protocols --include-preview-chains\n" +
" dune sim evm supported-protocols --include-preview-protocols\n" +
" dune sim evm supported-protocols -o json",
RunE: runSupportedProtocols,
}

cmd.Flags().Bool("include-preview-chains", false, "Include chains that are marked as preview (not yet publicly available)")
cmd.Flags().Bool("include-preview-protocols", false, "Include protocols that are marked as preview on the requested chains")
output.AddFormatFlag(cmd, "text")

return cmd
}

type supportedProtocolsResponse struct {
ProtocolFamilies []supportedProtocolFamily `json:"protocol_families"`
}

type supportedProtocolFamily struct {
Family string `json:"family"`
Chains []supportedProtocolChain `json:"chains"`
SubProtocols []string `json:"sub_protocols"`
}

type supportedProtocolChain struct {
ChainID json.Number `json:"chain_id"`
ChainName string `json:"chain_name"`
Status string `json:"status"`
}

func runSupportedProtocols(cmd *cobra.Command, _ []string) error {
client := SimClientFromCmd(cmd)
if client == nil {
return fmt.Errorf("sim client not initialized")
}

params := url.Values{}
if v, _ := cmd.Flags().GetBool("include-preview-chains"); v {
params.Set("include_preview_chains", "true")
}
if v, _ := cmd.Flags().GetBool("include-preview-protocols"); v {
params.Set("include_preview_protocols", "true")
}

data, err := client.Get(cmd.Context(), "/v1/evm/defi/supported-protocols", params)
if err != nil {
return err
}

w := cmd.OutOrStdout()
switch output.FormatFromCmd(cmd) {
case output.FormatJSON:
var raw json.RawMessage = data
return output.PrintJSON(w, raw)
default:
var resp supportedProtocolsResponse
if err := json.Unmarshal(data, &resp); err != nil {
return fmt.Errorf("parsing response: %w", err)
}

columns := []string{"FAMILY", "CHAINS", "SUB_PROTOCOLS"}
rows := make([][]string, len(resp.ProtocolFamilies))
for i, f := range resp.ProtocolFamilies {
rows[i] = []string{
f.Family,
formatProtocolChains(f.Chains),
strings.Join(f.SubProtocols, ","),
}
}
output.PrintTable(w, columns, rows)
return nil
}
}

// formatProtocolChains renders chains as "name(id)" with a "*" suffix for
// preview-status entries, joined by commas.
func formatProtocolChains(chains []supportedProtocolChain) string {
parts := make([]string, len(chains))
for i, c := range chains {
entry := fmt.Sprintf("%s(%s)", c.ChainName, c.ChainID.String())
if strings.EqualFold(c.Status, protocolStatusPreview) {
entry += "*"
}
parts[i] = entry
}
return strings.Join(parts, ",")
}
79 changes: 79 additions & 0 deletions cmd/sim/evm/supported_protocols_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package evm_test

import (
"bytes"
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestEvmSupportedProtocols_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", "supported-protocols"})

require.NoError(t, root.Execute())

out := buf.String()
assert.Contains(t, out, "FAMILY")
assert.Contains(t, out, "CHAINS")
assert.Contains(t, out, "SUB_PROTOCOLS")
}

func TestEvmSupportedProtocols_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", "supported-protocols", "-o", "json"})

require.NoError(t, root.Execute())

var resp map[string]interface{}
require.NoError(t, json.Unmarshal(buf.Bytes(), &resp))
assert.Contains(t, resp, "protocol_families")

families, ok := resp["protocol_families"].([]interface{})
require.True(t, ok, "protocol_families should be an array")
require.NotEmpty(t, families, "should have at least one protocol family")

first, ok := families[0].(map[string]interface{})
require.True(t, ok)
assert.Contains(t, first, "family")
assert.Contains(t, first, "chains")
assert.Contains(t, first, "sub_protocols")

chains, ok := first["chains"].([]interface{})
require.True(t, ok, "chains should be an array")
if len(chains) > 0 {
c, ok := chains[0].(map[string]interface{})
require.True(t, ok)
assert.Contains(t, c, "chain_id")
assert.Contains(t, c, "chain_name")
assert.Contains(t, c, "status")
}
}

func TestEvmSupportedProtocols_IncludePreviewChainsFlag(t *testing.T) {
key := simAPIKey(t)

root := newSimTestRoot()
var buf bytes.Buffer
root.SetOut(&buf)
root.SetArgs([]string{
"sim", "--sim-api-key", key, "evm", "supported-protocols",
"--include-preview-chains", "-o", "json",
})

require.NoError(t, root.Execute())

var resp map[string]interface{}
require.NoError(t, json.Unmarshal(buf.Bytes(), &resp))
assert.Contains(t, resp, "protocol_families")
}
3 changes: 2 additions & 1 deletion cmd/sim/sim.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ func NewSimCmd() *cobra.Command {
"lookups.\n\n" +
"Available subcommands:\n" +
" evm - Query EVM chains: balances, activity, transactions, collectibles,\n" +
" token-info, token-holders, defi-positions, supported-chains\n" +
" token-info, token-holders, defi-positions, supported-chains,\n" +
" supported-protocols\n" +
" svm - Query SVM chains (Solana, Eclipse): balances, transactions\n" +
" auth - Save your Sim API key to the local config file\n\n" +
"Authentication:\n" +
Expand Down
Loading