Skip to content

Commit 2855233

Browse files
dzhelezovclaude
andcommitted
docs(readme): tighten to a top-tier composition
Calibrated against Ponder's own README (badges → hook → quickstart → convincing core → links out, no hyperbole). Cut ~45% of the length: the deep How-it-works bullets, the metrics JSON schema, the second benchmark table, the repo tree, and the verbose Versioning/Limitations prose now live in the docs they already have (INTEGRATION.md, BENCHMARKS.md, REPORT.md, MIGRATION.md, PUBLISHING.md). Added npm/CI/Telegram badges and the product framing (Ponder's pedigree + the speed it lacked); Euler is now an example and a scale demo, not the identity. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9a90be3 commit 2855233

1 file changed

Lines changed: 41 additions & 112 deletions

File tree

README.md

Lines changed: 41 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,17 @@
11
# @subsquid/ponder
22

3-
A drop-in fork of [Ponder](https://github.com/ponder-sh/ponder) that runs the historical backfill through the [SQD Portal](https://sqd.dev/portal/) instead of per-chain JSON-RPC. Backfills run several times faster, and complete on chains where an RPC backfill is impractical. Realtime stays on your RPC, your handlers and schema don't change, and the switch is one line of config per chain.
3+
[![npm](https://img.shields.io/npm/v/@subsquid/ponder?color=cb0000&logo=npm)](https://www.npmjs.com/package/@subsquid/ponder)
4+
[![CI](https://github.com/subsquid-labs/portal-ponder/actions/workflows/ci.yml/badge.svg)](https://github.com/subsquid-labs/portal-ponder/actions/workflows/ci.yml)
5+
[![Telegram](https://img.shields.io/badge/chat-SquidDevs-2CA5E0?logo=telegram)](https://t.me/HydraDevs)
46

5-
**In numbers:** ~ faster on a full Ethereum backfill · 15 chains and 28M events in one app in 45 minutes · Portal logs byte-identical to `eth_getLogs`. See [Benchmarks](#benchmarks).
7+
A drop-in fork of [Ponder](https://github.com/ponder-sh/ponder) that runs the historical backfill through the [SQD Portal](https://sqd.dev/portal/) instead of per-chain JSON-RPC — **several times faster**, across 130+ EVM networks. Realtime stays on your RPC, your handlers and schema don't change, and the switch is one line of config per chain.
68

7-
## Quickstart
8-
9-
Try it in a couple of minutes against the free public Portal — no key required:
10-
11-
```bash
12-
git clone https://github.com/subsquid-labs/portal-ponder
13-
cd portal-ponder/examples/euler-subgraph
14-
npm install
15-
npm run dev
16-
```
17-
18-
This runs a real indexer — the Euler V2 subgraph ported to Ponder — and backfills Ethereum history from the Portal. Watch the events/second in the Ponder dev UI; that throughput is the point. Add an Ethereum RPC for realtime as usual (the Portal backfill itself needs no key); see the [example README](examples/euler-subgraph/).
19-
20-
The example points at `https://portal.sqd.dev`, the **free public Portal**. It needs no key and is ideal for trying the fork and for development. It shares capacity across all users, so it is rate-limited and slower under load — for production throughput, see [Going to production](#going-to-production).
9+
Ponder is mature, production-proven, and a pleasure to work with. Historical backfill speed was the one gap — this closes it and changes nothing else.
2110

22-
## Add it to an existing app
23-
24-
Point the config at the fork and add a `portal` URL per chain. Nothing else changes:
11+
> **~8× faster** full-history backfill · **15 chains, 28M events** in one app in **45 min** · logs **byte-identical** to `eth_getLogs`
2512
2613
```ts
27-
// ponder.config.ts
14+
// ponder.config.ts — add `portal:` per chain; nothing else changes
2815
import { createConfig } from "@subsquid/ponder";
2916

3017
export default createConfig({
@@ -40,124 +27,66 @@ export default createConfig({
4027
```
4128

4229
```bash
43-
npm install @subsquid/ponder # provides the same `ponder` bin — `ponder dev` / `ponder start` work as before
30+
npm install @subsquid/ponder # same `ponder` bin — `ponder dev` / `ponder start` work as before
4431
```
4532

46-
Versions are `<ponder-version>-sqd.<rev>`: `0.16.6-sqd.1` is `ponder@0.16.6` plus the Portal layer. Remove the `portal` line (or point the dependency back at `ponder`) to return to stock Ponder. Before migrating a larger app, run the [compatibility check](#compatibility).
47-
48-
## Why it's faster, and why a fork
49-
50-
RPC was built for transactions, not for reading data. A backfill over RPC is a stream of point lookups — one `eth_getLogs` per topic, one `eth_getBlockByNumber` per matched block — and a factory that discovers thousands of contracts at runtime turns that into hundreds of thousands of small, serial requests. The Portal is built for the opposite: it answers an arbitrary `[from, to]` block range as a single HTTP stream, reorg-safe and at constant memory. A backfill that RPC serves as hundreds of thousands of round-trips becomes a handful of large streamed reads — which is where the [](#benchmarks) comes from.
51-
52-
Reaching that means integrating at Ponder's range-oriented historical-sync seam, where one interval maps directly to one Portal range scan. That seam isn't part of Ponder's public API, so the integration is a small fork rather than a plugin. The fork is generated from upstream Ponder plus a short patch and tracks it closely — a thin plugin can follow once Ponder exposes the hook.
53-
54-
## How it works
55-
56-
The change is one module (`portal/portal.ts`, a `HistoricalSync` implementation) plus a short wiring patch that adds `portal?` to the chain config and routes to it when set.
57-
58-
- **Read-ahead chunk buffer.** Ponder requests small intervals; the Portal is latency-bound per request but has large parallel bandwidth. The fork fetches large aligned chunks, serves every interval from that cache, and prefetches the next *N* chunks concurrently so latency overlaps instead of serializing. This is the largest single gain.
59-
- **Correctness-safe factory discovery.** Child addresses are discovered per chunk into a shared set; a data chunk fetches only once discovery has completed through its own block range, so out-of-order parallel fetches never miss a child event. Discovery is clamped to the factory's real start block.
60-
- **Block-density auto-scaling.** Chunk size scales with the chain's head, keeping round-trips roughly constant across chains (Arbitrum's 478M blocks don't mean 19× the requests).
61-
- **Server-side filtering.** Every row filter is pushed to the Portal's native filters (logs by address and topics, traces by call target and sighash, account transactions by from/to); field projection requests only the columns the sync store persists. The exception is block-interval sources, which the Portal cannot express as a modulo filter (range-scan + client filter).
62-
- **Memory safety on dense sources.** Trace and block sources cap the chunk grid (`PORTAL_TRACE_CHUNK_BLOCKS`, default 25k) so a busy contract cannot exhaust memory.
63-
- **Finality-gap fallback.** The Portal serves finalized data; if its head lags Ponder's target, intervals past it are delegated to the RPC sync, so the backfill stays complete.
64-
- **Resilience.** Socket errors and HTTP 503/529/429 (honoring `Retry-After`) are retried with backoff.
65-
66-
Tunables: `PORTAL_CHUNK_BLOCKS`, `PORTAL_READAHEAD`, `PORTAL_TRACE_CHUNK_BLOCKS`, `PORTAL_API_KEY`. Full write-up: [`portal/INTEGRATION.md`](portal/INTEGRATION.md).
67-
68-
## Compatibility
69-
70-
**Source types** — all of Ponder's are supported: `logs`, log-`factory()`, `transactions`, `receipts` (`includeTransactionReceipts`), `traces` (`includeCallTraces` + transfer filters), `blocks: { interval }`, and `accounts: {}`. `readContract` calls in handlers use your `rpc` as usual.
71-
72-
**Chains** — the Portal serves [130+ EVM networks](https://docs.sqd.dev/en/data/all-networks) (ethereum, base, arbitrum, optimism, polygon, …). Per-network capabilities differ (traces and state-diffs aren't on every chain; some have block-range caveats), and dataset availability is per-portal.
33+
## Quickstart
7334

74-
**Check your indexer before migrating** with the compatibility report. It inspects a `ponder.config.ts` and, per source, confirms the type is supported, the target portal serves the chain's dataset, and the network has the needed capability:
35+
Try it against the free public Portal — no key required:
7536

7637
```bash
77-
PORTAL_API_KEY=… PORTAL_BASE=<your portal> \
78-
node --experimental-strip-types harness/compat/report.ts ./ponder.config.ts
38+
git clone https://github.com/subsquid-labs/portal-ponder
39+
cd portal-ponder/examples/euler-subgraph
40+
npm install && npm run dev
7941
```
8042

81-
For a clean event or factory indexer (Uniswap, Euler, …) it reports `READY NOW`. The full adoption path — compat check, swap the dependency, run, validate, roll back — is in [`MIGRATION.md`](MIGRATION.md).
82-
83-
## Benchmarks
84-
85-
Resync wall-clock (events indexed end to end) is the metric. These numbers are on a dedicated Portal; the free public Portal shares capacity and is slower under concurrency. Full analysis and the reproducible harness: [`harness/bench/BENCHMARKS.md`](harness/bench/BENCHMARKS.md).
86-
87-
**Single chain — full Ethereum Euler history** (deploy → head, ~5M blocks, 466 vaults, 457,931 events → pglite):
43+
This runs a real indexer and backfills Ethereum history from the Portal — watch the events/second in the Ponder dev UI. The example uses `portal.sqd.dev`, the free public Portal: ideal for trying the fork and for development, but shared and rate-limited under load. For production, see [Going to production](#going-to-production).
8844

89-
| Integration | Resync wall-clock | Speedup |
90-
|---|---|---|
91-
| Stock RPC (one stream per Ponder interval) | ~38 min ||
92-
| Read-ahead chunk buffer | ~17 min | ~|
93-
| **Parallel prefetch (the fork)** | **4m 45s** | **~** |
94-
95-
**Multi-chain** — Ethereum, Base, and Arbitrum in one `ponder start`: ~1.05M events in ~16 min, 0 errors.
96-
97-
**Correctness** — Portal logs are byte-identical to `eth_getLogs` (differential test, Base WETH transfers, 380/380 logs, all fields); the transforms are unit-tested against captured Portal data.
98-
99-
## Flagship: every Euler V2 chain in one indexer
100-
101-
[`harness/euler-multichain/`](harness/euler-multichain/) is the reference deployment: all 15 Portal-supported Euler V2 chains in a single Ponder app, full history per chain, the complete 24-event EVault set, Portal backfill into Postgres. It is the production shape of an Euler indexer and the fork's hardest stress test — one Portal endpoint, one database, fifteen chains backfilling at once.
45+
## Why it's faster, and why a fork
10246

103-
**28,405,932 events across all 15 chains, full history, in 45 minutes** on roughly one core and 16 GB (plus a separate Postgres), byte-verified complete against the Portal (60/60 sampled windows exact) and reproducible across two runs. Per-chain breakdown and analysis: [`harness/euler-multichain/REPORT.md`](harness/euler-multichain/REPORT.md).
47+
RPC was built for transactions, not for reading data. A backfill over RPC is a stream of point lookups — one `eth_getLogs` per topic, one `eth_getBlockByNumber` per matched block — and a factory that discovers thousands of contracts at runtime turns that into hundreds of thousands of small, serial requests. The Portal is built for the opposite: it answers an arbitrary `[from, to]` block range as one HTTP stream. Hundreds of thousands of round-trips become a handful of large streamed reads.
10448

105-
## Going to production
49+
The speed comes from integrating at Ponder's range-oriented historical-sync seam, where one interval maps to one Portal range scan. That seam isn't part of Ponder's public API, so this is a small fork rather than a plugin — generated from upstream Ponder plus a short patch, so it tracks Ponder closely.
10650

107-
The free public Portal needs no key and is ideal for trying the fork and for development. Because it shares capacity across everyone, it is rate-limited and slows down under concurrency — a large production backfill will feel that.
108-
109-
For production throughput and reliability, use a **dedicated Portal**: your own capacity, no shared rate limits. Dedicated Portals are set up with the SQD team today — [talk to us](https://sqd.dev/portal/). Self-served shared tiers (free, starter, and growth) are coming soon.
51+
## Benchmarks
11052

111-
## Examples
53+
Full Ethereum history, deploy → head (~5M blocks, 457,931 events), on a dedicated Portal:
11254

113-
Each is a real indexer that runs end to end on `@subsquid/ponder` (Portal backfill, realtime and `readContract` on `rpc`):
55+
| Backfill | Wall-clock | Speedup |
56+
|---|--:|--:|
57+
| Stock RPC | ~38 min ||
58+
| The fork | **4m 45s** | **~** |
11459

115-
- [`examples/euler-subgraph/`](examples/euler-subgraph/) — the Euler V2 subgraph ported to Ponder and the Portal (factory templates, `readContract` state reads, aggregation). A worked *subgraph → Ponder* migration, and the Quickstart above.
116-
- [`examples/uniswap-portal/`](examples/uniswap-portal/) — all five source types in one app (logs, receipts, traces, block-interval, accounts).
117-
- [`examples/euler-multichain/`](examples/euler-multichain/) — a compact three-chain factory indexer (the benchmark app).
60+
At scale, one app indexing a real protocol (Euler V2) across all **15 chains** it runs on backfilled **28,405,932 events in 45 minutes** on ~1 core and 16 GB, byte-verified complete against the Portal. Full write-up: [`REPORT.md`](harness/euler-multichain/REPORT.md) · methodology: [`BENCHMARKS.md`](harness/bench/BENCHMARKS.md).
11861

119-
## Metrics & observability
62+
## Compatibility
12063

121-
The backfill emits its own metrics alongside Ponder's normal logging and UI. Set `PORTAL_METRICS_FILE=<path>` and the sync writes `<path>.<chainId>` (JSON, refreshed each interval):
64+
All of Ponder's source types are supported — logs, factories, transactions, receipts, traces, block intervals, accounts; `readContract` uses your RPC. The Portal serves [130+ EVM networks](https://docs.sqd.dev/en/data/all-networks); per-network capabilities and per-portal availability vary. Check an indexer before migrating:
12265

12366
```bash
124-
PORTAL_METRICS_FILE=/tmp/portal-metrics ponder start
125-
cat /tmp/portal-metrics.1 | jq # chainId 1: wall-clock, chunk width, fetch counts, inserted rows, RPC-fallback intervals
67+
node --experimental-strip-types harness/compat/report.ts ./ponder.config.ts # → READY / NEEDS_TRACES / NO_DATASET per source
12668
```
12769

128-
Read stability from `fetch.errors` / `fetch.retries` / `rpcFallbackIntervals` (all 0 in a healthy run) and efficiency from bytes-per-event. `PONDER_LOG_LEVEL=debug` adds a per-interval `service=portal` line. The bench harness (`harness/bench/`) turns the metrics file into a comparable wall-clock / events-per-second / RSS table.
70+
Full adoption path — check, swap, run, validate, roll back — in [`MIGRATION.md`](MIGRATION.md).
12971

130-
## Limitations
72+
## Going to production
13173

132-
- **The bottleneck moves to indexing.** Once the Portal makes the backfill fast, decode and store dominate; use Postgres and parallel indexing for very large resyncs. This is orthogonal to the Portal.
133-
- **Block-interval sources range-scan the window** (the Portal has no modulo filter), so they read more than their matched-block count. Use a tight interval or range.
134-
- **`readContract`-heavy indexers stay RPC-bound** on those calls — the Portal accelerates logs and state, not handler contract reads.
135-
- **It's a fork, not a plugin.** Ponder's internals aren't publicly exported, so a thin plugin awaits an upstream `HistoricalSync` hook. The seam is small and stable, so the fork tracks Ponder with a short patch — see [`PUBLISHING.md`](PUBLISHING.md#why-a-fork-not-a-thin-plugin).
74+
The free public Portal is ideal for trying the fork and for development, but shares capacity across all users and is rate-limited under load. For production throughput and reliability, use a **dedicated Portal** — your own capacity, no shared limits. Dedicated Portals are set up with the SQD team today: [talk to us](https://sqd.dev/portal/). Self-served tiers (free, starter, growth) are coming soon.
13675

137-
## Versioning & releases
76+
## Examples
13877

139-
`@subsquid/ponder@X.Y.Z-sqd.<rev>` is `ponder@X.Y.Z` plus the Portal layer — the Ponder version is visible in the number, and `-sqd.<rev>` lets the fork ship a fix on the same Ponder version. The fork is generated, not hand-maintained: this repo holds only the Portal layer (`portal/`) and a per-version `wiring/<ver>.patch`; [`scripts/sync-upstream.sh`](scripts/sync-upstream.sh) clones `ponder@<ver>`, applies the layer, and builds. [`versions.json`](versions.json) is the supported-version matrix, and CI proves the seam against each. Details: [`PUBLISHING.md`](PUBLISHING.md).
78+
- [`euler-subgraph`](examples/euler-subgraph/) — a subgraph ported to Ponder + Portal (factories, `readContract`, aggregation). The Quickstart above.
79+
- [`uniswap-portal`](examples/uniswap-portal/) — all five source types in one app (logs, receipts, traces, block intervals, accounts).
80+
- [`euler-multichain`](examples/euler-multichain/) — a compact multichain factory indexer.
14081

141-
## Contributing, feedback & bug reports
82+
## Learn more
14283

143-
Questions, bugs, feedback, and contributions are all welcome:
84+
- [**How it works**](portal/INTEGRATION.md) — the historical-sync seam, read-ahead chunk buffer, factory discovery, adaptive concurrency, and memory backpressure.
85+
- [**Observability**](portal/INTEGRATION.md)`PORTAL_METRICS_FILE` writes a per-chain JSON metrics file (throughput, bytes, errors, RPC-fallback); `PORTAL_GATE_LOG=1` logs the adaptive controller.
86+
- [**Versioning & releases**](PUBLISHING.md)`@subsquid/ponder@<ponder-version>-sqd.<rev>`, generated from upstream Ponder + a per-version patch.
87+
88+
## Contributing & support
14489

14590
- **Bugs & feature requests**[open a GitHub issue](https://github.com/subsquid-labs/portal-ponder/issues).
14691
- **Questions & help**[SquidDevs on Telegram](https://t.me/HydraDevs).
147-
- **Contributions** — pull requests are welcome. The repo holds only the Portal layer (`portal/`) and the per-version wiring patch; the published package is generated from upstream Ponder — see [`PUBLISHING.md`](PUBLISHING.md) for how the fork is built and how to add a Ponder version.
148-
149-
## Repo layout
150-
151-
```
152-
portal/ the Portal layer (the entire diff vs upstream Ponder)
153-
portal.ts the Portal-backed HistoricalSync
154-
portal-transform.ts pure NDJSON → Sync* transforms (+ tests)
155-
config.ts · INTEGRATION.md · wiring/<ver>.patch
156-
scripts/sync-upstream.sh generate @subsquid/ponder@<ver> from ponder@<ver> + the layer
157-
versions.json the @subsquid/ponder ↔ ponder version matrix
158-
examples/ runnable indexers (euler-subgraph · uniswap-portal · euler-multichain)
159-
harness/bench/ benchmark base + instrumentation
160-
harness/compat/ compatibility report (analyzer + docs-matrix snapshot)
161-
harness/euler-multichain/ the 15-chain flagship + REPORT.md
162-
MIGRATION.md · PUBLISHING.md
163-
```
92+
- **Pull requests welcome** — the repo holds only the Portal layer (`portal/`) and a per-version wiring patch; see [`PUBLISHING.md`](PUBLISHING.md) for how the fork is built.

0 commit comments

Comments
 (0)