leani_(LEE-nee)
Public preview

Ethereum data, shaped for your application.

An Ethereum indexing node you run inside your own infra. Backfill from public datasets, follow the head over Ethereum P2P, and deterministically reduce blocks into only the state, outputs, and durable streams you choose to retain. Point viem or any JSON-RPC client at it, or stream changes over the SDK.

$ leani subscribe blocks --once# On first use, review and accept the checkpoint.# --once may print an early peer preview.# Print only blocks Ethereum has finalized:$ leani subscribe blocks --finality finalized --once

Build the current alpha from source, then follow checkpoint setup on first use.Install Leani →

import { createPublicClient, http } from "viem";const eth = createPublicClient({ transport: http("http://127.0.0.1:18545"),});const chainId = await eth.getChainId();console.log(chainId); // 1 — Ethereum Mainnet

Use the RPC port of the node created by leani init blocks.Run the RPC example →

import { createLeaniClient } from "@leani/sdk";const leani = createLeaniClient({ baseUrl: "http://127.0.0.1:18080",});for await (const change of leani.blocks.subscribe()) { if (change.operation === "apply" && change.data) { console.log(change.data.blockNumber); }}

Start a block-summary node with leani init blocks and leani serve.Seed state and handle reorgs →

# Why we built it

We run blobs.money, a live dashboard for Ethereum blob economics. It needs block headers, every type-3 transaction, and receipts. Nothing else.

Getting that meant paying for metered RPC: millions ofeth_getBlockByNumber and eth_getBlockReceiptscalls to backfill history that public datasets already serve, then a WebSocket to follow the head. The alternative — running our own full node — meant terabyte-class NVMe and days of sync, keeping the whole chain on disk to reduce a sliver of it to application-shaped data.

So we built the node we wanted. It can use fast, trusted Xatu datasets for aggregation or commitment-checked EraE material for an independent audit path. It follows the head over Ethereum P2P, anchors finality from consensus, records provenance, and discards raw input when policy allows.

Then we generalized it: any data you can derive from blocks, transactions, receipts, and logs — events, balances, pool prices — with the same discipline. The same normalized input and deterministic processor contract applies whichever capable source supplies the block.

Your app doesn't need the whole chain. It needs its own data.

# Two lanes in. One processor contract.

Historical backfill and live head-following normalize into the same block envelope. Each source declares its capabilities and trust model; raw input can disappear after commit or remain under an explicit retention policy.

THE BLOCK ENGINE
INSIDE THE PIPELINE

Parallel map

Your processors shape each block.

Each small module is a processor with its own map and reduce. Deterministic map work can run concurrently, turning the requested block material into a compact, versioned delta.

Built-in or your own Rust. Each processor owns both steps.

map()
Block material → compact delta
reduce()
Ordered delta → state + changes
Build your processor →

Xatu is trusted; EraE commitments are checked. Live P2P provenance and CL finality stay explicit. Raw bytes are optional after commit.

The durable boundary

# Backfill meets live without a gap

History and head following run as independent lanes, then meet at an exact parent-hash boundary. Downstream delivery uses the same committed change protocol for both.

  1. 01

    Resume from coverage

    Committed intervals—not downloaded files—decide the first missing block after a restart.

  2. 02

    Commit the branch

    Apply, undo, and finality events are published only after processor state commits.

  3. 03

    Commit, then ACK

    Named consumers atomically save application data and their cursor before acknowledging.

  4. 04

    Prune by policy

    Required consumers fence pruning; lag and byte budgets create explicit backpressure.

historical coverageexact hash handofflive canonical headdurable cursor

Transport is at-least-once. Exactly-once application behavior comes from committing the destination mutation and cursor in one transaction, then acknowledging.

P2P history fallback can fill the entire eligible gap by default; settinghistory_fallback_blocks opts into a bounded suffix.Coverage details →

# Processors do the work

A processor is a deterministic map/reduce over blocks, transactions, receipts, and logs. Configure a built-in—or register trusted native Rust code in a custom binary. These are complete processor sections, not legacy shorthand.

