# Introducing Replay: Solana mainnet, paused on a frame you can drive

> **Product launch — May 21, 2026.** The first product out of the k256 lab is live.

Reproduce the bug at the slot it happened. Stage a program
upgrade against today's mainnet accounts. Decide a protocol
change with evidence, not a hunch. Replay puts you inside Solana
mainnet at any moment you choose and lets you drive it — advance
the chain slot by slot, splice your own transactions, patch any
account, deploy any program, and read it all back over full
JSON-RPC, WebSocket, and Geyser. It's the closest thing to a time
machine for the Solana state machine, drivable from a terminal or
from an AI agent over Model Context Protocol.

This post is the long version of what it is, why it exists, what
you can do with it, and how to start. The five-second version: pick
a slot, get a dedicated environment that *is* Solana mainnet at
that frame, then drive it forward however you like — your splices,
your patches, your programs.

- **Live now.** Dashboards, HTTP API, and MCP server are all
  shipped, with paying customers on them.
- **From $1,599 / month.** Dedicated environment, validator
  version of your choice, region of your choice.
- **Get started:** <https://k256.xyz/replay>

---

## §01 The gap nothing else fills

The Solana developer toolchain has good tools for many problems —
and one persistent hole: *being mainnet at a specific moment, and
driving it forward*.

Walk through what's available today:

- `solana-test-validator` boots an empty chain. Useful for unit
  tests where you control every account from genesis, useless when
  the bug you're chasing depends on the exact state of USDC, Jupiter,
  Raydium, and the wallet that bridged ten minutes ago.
- **Devnet and testnet** are different chains with different state.
  Programs on devnet aren't the same programs deployed to mainnet,
  and the accounts they touch are different too. Useful for early
  development, not for reproducing what happened in production.
- **Forking RPC proxies** intercept reads and forward them to a
  real mainnet node. They give you state-at-a-slot for the calls
  they implement, but they don't run a validator — you can't
  advance the chain forward, you can't replay a block, you can't
  splice your own transaction at slot 418,908,420 and watch what
  happens next. The reads work; the writes that matter don't.
- **Local-fork tools** that snapshot a few accounts at a moment in
  time — `anchor test --provider.cluster mainnet` and friends —
  do account-level forking, not chain-level. The bank doesn't tick.

> The piece that was missing: an *actual* Solana validator, booted
> from an *actual* mainnet snapshot, that you can advance, mutate,
> and read like the real thing — because at the protocol layer, it
> is the real thing.

That's what Replay is. Not a simulator, not a proxy, not a fork.
Your own Solana mainnet, isolated to you, with no public consensus
to keep up with and nobody else on it, that you can advance,
mutate, and read like the real thing. Because at the protocol
layer, it *is* the real thing.

---

## §02 What it's for

Almost every conversation we have lands on one of three jobs. Each
used to cost a day and a fragile setup. The shape of the fix is
always the same — boot, drive, read — on a fork you can throw away
and run again.

### Incident response

A program misbehaves at a specific signature. The old workflow:
spin up a local validator, hand-craft the surrounding state,
fail to reproduce, ask the operator for a database dump, give
up.

The Replay workflow: boot the snapshot from before the incident,
splice the offending transaction at the next slot, watch the bank
state and the log messages. Patch the upstream account that
looked suspicious, re-splice, see if the failure flips. No prod
risk, no flaky local repros, and the fork is throwaway — you can
run the experiment ten times.

### Program upgrades

`POST /programs/deploy` uploads a compiled `.so` and installs
it at any program id. Raw ELFs up to 10 MiB. No on-chain
authority dance, no cooldown — it's your fork — so you can stage
a candidate against today's mainnet accounts and run your real
workloads against it before you ship. The regression surfaces
here, not in the post-mortem.

The same request models upgradeable programs
(`status=deployed`), models the post-finalization immutable
state (`status=finalized`), or converts a program from a legacy
loader. One endpoint, one body.

### Protocol research

Walk the chain forward through a window of canonical blocks while
splicing your proposed transactions in. Read the assembled block
back. Splice again with a different parameter, advance again,
compare. Decide with evidence — before any of it touches
production.

It's the same loop you'd run if you were running mainnet — except
you control the slot clock and nobody else sees your work.

