0block docs
Dashboard & API Keys

Tracking your transactions

The received → forwarded → acked → landed/reverted lifecycle, and how 0block measures landing speed with slot_delta (and landing_ms).

Beta — rolling out now. The dashboard and its control-plane API are in active rollout. The contract documented here is stable, but availability may vary by deployment.

Every submission made with an API key is telemetered. 0block records where it is in its lifecycle and — once it lands — how long that took, which leader produced its block, and whether the program succeeded. You read this back through GET /transactions and GET /stats.

The lifecycle

A transaction moves through these statuses:

received ─▶ forwarded ─▶ acked ─┬▶ landed
                                ├▶ reverted
                                ├▶ failed
                                └▶ expired
StatusMeaningTimestamp set
received0block accepted the tipped submission and recorded it (noting the current chain slot as submitted_slot).received_at
forwardedThe raw bytes were forwarded to the staked upstream.forwarded_at
ackedThe upstream acknowledged the submission (a JSON-RPC result). This is a submission-ack, not landing.acked_at
landedConfirmed on-chain and the program succeeded.landed_at (+ slot, slot_delta, landing_ms, landed_leader)
revertedLanded in a block, but the program reverted — included on-chain yet did not succeed. onchain_err and program_error_code are populated. Carries the same slot / slot_delta / landing_ms as a landed tx.landed_at (+ slot, slot_delta, landing_ms, landed_leader)
failedThe submission failed — the upstream returned an error, or a pre-check rejected it. Never made it into a block. error_code is populated.
expiredStill not landed after the tracking window (90 seconds). 0block stops polling.

acked is not confirmation. It only means the upstream accepted the transaction for forwarding. The only landing signals are landed and reverted, which 0block derives independently — never from the submission response.

landed and reverted are both on-chain inclusions — the difference is execution: landed succeeded, reverted was included but the program returned an error. failed and expired never made it into a block.

How landing is detected

0block does not treat a submission-ack as finality. On receipt it notes the current chain slot as submitted_slot. Landing is then detected one of two ways, recorded per-transaction in latency_source:

  • grpc — a real-time Geyser gRPC stream reports the exact slot from the transaction's first shred. This is precise: submitted_slot, slot, and landing_ms are all exact.
  • poll — a fallback landing poller batches the signatures of in-flight transactions into a single getSignatureStatuses call (roughly every couple of seconds). Detection is bounded by that ~2s cadence, and submitted_slot is a processed + 1 estimate, so poll-mode slot_delta and landing_ms are approximate.

Anything still unlanded after 90 seconds is marked expired and polling stops. An expired transaction may still have landed later on-chain — it just fell outside 0block's tracking window, so confirm independently if it matters.

Measuring landing speed

slot_delta is the primary landing-speed metric. It counts how many slots after the one you sent during the transaction landed:

slot_delta = max(0, slot - submitted_slot)
  • 0 — landed in the same slot it was sent during. This is the best possible outcome.
  • 1, 2, … — that many slots later.
  • null — the transaction hasn't landed, or its landing slot couldn't be observed.

This matches the slot-latency convention used by ping-thing and validators.app. Its accuracy depends on submitted_slot: exact under grpc (first-shred slot), an estimate under poll (processed + 1). Because it's counted in slots, slot_delta is immune to wall-clock jitter — it's the metric to watch for execution quality, and it's what GET /stats aggregates into same_slot_rate and slot_distribution. Reverted transactions landed in a block too, so they carry a real slot_delta and count toward these inclusion metrics.

landing_ms is wall-clock milliseconds from when 0block received the submission to when it detected the landing. It is precise when latency_source is grpc (real-time detection); under poll it includes up to ~2s of polling granularity and therefore overstates true landing latency, so treat it as an upper bound and prefer slot_delta.

relay_ms is a separate number: 0block's own processing overhead — leader ack minus receipt, typically ~40–90 ms. It's the latency the gateway itself adds, distinct from how long the network took to land the transaction.

Which leader landed it

Two leader identities are recorded, and they can differ:

  • target_leader — the leader of the slot you aimed at (the producer of submitted_slot).
  • landed_leader — the validator that actually produced the block the transaction landed in. Only set once landed/reverted.

They match when a transaction lands within its target leader's window, and differ when it lands a few slots late and crosses into the next leader's window. landed_leader is the real block producer — it's what you'd cross-reference on an explorer.

When a transaction reverts

A reverted transaction was included on-chain but the program returned an error. It is a distinct terminal status from failed (which never made it into a block):

  • onchain_err — the raw serialized Solana transaction error, e.g. {"InstructionError":[3,{"Custom":6062}]}.
  • program_error_code — the parsed custom program error code (e.g. 6062) when the error carries one; null otherwise.

A reverted transaction still has slot, slot_delta, landing_ms, and landed_leader — it was included, it just didn't succeed.

Reading the data

Each transaction record carries the full set of lifecycle fields:

{
  "id": "e2b1…",
  "signature": "5t4eBfVv8A3tN6d2zjZphqStjc6b4YbTL7Yt8Y6XWf9",
  "key_prefix": "0b_ab12cd34",
  "status": "landed",
  "error_code": null,
  "received_at": "2026-07-05T18:22:31.010Z",
  "forwarded_at": "2026-07-05T18:22:31.014Z",
  "acked_at": "2026-07-05T18:22:31.068Z",
  "landed_at": "2026-07-05T18:22:31.422Z",
  "submitted_slot": 265470112,
  "slot": 265470112,
  "slot_delta": 0,
  "landing_ms": 412,
  "relay_ms": 58,
  "latency_source": "grpc",
  "target_leader": "Fd7btgySLzy5S4YfF6RQXbTHfMehFAJujFhH7QoZWZ2",
  "landed_leader": "Fd7btgySLzy5S4YfF6RQXbTHfMehFAJujFhH7QoZWZ2",
  "onchain_err": null,
  "program_error_code": null
}

This example landed in the same slot it was sent during (slot_delta 0) — the best case — and was detected in real time (latency_source grpc), so landing_ms 412 is exact. relay_ms 58 is 0block's own overhead. target_leader equals landed_leader, so it landed inside its target leader's window.

  • Filter by lifecycle stage with ?status= (e.g. ?status=landed, ?status=reverted, or ?status=expired).
  • Aggregate landing quality over a window with GET /stats: same_slot_rate (share of included transactions at slot_delta 0), slot_distribution (how landings spread across 05+), and latency percentiles (p50/p90/p99) for both landing_ms and relay_ms.

See the Transactions & Stats reference for the full field list and query parameters.