[[processors]]id = "blobs-money"instance = "blobs-production"version = "1.4.0"history_mode = "on_demand"start_block = 19426589 # Dencunpublish = "included_and_finalized"[processors.state]mode = "checkpointed"[processors.artifacts]mode = "none"[processors.output]mode = "full"[processors.delivery]mode = "window"[processors.checkpoint]mode = "automatic"keep = 3[processors.undo]mode = "unfinalized"safety_blocks = 256
block: { … };
blobBaseFee: string;blobCount: number;blobEthBurnedWei: string;blobGasUsed: string;blockHash: Hex;blockNumber: number;excessBlobGas: string;executionBaseFee: string;executionEthBurnedWei: string;finality: "preview" | "included" | "finalized";gasLimit: string;gasUsed: string;maxBlobsPerBlock: number;network: string;parentHash: Hex;reserveFeeWei: string;size: string;targetBlobsPerBlock: number;timestamp: number;transactionCount: number;transformVersion: number;
transactions: { … }[];
blobBurnedWei: string;blobCount: number;blobVersionedHashes: Hex[];blockHash: Hex;blockNumber: number;executionBurnedWei: string;network: string;senderAddress: Hex;totalBurnedWei: string;transactionIndex: number;txHash: Hex;
GET /v1/processors/blobs-production/streamevent: hello
{ … }
apiVersion"1"
chainId1
coverage{ … }
available[ 1 ]
0{ … }
finality"finalized"
fromBlock19430000
toBlock19430000
chainId1
completefalse
configuredStartBlock19426589
finalizedThrough19430000
processedThrough19430000
requestednull
state"backfilling"
processor{ … }
artifactRetention"none"
changeSchema"blobs.block-bundle.v1"
codeHash"0x6147829a779380bef409a63b7ede907aac4350f50137c495ff71e758868d5697"
configHash"0x8ea21cedc162b4fa3edf143b115e0d3a87c8c958be74e0b1a61ebf8fb53bfa9f"
deliveryOrdering"block_versioned_idempotent"
genericApi"processor-v1"
id"blobs-money"
instance"blobs-production"
queryExtensions[ 1 ]
0{ … }
aliasPath"/v1/q/blobs"
basePath"/v1/processors/blobs-production/query"
id"blobs-v1"
subscriptionstrue
version"1.4.0"
id: 02000000000000000000000000000000000110626c6f62732d70726f64756374696f6e05312e342e3001743f67ce7c929c7babc790a657015f2cevent: apply
data{ … }
apiVersion"1"
block{ … }
hash"0x460f4c111c4a3634fa3c26d429e51fe9a9326e12a138788e5226038ce5334f47"
number19430000
parentHash"0x40a4b2e74ae938e20245ac697051f964b2308191ff0e1d65fb8f40803a084ab0"
timestamp1710500000
chainId1
cursor"02000000000000000000000000000000000110626c6f62732d70726f64756374696f6e05312e342e3001743f67ce7c929c7babc790a657015f2c"
data{ … }
block{ … }
blobBaseFee"1"
blobCount3
blobEthBurnedWei"393216"
blobGasUsed"393216"
blockHash"0x460f4c111c4a3634fa3c26d429e51fe9a9326e12a138788e5226038ce5334f47"
blockNumber19430000
excessBlobGas"0"
executionBaseFee"10000000000"
executionEthBurnedWei"210000000000000"
finality"finalized"
gasLimit"30000000"
gasUsed"21000"
maxBlobsPerBlock6
network"mainnet"
parentHash"0x40a4b2e74ae938e20245ac697051f964b2308191ff0e1d65fb8f40803a084ab0"
reserveFeeWeinull
size"1024"
targetBlobsPerBlock3
timestamp1710500000
transactionCount1
transformVersion3
transactions[ 1 ]
0{ … }
blobBurnedWei"393216"
blobCount3
blobVersionedHashes[ 3 ]
0"0xa89b09021100e6907075c37e14644d808b64e574b20380c31478533d616640d3"
1"0x7467efe5108e92e2b5965492adb8ca767497c2db4ee1ca6d7f68fbfe29bd17da"
2"0x75c3af1ba7c574c1fd68fb4a020f8752ab8b65c819bb830dc8a5d32fc257feb3"
blockHash"0x460f4c111c4a3634fa3c26d429e51fe9a9326e12a138788e5226038ce5334f47"
blockNumber19430000
executionBurnedWei"210000000000000"
network"mainnet"
senderAddress"0x1111111111111111111111111111111111111111"
totalBurnedWei"210000000393216"
transactionIndex0
txHash"0x3bbf7db04ef26a674e8e95b3186b58905153f80b22fbd9ed92eaeda6012e7369"
emittedAt"2024-03-15T10:53:20.000Z"
finality"finalized"
key"0x0000000001287a70"
kind"blobs.block.put"
operation"apply"
originId"illustrative-fixture"
originKind"historical_backfill"
publicationRevision"0"
schema"blobs.block-bundle.v1"
sequence"1"