---

## §03 What you see when you boot

You pick a frame from the catalog — a rolling window of recent
mainnet states across the Solana versions we run. Cached frames
come up in seconds; new ones in a few minutes. You pick the
version, pick streaming on or off, hit Start.

When the dashboard says `phase=ready`, you are now Solana
mainnet at exactly that slot, on an environment that is yours
alone. JSON-RPC is accepting queries; `getSlot` returns that
slot; `getAccountInfo` on any address returns what the chain
actually held at that moment.

The lifecycle is explicit and visible. Phases:

| Phase | Meaning |
| --- | --- |
| `idle` | No environment running. |
| `downloading` | Pulling an uncached snapshot. `/status.download` carries progress. Cached boots skip this phase. |
| `booting` | Environment is starting. |
| `extracting` | Decompressing the selected snapshot archive. |
| `indexing` | Indexing accounts. |
| `ready` | JSON-RPC accepts queries. The only phase where you can advance, patch, or deploy. |
| `advancing` | An advance job is running. |
| `dead` | Environment failed; `/status.last_error` carries the reason. |

`/status` is the single source of truth; the dashboard polls it
every second, and so does every reasonable client.

---

## §04 Six verbs

The product surface is small on purpose. Six things you do
repeatedly, each documented end-to-end, each reachable from the
dashboard, the HTTP API, and the MCP server.

### Boot — pick the frame

`POST /boot` with the snapshot you picked from the catalog.
Returns `202` immediately; uncached boots walk through
`downloading → extracting → indexing → ready`; cached boots skip
the network fetch and still decompress locally before indexing. Integrity
is checked on the way in — anything off, the environment stays
`dead` and tells you what failed.

A fresh boot resets the local fork — every advance and mutation
since the last boot is dropped. That's the only knob that clears
`mutation.dirty`, and it's deliberate: rebooting is cheap, so
the operational story is "boot a clean canonical mainnet, do your
work, throw it away."

### Accounts — read and mutate the bank

The Accounts tab is your production address book. The sidebar has
quick-add chips for the programs you always reach for — Token,
USDC, Jupiter, Raydium, WSOL — and a paste-anywhere input for the
ones you don't. The detail panel reads the bank: owner, balance,
data size, rent epoch, raw bytes.

`POST /accounts/patch` writes one or more accounts straight
into the local fork. Every patch is logged. You can merge (keep
existing fields you don't set) or replace (overwrite the whole
account). The moment a patch lands, `mutation.dirty=true` and
the UI flips a label to remind you you're on a labeled local
fork. Reboot to return to canonical mainnet.

A short example, taken verbatim from the public docs:

```bash
curl -s -X POST \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "content-type: application/json" \
  -d '{
    "patches": [
      { "pubkey": "<YOUR_PUBKEY>", "mode": "merge", "lamports": "1000000000" }
    ],
    "confirm_dangerous": true
  }' \
  https://api.k256.xyz/v1/replay/servers/<SERVER_ID>/accounts/patch
```

Topping up a wallet's lamports is the trivial case. Patching a
token mint to model an upgrade, overwriting a stale price oracle,
or replacing an entire program account before staging a deploy —
that's where this earns its keep.

### Advance — splice transactions into the stream

`POST /advance` walks the fork forward to `target_slot`,
optionally splicing your transactions into specific slots along
the way. Replay replays canonical mainnet blocks and weaves your
transactions in where you asked for them — eight splice anchors,
one queue:

| Anchor | JSON form | Meaning |
| --- | --- | --- |
| BlockStart | `"block_start"` | Inject at the very front of the block. |
| BlockEnd | `"block_end"` | Append after every canonical tx. |
| BeforeIndex | `{ "before_index": N }` | Inject right before the canonical tx at index N. |
| AfterIndex | `{ "after_index": N }` | Inject right after index N. |
| BeforeSignature | `{ "before_signature": "<sig>" }` | Inject before a known canonical signature. |
| AfterSignature | `{ "after_signature": "<sig>" }` | Inject after one. |
| ReplaceIndex | `{ "replace_index": N }` | Drop a canonical tx and put yours in its place. |
| AtIndexClamped | `{ "at_index_clamped": N }` | Insert at N, clamp to block_end if N overshoots. |

