Skip to main content

Historical replay

Every event the full-depth stream pushes is also written to an hourly archive. Pull an hour, replay it through the same code that consumes the live socket, and you have a backtest. Free, no key.

https://replay.shrine.trade/pump/YYYY/MM/DD/HH.jsonl.zst

The hour is UTC. A file appears a few minutes after its hour ends, and the archive runs from the day it was switched on; earlier hours do not exist.

Browse the bucket

The archive lives in a public Cloudflare R2 bucket. Object storage has no directory listing, so the bucket keeps its own: https://replay.shrine.trade/pump/index.json lists every hour it holds, oldest first, and is rewritten after each upload.

{ "base": "https://replay.shrine.trade/pump", "pattern": "<base>/<hour>.jsonl.zst", "count": 312, "hours": ["2026/09/11/07", "2026/09/11/08", "…"] }

Join base, an entry of hours and .jsonl.zst to get a file. To fetch everything, walk hours; to fill a gap, look for the missing entry.

Format

One JSON object per line, zstd compressed. Each line is exactly the live stream event, with one extra field: localTimestamp, the millisecond our server saw the transaction, which the live stream does not carry. Everything else, signature, block, timestamp, action, protocol, mint, breakdown, tradersInvolved, reserves and the launch authorities, is as documented on the stream page.

An hour is a few hundred megabytes compressed and a couple of gigabytes unpacked. Stream it rather than loading it whole.

Copy this

// Node 22.15+ has zstd in zlib. Older Node: npm install fzstd.
import { createZstdDecompress } from "node:zlib";
import { createInterface } from "node:readline";
import { Readable } from "node:stream";

const url = "https://replay.shrine.trade/pump/2026/09/11/07.jsonl.zst";
const res = await fetch(url);
const lines = createInterface({ input: Readable.fromWeb(res.body).pipe(createZstdDecompress()) });

let buys = 0;
for await (const line of lines) {
const e = JSON.parse(line);
if (e.action === "buy" && e.protocol === "PUMPFUN") buys++;
}
console.log("pump.fun buys that hour:", buys);

Notes

  • Same shape as live. Code written against subscribe_stream runs unchanged on the archive; feed it lines instead of socket events.
  • Filter early. Every transaction on every launchpad is in there. Keep the protocols and actions you care about and drop the rest while streaming.
  • Ordering. Lines are in the order events were seen, which is block order with the occasional neighbour swapped. Sort by block then signature when it matters.
  • Gaps. A restart of the collector can lose a minute or two inside an hour. There is no marker; compare block numbers if you need to know.