One atomic block bundle—economics plus every blob transaction. Node commits are atomic; consumers receive at-least-once and commit their cursor before ACK.see it live →docs →

[[processors]]id = "erc20-balances"instance = "erc20-watchlist"version = "1.0.0"history_mode = "on_demand"start_block = 1234567publish = "included_and_finalized"[processors.state]mode = "durable"[processors.artifacts]mode = "none"[processors.output]mode = "latest"[processors.delivery]mode = "window"[processors.checkpoint]mode = "automatic"keep = 3[processors.undo]mode = "unfinalized"safety_blocks = 256[processors.settings]addresses = ["0x1111111111111111111111111111111111111111"]tokens = ["0x2222222222222222222222222222222222222222"]complete_from_start = true
token: Hex;address: Hex;balance: string;asOfBlock: number;blockHash: Hex;finality: "preview" | "included" | "finalized";coverageFrom: number;complete: boolean;method: "erc20_transfer_ledger";
GET /v1/processors/erc20-watchlist/streamevent: hello
{ … }
apiVersion"1"
chainId1
coverage{ … }
available[ 1 ]
0{ … }
finality"finalized"
fromBlock19430000
toBlock19430000
chainId1
completefalse
configuredStartBlock1234567
finalizedThrough19430000
processedThrough19430000
requestednull
state"live"
processor{ … }
artifactRetention"none"
changeSchema"erc20.balance.change.v1"
codeHash"0xacb6d26482e17309a53171a6524dbf27fae7020927facb647cad98fee2bc8ddd"
configHash"0x286fc393037f30bc3360400281755f19a6741292e7585e781a4294c02facb8ac"
deliveryOrdering"canonical"
genericApi"processor-v1"
id"erc20-balances"
instance"erc20-watchlist"
queryExtensions[ 1 ]
0{ … }
aliasPath"/v1/q/erc20"
basePath"/v1/processors/erc20-watchlist/query"
id"erc20-v1"
subscriptionstrue
version"1.0.0"
id: 0200000000000000000000000000000000010f65726332302d77617463686c69737405312e302e30011ead1cd8d6ca2b8f409b07f68687fe5fevent: apply
data{ … }
apiVersion"1"
block{ … }
hash"0x8a707419475207771c1004663d7510f330100ca73e206cc3c9bf51e0cef7c0b7"
number19430000
parentHash"0x5c68562e249e87eee7f656b04177dd585413adf9d3e1cb09f67420223e928e41"
timestamp1710500000
chainId1
cursor"0200000000000000000000000000000000010f65726332302d77617463686c69737405312e302e30011ead1cd8d6ca2b8f409b07f68687fe5f"
data{ … }
address"0x1111111111111111111111111111111111111111"
asOfBlock19430000
balance"42"
blockHash"0x8a707419475207771c1004663d7510f330100ca73e206cc3c9bf51e0cef7c0b7"
completetrue
coverageFrom1234567
finality"finalized"
method"erc20_transfer_ledger"
token"0x2222222222222222222222222222222222222222"
emittedAt"2024-03-15T10:53:20.000Z"
finality"finalized"
key"0x22222222222222222222222222222222222222221111111111111111111111111111111111111111"
kind"erc20.balance.put"
operation"apply"
originId"illustrative-fixture"
originKind"historical_backfill"
publicationRevision"0"
schema"erc20.balance.change.v1"
sequence"1"

Event-derived watchlist balances with declared coverage. Exact for conventional Transfer semantics when indexing begins before the relevant initial mint; rebasing, reflection, and non-standard tokens need a different model.docs →

