Skip to content

Commit 3dee67a

Browse files
committed
Add search by contract functionality
1 parent 92ffb50 commit 3dee67a

14 files changed

Lines changed: 244 additions & 95 deletions

File tree

cli/root.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,9 @@ var apiKeyFlag string
2424

2525
var rootCmd = &cobra.Command{
2626
Use: "dune",
27-
Short: "Dune CLI — query, explore, and manage blockchain data on Dune Analytics",
28-
Long: "A command-line interface for the Dune Analytics platform.\n\n" +
29-
"Discover datasets across the Dune catalog, execute SQL queries (DuneSQL dialect),\n" +
30-
"retrieve execution results, and manage your saved queries — all from the terminal.\n\n" +
31-
"Authenticate with an API key via --api-key, the DUNE_API_KEY environment variable,\n" +
32-
"or by running `dune auth`.",
27+
Short: "Dune CLI — interact with the Dune Analytics API",
28+
Long: "A command-line interface for interacting with the Dune Analytics API.\n" +
29+
"Manage queries, execute DuneSQL queries, search datasets, browse documentation, and monitor usage.",
3330
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
3431
if cmd.Annotations["skipAuth"] == "true" {
3532
return nil

cmd/dataset/dataset.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ import "github.com/spf13/cobra"
66
func NewDatasetCmd() *cobra.Command {
77
cmd := &cobra.Command{
88
Use: "dataset",
9-
Short: "Discover and explore datasets across the Dune catalog",
9+
Short: "Search and discover Dune datasets",
10+
Long: "Search the Dune dataset catalog to discover tables and their schemas.\n\n" +
11+
"Use 'dataset search' for keyword-based discovery across all tables, or\n" +
12+
"'dataset search-by-contract' to find decoded tables for a specific contract address.",
1013
}
1114
cmd.AddCommand(newSearchCmd())
15+
cmd.AddCommand(newSearchByContractCmd())
1216
return cmd
1317
}

cmd/dataset/search.go

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,31 @@ import (
1313
func newSearchCmd() *cobra.Command {
1414
cmd := &cobra.Command{
1515
Use: "search",
16-
Short: "Search for tables and datasets across the Dune catalog",
17-
Long: "Natural-language table discovery across the Dune catalog. Use this command\n" +
18-
"to find concrete table names for use in SQL queries.\n\n" +
19-
"Filter by category (canonical for chain primitives, decoded for ABI-level\n" +
20-
"events/calls, spell for curated datasets, community for user-contributed),\n" +
21-
"by blockchain, schema, dataset type, or ownership scope.",
22-
RunE: runSearch,
16+
Short: "Search for datasets across the Dune catalog",
17+
Long: "Search for datasets across the Dune catalog by keyword, category, blockchain, and more.\n\n" +
18+
"The catalog includes canonical blockchain data, decoded contract tables,\n" +
19+
"Spellbook transformations, and community datasets. Use --include-schema\n" +
20+
"to get column names and types for SQL generation.\n\n" +
21+
"Examples:\n" +
22+
" dune dataset search --query \"uniswap swaps\"\n" +
23+
" dune dataset search --query \"transfers\" --categories decoded --blockchains ethereum\n" +
24+
" dune dataset search --query \"dex trades\" --categories spell --include-schema --output json\n" +
25+
" dune dataset search --owner-scope me",
26+
RunE: runSearch,
2327
}
2428

25-
cmd.Flags().String("query", "", "natural-language search intent or entity hints (e.g. 'uniswap v3 swaps'); use '*' to browse without keyword bias")
26-
cmd.Flags().StringArray("categories", nil, "filter by table family: canonical (chain primitives), decoded (ABI-level events/calls), spell (curated datasets), community (user-contributed)")
27-
cmd.Flags().StringArray("blockchains", nil, "chain scope to reduce ambiguity and improve ranking (e.g. ethereum, solana)")
28-
cmd.Flags().StringArray("dataset-types", nil, "fine-grained dataset type filter: dune_table, decoded_table, spell, uploaded_table, transformation_table, transformation_view")
29-
cmd.Flags().StringArray("schemas", nil, "schema/namespace constraint for high precision (e.g. dex, uniswap_v3_ethereum)")
30-
cmd.Flags().String("owner-scope", "", "ownership filter: all, me, or team; does NOT automatically include private datasets")
31-
cmd.Flags().Bool("include-private", false, "widen results to include private datasets visible to the authenticated user/team alongside public ones")
32-
cmd.Flags().Bool("include-schema", false, "include column-level schema (name, type, nullable) for every result; useful when preparing SQL")
33-
cmd.Flags().Bool("include-metadata", false, "include category-specific metadata (page_rank_score, description, abi_type, contract_name, project_name, etc.)")
34-
cmd.Flags().Int32("limit", 20, "number of results per page; use 5-15 for quick checks, 20-50 for deeper exploration")
35-
cmd.Flags().Int32("offset", 0, "pagination offset; use previous response pagination info for next page")
29+
cmd.Flags().String("query", "", "search query text")
30+
cmd.Flags().StringArray("categories", nil, "filter by category (canonical, decoded, spell, community)")
31+
cmd.Flags().StringArray("blockchains", nil, "filter by blockchain")
32+
cmd.Flags().StringArray("dataset-types", nil,
33+
"filter by dataset type (dune_table, decoded_table, spell, uploaded_table, transformation_table, transformation_view)")
34+
cmd.Flags().StringArray("schemas", nil, "filter by schema")
35+
cmd.Flags().String("owner-scope", "", "ownership filter (all, me, team)")
36+
cmd.Flags().Bool("include-private", false, "include private datasets")
37+
cmd.Flags().Bool("include-schema", false, "include column schema in results")
38+
cmd.Flags().Bool("include-metadata", false, "include metadata in results")
39+
cmd.Flags().Int32("limit", 20, "maximum number of results")
40+
cmd.Flags().Int32("offset", 0, "pagination offset")
3641
output.AddFormatFlag(cmd, "text")
3742

3843
return cmd

cmd/dataset/search_by_contract.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package dataset
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/duneanalytics/cli/cmdutil"
8+
"github.com/duneanalytics/cli/output"
9+
"github.com/duneanalytics/duneapi-client-go/models"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
func newSearchByContractCmd() *cobra.Command {
14+
cmd := &cobra.Command{
15+
Use: "search-by-contract",
16+
Short: "Search for decoded tables associated with a specific contract address",
17+
Long: `Search for decoded tables (events and calls) associated with a specific contract address.
18+
19+
This command finds all available decoded tables for a given smart contract address.
20+
Decoded tables contain blockchain data that has been parsed according to a contract's ABI,
21+
providing a structured view of contract interactions.
22+
23+
Table types returned:
24+
- Event tables: contain parsed event logs emitted by smart contracts
25+
- Call tables: contain parsed function calls made to smart contracts
26+
27+
When to use this command:
28+
- You have a specific contract address (EVM or Tron) and want to find its decoded tables
29+
- You want to analyze on-chain activity for a particular smart contract
30+
- You need to find event or call tables for a known contract
31+
32+
For broader table discovery by keyword or project name, use 'dune dataset search' instead.
33+
34+
Examples:
35+
dune dataset search-by-contract --contract-address 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984
36+
dune dataset search-by-contract --contract-address 0x1f98... --blockchains ethereum --blockchains arbitrum
37+
dune dataset search-by-contract --contract-address 0x1f98... --include-schema
38+
dune dataset search-by-contract --contract-address 0x1f98... --limit 50 --offset 20`,
39+
RunE: runSearchByContract,
40+
}
41+
42+
cmd.Flags().String("contract-address", "",
43+
"The contract address to search for. Accepts EVM addresses (starting with 0x) or Tron addresses. "+
44+
"Example: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984'.")
45+
_ = cmd.MarkFlagRequired("contract-address")
46+
cmd.Flags().StringArray("blockchains", nil,
47+
"Filter results to specific blockchains (e.g. ethereum, arbitrum). "+
48+
"Can be specified multiple times. If not provided, searches across all blockchains where the contract exists.")
49+
cmd.Flags().Bool("include-schema", false,
50+
"If set, include the column-level schema (name, type, nullable) for every result. "+
51+
"Enable when preparing SQL generation.")
52+
cmd.Flags().Int32("limit", 20,
53+
"Maximum number of results to return. "+
54+
"Use smaller values (5-10) for quick checks, larger values (50-100) for comprehensive discovery.")
55+
cmd.Flags().Int32("offset", 0,
56+
"Number of results to skip for pagination. Use for paginating through large result sets.")
57+
output.AddFormatFlag(cmd, "text")
58+
59+
return cmd
60+
}
61+
62+
func runSearchByContract(cmd *cobra.Command, _ []string) error {
63+
client := cmdutil.ClientFromCmd(cmd)
64+
65+
contractAddress, _ := cmd.Flags().GetString("contract-address")
66+
67+
req := models.SearchDatasetsByContractAddressRequest{
68+
ContractAddress: contractAddress,
69+
}
70+
71+
if cmd.Flags().Changed("blockchains") {
72+
v, _ := cmd.Flags().GetStringArray("blockchains")
73+
req.Blockchains = v
74+
}
75+
if cmd.Flags().Changed("include-schema") {
76+
v, _ := cmd.Flags().GetBool("include-schema")
77+
req.IncludeSchema = &v
78+
}
79+
if cmd.Flags().Changed("limit") {
80+
v, _ := cmd.Flags().GetInt32("limit")
81+
req.Limit = &v
82+
}
83+
if cmd.Flags().Changed("offset") {
84+
v, _ := cmd.Flags().GetInt32("offset")
85+
req.Offset = &v
86+
}
87+
88+
resp, err := client.SearchDatasetsByContractAddress(req)
89+
if err != nil {
90+
return err
91+
}
92+
93+
w := cmd.OutOrStdout()
94+
switch output.FormatFromCmd(cmd) {
95+
case output.FormatJSON:
96+
return output.PrintJSON(w, resp)
97+
default:
98+
columns := []string{"FULL_NAME", "CATEGORY", "DATASET_TYPE", "BLOCKCHAINS"}
99+
rows := make([][]string, len(resp.Results))
100+
for i, r := range resp.Results {
101+
dt := ""
102+
if r.DatasetType != nil {
103+
dt = *r.DatasetType
104+
}
105+
rows[i] = []string{
106+
r.FullName,
107+
r.Category,
108+
dt,
109+
strings.Join(r.Blockchains, ", "),
110+
}
111+
}
112+
output.PrintTable(w, columns, rows)
113+
fmt.Fprintf(w, "\n%d of %d results\n", len(resp.Results), resp.Total)
114+
return nil
115+
}
116+
}

cmd/docs/search.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,15 @@ const defaultMCPEndpoint = "https://docs.dune.com/mcp"
1313

1414
func newSearchCmd() *cobra.Command {
1515
cmd := &cobra.Command{
16-
Use: "search",
17-
Short: "Search the Dune documentation for guides, API references, and code examples",
18-
Long: "Search across all Dune documentation pages including guides, API references,\n" +
19-
"DuneSQL syntax, and code examples. Does not require authentication.",
16+
Use: "search",
17+
Short: "Search the Dune documentation",
18+
Long: "Search the official Dune documentation. No authentication required.\n\n" +
19+
"Look up DuneSQL syntax, API references, table naming conventions,\n" +
20+
"and Dune platform concepts.\n\n" +
21+
"Examples:\n" +
22+
" dune docs search --query \"DuneSQL string functions\"\n" +
23+
" dune docs search --query \"execute query API\" --api-reference-only\n" +
24+
" dune docs search --query \"decoded tables\" --code-only",
2025
Annotations: map[string]string{"skipAuth": "true"},
2126
RunE: runSearch,
2227
}

cmd/execution/results.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,19 @@ var PollInterval = 2 * time.Second
1717
func newResultsCmd() *cobra.Command {
1818
cmd := &cobra.Command{
1919
Use: "results <execution-id>",
20-
Short: "Get execution results for a query execution by execution ID",
21-
Long: "Retrieve the results of a query execution. By default, waits for the execution\n" +
22-
"to complete (up to the timeout) before returning results.\n\n" +
23-
"Behavior:\n" +
24-
" 1. Checks the current execution status\n" +
25-
" 2. If still running: polls until complete or timeout is reached\n" +
26-
" 3. If completed: returns the result data\n" +
27-
" 4. If failed/cancelled: returns the error details\n\n" +
28-
"Use --no-wait to return the current state immediately without polling.",
20+
Short: "Fetch results of a query execution",
21+
Long: "Fetch results of a query execution by its execution ID.\n\n" +
22+
"Use this after submitting a query with --no-wait, or to re-fetch results from\n" +
23+
"a completed execution. The command handles all execution states:\n" +
24+
" - Completed: displays result rows\n" +
25+
" - Pending/Executing: shows current state (re-run later)\n" +
26+
" - Failed: returns the error message\n" +
27+
" - Cancelled: returns cancellation notice\n\n" +
28+
"Use --limit and --offset to paginate through large result sets.\n\n" +
29+
"Examples:\n" +
30+
" dune execution results 01JXYZ...\n" +
31+
" dune execution results 01JXYZ... --limit 100 --offset 200\n" +
32+
" dune execution results 01JXYZ... --output json",
2933
Args: cobra.ExactArgs(1),
3034
RunE: runResults,
3135
}

cmd/query/create.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,21 @@ import (
1212
func newCreateCmd() *cobra.Command {
1313
cmd := &cobra.Command{
1414
Use: "create",
15-
Short: "Create a new Dune query and return the query ID",
16-
Long: "Create a new SQL query on Dune. Returns the query ID on success.\n\n" +
17-
"The query is written in DuneSQL dialect. If the query targets tables with\n" +
18-
"known partition columns, include a WHERE filter on those columns\n" +
19-
"(e.g. WHERE block_date >= CURRENT_DATE - INTERVAL '7' DAY) to enable\n" +
20-
"partition pruning and reduce query cost.",
21-
RunE: runCreate,
15+
Short: "Create a new saved query",
16+
Long: "Create a new saved DuneSQL query. Returns the numeric query ID on success.\n" +
17+
"Use --temp to create a temporary query that won't appear in your saved queries list.\n\n" +
18+
"Examples:\n" +
19+
" dune query create --name \"Recent Blocks\" --sql \"SELECT * FROM ethereum.blocks LIMIT 10\"\n" +
20+
" dune query create --name \"My Query\" --sql \"SELECT 1\" --private --description \"Test query\"\n" +
21+
" dune query create --name \"Throwaway\" --sql \"SELECT 1\" --temp",
22+
RunE: runCreate,
2223
}
2324

24-
cmd.Flags().String("name", "", "human-readable query title, max 600 characters (required)")
25-
cmd.Flags().String("sql", "", "the SQL query text in DuneSQL dialect, max 500,000 characters (required)")
26-
cmd.Flags().String("description", "", "short description of what the query does, max 1,000 characters")
27-
cmd.Flags().Bool("private", false, "make the query private; may be forced by team privacy settings")
28-
cmd.Flags().Bool("temp", false, "create a temporary query that won't appear in the dune.com library or be accessible when shared")
25+
cmd.Flags().String("name", "", "query name (required)")
26+
cmd.Flags().String("sql", "", "DuneSQL query text (required)")
27+
cmd.Flags().String("description", "", "query description")
28+
cmd.Flags().Bool("private", false, "make the query private")
29+
cmd.Flags().Bool("temp", false, "create a temporary query (auto-deleted, not shown in your saved queries)")
2930
_ = cmd.MarkFlagRequired("name")
3031
_ = cmd.MarkFlagRequired("sql")
3132
output.AddFormatFlag(cmd, "text")

cmd/query/query.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import "github.com/spf13/cobra"
66
func NewQueryCmd() *cobra.Command {
77
cmd := &cobra.Command{
88
Use: "query",
9-
Short: "Create, retrieve, update, execute, and archive Dune queries",
9+
Short: "Manage Dune queries",
10+
Long: "Create, retrieve, update, archive, and execute DuneSQL queries.\n\n" +
11+
"Use 'query create' to save a reusable query, 'query run' to execute a saved query,\n" +
12+
"or 'query run-sql' to execute raw DuneSQL without saving.",
1013
}
1114
cmd.AddCommand(newCreateCmd())
1215
cmd.AddCommand(newGetCmd())

cmd/query/run.go

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,24 @@ import (
1313
func newRunCmd() *cobra.Command {
1414
cmd := &cobra.Command{
1515
Use: "run <query-id>",
16-
Short: "Execute a saved Dune query by its ID and display results",
17-
Long: "Execute a saved Dune query by its numeric ID. By default, waits for the\n" +
18-
"execution to complete and displays the result rows. Use --no-wait to submit\n" +
19-
"the execution and exit immediately with just the execution ID.\n\n" +
20-
"Credits are consumed based on actual compute resources used. Use --performance\n" +
21-
"to select the engine size (medium or large).",
22-
Args: cobra.ExactArgs(1),
23-
RunE: runRun,
16+
Short: "Execute a saved query and display results",
17+
Long: "Execute a saved DuneSQL query by its numeric ID and display results.\n\n" +
18+
"By default, polls every 5 seconds for up to ~5 minutes waiting for completion.\n" +
19+
"Use --no-wait to submit the execution and exit immediately; then fetch\n" +
20+
"results later with 'dune execution results <execution-id>'.\n\n" +
21+
"Examples:\n" +
22+
" dune query run 12345\n" +
23+
" dune query run 12345 --param wallet=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --param days=30\n" +
24+
" dune query run 12345 --performance large --limit 100\n" +
25+
" dune query run 12345 --no-wait",
26+
Args: cobra.ExactArgs(1),
27+
RunE: runRun,
2428
}
2529

26-
cmd.Flags().StringArray("param", nil, "typed query parameter in key=value format (repeatable); numbers are stringified, datetimes use YYYY-MM-DD HH:mm:ss")
27-
cmd.Flags().String("performance", "medium", `engine size for the execution: "medium" (default) or "large"; credits are consumed based on actual compute resources used`)
28-
cmd.Flags().Int("limit", 0, "maximum number of result rows to return (0 = all)")
29-
cmd.Flags().Bool("no-wait", false, "submit the execution and exit immediately, printing only the execution ID and state")
30+
cmd.Flags().StringArray("param", nil, "query parameter in key=value format (repeatable)")
31+
cmd.Flags().String("performance", "medium", `performance tier: "medium" (default) or "large" for higher compute resources`)
32+
cmd.Flags().Int("limit", 0, "maximum number of rows to display (0 = all)")
33+
cmd.Flags().Bool("no-wait", false, "submit execution and exit without waiting for results")
3034
cmd.Flags().Int("timeout", 300, "maximum seconds to wait for the execution to complete before timing out")
3135
output.AddFormatFlag(cmd, "text")
3236

@@ -106,4 +110,3 @@ func parseParams(raw []string) (map[string]any, error) {
106110
}
107111
return params, nil
108112
}
109-

cmd/query/run_sql.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,25 @@ import (
1010
func newRunSQLCmd() *cobra.Command {
1111
cmd := &cobra.Command{
1212
Use: "run-sql",
13-
Short: "Execute a raw DuneSQL query and display results",
14-
Long: "Execute an inline SQL statement in DuneSQL dialect without saving it as a\n" +
15-
"query on Dune. By default, waits for completion and displays result rows.\n\n" +
16-
"Use --no-wait to submit the execution and exit immediately with just the\n" +
17-
"execution ID. Credits are consumed based on actual compute resources used.",
18-
Args: cobra.NoArgs,
19-
RunE: runRunSQL,
13+
Short: "Execute raw DuneSQL and display results",
14+
Long: "Execute a DuneSQL query directly without creating a saved query.\n" +
15+
"Ideal for ad-hoc exploration and one-off analysis.\n\n" +
16+
"By default, polls every 5 seconds for up to ~5 minutes waiting for completion.\n" +
17+
"Use --no-wait to submit and exit immediately.\n\n" +
18+
"Examples:\n" +
19+
" dune query run-sql --sql \"SELECT block_number, block_time FROM ethereum.blocks ORDER BY block_number DESC LIMIT 5\"\n" +
20+
" dune query run-sql --sql \"SELECT * FROM ethereum.transactions WHERE block_number = {{block_num}}\" --param block_num=20000000\n" +
21+
" dune query run-sql --sql \"SELECT COUNT(*) FROM ethereum.transactions\" --performance large",
22+
Args: cobra.NoArgs,
23+
RunE: runRunSQL,
2024
}
2125

22-
cmd.Flags().String("sql", "", "the SQL query text in DuneSQL dialect (required)")
26+
cmd.Flags().String("sql", "", "DuneSQL query to execute (required)")
2327
_ = cmd.MarkFlagRequired("sql")
24-
cmd.Flags().StringArray("param", nil, "typed query parameter in key=value format (repeatable); numbers are stringified, datetimes use YYYY-MM-DD HH:mm:ss")
25-
cmd.Flags().String("performance", "medium", `engine size for the execution: "medium" (default) or "large"; credits are consumed based on actual compute resources used`)
26-
cmd.Flags().Int("limit", 0, "maximum number of result rows to return (0 = all)")
27-
cmd.Flags().Bool("no-wait", false, "submit the execution and exit immediately, printing only the execution ID and state")
28+
cmd.Flags().StringArray("param", nil, "query parameter in key=value format (repeatable)")
29+
cmd.Flags().String("performance", "medium", `performance tier: "medium" (default) or "large" for higher compute resources`)
30+
cmd.Flags().Int("limit", 0, "maximum number of rows to display (0 = all)")
31+
cmd.Flags().Bool("no-wait", false, "submit execution and exit without waiting for results")
2832
cmd.Flags().Int("timeout", 300, "maximum seconds to wait for the execution to complete before timing out")
2933
output.AddFormatFlag(cmd, "text")
3034

0 commit comments

Comments
 (0)