# cryo: pulling EVM chain data into parquet without writing a script per dataset

> cryo is a Rust command line tool that extracts blocks, transactions, logs, traces, contracts and state diffs from an EVM node over JSON-RPC into parquet, csv, json or a Python dataframe. Its strength is column-level schema discipline, and its weakness is that the README still describes a dependency the manifest no longer uses.

**paradigmxyz/cryo** — cryo is the easiest way to extract blockchain data to parquet, csv, json, or python dataframes

- Repository: https://github.com/paradigmxyz/cryo
- Stars: 1,589 · Forks: 186
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/paradigmxyz-cryo

## One command covers the datasets an analyst actually asks for

cryo is a command line tool for extracting blockchain data into tabular formats, and the pitch is that you name a dataset instead of writing a script for it. The usage line is short:

```bash
cryo <dataset> [OPTIONS]
```

The README's example table shows what that looks like in practice. Extracting every log across a million block range is `cryo logs -b 16M:17M`. Blocks, transactions and traces together are `cryo blocks txs traces`. Switching the output format to csv rather than parquet adds `--csv`. Limiting the columns to number and timestamp is `--include number timestamp`. A dry run that prints the output schema without collecting anything is `--dry`, and pulling all events for one contract is a single `--contract` argument with an address.

The dataset names in the documentation are the ones an EVM analyst would recognise: blocks, transactions, logs, contracts, traces, state diffs and VM traces. That breadth is the difference between cryo and a one-off script, because the schemas are designed to join with each other rather than to stand alone.

The data source is the ETH_RPC_URL environment variable unless you pass `--rpc <url>`, so switching between a local node and a hosted endpoint is a matter of one variable.

## The schema design rules are the reason to use cryo at all

Most extraction tools give you whatever the node returned. cryo applies a documented set of design rules, and the README calls it an attempt to ensure the dataset schemas conform to a common set of guidelines. That word attempt is worth keeping in mind, but the rules themselves are concrete.

Rows carry enough information to be orderable by default, unless the row has no intrinsic order. Columns are named after their JSON-RPC or ethers.rs defaults, except where a much more explicit name exists. A given piece of information uses the same datatype and column name across tables so that joins are straightforward.

The u256 handling is the detail that will save the most time. A value column of type u256 exposes value_binary, value_string, value_f32, value_f64, value_u32, value_u64 and value_d128, and which ones appear is chosen at runtime with the `--u256-types` argument. Without that, every downstream consumer has to parse a 256 bit integer out of a string, and a token amount silently truncated to a float is a very hard bug to find later.

Two more rules push in the direction of smaller data. Columns related to non-identifying cryptographic signatures are omitted by default, such as a block's state_root or a transaction's v, r and s values. And every table allows a chain_id column, so data from several chains can live in the same table.

The standard types are fixed across tables: block_number, transaction_index and nonce as u32; gas_used, gas_limit and chain_id as u64; timestamp as u32.

## JSON-RPC only, and that shapes both the speed and the ceiling

cryo obtains all of its data using the JSON-RPC protocol standard, and the README publishes the request pattern per dataset. Blocks come one per request via eth_getBlockByNumber. Transactions need eth_getBlockByNumber, eth_getBlockReceipts and eth_getTransactionReceipt together. Logs use eth_getLogs with many blocks and many results per request. Contracts, traces, state diffs and VM traces all use the trace family of methods, with state diffs and VM traces both going through trace_replayBlockTransactions.

That table is the performance story. A log query over a block range is one request for a lot of data, which is why log extraction is fast. A trace query is one request per block, which is why trace extraction over a wide range is slow and expensive. Anyone planning a backfill should read that table before choosing ranges.

The ceiling is stated in the README: a future version of cryo will be able to bypass JSON-RPC and query node data directly. Until then, everything goes through the public RPC surface, and the trace methods in particular are not always available on hosted providers.

The README also says cryo can be used on any chain that ethers-rs is compatible with, listing Ethereum, Optimism, Arbitrum, Polygon, BNB and Avalanche. One caveat belongs next to that sentence: the workspace manifest in Cargo.toml lists alloy 0.6.4 as the dependency rather than ethers.rs, so the README's wording and the current build disagree about the client library. Check which one the code you install actually uses.

## Default settings will get you rate limited by a public RPC

The README carries a warning that deserves to be the first thing a new user reads. The default settings will slam a node too hard for use with third party RPC providers, and the recommended remedy is to impose rate limits with --requests-per-second and --max-concurrent-requests. The README notes that such settings will be handled automatically in a future release.

That is a real operational constraint rather than a footnote. Without those flags, cryo will issue requests as fast as it can, and a public endpoint will either throttle, ban or return errors. Because the release history is thin, that automatic handling is not something to wait for.

The dependency list backs the mechanism up. The workspace pulls in governor 0.6.0, which is a rate limiting library, and indicatif 0.17.7 for progress reporting, so the capability is there in the codebase and exposed as flags rather than enabled by default.

The second rate related knob is worth understanding as well. cryo always prints data schemas before collecting any data, and the --dry flag performs a dry run to show the schema without collecting. Running the dry run first on a small range tells you the column list, and it costs a fraction of a real extraction.

## Installing the CLI, or the Python package when you want it in a notebook

There are two installation routes for the command line tool. From a clone of the repository, with Rust already installed:

```bash
git clone https://github.com/paradigmxyz/cryo
cd cryo
cargo install --path ./crates/cli
```

Or straight from crates.io, which also needs Rust:

```bash
cargo install cryo_cli
```

Either way the binary lands in ~/.cargo/bin, and the README notes that this directory has to be on your PATH, for instance by adding the export PATH line for ~/.cargo/bin to your ~/.bashrc or ~/.profile.

For Python users the package is built with maturin. From PyPI:

```bash
pip install maturin
pip install cryo
```

Or from source, where the build output wheel is what you install:

```bash
pip install maturin
git clone https://github.com/paradigmxyz/cryo
cd cryo/crates/python
maturin build --release
pip install --force-reinstall <OUTPUT_OF_MATURIN_BUILD>.whl
```

The repository also ships three shell examples, examples/all.sh, examples/erc20_balances.sh and examples/uniswap.sh, and the README points at examples/uniswap.sh as the more complex example, so it is the fastest way to see a multi flag invocation that actually works.

## Four crates, and a rule against panics that tells you about the code quality

The code guide describes the crate layout, and it is short enough to hold in your head. cryo_cli converts textual data into cryo function calls, cryo_freeze holds the core cryo code, cryo_python is the Python adapter, and cryo_to_df is a procedural macro for generating dataset definitions.

That macro is the mechanism behind the consistency claim. If dataset definitions are generated rather than hand written, then the rules about column naming, integer types and omitted signature columns are applied uniformly instead of being re-implemented per dataset. It also explains why adding a dataset is a matter of declaring it rather than writing a collector.

The guide also states a house rule: do not use panics, including panic!, todo!, unwrap() and expect(), except in tests, build scripts, lazy static blocks and procedural macros. A rule like that is a signal of the intended failure behaviour, since a CLI that aborts on an unexpected node response is far harder to operate than one that reports the error.

The Python side is built on pyo3 0.20.0 with pyo3-asyncio and pyo3-polars, which is how the tool hands results to a dataframe library rather than returning rows one at a time. The tabular core is polars 0.38.3, configured with the parquet, json, lazy and binary encoding features, which is the same engine behind the parquet and csv outputs from the CLI.

## The versioning gap is the first thing to check

The repository publishes exactly one GitHub release, 0.2.0, dated 2023-08-15. The workspace manifest, however, declares version 0.3.2 for the package and for cryo_cli, cryo_freeze and cryo_to_df, and the crate published to crates.io as cryo_cli is what the second install method pulls. The README itself calls cryo an early WIP and asks for bug reports and feedback in the issue tracker.

So the tags, the manifest version and the crates.io package are three different points on the same line, and they do not agree. The repository is not archived and the last push was on 2026-10-17, so work is happening, but a released artefact trail that thin means you are likely to install whatever crates.io currently holds rather than a version you can match to a tag.

Licensing is the straightforward part. The workspace declares MIT OR Apache-2.0, and the repository contains both LICENSE-APACHE and LICENSE-MIT, so you can take either. There is no copyleft obligation and no network use clause, which matters if you plan to run cryo inside a commercial pipeline.

The practical upgrade rule follows from the above. Pin the exact cryo_cli version you validated, keep the schema output of a --dry run next to your pipeline config, and re-run that dry run after every version bump, because the README is explicit that many CLI options change output schemas by adding or removing columns and by changing column datatypes.

## Conclusion

cryo fits a data engineer who repeatedly needs column-clean EVM history in a tabular format and does not want to write a bespoke script for every dataset, and it does not fit a team that needs node level access, because the README states that a future version will bypass JSON-RPC and query node data directly, which is not available yet. Verify three things before adopting it: that the dataset list exposed by cryo datasets matches the tables you actually need, that --requests-per-second and --max-concurrent-requests are set before pointing it at a third party RPC endpoint, and whether the ethers.rs wording in the README or the alloy dependency in Cargo.toml reflects the code you are about to run. The workspace version is 0.3.2 under an MIT OR Apache-2.0 licence, and the last push was on 2026-10-17.

## FAQ

### How do I install the cryo command line tool?

With Rust installed, run cargo install cryo_cli for the crates.io version, or clone the repository and run cargo install --path ./crates/cli. The binary is placed in ~/.cargo/bin, which has to be on your PATH.

### How do I install cryo as a Python package?

Install maturin first and then run pip install cryo, or build from source with maturin build --release inside crates/python and install the resulting wheel. The Python binding is built on pyo3 and pyo3-polars.

### How do I avoid getting rate limited by a public RPC provider when using cryo?

Set --requests-per-second and --max-concurrent-requests. The README states that the default settings will slam a node too hard for use with third party RPC providers, and that automatic handling of those settings is planned for a future release.

### What datasets can cryo extract?

The documented datasets are blocks, transactions, logs, contracts, traces, state diffs and VM traces, obtained over JSON-RPC with eth_getBlockByNumber, eth_getBlockReceipts, eth_getTransactionReceipt, eth_getLogs and the trace_block and trace_replayBlockTransactions methods.

### How do I see the output columns of a cryo dataset before extracting it?

Run the command with the --dry flag. cryo always prints data schemas before collecting any data, and --dry performs a dry run so you can view the schema without collecting.

## Sources

- [Issues](https://github.com/paradigmxyz/cryo/issues)
- [License: Apache-2.0](https://github.com/paradigmxyz/cryo/blob/main/LICENSE)
- [paradigmxyz/cryo on GitHub](https://github.com/paradigmxyz/cryo)
- [README](https://github.com/paradigmxyz/cryo/blob/main/README.md)
- [Releases](https://github.com/paradigmxyz/cryo/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/paradigmxyz-cryo