[[processors]]id = "evm-events"instance = "weth-transfers"version = "1.0.0"history_mode = "on_demand"start_block = 12965000publish = "finalized_only"[processors.state]mode = "durable"[processors.artifacts]mode = "none"[processors.output]mode = "window"finalized_only = true[processors.output.window]max_blocks = 216000[processors.delivery]mode = "window"[processors.checkpoint]mode = "automatic"keep = 3[processors.undo]mode = "none"safety_blocks = 0[processors.settings]addresses = ["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"][[processors.settings.events]]abi = "event Transfer(address indexed from, address indexed to, uint256 value)"[processors.settings.events.output]collection = "weth.transfers"kind = "weth.transfer"key_fields = []
schemaVersion: number;contract: Hex;event: string;signature: string;blockNumber: number;blockHash: Hex;blockTimestamp: number;bucketStartTimestamp?: number;transactionHash: Hex;transactionIndex: number;logIndex: number;finality: "preview" | "included" | "finalized";values: Record<string, string>;
GET /v1/processors/weth-transfers/streamevent: hello
{ … }
apiVersion"1"
chainId1
coverage{ … }
available[ 1 ]
0{ … }
finality"finalized"
fromBlock19430000
toBlock19430000
chainId1
completefalse
configuredStartBlock12965000
finalizedThrough19430000
processedThrough19430000
requestednull
state"live"
processor{ … }
artifactRetention"none"
changeSchema"evm.event.change.v1"
codeHash"0x6bf652372a5750664c8b0597faefb4317c4d8ec0a2fb59c8173453726985aed1"
configHash"0x38b6203c8ca408c1a00aa1fb528d2bc33ae3c59559d6259007a4aa75c06eedbd"
deliveryOrdering"block_versioned_idempotent"
genericApi"processor-v1"
id"evm-events"
instance"weth-transfers"
queryExtensions[ 0 ]
subscriptionstrue
version"1.0.0"
id: 0200000000000000000000000000000000010e776574682d7472616e736665727305312e302e30017961ec9fa579b8d10bbb5738b6b701d1event: apply
data{ … }
apiVersion"1"
block{ … }
hash"0x8a707419475207771c1004663d7510f330100ca73e206cc3c9bf51e0cef7c0b7"
number19430000
parentHash"0x5c68562e249e87eee7f656b04177dd585413adf9d3e1cb09f67420223e928e41"
timestamp1710500000
chainId1
cursor"0200000000000000000000000000000000010e776574682d7472616e736665727305312e302e30017961ec9fa579b8d10bbb5738b6b701d1"
data{ … }
blockHash"0x8a707419475207771c1004663d7510f330100ca73e206cc3c9bf51e0cef7c0b7"
blockNumber19430000
blockTimestamp1710500000
contract"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
event"Transfer"
finality"finalized"
logIndex0
schemaVersion1
signature"Transfer(address,address,uint256)"
transactionHash"0x3333333333333333333333333333333333333333333333333333333333333333"
transactionIndex0
values{ … }
from"0x0000000000000000000000000000000000000000"
to"0x1111111111111111111111111111111111111111"
value"42"
emittedAt"2024-03-15T10:53:20.000Z"
finality"finalized"
key"0x333333333333333333333333333333333333333333333333333333333333333300000000"
kind"weth.transfer.put"
operation"apply"
originId"illustrative-fixture"
originKind"historical_backfill"
publicationRevision"0"
schema"evm.event.change.v1"
sequence"1"

Declarative static-ABI decoding—no EVM or contract code, just logs. Dynamic values, tuples, arrays, and anonymous events are rejected by doctor. Illustrative response; hashes, cursors, and values vary by run.docs →

