Skip to content

Commit 60112ac

Browse files
authored
Add search by contract functionality (#16)
* Add search by contract functionality * Enrich command and flag descriptions for AI agent consumption Merge the detailed flag descriptions from PR #15 (max lengths, type format hints, behavioral details, category explanations) with the examples and Long descriptions from the search_by_contract work. Every Short, Long, and flag help string is now maximally descriptive so AI agents can select the right commands and flags without ambiguity.
1 parent 92ffb50 commit 60112ac

15 files changed

Lines changed: 256 additions & 49 deletions

File tree

cli/root.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ var rootCmd = &cobra.Command{
2828
Long: "A command-line interface for the Dune Analytics platform.\n\n" +
2929
"Discover datasets across the Dune catalog, execute SQL queries (DuneSQL dialect),\n" +
3030
"retrieve execution results, and manage your saved queries — all from the terminal.\n\n" +
31+
"Capabilities:\n" +
32+
" - Search datasets by keyword, contract address, category, or blockchain\n" +
33+
" - Create, update, archive, and retrieve saved DuneSQL queries\n" +
34+
" - Execute saved queries or raw DuneSQL and display results\n" +
35+
" - Browse Dune documentation for DuneSQL syntax, API references, and guides\n" +
36+
" - Monitor credit usage, storage consumption, and billing periods\n\n" +
3137
"Authenticate with an API key via --api-key, the DUNE_API_KEY environment variable,\n" +
3238
"or by running `dune auth`.",
3339
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {

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: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,34 @@ func newSearchCmd() *cobra.Command {
1616
Short: "Search for tables and datasets across the Dune catalog",
1717
Long: "Natural-language table discovery across the Dune catalog. Use this command\n" +
1818
"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,
19+
"The catalog includes:\n" +
20+
" - canonical: chain-native primitives (blocks, transactions, logs, traces)\n" +
21+
" - decoded: ABI-level events and function calls parsed from contract interactions\n" +
22+
" - spell: curated Spellbook transformation datasets (e.g. dex.trades)\n" +
23+
" - community: user-contributed tables and uploads\n\n" +
24+
"Filter by category, blockchain, schema, dataset type, or ownership scope.\n" +
25+
"Use --include-schema to get column names and types for SQL generation.\n\n" +
26+
"For contract-specific lookup, use 'dune dataset search-by-contract' instead.\n\n" +
27+
"Examples:\n" +
28+
" dune dataset search --query \"uniswap swaps\"\n" +
29+
" dune dataset search --query \"transfers\" --categories decoded --blockchains ethereum\n" +
30+
" dune dataset search --query \"dex trades\" --categories spell --include-schema --output json\n" +
31+
" dune dataset search --query \"*\" --owner-scope me",
32+
RunE: runSearch,
2333
}
2434

2535
cmd.Flags().String("query", "", "natural-language search intent or entity hints (e.g. 'uniswap v3 swaps'); use '*' to browse without keyword bias")
2636
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")
37+
cmd.Flags().StringArray("blockchains", nil, "chain scope to reduce ambiguity and improve ranking (e.g. ethereum, solana, arbitrum)")
38+
cmd.Flags().StringArray("dataset-types", nil,
39+
"fine-grained dataset type filter: dune_table, decoded_table, spell, uploaded_table, transformation_table, transformation_view")
2940
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")
41+
cmd.Flags().String("owner-scope", "", "ownership filter: all (default), me, or team; does NOT automatically include private datasets")
3142
cmd.Flags().Bool("include-private", false, "widen results to include private datasets visible to the authenticated user/team alongside public ones")
3243
cmd.Flags().Bool("include-schema", false, "include column-level schema (name, type, nullable) for every result; useful when preparing SQL")
3344
cmd.Flags().Bool("include-metadata", false, "include category-specific metadata (page_rank_score, description, abi_type, contract_name, project_name, etc.)")
3445
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")
46+
cmd.Flags().Int32("offset", 0, "pagination offset; combine with limit to page through large result sets")
3647
output.AddFormatFlag(cmd, "text")
3748

3849
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: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,25 @@ 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",
16+
Use: "search",
17+
Short: "Search the Dune documentation for guides, API references, and code examples",
1818
Long: "Search across all Dune documentation pages including guides, API references,\n" +
19-
"DuneSQL syntax, and code examples. Does not require authentication.",
19+
"DuneSQL syntax, table naming conventions, and code examples.\n" +
20+
"Does not require authentication.\n\n" +
21+
"Useful for looking up:\n" +
22+
" - DuneSQL functions and syntax (date/time, string, aggregate, window functions)\n" +
23+
" - API endpoint references and authentication\n" +
24+
" - Table naming conventions and dataset structure\n" +
25+
" - Dune platform concepts (decoding, Spellbook, materialized views)\n\n" +
26+
"Examples:\n" +
27+
" dune docs search --query \"DuneSQL string functions\"\n" +
28+
" dune docs search --query \"execute query API\" --api-reference-only\n" +
29+
" dune docs search --query \"decoded tables\" --code-only",
2030
Annotations: map[string]string{"skipAuth": "true"},
2131
RunE: runSearch,
2232
}
2333

24-
cmd.Flags().String("query", "", "search query text, e.g. 'DuneSQL date functions' or 'API authentication' (required)")
34+
cmd.Flags().String("query", "", "natural-language search query for Dune docs; e.g. 'DuneSQL date functions', 'API authentication', 'decoded tables meaning', 'query pagination' (required)")
2535
_ = cmd.MarkFlagRequired("query")
2636
cmd.Flags().Bool("api-reference-only", false, "prioritize API reference pages over conceptual guides")
2737
cmd.Flags().Bool("code-only", false, "prioritize pages with executable examples and code snippets")

cmd/execution/execution.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ func NewExecutionCmd() *cobra.Command {
77
cmd := &cobra.Command{
88
Use: "execution",
99
Short: "Retrieve and inspect query execution results",
10+
Long: "Retrieve and inspect the results of query executions.\n\n" +
11+
"Use 'execution results <execution-id>' to fetch results from a previously\n" +
12+
"submitted query execution. The execution ID is returned when running queries\n" +
13+
"with 'dune query run --no-wait' or 'dune query run-sql --no-wait'.",
1014
}
1115
cmd.AddCommand(newResultsCmd())
1216
return cmd

cmd/execution/results.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,31 @@ 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",
20+
Short: "Retrieve execution results for a query execution by execution ID",
2121
Long: "Retrieve the results of a query execution. By default, waits for the execution\n" +
2222
"to complete (up to the timeout) before returning results.\n\n" +
2323
"Behavior:\n" +
2424
" 1. Checks the current execution status\n" +
25-
" 2. If still running: polls until complete or timeout is reached\n" +
25+
" 2. If still running: polls every 2 seconds until complete or timeout is reached\n" +
2626
" 3. If completed: returns the result data\n" +
2727
" 4. If failed/cancelled: returns the error details\n\n" +
28-
"Use --no-wait to return the current state immediately without polling.",
28+
"Use --no-wait to return the current state immediately without polling.\n" +
29+
"Use --limit and --offset to paginate through large result sets.\n\n" +
30+
"Use this after submitting a query with --no-wait, or to re-fetch results from\n" +
31+
"a completed execution.\n\n" +
32+
"Examples:\n" +
33+
" dune execution results 01JXYZ...\n" +
34+
" dune execution results 01JXYZ... --limit 100 --offset 200\n" +
35+
" dune execution results 01JXYZ... --no-wait\n" +
36+
" dune execution results 01JXYZ... --output json",
2937
Args: cobra.ExactArgs(1),
3038
RunE: runResults,
3139
}
3240

33-
cmd.Flags().Int("limit", 0, "maximum number of result rows to return (0 = all)")
34-
cmd.Flags().Int("offset", 0, "number of rows to skip before returning results, used for pagination")
35-
cmd.Flags().Bool("no-wait", false, "return the current execution state immediately without waiting for completion")
36-
cmd.Flags().Int("timeout", 300, "maximum seconds to wait for the execution to complete before timing out")
41+
cmd.Flags().Int("limit", 0, "maximum number of result rows to return (0 = all); use with --offset to paginate large result sets")
42+
cmd.Flags().Int("offset", 0, "number of rows to skip before starting to return results; use with --limit for pagination through large result sets")
43+
cmd.Flags().Bool("no-wait", false, "return the current execution state immediately without waiting for completion; useful for checking status of long-running queries")
44+
cmd.Flags().Int("timeout", 300, "maximum seconds to wait for the execution to complete before timing out (default 300s = 5 minutes)")
3745
output.AddFormatFlag(cmd, "text")
3846

3947
return cmd

cmd/query/create.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,26 @@ 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",
15+
Short: "Create a new saved Dune query and return the query ID",
1616
Long: "Create a new SQL query on Dune. Returns the query ID on success.\n\n" +
1717
"The query is written in DuneSQL dialect. If the query targets tables with\n" +
1818
"known partition columns, include a WHERE filter on those columns\n" +
1919
"(e.g. WHERE block_date >= CURRENT_DATE - INTERVAL '7' DAY) to enable\n" +
20-
"partition pruning and reduce query cost.",
21-
RunE: runCreate,
20+
"partition pruning and reduce query cost.\n\n" +
21+
"Use --temp to create a temporary query that won't appear in the dune.com\n" +
22+
"library or be accessible when shared. Temporary queries are auto-deleted.\n\n" +
23+
"Examples:\n" +
24+
" dune query create --name \"Recent Blocks\" --sql \"SELECT * FROM ethereum.blocks LIMIT 10\"\n" +
25+
" dune query create --name \"My Query\" --sql \"SELECT 1\" --private --description \"Test query\"\n" +
26+
" dune query create --name \"Throwaway\" --sql \"SELECT 1\" --temp",
27+
RunE: runCreate,
2228
}
2329

2430
cmd.Flags().String("name", "", "human-readable query title, max 600 characters (required)")
2531
cmd.Flags().String("sql", "", "the SQL query text in DuneSQL dialect, max 500,000 characters (required)")
2632
cmd.Flags().String("description", "", "short description of what the query does, max 1,000 characters")
2733
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")
34+
cmd.Flags().Bool("temp", false, "create a temporary query that won't appear in the dune.com library or be accessible when shared; auto-deleted")
2935
_ = cmd.MarkFlagRequired("name")
3036
_ = cmd.MarkFlagRequired("sql")
3137
output.AddFormatFlag(cmd, "text")

cmd/query/query.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ func NewQueryCmd() *cobra.Command {
77
cmd := &cobra.Command{
88
Use: "query",
99
Short: "Create, retrieve, update, execute, and archive Dune queries",
10+
Long: "Create, retrieve, update, archive, and execute DuneSQL queries.\n\n" +
11+
"Subcommands:\n" +
12+
" create - Save a new reusable DuneSQL query and get its query ID\n" +
13+
" get - Fetch a saved query's SQL, metadata, and execution state\n" +
14+
" update - Modify a query's SQL, title, description, privacy, or tags\n" +
15+
" archive - Hide a query from the library (still retrievable by ID)\n" +
16+
" run - Execute a saved query by ID and display results\n" +
17+
" run-sql - Execute raw DuneSQL inline without saving a query",
1018
}
1119
cmd.AddCommand(newCreateCmd())
1220
cmd.AddCommand(newGetCmd())

cmd/query/run.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,28 @@ func newRunCmd() *cobra.Command {
1515
Use: "run <query-id>",
1616
Short: "Execute a saved Dune query by its ID and display results",
1717
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" +
18+
"execution to complete (polling every 2 seconds) and displays the result rows.\n" +
19+
"Use --no-wait to submit the execution and exit immediately with just the\n" +
20+
"execution ID; then fetch results later with 'dune execution results <execution-id>'.\n\n" +
2021
"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,
22+
"to select the engine size (medium or large).\n\n" +
23+
"Important: if the query targets tables with known partition columns (returned by\n" +
24+
"'dune dataset search' or 'dune dataset search-by-contract'), ensure the SQL includes\n" +
25+
"a WHERE filter on those partition columns (e.g. WHERE block_date >= CURRENT_DATE -\n" +
26+
"INTERVAL '7' DAY). This enables partition pruning and significantly reduces query cost.\n\n" +
27+
"Examples:\n" +
28+
" dune query run 12345\n" +
29+
" dune query run 12345 --param wallet=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --param days=30\n" +
30+
" dune query run 12345 --performance large --limit 100\n" +
31+
" dune query run 12345 --no-wait\n" +
32+
" dune query run 12345 --timeout 600",
33+
Args: cobra.ExactArgs(1),
34+
RunE: runRun,
2435
}
2536

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")
37+
cmd.Flags().StringArray("param", nil, "typed query parameter in key=value format (repeatable); supported types: text, number (stringified, e.g. '30'), datetime (YYYY-MM-DD HH:mm:ss), enum")
2738
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)")
39+
cmd.Flags().Int("limit", 0, "maximum number of result rows to return (0 = all available rows)")
2940
cmd.Flags().Bool("no-wait", false, "submit the execution and exit immediately, printing only the execution ID and state")
3041
cmd.Flags().Int("timeout", 300, "maximum seconds to wait for the execution to complete before timing out")
3142
output.AddFormatFlag(cmd, "text")
@@ -106,4 +117,3 @@ func parseParams(raw []string) (map[string]any, error) {
106117
}
107118
return params, nil
108119
}
109-

0 commit comments

Comments
 (0)