Replay walks forward block by block — canonical, then yours,
then canonical again — in exactly the order the bank applies
them. `getBlock` over JSON-RPC returns the assembled block:
your splice at the index you put it. You can cancel any advance
mid-flight (`POST /advance/cancel`) and inspect what landed;
blocks already rooted stay applied, there's no rollback.

### Blocks — your replay chain

The Blocks tab is the canonical block explorer for the chain
*you* just built. Walk the chain forward from your boot point.
Click any row for decoded instructions, log messages, pre/post
balances. Filter by signature or any pubkey in the tx — the
recipient, the signer, the program. The list reflects your
replay chain, not upstream mainnet, so the transaction you
spliced in at slot 418,908,420 sits exactly where you put it.

### RPC — point your existing tools at it

There is nothing to rewrite. Your environment speaks the standard
Solana JSON-RPC surface, WebSocket PubSub, and — if you booted
with streaming on — gRPC. Direct endpoint values stay hidden until
you reveal them in the app; once copied, treat those URLs like
operational secrets:

```bash
# JSON-RPC against the endpoint revealed and copied from the app
REPLAY_RPC_ENDPOINT="<copied replay RPC endpoint>"

curl -s -X POST \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getSlot"}' \
  "$REPLAY_RPC_ENDPOINT"
```

`@solana/web3.js`, `anchor`, `solana-cli`, your indexer, your
bots, your dashboards — paste the endpoint and point. Browser apps
served over HTTPS should use an HTTPS/WSS proxy or bridge instead
of putting copied runtime endpoints directly into client code.
Block height does not advance unless you call `/advance`, so anything
that loops on `onSlotChange` blocks until you ask it to. That's the contract.

> One thing to be aware of: `getSlot` returns the same value
> across calls until you advance. Any client that polls in a
> loop waiting for new slots will deadlock unless something is
> also calling `POST /advance`. We surface this in the docs and
> the LLM operator quickstart so agents don't write busy-waits.

The API remains the control surface for boot, advance, checkpoint,
patch, deploy, and audit workflows. Standard Solana client traffic
uses the copied runtime endpoints or an HTTPS/WSS proxy that you operate.

### History — every advance, recorded

The History tab is the audit trail for your session. Start slot,
target slot, duration, status. Twenty most recent jobs, in
memory. Expand a row for the exit code, the finality choice, and
the full job ID. The signal you want when you're iterating on a
splice plan and need to know which run produced which state.

The same audit log carries every patch, splice, and program
deploy — `GET /accounts/patch/history` returns up to 2,000
records (newest first), spanning every mutation kind, with the
before/after state hashes for each. Pipe it to `jq` if you want
the diff.

---

## §05 Two transports, one bearer

Same product, two access patterns. Your `k256_live_` key reaches
every Replay environment your org owns; a specific environment is
selected by its `server_id` in the path.

- **HTTP** — `https://api.k256.xyz/v1/replay/servers/<SERVER_ID>/<path>`
  with `Authorization: Bearer <key>`. Every verb is documented
  in the operator quickstart, served as plain markdown from
  `/replay/llm-docs.md` so a developer (or an agent) can paste
  the URL and have the entire contract in one fetch.
- **MCP** — `https://api.k256.xyz/mcp`. Streamable HTTP
  transport per the Model Context Protocol `2025-11-25` spec.
  Same bearer, same wire contract, same error kinds. Tool names
  are the dotted operation ids — `replay.get_status`,
  `replay.boot_server`, `replay.advance_server`,
  `replay.patch_accounts`, and so on. An agent that learns one
  learns the other.

Wire it into your agent host once, restart, and the agent can
boot a snapshot, advance to a slot, splice a transaction, patch
an account, inspect logs, and tear down — on its own. A one-line
install for the hosts we test against:

```
~/.cursor/mcp.json            # Cursor
claude mcp add k256-replay …  # Claude Code & Desktop
codex mcp add k256-replay …   # Codex CLI
.vscode/mcp.json              # VS Code
```

The MCP server is the same surface as the HTTP API, just wearing
a different hat. No second source of truth, no version skew —
every HTTP verb ships with its MCP tool in the same release.

