Using P2C
How to receive the post-pack stream and act on it.
Payment paths (use one or both):
- PSA — selling / reselling pre-conf. You pay an epoch fee based on txn Updates.
- MCA — backrun. Share 65% of backrun profit; pre-conf is free for backrun-only (no PSA). If you also sell, fund a PSA too.
The validator connects out to your gRPC server and pushes transactions at the point of no return. Your reply bundle still needs a tip for scheduling priority — see Minimum Tip Requirements.
This page covers Relayer gRPC services, packet layout, reply bundles, and validator Admin RPC for endpoints.
Audience: Searchers and TIN partners consuming the stream; validator operators managing endpoints.
Related: Post-pack overview · PSA · MCA · Setup guide · Tips
1. Setup: Required gRPC services
Run a gRPC server and share that URL with the Rakurai team — see Setup guide. Keep a funded PSA if you resell, and/or an active MCA if you backrun — otherwise the matching stream stops.
Sample URL to share:
https://sample-server.com:20000
You run a server and listen. The validator dials your URL. Bind so validators can reach you (public IP or hostname). Both http:// and https:// work.
1.1. Services your server must expose
Protos: auth.proto, block_engine.proto.
| Service | Purpose |
|---|---|
auth.AuthService | Auth with role RELAYER (challenge signed by the validator identity, then a bearer token) |
block_engine.BlockEngineValidator | GetBlockEngineEndpoints so validators can pick the best region. On a P2C-only host, SubscribePackets / SubscribeBundles may return UNIMPLEMENTED |
block_engine.BlockEngineRelayer | The transaction streams in the table below |
1.2. Relayer streams — which RPC to implement
Pick your use case first. That decides which streams you need.
| Your use case | Config flags | Streams you need |
|---|---|---|
| Resell / pre-conf (PSA) | resell | StartExpiringPacketStream (required) |
| Backrun (MCA) | mev | StartExpiringMevPacketStream (required) |
| Backrun + TPU | mev + enable_tpu_p2c_update | Mev stream plus optional StartExpiringTpuPacketStream |
| Resell + Backrun | resell + mev | PacketStream and MevPacketStream (optional TPU on Mev path) |
| Relayer RPC | When required | What you receive | Front-run protected? |
|---|---|---|---|
StartExpiringPacketStream | Required if resell is on | Post-pack confirmations at the point of no return (resell / no tip-boost hash). Scheduler only — no TPU on this path. | Yes |
StartExpiringMevPacketStream | Required if mev is on | Post-pack confirmations at the point of no return (Mev / tip-boost-eligible hash). Reply with a backrun bundle (original packet unchanged + your txs). | Yes |
StartExpiringTpuPacketStream | Optional — only with mev + enable_tpu_p2c_update | Transactions the validator sees on TPU while it is not the leader. Same message shape as the other streams. | No — not yet at the point of no return |
StartP2cUpdateCountStream | Optional | Once per slot: how many transactions were sent on the post-pack confirmation and TPU streams (uuid, slot, counts). | — |
All streams send the same PacketBatchUpdate message type. Tell them apart by which gRPC method delivered the message — there is no source field on the packet. Details: §2.2 · Setup guide §8.1.
If TPU or count RPCs are missing (or return UNIMPLEMENTED), newer validators keep the post-pack confirmation stream(s) and continue. If resell is on and StartExpiringPacketStream is missing — or mev is on and StartExpiringMevPacketStream is missing — the connection fails. Sample toggles: p2c_server --disable-tpu-packet-stream / --disable-p2c-update-count (both optional streams are on by default).
enable_tpu_p2c_update is set per P2C URL (on-chain / Admin). When it is false, the validator does not open the TPU stream even if your server implements it.
1.3. Reusing your block-engine URL
If one host handles both bundles (you → validator) and P2C (validator → you), register that single URL for both paths. The server must expose the Relayer role and the Validator role — otherwise bundles may work while P2C never starts.
Full details: Setup guide — same URL for bundles and P2C. Or register a separate P2C URL that implements Relayer + GetBlockEngineEndpoints (and leaves bundle subscribe RPCs unimplemented).
Once your endpoint is on-chain, opted-in Rakurai validators push transactions to you. Settle with PSA for resell, MCA for backrun, or both — see Post-pack — access and payments.
2. Transaction / packet structure
For each transaction, the validator sends one PacketBatchUpdate with msg = batches on either the scheduler or TPU Relayer stream (same shape; which stream you accepted is the source):
PacketBatchUpdate
└── batches: ExpiringPacketBatch
├── header.ts
├── batch: PacketBatch
│ └── packets[]: Packet
│ ├── data ← raw Solana wire transaction bytes
│ └── meta: Meta
│ ├── size
│ ├── addr
│ ├── port
│ ├── flags: PacketFlags
│ └── sender_stake
└── expiry_ms = <working-bank slot as u32> ← field name is historical; value is a slot, not a timeout in ms
The field is named expiry_ms for Jito wire compatibility. On the P2C path it carries the validator’s working-bank slot as a u32 — the leader slot that produced the update — not a millisecond expiry.
2.1. Per-slot counts (StartP2cUpdateCountStream)
When enabled, the validator pushes one P2cUpdateCount once per slot (and flushes on disconnect). Each message is a count of transactions successfully sent through the P2C update streams for that slot:
| Field | Meaning |
|---|---|
uuid | Post-pack endpoint UUID |
slot | Slot these counts belong to |
scheduler_count | Transactions successfully sent on the post-pack confirmation stream (StartExpiringPacketStream or StartExpiringMevPacketStream) |
tpu_count | Transactions successfully sent on StartExpiringTpuPacketStream |
total_count | scheduler_count + tpu_count |
p2c_tpu_enabled | Whether this endpoint has TPU updates enabled in config |
2.2. meta fields
Meta and PacketFlags are the packet gRPC types in packet.proto. Decode the transaction from data; treat meta as P2C fills it, not as a TPU/relayer packet.
| Field | Type | What the proto is | What P2C sends |
|---|---|---|---|
size | uint64 | Byte length of data | data.len() |
addr | string | Source address on a normal packet | Not an IP. P2C copies a per-transaction string here. On the TPU path that string is the validator identity signature over the transaction signature (proof this leader emitted the update). Do not parse it as a socket address. |
port | uint32 | Source port | Always 0 |
flags | PacketFlags | Per-packet flags (see below) | Omitted (None) — treat as unset / all false |
sender_stake | uint64 | Stake of the sending node | Always 0 |
PacketFlags (present in the proto, not set on P2C):
| Flag | Meaning on a normal packet |
|---|---|
discard | Packet should be dropped |
forwarded | Already forwarded |
repair | Repair traffic |
simple_vote_tx | Simple vote |
tracer_packet | Tracer |
from_staked_node | Came from a staked node |
Do not use sender_stake, port, or flags to decide whether to backrun. When you reply, put the original Packet unchanged — data and meta — as the first packet(s) of the bundle.
Decode in Rust:
use solana_transaction::versioned::VersionedTransaction; let txn: VersionedTransaction = bincode::deserialize(&packet.data)?;
packet.data is the raw Solana wire transaction. Decode it, inspect accounts and instructions, then decide whether to backrun.
3. Send a bundle
Receive the post-pack confirmation Packet, then send back a bundle (SendBundle through block-engine).
When building the bundle, include:
- The original post-pack confirmation packet(s) unchanged (the same
Packetyou received) - Any additional transactions (e.g. backrun / arbitrage)
- A transaction or instruction with a tip to one of Rakurai’s tip accounts
- P2C MEV: 0.001 SOL — Minimum tip to receive the 20% virtual priority boost on P2C MEV bundles. A lower tip may result in reduced QoS and no priority boost.
- Non-P2C: 0.0005 SOL — Recommended tip for regular non-P2C bundles to get the best QoS. Lower tip may lead to potentially lower QoS.
- Below 100K lamports: Bundles with low tips are processed on a best-effort basis.
A Bundle is a header plus a list of Packets — the same packet shape as the stream:
Bundle ├── header └── packets[]: Packet ← original post-pack packet(s) first, then your txs
4. Commands
Searcher / TIN partner (on-chain money paths):
| What | CLI |
|---|---|
| Fund and inspect PSA | rakurai-p2c |
| Report and settle MCA | rakurai-revshare |
Validator operators use Admin RPC below to inspect which post-pack endpoints are active and to blocklist services. Registering a new endpoint (adding your gRPC URL on-chain) is not done here — share your endpoint with the Rakurai team via Setup guide.
Admin IPC is request/response: keep the socket open briefly so socat can read the reply before stdin closes.
4.1. getPostPackConfirmationConfig
Returns the live status maintained by the scheduler (admin + on-chain merge, blocklist, and what is actually connected).
| Field | Description |
|---|---|
onchain_entries | Entries loaded from the on-chain PDA (url, uuid, enable_tpu_p2c_update) |
blocklisted_uuids | Endpoint UUIDs blocked via setPostPackConfirmationUuidBlocklist |
blocklisted_entries | Full merged entries whose uuid is blocklisted |
active_entries | Merged admin + on-chain (admin wins on same URL), excluding blocklisted UUIDs — these are the endpoints receiving scheduler updates (and TPU when enable_tpu_p2c_update is true) |
(echo '{"jsonrpc":"2.0","id":1,"method":"getPostPackConfirmationConfig","params":[]}'; sleep 1) \ | socat - UNIX-CONNECT:admin.rpc | jq
Example response:
{ "admin_entries": [ {"url":"http://127.0.0.1:20000","uuid":"PostPackConfig2","enable_tpu_p2c_update":true}, {"url":"http://127.0.0.1:10000","uuid":"PostPackConfig1","enable_tpu_p2c_update":false} ], "onchain_entries": [], "blocklisted_uuids": ["PostPackConfig1"], "blocklisted_entries": [ {"url":"http://127.0.0.1:10000","uuid":"PostPackConfig1","enable_tpu_p2c_update":false} ], "active_entries": [ {"url":"http://127.0.0.1:20000","uuid":"PostPackConfig2","enable_tpu_p2c_update":true} ] }
4.2. setPostPackConfirmationUuidBlocklist
Blocklists post-pack confirmation endpoints by UUID. Each call replaces the full blocklist. Pass an empty array to clear.
Blocklisted UUIDs are removed from active_entries on the next scheduler config sync. If a blocklisted endpoint already has an open gRPC connection, it is torn down immediately on sync; other endpoints stay connected.
Example — block one endpoint by UUID:
(echo '{"jsonrpc":"2.0","id":1,"method":"setPostPackConfirmationUuidBlocklist","params":[["PostPackConfig1"]]}'; sleep 1) \ | socat - UNIX-CONNECT:admin.rpc
Example — clear blocklist (reconnect blocklisted endpoints on next sync):
(echo '{"jsonrpc":"2.0","id":1,"method":"setPostPackConfirmationUuidBlocklist","params":[[]]}'; sleep 1) \ | socat - UNIX-CONNECT:admin.rpc
setPostPackConfirmationUuidBlocklist takes one parameter: an array of UUIDs. To clear the blocklist you must pass an empty array inside the parameter list — params:[[]]. Writing params:[] omits the parameter entirely and the blocklist is not cleared.
Related
- Tips — tip for reply bundles and virtual priority
- PSA · MCA
- Setup guide — Validator gRPC and discovery