[[processors]]id = "uniswap-latest"instance = "usdc-weth-latest"version = "2.0.0"history_mode = "on_demand"start_block = 12369621publish = "included_and_finalized"[processors.state]mode = "checkpointed"[processors.artifacts]mode = "none"[processors.output]mode = "latest"[processors.delivery]mode = "window"[processors.checkpoint]mode = "automatic"keep = 3[processors.undo]mode = "unfinalized"safety_blocks = 256[[processors.settings.pools]]address = "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640" # USDC/WETH 0.05%kind = "v3"
pool: Hex;kind: "v2" | "v3";reserve0: string | null;reserve1: string | null;amount0: string | null;amount1: string | null;sqrtPriceX96: string | null;blockNumber: number;blockHash: Hex;logIndex: number;finality: "preview" | "included" | "finalized";
GET /v1/processors/usdc-weth-latest/streamevent: hello
{ … }
apiVersion"1"
chainId1
coverage{ … }
available[ 1 ]
0{ … }
finality"finalized"
fromBlock19430000
toBlock19430000
chainId1
completefalse
configuredStartBlock12369621
finalizedThrough19430000
processedThrough19430000
requestednull
state"live"
processor{ … }
artifactRetention"none"
changeSchema"uniswap.price.change.v2"
codeHash"0x19adcbcc4e6750d9afce9dcd2065765351b78664115c96edd4cc11bfe64bb6cd"
configHash"0x7e2e584b40d5686f5dedfd8a75343d6e6da17984dd426d9bb5210be6c8ea027f"
deliveryOrdering"canonical"
genericApi"processor-v1"
id"uniswap-latest"
instance"usdc-weth-latest"
queryExtensions[ 1 ]
0{ … }
aliasPath"/v1/q/uniswap"
basePath"/v1/processors/usdc-weth-latest/query"
id"uniswap-v2"
subscriptionstrue
version"2.0.0"
id: 02000000000000000000000000000000000110757364632d776574682d6c617465737405322e302e3001d41d87c20935f657a418afd2d1dca5a4event: apply
data{ … }
apiVersion"1"
block{ … }
hash"0xc57158346aeb29a7d5777c0f61a9c51119d003e4c7eb9ab877096b19416bc0fe"
number19430000
parentHash"0xa46402fef3ac2bc0dc3c57c03810e91b9d77e63fa55a04f89509b11c255037b0"
timestamp1710500000
chainId1
cursor"02000000000000000000000000000000000110757364632d776574682d6c617465737405322e302e3001d41d87c20935f657a418afd2d1dca5a4"
data{ … }
amount0"0"
amount1"0"
blockHash"0xc57158346aeb29a7d5777c0f61a9c51119d003e4c7eb9ab877096b19416bc0fe"
blockNumber19430000
finality"finalized"
kind"v3"
logIndex0
pool"0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640"
reserve0null
reserve1null
sqrtPriceX96"83451394949906432"
emittedAt"2024-03-15T10:53:20.000Z"
finality"finalized"
key"0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640"
kind"uniswap.price.current.put"
operation"apply"
originId"illustrative-fixture"
originKind"historical_backfill"
publicationRevision"0"
schema"uniswap.price.change.v2"
sequence"1"

Latest exact reserves or sqrtPriceX96 per configured pool—never floating point, no EVM execution. Use uniswap-observations when you need immutable history.docs →

