TL;DR - Query Live Oracle Data
This page embeds a real bag.watch notebook, so every code block is editable and executable. Click into the cell below, tweak the feed, and hit Run to watch mainnet state refresh right in the note.
Result
| eth_usd_price |
|---------------|
| 4068.87 |You’re hitting Chainlink’s ETH/USD oracle straight from your browser—no backend, no API keys. Edit the cell, change transports, or join it with anything else on the page to keep the wow loop going. Yes, you can run and modify every query here.
Reading the Chain: How to Talk to Smart Contracts in SQL
Query Ethereum contracts directly in SQL - fetch live data from Chainlink, Uniswap, or any EVM contract right from your browser.
Every time you hit a blockchain RPC, you get hex. Not insight.
eth_call returns a wall of bytes like:
0x0000000000000000000000000000000000000000000000000000000000000020...You just wanted to know the token name.
If you’ve used Etherscan, you’ve seen the magic it performs automatically:
it takes that raw output, looks up the ABI, and decodes it into readable fields and values - all behind the scenes.
But what if you could do that yourself - locally, in SQL - and query any contract, live, from your browser?
That’s the idea behind bag.watch - local-first notebooks for onchain ops. Everything runs client-side - no servers, no installs, no API keys, nothing sitting between you and the chain.
bag.watch treats the EVM as a database: contracts feel like tables, functions behave like parameterized queries, and transports are just connections you can swap at will.
With a few lines of SQL, you can:
- Connect to any EVM RPC (public, private, or local Anvil)
- Read contract storage and function outputs directly
- Mix analytics and calldata previews in a single notebook cell
Let’s read the chain.
Connecting to the Chain
In bag.watch, a blockchain connection is just a transport handle:
a JSON object that wraps your RPC endpoint, headers, and chain id.
Use http_transport to create one:
In a notebook cell, run this query exactly as shown - it’s live SQL against the EVM.
You can also connect to any public endpoint:
This returns a TRANSPORT type you can reuse in later queries, by using query parameters starting with $ like $transport.
Reading a Token’s Metadata
Let’s do something useful: ask a contract who it is.
Take USDC on Ethereum (0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48).
With read_contract, you can call its name() and decimals() functions in SQL.
Each read_contract call yields a result table. When the ABI output lacks explicit field names (like name() returning a single string), the columns default to output0, output1, and so on - just alias them before composing downstream logic.
Output
| name | decimals |
|-------------|-----------|
| USD Coin | 6 |This is the exact decode path your wallet or block explorer runs for ERC-20 metadata - only now you can blend it with whatever analytics you want.
You just performed a real onchain eth_call - encoded arguments, decoded the output, and surfaced it as clean SQL columns.
No ethers.js, no viem, no scripts, no hex gymnastics.
bag.watch automatically:
- Encodes your call based on the ABI JSON
- Sends it via the provided RPC transport
- Decodes the result into native DuckDB types or custom bag.watch types like
uint256
It’s the same decoding logic Etherscan does automatically - but now you control it, locally.
Reading Live Oracle Data
Let’s fetch something dynamic: the ETH/USD price feed from Chainlink.
The feed contract lives at 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419 and exposes latestAnswer().
Call that function directly and let format_units take care of the 8-decimal scaling:
Result
| eth_usd_price |
|---------------|
| 4068.87 |That’s the same live value your DEX or DeFi dashboard would read - now in a SQL cell you can join, aggregate, and chart.
You’re looking at the same price feed that governs most of DeFi’s risk logic - the one Aave, Compound, and EigenLayer monitor before letting collateral move.
Chainlink answers are signed integers scaled by 1e8, so format_units does the decimal math for you. When you’re dealing with raw ETH balances or allowances, reach for format_ether(output0::UINT256) to get clean 18-decimal numbers without manual division.
Comparing Oracle and Pool Prices
Want to cross-check that oracle price against the Uniswap v3 ETH/USDC pool? Another read_contract call pulls the pool’s slot0() struct directly from mainnet, so you’re staring at the exact state Uniswap uses to settle trades—no indexers, subgraphs, or archives in between.
Result
| uni_eth_usd_price |
|-------------------|
| 4064.44 |Duplicate the cell, swap the pool address, or change transports and you’ll see the table update instantly. That’s how you verify pool health or slippage in real time, not a day later through someone else’s index.
Computing the Price
The pool’s implied price closely tracks the oracle - both fetched directly over RPC, right inside your notebook.
slot0() returns a tuple of narrow ints (uint160, int24, etc.), but read_contract expects each decoded word to be a full 32-byte slot.
USDC is token0 (6 decimals) and WETH is token1 (18 decimals) in this pool, so the raw sqrtPriceX96 encodes WETH per USDC. To report USDC per ETH, invert that ratio and multiply by 10^(18-6) = 1e12, which is what the pow(2::DOUBLE, 96) / sqrt_price_x96 pattern in the query does.
You can combine them in a single SQL view:
Output
| chainlink_eth_usd | uniswap_eth_usd | pct_diff |
|-------------------|-----------------|----------|
| 4068.87 | 4064.44 | -0.01 |You just joined an oracle feed and a DEX pool like two SQL tables - and the data never left the RPCs you pointed at.
No indexers, no ETL, no delay.
Composing Your Own Queries
Now that you can read contracts on demand, treat them like tables: join oracle feeds, compute deltas, or simulate vault values - all reproducible and client-side.
Output
| eth_for_100_usd |
|-----------------|
| 0.0246 |Now the oracle feed behaves like any other numeric column inside bag.watch - DuckDB runs underneath to give you OLAP-class SQL right in the browser, so it’s instantly ready for dashboards, alerts, or quick mental math.
Everything here ran locally - no indexer, no backend.
Wrap-Up
Here’s what we just walked through:
- Connect to any EVM RPC with
http_transport - Query contracts with
read_contract - Decode ABI results into clean SQL tables
- Combine oracle and DEX data for real-time analysis
All of it stays inside the browser - WebAssembly DuckDB on the front-end, live RPCs on the back-end - so “readable chain state” becomes an everyday reality. Contracts feel like tables because they are, once you can query them this way.
What used to require scripts, subgraphs, or bespoke infra now fits inside a single notebook cell. bag.watch makes onchain data composable, reproducible, and frankly fun.
Try a query yourself → bag.watch/app
Next:
👉 Mastering CREATE2 - Predict, Mine & Optimize Ethereum Contract Addresses