Rewind (Historical Streaming)
Coming soon - design preview: Rewind is not live yet. The connection parameters and code examples on this page are a design preview of the upcoming API - the
mode=rewindparameters are not yet accepted by the Warp endpoints and may change before launch. Want early access or to shape the design? Contact us or ping us on Discord.
Overview
Rewind is Warp, pointed at the past. Every Warp stream type - Events, Addresses, Coins, Objects - can start from any historical checkpoint instead of the live tip. The same filters, the same payloads, the same delivery guarantees, over one connection:
- Bounded replay (
from→to): stream everything that matched your filters between two checkpoints, then the stream completes. Backtests, incident reprocessing, database rebuilds. - Catch-up to live (
from→ live): replay history from a checkpoint, and when Rewind reaches the chain tip the connection hands off to live Warp seamlessly - no gap, no duplicates, no reconnect. Your consumer restarts where it died and just keeps going.
Checkpoints are Sui's universal cursor: a globally agreed, gap-free sequence over everything that ever happened on chain. Rewind leans on that directly - positions are plain checkpoint coordinates, not opaque tokens, so you can construct one from a checkpoint number you got anywhere (your database, our explorer, an incident timeline).
How It Works
┌────────────────┐
│ Sui Archive │───┐
│ (full history) │ │ ┌───────────────────────┐ WebSocket or SSE ┌───────────┐
└────────────────┘ ├──►│ Inodra Rewind │───────Connection──────►│ Your App │
┌────────────────┐ │ │ scan → filter → order │ (history, then live) │ │
│ Sui Live Node │───┘ └───────────────────────┘ └───────────┘
└────────────────┘- You connect to an existing Warp stream with a
from_checkpoint(and optionallyto_checkpoint) - Inodra scans history from that checkpoint, applies your stream's filters server-side, and delivers matches in ledger order
- Bounded replays finish with a
rewind_completeevent; open-ended replays emitcaught_upand continue as a normal live Warp stream on the same connection
Quick Start
Rewind uses your existing Warp streams - create one in the dashboard first (see Warp Quick Start), then connect with rewind parameters.
Catch up from a checkpoint, then go live (WebSocket, recommended):
const streamId = 'your-stream-id'
const apiKey = 'your-api-key'
const ws = new WebSocket(
`wss://mainnet-api.inodra.com/v1/warp/${streamId}/ws?api_key=${apiKey}` +
`&mode=rewind&from_checkpoint=187432001`
)
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
if (msg.event === 'caught_up') {
console.log('Replay done - now streaming live from checkpoint', msg.data.checkpoint)
} else if (msg.event === 'rewind_progress') {
console.log('Scanned through checkpoint', msg.data.checkpoint)
} else {
console.log(msg.event, msg.data) // your stream type: 'event', 'address', 'coin', 'object'
}
// ACKs work exactly like live Warp - exactly-once delivery, and Rewind paces to your ACKs
if (msg.id) {
ws.send(JSON.stringify({ type: 'ack', id: msg.id }))
}
}Bounded replay of a fixed range (SSE):
const streamId = 'your-stream-id'
const apiKey = 'your-api-key'
const res = await fetch(
`https://mainnet-api.inodra.com/v1/warp/${streamId}/stream` +
`?mode=rewind&from_checkpoint=187000000&to_checkpoint=187500000`,
{ headers: { 'x-api-key': apiKey } }
)
let buffer = ''
for await (const chunk of res.body) {
buffer += new TextDecoder().decode(chunk)
const parts = buffer.split('\n\n')
buffer = parts.pop()
for (const part of parts) {
const eventLine = part.split('\n').find((l) => l.startsWith('event:'))
const dataLine = part.split('\n').find((l) => l.startsWith('data:'))
if (!dataLine) continue
const type = eventLine?.slice(6).trim()
const data = JSON.parse(dataLine.slice(5))
if (type === 'rewind_complete') {
console.log('Range fully replayed:', data)
} else {
console.log(type, data)
}
}
}
// The server closes the connection after rewind_completeConnection Parameters
Rewind adds three parameters to the existing Warp connect endpoints (/v1/warp/{streamId}/ws and /v1/warp/{streamId}/stream):
| Parameter | Required | Description |
|---|---|---|
mode=rewind | Yes | Selects rewind. Mutually exclusive with mode=resume (see Connect modes). |
from_checkpoint | Yes | Checkpoint sequence number to start from (inclusive). Any checkpoint back to genesis. |
to_checkpoint | No | Checkpoint to stop at (inclusive). Omit to hand off to live streaming when caught up. |
Instead of from_checkpoint, you can pass a full position cursor as from - or, on SSE, in the standard Last-Event-ID header. Positions are legible ledger coordinates:
<checkpoint>:<transactionIndex>:<eventIndex> e.g. 187432001:14:2Every replayed message carries its position as the message id (WebSocket) / SSE id: field, so native EventSource reconnection resumes a dropped rewind automatically, exactly where it stopped - the browser sends Last-Event-ID for you. WebSocket clients pass the last processed id as from on reconnect.
Stream Event Types
Rewind connections emit the standard Warp lifecycle events (connected, quota_exceeded, error) plus your stream type's data events, and three rewind-specific events:
| Event | When | Payload |
|---|---|---|
rewind_progress | Periodically while scanning sparse ranges | { "checkpoint": 187210000 } - fully covered through here |
caught_up | Rewind reached the live tip (no to_checkpoint) | { "checkpoint": 187994210 } - live from the next checkpoint |
rewind_complete | Bounded rewind reached to_checkpoint | { "from": ..., "to": ..., "matched": 45210 } |
rewind_progress is a watermark: it tells you every match at or below that checkpoint has been delivered, even when your filter hasn't matched anything for a while. Persist it alongside your data - it's a valid resume position and it keeps "quiet" replays observably alive.
Ordering & Delivery Guarantees
- Ledger order, gap-free: matches arrive strictly ordered by
(checkpoint, transactionIndex, eventIndex). The history→live handoff happens on a checkpoint boundary - nothing is skipped or delivered twice across the seam. - WebSocket: exactly-once, exactly like live Warp - messages are marked delivered only after your ACK, and an unACKed replay resumes from the last acknowledged position.
- SSE: at-least-once. Reconnect with
Last-Event-ID(automatic withEventSource) and delivery continues from that position; you may see the last in-flight message again. - Backpressure: replaying history produces data far faster than the chain does. The server paces delivery to your consumption (ACK window on WebSocket, socket backpressure on SSE) - a slow consumer slows the rewind down; it never overruns you or buffers unboundedly. If a connection drops mid-rewind, nothing is lost: reconnect with your last position.
History Depth & Performance
Rewind reads from Inodra's full-history archive - the same storage that serves archival gRPC and GraphQL back to genesis.
Two things affect replay throughput:
- Filter density: for indexed filter dimensions (event types, emitting modules, senders, addresses, objects), Rewind skips directly to matching checkpoints - a sparse filter over a huge range is fast because most of the range is never touched.
- Range temperature: recent history is served hot; deep-history segments are fetched from cold archival storage on demand. Deep sparse replays interleave
rewind_progresswatermarks while the scan works, so your client always sees forward motion.
Preview limits: maximum rewind range per connection, per-plan history depth, and concurrent rewind connections are being finalized during the preview. Current preview builds allow one active connection per stream (same as live Warp).
Billing
Preview pricing - subject to change.
Replayed messages are billed like live Warp delivery (per event + connection time - see Credits & Billing). Historical scanning adds a per-range scan component for deep or unindexed ranges; indexed, filtered replays only pay for what's delivered. Exact rates will be published when Rewind reaches general availability.
Common Use Cases
Recovery & Reindexing
Your consumer, indexer, or database died at checkpoint N. Connect with from_checkpoint=N and no to_checkpoint: Rewind backfills everything you missed and hands off to live on the same connection. No dual code paths, no gap math, no "poll then switch" logic.
Backtesting
Replay every Swap a DEX emitted across a historical window - with the same field filtering your live strategy uses - into your backtest at full speed. to_checkpoint bounds the run, and rewind_complete tells you the dataset is complete (and how many events matched).
Incident Reprocessing
Something went wrong between two checkpoints. Replay exactly that window through the exact filters your production stream uses and re-run your pipeline against what actually happened, in the order it happened.
Populating a New System
Launching a feature that needs history? Point a bounded rewind at genesis-to-now with your filters, bulk-load the results, then flip the same stream to live mode. One filter definition, one delivery format, both directions of time.
Rewind vs. Raw Checkpoint Streaming
Rewind delivers filtered, decoded, application-level data with Warp's delivery guarantees. If you need the raw firehose instead - every full checkpoint over a historical range, BCS or structured - the same engine powers checkpoint-range streaming on the gRPC gateway's SubscribeCheckpoints. That mode is available for enterprise plans today; contact us for access.
| Feature | Rewind (Warp) | Checkpoint-range streaming (gRPC) |
|---|---|---|
| Payload | Matched events/addresses/coins/objects, JSON | Full checkpoints (proto, mask-gated) |
| Filtering | Full Warp filters incl. field filtering | Field masks only (all transactions) |
| Delivery | Exactly-once (WS ACKs) / at-least-once (SSE) | gRPC stream, client tracks cursor |
| Best for | App-level consumers, pipelines, backtests | Indexers, mirrors, checkpoint tooling |
Next Steps
- Warp Overview - stream types, protocols, and live streaming
- Setup & Connection - authentication, reconnection, best practices
- Event Streams - event filters you can replay
- Credits & Billing - how streaming is billed