Skip to content

Conversation

gtspencer
Copy link
Contributor

Description

Added support for the recently released (Coinbase SQL API)[https://docs.cdp.coinbase.com/data/sql-api/welcome]

Tests

Chatbot: CDP Langchain Chatbot
Network: Base
Setup: Create and set CDP_CLIENT_API_KEY
Prompt: What is the size of the most recent base block?
-------------------
Query executed with result: {"result":[{"size":"143123"}]}
-------------------
The size of the most recent block on Base is **143,123 bytes**.
-------------------
Prompt: Can you give me the gas used for the most recent base block?
-------------------
Query executed with result: {"result":[{"gas_used":"37810031"}]}
-------------------
The gas used for the most recent block on Base is **37,810,031**.
-------------------

Checklist

A couple of things to include in your PR for completeness:

  • Added documentation to all relevant README.md files
  • Added a changelog entry

Notes (and questions)

  • New API key needed (CDP client key); I couldn't find this anywhere else but if there's already a naming convention, happy to change it.
  • A few 500 errors on some tables, likely due to the Alpha status of the system
  • The (website)[https://docs.cdp.coinbase.com/data/sql-api/schema] defines a schema endpoint that can be called for an updated schema, but that link isn't listed anywhere. I've hardcoded the table schema in a file until that endpoint is public
  • This action doesn't need a wallet provider, but it only works on Base; should the supportsNetwork function return true only for Base?

@cb-heimdall
Copy link

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

@github-actions github-actions bot added documentation Improvements or additions to documentation action provider New action provider typescript labels Sep 16, 2025
@phdargen
Copy link
Contributor

Hi @gtspencer, thanks a lot for getting started with this! Was also on my TODO list and could be a very useful feature to add

  • The CDP client key isn't used elsewhere yet and how you named it looks good
  • Yes I think supportsNetwork should only return true for base (base-sepolia is also supported as far as I know), that's how we also handled it for other 'readonly' actions

@phdargen
Copy link
Contributor

I think it would be better to rename this from BaseSqlApiActionProvider to CdpSqlApiActionProvider as more networks might be supported in the future.

It can then be moved in the cdp folder together with CdpApiActionProvider etc

Comment on lines 40 to 41
const cdpApiKey = process.env.CDP_API_CLIENT_KEY;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a config to set the api key and thow error when not set (see for example ZerionActionProvider)

Suggested change
const cdpApiKey = process.env.CDP_API_CLIENT_KEY;
const cdpApiClientKey = config.cdpApiClientKey || process.env.CDP_API_CLIENT_KEY;
if (!cdpApiClientKey) {
throw new Error("CDP_API_CLIENT_KEY is not configured.");
}
this.cdpApiClientKey = cdpApiClientKey;

Comment on lines 52 to 58
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const result = await response.text();

return `Query executed with result: ${result}.`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should return more detailed error info

Suggested change
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.text();
return `Query executed with result: ${result}.`;
if (!response.ok) {
const errorData = await response.json();
return `Error ${response.status} executing Base SQL query: ${errorData.errorMessage || response.statusText}`;
}
const data = await response.json();
return JSON.stringify(data.result);

],
},
{
tableName: "base.transfers",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems base.transfers table is not live yet, will check with the team. Comment out for now

Comment on lines 14 to 15
sqlQuery: z.string().describe("The sql query to execute, using the defined tables and fields"),
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add query limitations

Suggested change
sqlQuery: z.string().describe("The sql query to execute, using the defined tables and fields"),
});
sqlQuery: z
.string()
.min(1, "SQL query cannot be empty")
.max(10000, "Query exceeds maximum length of 10,000 characters")
.describe(
`The SQL query to execute using ClickHouse syntax. Must be a read-only SELECT statement. ` +
`Limitations: max 10,000 characters, max 5 JOINs, no cartesian products, 30s timeout. API supports a max of 10,000 result rows but limit it to 10 unless otherwise specified.`
),});

Comment on lines 310 to 316
This action can call Coinbase's Base SQL API to retrieve onchain data on Base.
Call this action if the user requests historical data on Base.
The SQL API schema is a set of opinionated tables and columns used to organize onchain data for efficient retrieval.
The supported table names, and fields in each table, for SQL queries are defined in the json string:
${schemaJson}
Copy link
Contributor

@phdargen phdargen Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest to add query requirements and use cases.

I think it would help to include the examples from the sql playground as one-shot examples to better guide the LLM.