Custom Geyser plugin upload is dashboard-only because the web app
handles the large multipart `.so` through a dashboard-only upload path;
the public per-server API does not expose a customer-callable
`POST /plugin/upload`. Logs streaming is HTTP-only on purpose:
`GET /logs/stream` is Server-Sent Events, while agents poll
`replay.tail_logs` instead.

---

## §06 Three-minute quickstart

If you have a key in hand and an environment provisioned, this is
the loop:

```bash
export KEY="<YOUR_API_KEY>"
export GW="https://api.k256.xyz/v1/replay/servers/<SERVER_ID>"

# 1. Wait until the environment is ready
until curl -fsS -H "Authorization: Bearer $KEY" \
  $GW/status | jq -e '.phase == "ready"' >/dev/null; do
  sleep 2
done

# 2. Read the current slot
SLOT=$(curl -s -H "Authorization: Bearer $KEY" $GW/status | jq -r .current_slot)
echo "ready at slot $SLOT"

# 3. Advance one slot — replay the next canonical block
curl -s -X POST \
  -H "Authorization: Bearer $KEY" \
  -H "content-type: application/json" \
  -d "{\"target_slot\": $((SLOT + 1)), \"root\": true}" \
  $GW/advance | jq

# 4. Splice a transaction at the end of a future block
curl -s -X POST \
  -H "Authorization: Bearer $KEY" \
  -H "content-type: application/json" \
  -d '{
    "target_slot": '$((SLOT + 2))',
    "splices": [
      { "slot": '$((SLOT + 1))', "position": "block_end",
        "transactions_base64": ["AQAB..."] }
    ]
  }' \
  $GW/advance | jq

# 5. Read your replay chain
curl -s -X POST \
  -H "Authorization: Bearer $KEY" \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getBlock","params":['$((SLOT + 1))', {"transactionDetails":"full","maxSupportedTransactionVersion":0}]}' \
  $GW | jq
```

The snippets above hit the gateway with `curl`. Once the fork is
advancing, you'll usually want to read its state from a real Solana
client — `@solana/web3.js`, `solana-py`, Yellowstone gRPC — pointed
at the copied runtime endpoints. The Replay examples repo at

<https://github.com/k256-xyz/replay-examples>

shows the wire shape of all three: a one-shot JSON-RPC call, a
PubSub subscription, and a Yellowstone gRPC stream. Read it, lift
the parts you need, point your real client at the same URLs.

---

## §07 Errors you can dispatch on

Every non-200 response carries a JSON body with
`{ "error": "<kind>", "message": "..." }`, and the kind is in
the docs. `409 boot_in_flight`. `409 advance_in_flight`.
`409 phase_not_ready`. `400 target_in_past`.
`413 elf_too_large`. Stable, finite, dispatchable. Build
dispatch tables, not regex parsers — and an agent that reads the
docs will route around the error states without you having to
catch them by hand.

---

## §08 What ships next

A short, deliberately conservative list:

- Snapshot retention windows on demand for customers who want
  reproducible state past the rolling window.
- More streaming presets beyond Yellowstone, driven by what
  customers want to ingest.
- Multi-region replicas of the same boot point for teams
  coordinating across geographies.

We'll write about each one as it ships. None of them are
blockers for the workflows above — Replay is useful today,
exactly as it stands.

---

## §09 Get started

If you've read this far, you already know whether Replay solves
a problem you have. The fastest way to find out is to provision
an environment and run the quickstart above.

- **Sign up:** <https://k256.xyz/sign-up>
- **Product page:** <https://k256.xyz/replay>
- **API explorer:** <https://k256.xyz/replay/api>
- **MCP setup:** <https://k256.xyz/replay/api?tab=mcp>
- **LLM operator quickstart (paste into Claude/ChatGPT/Cursor):**
  <https://k256.xyz/replay/llm-docs.md>
- **Public examples:** <https://github.com/k256-xyz/replay-examples>
- **Talk to us:** `contact@k256.xyz`

Pricing is $1,599 / month for one dedicated environment in one
region, with unlimited splices and patches, the Solana version
of your choice, and access to every endpoint and tool described
above. Sign up, pick a region, pick a version, and you're inside
a paused mainnet in minutes.

— k256