[[processors]]id = "transaction-stats"instance = "treasury-flow-total"version = "1.0.0"history_mode = "on_demand"start_block = 15537394publish = "finalized_only"[processors.state]mode = "checkpointed"[processors.artifacts]mode = "none"[processors.output]mode = "latest"[processors.delivery]mode = "window"[processors.checkpoint]mode = "automatic"keep = 3[processors.undo]mode = "none"safety_blocks = 0[processors.settings]from = "0x1111111111111111111111111111111111111111"to = "0x2222222222222222222222222222222222222222"complete_from_start = true
from: Hex;to: Hex;count: number;totalValueWei: string;asOfBlock: number;blockHash: Hex;finality: "preview" | "included" | "finalized";coverageFrom: number;complete: boolean;
GET /v1/processors/treasury-flow-total/streamevent: hello
{ … }
apiVersion"1"
chainId1
coverage{ … }
available[ 1 ]
0{ … }
finality"finalized"
fromBlock19430000
toBlock19430000
chainId1
completefalse
configuredStartBlock15537394
finalizedThrough19430000
processedThrough19430000
requestednull
state"live"
processor{ … }
artifactRetention"none"
changeSchema"transaction-stats.change.v1"
codeHash"0x514a31147ba3ab85d34a1b30901b4797c546e4d41a3dc6714cd9f9549bef4083"
configHash"0x133645b81b6609362c18c66ac3b8be9a85be2c95e9d9af7e47c16ed63383fe95"
deliveryOrdering"canonical"
genericApi"processor-v1"
id"transaction-stats"
instance"treasury-flow-total"
queryExtensions[ 0 ]
subscriptionstrue
version"1.0.0"
id: 0200000000000000000000000000000000011374726561737572792d666c6f772d746f74616c05312e302e3001e242149dc5da6a11c9bd7c714c44c5b2event: apply
data{ … }
apiVersion"1"
block{ … }
hash"0x8a707419475207771c1004663d7510f330100ca73e206cc3c9bf51e0cef7c0b7"
number19430000
parentHash"0x5c68562e249e87eee7f656b04177dd585413adf9d3e1cb09f67420223e928e41"
timestamp1710500000
chainId1
cursor"0200000000000000000000000000000000011374726561737572792d666c6f772d746f74616c05312e302e3001e242149dc5da6a11c9bd7c714c44c5b2"
data{ … }
asOfBlock19430000
blockHash"0x8a707419475207771c1004663d7510f330100ca73e206cc3c9bf51e0cef7c0b7"
completetrue
count32
coverageFrom15537394
finality"finalized"
from"0x1111111111111111111111111111111111111111"
to"0x2222222222222222222222222222222222222222"
totalValueWei"73"
emittedAt"2024-03-15T10:53:20.000Z"
finality"finalized"
key"0x746f74616c"
kind"transaction_stats.aggregate.put"
operation"apply"
originId"illustrative-fixture"
originKind"historical_backfill"
publicationRevision"0"
schema"transaction-stats.change.v1"
sequence"1"

Ordered aggregate with constant-sized state for top-level A-to-B transactions. Internal calls and traces are intentionally outside this processor. Illustrative response; hashes, cursors, and values vary by run.docs →

#[tokio::main]async fn main() -> std::process::ExitCode { let mut registry = ProcessorRegistry::new(); if let Err(error) = registry.register(BlockSummaryFactory) { eprintln!("error: {error}"); return Exit::Failure.into(); } match run_with_registry(registry).await { Ok(exit) => exit.into(), Err(error) => { eprintln!("error: {error:#}"); Exit::Failure.into() } }}
blockNumber: number;timestamp: number;
import { createLeaniClient } from "@leani/sdk";interface BlockSummary { blockNumber: number; timestamp: number;}const leani = createLeaniClient({ baseUrl: "http://127.0.0.1:9080" });const capabilities = await leani.capabilities();const extension = capabilities.queryExtensions.find( (candidate) => candidate.processor === "example-block-summary-local",);if (!extension) throw new Error("block summary query extension is unavailable");const summary = await leani.request<BlockSummary>( `${extension.basePath}/19426589`,);console.log(summary);
GET /v1/processors/example-block-summary-local/streamevent: hello
{ … }
apiVersion"1"
chainId1
processor{ … }
id"example-block-summary"
instance"example-block-summary-local"
version"1.0.0"
codeHash"0x6b30c95e17daf4820b96e5c3a1f07d48e92b56013fc7da8e045a1b7cd6390f82"
configHash"0xf05c37b1ae92d6084e1bc79f52a3d80167c04de935ab26f1780c4e3b9a51d627"
genericApi"processor-v1"
changeSchema"example.block-summary.change.v1"
queryExtensions[ 1 ]
0{ … }
id"block-summary-v1"
basePath"/v1/processors/example-block-summary-local/query"
aliasPath"/v1/q/block-summaries"
subscriptionstrue
artifactRetention"none"
deliveryOrdering"block_versioned_idempotent"
coverage{ … }
chainId1
requestednull
available[ 1 ]
0{ … }
fromBlock25650000
toBlock25696396
finality"included"
configuredStartBlock25650000
processedThrough25696396
finalizedThrough25696313
completetrue
state"live"
id: opaque-resume-cursorevent: apply
data{ … }
apiVersion"1"
sequence"46397"
cursor"opaque-resume-cursor"
operation"apply"
originKind"historical_backfill"
originId"example-block-summary-local"
publicationRevision"0"
chainId1
block{ … }
hash"0xc86f87600c81fb635325cca051fef40b875ca405d1b2f57881437963321e88ff"
number25696396
parentHash"0x0141a01b6e38f75cd3606834c3780f581d5772460e440e7c5758b69589929168"
timestamp1786025147
finality"included"
kind"example.block_summary.put"
schema"example.block-summary.change.v1"
key"0x000000000188188c"
data{ … }
blockNumber25696396
timestamp1786025147
emittedAt"2026-08-06T14:05:51.502Z"