Suggested change
This action can call Coinbase's Base SQL API to retrieve onchain data on Base.
Call this action if the user requests historical data on Base.
The SQL API schema is a set of opinionated tables and columns used to organize onchain data for efficient retrieval.
The supported table names, and fields in each table, for SQL queries are defined in the json string:
${schemaJson}
This action executes read-only SQL queries against indexed blockchain data using the CDP SQL API.
**Use Cases:**
- Query transaction history and patterns
- Analyze event logs and smart contract interactions
- Retrieve block information and metadata
- Examine token transfers and DeFi activity
**IMPORTANT Query Requirements:**
- Must be SELECT statements only (ClickHouse SQL dialect)
- Casts use the ::<Type> syntax (not CAST(... AS ...))
- Maximum query length: 10,000 characters
- Maximum result rows: 10,000
- Query timeout: 30 seconds
- Maximum JOINs: 5
- No cartesian products allowed
- No DDL/DML operations (INSERT, UPDATE, DELETE, etc.)
- Keep it simple and break down the task into several queries if appropriate.
**Available Tables:**
- base.events: Decoded event logs with parameters and signatures
- base.transactions: Complete transaction data including gas and signatures
- base.blocks: Block information and metadata
- base.encoded_logs: Raw log data that couldn't be decoded
**Table Schema Details:**
${schemaJson}
**Example Queries:**
1. Get ERC-20 token transfers for USDC:
SELECT
parameters['from']::String AS sender,
parameters['to']::String AS to,
parameters['value']::UInt256 AS amount,
address AS token_address
FROM base.events
WHERE
event_signature = 'Transfer(address,address,uint256)'
AND address = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'
LIMIT 10;
2. Get swap events from Uniswap v2-style DEXes:
SELECT parameters ['to']::String AS to,
parameters ['amount0In']::UInt256 AS amount0In,
parameters ['amount0Out']::UInt256 AS amount0Out,
parameters ['amount1In']::UInt256 AS amount1In,
parameters ['amount1Out']::UInt256 AS amount1Out,
parameters ['sender']::String AS sender
FROM base.events
WHERE event_signature = 'Swap(address,uint256,uint256,uint256,uint256,address)'
LIMIT 10;
3. Show me 10 rows from the events table:
SELECT * FROM base.events LIMIT 10;
4. Aggregate ZORA content rewards by coin and currency for payout recipient 0x0bC5f409e4d9298B93E98920276128b89280d832:
SELECT
parameters ['coin']::String as coin,
parameters ['currency']::String as currency,
sum(
(
replaceAll(
splitByChar(' ', parameters ['marketRewards']::String) [1],
'{',
''
)
)::UInt64
) as market_rewards
FROM base.events
WHERE
event_signature = 'CoinMarketRewardsV4(address,address,address,address,address,address,address,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256))'
AND parameters ['payoutRecipient']::String = lower('0x0bC5f409e4d9298B93E98920276128b89280d832')
GROUP BY coin, currency;

@phdargen
Copy link
Contributor

phdargen commented Sep 22, 2025

Hi @gtspencer, the very simple queries from your examples work fine but anything a little bit more complex (eg "find largets usdc (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) transfers in last block") just throws errors. Please have a look at my suggested improvements for the description and schema to better guide the LLM. gpt-4o-mini still failed most of my queries, gpt-5-mini was decent.

One issue I see is that the description is very long with all the excessive schema details which could overwhelm LLMs.
One option would be to have one action per table. Would be much simpler but also limit the capabilities.
Better might be a hybrid approach with:

  • Main query action (execute_sql_query) - Clean, concise description listing available tables with brief descriptions
  • Schema lookup actions (get_table_schema) - On-demand detailed field information for specific tables

But let's first get in the other changes suggested above and then go from there.

@gtspencer
Copy link
Contributor Author

Hey @phdargen good changes, just updated!

And completely agree, the entire schema in 1 file is somewhat unwieldy. I briefly played around with the result of your requested changes, and it feels better, but I'll test it out tomorrow with it broken into multiple actions, 1 for the main query, and 1 for the schema lookup and see how they compare to each other.

Comment on lines +327 to +328
- No DDL/DML operations (INSERT, UPDATE, DELETE, etc.)
- Keep it simple and break down the task into several queries if appropriate.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add this line:

Suggested change
- No DDL/DML operations (INSERT, UPDATE, DELETE, etc.)
- Keep it simple and break down the task into several queries if appropriate.
- No DDL/DML operations (INSERT, UPDATE, DELETE, etc.)
- Always normalize addresses with lower('<addr>')
- Keep it simple and break down the task into several queries if appropriate.

├── cdpSmartWalletActionProvider.test.ts # Tests for CDP Smart Wallet provider
├── cdpSqlApiActionProvider.ts # Main provider implementation
├── cdpSqlApiActionProvider.test.ts # Provider test suite
├── baseSqlApiDescription.ts # Variables describing the action and valid SQL Schemas
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

baseSqlApiDescription -> cdpSqlApiDescription

@phdargen
Copy link
Contributor

Hi @gtspencer, changes look good so far! Here some updates:

  • base-sepolia tables can be queried with eg base_sepolia.transactions but I suggest we concentrate on base-main for now for simplicity
  • the problem with the transfers tables has been fixed, please add back in
  • details about the sql grammar have been added here: https://docs.cdp.coinbase.com/data/sql-api/sql. Again might be a bit too much to drop in the main action description but an action to retrieve this info on-demand in case of syntax errors could help

FROM base.events
WHERE
event_signature = 'Transfer(address,address,uint256)'
AND address = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
AND address = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'
AND address = lower('0x833589fcd6edb6e08f4c7c32d4f71b54bda02913')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action provider New action provider documentation Improvements or additions to documentation typescript

Development

Successfully merging this pull request may close these issues.

3 participants