Using chainstar
Using chainstar for chainstar. Raw blockchain data to clean dimensional tables in your own warehouse, from one command.
chainstar quickstart --chain ethereum --yes # the latest 20 blocks
chainstar run --chain ethereum --blocks 20000000:20000100
run writes Parquet files to ./data/ethereum/ and the tables to ./warehouse.duckdb.
Without --rpc-url or CHAINSTAR_RPC_URL both use the chain's free public endpoint;
with your own:
export CHAINSTAR_RPC_URL="https://your-ethereum-rpc.example"
chainstar doctor --chain ethereum
chainstar run --chain ethereum --blocks 20000000:20000100
Bitcoin and Solana work the same way:
chainstar run --chain bitcoin --blocks 972460:972463
chainstar run --chain solana --blocks 459564290:459564310 # slots, see below
Query the tables with the DuckDB command-line tool:
duckdb warehouse.duckdb "
select d.token_address, d.symbol, count(*) as transfers, sum(f.amount) as volume
from chainstar.fct_transfers f
join chainstar.dim_tokens d using (token_id)
group by 1, 2 order by 3 desc limit 10"
Every command has --help with examples. Add --debug before the command
(chainstar --debug run ...) to see the full traceback when something fails.
Commands
| Command | What it does |
|---|---|
chainstar quickstart |
Guided first run: asks for a chain and how many of the latest blocks, reads the range from the chain head, runs everything, and prints next steps. --yes for scripts. |
chainstar run |
Runs extract, decode, load and model for a block range, then prints row counts. |
chainstar export |
Saves a table, or the transfers view with token symbols, as CSV, Excel or Parquet, with optional filters. |
chainstar wallet |
All token transfers sent or received by one address in a range, as a spreadsheet. |
chainstar doctor |
Checks that cryo is installed (Ethereum and Base only), the RPC serves the expected chain (EVM chain id, Bitcoin network main, or Solana mainnet's genesis hash), the target is reachable, and dbt is installed. |
chainstar init |
Writes a commented chainstar.yml you can edit. |
chainstar run options:
| Option | Default | Meaning |
|---|---|---|
--chain |
from chainstar.yml |
ethereum, base, bitcoin or solana |
--blocks START:END |
from chainstar.yml |
Block range. END is exclusive, as in cryo: 100:200 is 100 blocks. On Solana the numbers are slots. |
--rpc-url |
$CHAINSTAR_RPC_URL, then the chain's public endpoint |
JSON-RPC endpoint for the chain |
--target |
$CHAINSTAR_TARGET, then chainstar.yml, then duckdb:warehouse.duckdb |
duckdb:PATH or postgres:postgresql://USER@HOST:PORT/DBNAME |
--data-dir |
./data |
Where Parquet files are kept |
--requests-per-second |
no limit; the public endpoint's rate when using one | Cap on requests sent to the RPC |
--chunk-size |
100; 10 on public endpoints |
Blocks (Solana: slots) per Parquet file. Lower it if your RPC often fails requests (see below). |
--config |
./chainstar.yml |
Config file, if you use one |
Command-line flags override environment variables, which override chainstar.yml.
chainstar quickstart options: --chain (default base), --recent N (default 20, or
1 on Bitcoin), --rpc-url, --target (default duckdb:warehouse.duckdb), --data-dir
(default ./data) and --yes. On Ethereum and Base the range ends 5 blocks behind the
head, since chainstar does not handle reorganisations; on Bitcoin it is the latest
block, and on Solana the latest finalized slots.
Re-running and adding ranges
Runs are safe to repeat. Parquet files already written are skipped, by cryo on Ethereum and Base and by chainstar on Bitcoin and Solana, and each load replaces a chain's raw rows with everything in its data directory, not only the range you just asked for. So you can:
- re-run the same command after a failure; only the missing chunks are fetched again;
- run further ranges into the same warehouse; they add up;
- load all four chains into the same warehouse; every table has a
chaincolumn.
The extract lines count the range you asked for; the load line counts everything loaded, and the line under it says which blocks the warehouse now holds. When that is more than this run's range, chainstar says so:
extract erc20_transfers 4,640 rows 0.4s
...
load chainstar_raw 4 tables, 18,844 rows 0.1s
ethereum now in the warehouse: blocks 26,240,971 to 26,240,990 (20 blocks)
(each run loads every chunk saved in data/ethereum, so earlier runs' blocks are included)
To keep a range on its own, give it its own --data-dir and --target. dbt rebuilds
the star-schema tables in full on each run.
Public endpoints
When no RPC URL is set, chainstar uses these free shared endpoints and prints a one-line notice saying so. They are run by PublicNode, are shared by everyone, and limit request rates, so chainstar asks them gently:
| Chain | Endpoint | Requests per second | Blocks per chunk |
|---|---|---|---|
| Ethereum | https://ethereum-rpc.publicnode.com |
5 | 10 |
| Base | https://base-rpc.publicnode.com |
5 | 10 |
| Bitcoin | https://bitcoin-rpc.publicnode.com |
2 | 10 |
| Solana | https://solana-rpc.publicnode.com |
5 | 10 (slots) |
All four ran chainstar quickstart in October 2026 without a refused request: 20
Ethereum blocks in 4.8 minutes, 20 Base blocks in 3.4 minutes (most of both reading
names and symbols of 300 to 400 tokens, three calls each), 20 Solana slots in 26 seconds,
and one Bitcoin block in 6 seconds. In earlier runs the Ethereum endpoint refused a few
percent of requests at 5 per second with 100-block chunks, and none with 10-block chunks.
--requests-per-second and --chunk-size override these, and your own --rpc-url or
CHAINSTAR_RPC_URL replaces them (with no rate cap unless you set one). Public
endpoints can change their limits or go away at any time; if they refuse requests,
chainstar's hint says to wait and run the same command again, which resumes from the
chunks already saved.
Exporting
chainstar export [TABLE] saves a table to a file. TABLE is transfers (the default)
or any star-schema table: fct_transfers, fct_fees, dim_wallets, dim_tokens,
dim_contracts, dim_dates.
chainstar export # transfers.csv
chainstar export transfers --output transfers.xlsx
chainstar export transfers --chain base --wallet 0xYourAddress --output mine.xlsx
chainstar export transfers --token 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 --limit 1000
chainstar export fct_fees --format parquet --target postgres:postgresql://me@localhost:5432/chain
| Option | Meaning |
|---|---|
--output, -o |
File to write. Default <table>.<format> in the current folder. |
--format |
csv, xlsx or parquet. Default: from --output's extension, else csv. |
--chain |
Only this chain's rows. |
--wallet ADDRESS |
Only rows where the address sends or receives (transfers and fees: from_address or to_address; dim_wallets: address). Ethereum and Base addresses match in any letter case. |
--token ADDRESS |
Only this token: ERC-20 contract, SPL mint, or native for BTC and SOL. |
--limit N |
At most N rows. |
--target, --config |
Which warehouse, as for run. |
transfers is fct_transfers joined to dim_tokens, ordered by chain, block and
position: block_timestamp, chain, block_number, transaction_hash,
transfer_position, token_address, token_symbol, token_name, from_address,
to_address, amount, amount_raw.
In every format amount_raw stays text, so 256-bit token amounts keep every digit, and
amount is a number. Excel files are written by DuckDB's
excel extension, which DuckDB
downloads the first time (the Docker image includes it), so chainstar needs no extra
Python package. An Excel sheet holds at most 1,048,576 rows including the header; a
larger export is refused before anything is written, with a suggestion to filter it or
use CSV or Parquet, which have no limit. Excel files store numbers as floating point, so
block_number and the like arrive as numbers with up to 15 significant digits, which
covers every block number and index.
On Bitcoin, --wallet matches outputs the address received, and outputs of transactions
the address funded alone (see from_address in fct_transfers); a
transaction funded by several addresses has no single sender.
Wallet history
chainstar wallet ADDRESS builds a warehouse for one address and writes its transfers
to a spreadsheet:
chainstar wallet 0xYourAddress --chain base --recent 1000
chainstar wallet 0xYourAddress --chain ethereum --blocks 20000000:20010000 \
--rpc-url "$MY_RPC" --blocks-per-request 1000
chainstar wallet YourSolanaAddress --chain solana --recent 100 --output mine.csv
Give either --blocks START:END or --recent N. The output defaults to
<chain>-<address>.xlsx; the data goes to data/wallet-<chain>-<address>/, with its own
warehouse.duckdb, so it never mixes with your main warehouse.
What it costs, honestly:
- Ethereum and Base: the node does the filtering. cryo 0.3.2 can filter ERC-20
Transferevents by sender (--from-address, the event's topic1) or by recipient (--to-address, topic2);eth_getLogscombines topics with AND, so chainstar makes one filtered pass for each direction. Only matching events are downloaded, then only the blocks that contain one (for their timestamps), then the token names. Transactions, and thereforefct_fees, are skipped. With--blocks-per-request 1(the default) that is two requests per block: the free public endpoints refuse a filteredeth_getLogsover more than one block without a contract address. Many paid providers accept hundreds or thousands of blocks per request, which makes a long history quick; check your provider'seth_getLogsblock-range limit. Checked on 20 Base blocks: the same 8 transfers, byte for byte, as a full run filtered withexport --wallet, in 18 seconds instead of 202. - Bitcoin and Solana: their RPC interfaces cannot filter by address, so every block
in the range is downloaded in full (a Bitcoin block is about 6 MB compressed, a Solana
block 4 to 8 MB of JSON before compression) and the export keeps the address's rows. It costs the same as
chainstar runon that range.
When something goes wrong
chainstar prints one error: line saying what happened and, for common problems, a
hint: line saying what to try, instead of a traceback. For example:
error: getblockchaininfo: could not reach https://no-such-host.invalid/<redacted> (URLError)
hint: check your internet connection and the RPC URL; a free public endpoint may also be down for a while, so try again later or use your own RPC URL
Covered: cryo missing (with install and Docker instructions), RPC unreachable or rate
limited (HTTP 429), the RPC serving another chain, a range past the chain head, Postgres
refusing the password or not running, a DuckDB file another program has open, folders
that cannot be written, a full disk, and DuckDB extensions that cannot be downloaded.
Anything else is reported as a likely bug, with a pointer to --debug and the issue
tracker. Ctrl-C stops cleanly; finished chunks are kept.
Your RPC URL stays private
Hosted RPC URLs usually contain an API key, so chainstar:
- never writes the URL to
chainstar.yml(it refuses to read one that contains it); - passes it to cryo through an environment variable, not the command line, and sends it nowhere but your endpoint for Bitcoin and Solana;
- turns off cryo's report files;
- prints only the scheme and host, e.g.
https://eth-mainnet.g.alchemy.com/<redacted>, also in--debugtracebacks.
chainstar sends no telemetry, and turns off dbt's anonymous usage statistics.