Native Rust processors register state transforms and typed read extensions through the public factory API — same durability, streams, coverage, and query controls as built-ins.build your own →docs →

# Storage is a policy, not a promise

State, query output, delivery, artifacts, checkpoints, undo, and raw history have independent lifecycle controls and hard budgets. Keep a current value, a rolling window, a reusable history, or only an acknowledged external copy.

ProfileRetained by leaniBoundBest fitMeasured example
externalizedState, coverage, and delivery only until the required consumer ACKsByte/age budgets with pause-on-limit backpressurePostgreSQL, ClickHouse, object storage, or another application-owned sink1M real blocks: 9.5 MB blobs / 11.4 MB Uniswap peak node files
latest / aggregateCurrent entities or constant-sized ordered stateOutput cardinality plus checkpoints and configured undoBalances, pool state, counters, totals, dashboardsTransaction totals remain one current aggregate
windowOnly the selected block, age, row, or byte windowExplicit max_blocks / max_age / max_rows / max_bytesRecent APIs, rolling analytics, replay windows30-day event profile ships as a checked example
full outputEvery materialized processor entity and required indexesGrows with output cardinality and indexed historyReusable local historical query surfaces1M dev-native blobs blocks: 1.58 GiB compacted DB
  1. Measured figures are workload-specific evidence, not host-size or cost guarantees. The real-source runs used gzip HTTP delivery, immediate acknowledgement, four source workers, and did not include an application database.
  2. The 1.58 GiB full-output figure retained 2.55 million entities and 550,000 index rows. Externalized, latest, or windowed policy is the intended choice when that reusable query surface is unnecessary.
  3. Trusted Xatu input and commitment-checked EraE input have different trust and performance profiles; retention policy does not change source trust.

Reproduce the Mainnet evidence gate →Lifecycle profiles →

# An exact, capability-reported RPC subset

Existing Ethereum clients can use the block, transaction, receipt, log, chain-metadata, and subscription methods leani advertises. Availability still depends on configured sources, retained material, and hash locators.

implemented exactly when available

  • eth_getBlockByNumber / ByHash
  • eth_getBlockTransactionCountByNumber / ByHash
  • eth_getTransactionByBlockNumberAndIndex / ByHashAndIndex
  • eth_getBlockReceipts
  • eth_getTransactionByHash / Receipt
  • eth_getLogs (bounded recent + on-demand)
  • eth_blockNumber · eth_chainId · eth_config · eth_syncing
  • leani_getCapabilities
  • eth_subscribe: newHeads, logs (reorg-correct)

Number and range lookups can use capable historical sources. Hash-only history is recent-only unless a locator or retained index is enabled. Raw RPC history is post-Merge exact; pre-Merge ommer-aware responses are deferred.

refused, explicitly

  • eth_call
  • eth_getBalance · eth_getCode · eth_getStorageAt
  • eth_estimateGas
  • debug/trace methods

No EVM, no state trie—so these fail explicitly instead of returning something almost right. Keep an execution provider for stateful methods and as an independent fallback while evaluating the public preview.

# The operational contract is part of the product

Ethereum Mainnet · no API key

# Follow Ethereum blocks

Install the alpha from source, then read block summaries directly from Ethereum's network with one command. On first use, Leani asks you to approve a checkpoint, a trusted starting point for checking the chain. Prepare a reusable node when you want queries and SDK subscriptions.

1 · inspect a first block

leani subscribe blocks --once

2 · prepare a reusable node

mkdir leani-blocks && cd leani-blocksleani init blocksleani serve# In another terminal in this directory:# leani subscribe blocks --format json

Your first result may be a preview while Leani checks the block's place in Ethereum's chain. Rows marked included are on the followed chain and can still be reorged. Add --finality finalized to print only blocks Ethereum consensus has finalized. See the full quickstart, thenindex a bounded historical range.