Using P2C
This is the integration page for the post-pack stream: how to receive it, and how to act on it. Once your PSA is funded, the validator connects out to your gRPC server and pushes each scheduled transaction to you at the point of no return. Your job is to decode it, decide whether there is an opportunity, and reply with a bundle before the block is public.
Three separate payments are in play and none substitutes for another: the PSA pays for the stream, the MCA shares the profit you make from it, and your reply bundle still needs a tip to be scheduled.
This page covers the Relayer gRPC services your server must expose, the packet layout and how to decode it, how to construct a valid reply bundle, and the validator Admin RPC for inspecting and blocklisting post-pack endpoints.
Audience: Searchers and TIN partners consuming the stream; validator operators managing endpoints.
1. Setup: Required gRPC services
To enable post-pack confirmations, run a gRPC server and share that URL with the Rakurai team on Slack or Telegram. Keep the matching PSA funded, or the stream stops.
Sample gRPC endpoint to share:
https://sample-server.com:20000
The validator connects to your URL. You listen; the validator does not open a port for you. Bind the server so validators can reach it (public IP or resolvable hostname). http:// and https:// are both accepted.
Required services (protos: auth.proto, block_engine.proto):
| Service | What the validator uses |
|---|---|
auth.AuthService | Role RELAYER (challenge signed by the validator identity, then a bearer token) |
block_engine.BlockEngineRelayer | StartExpiringPacketStream — validator streams PacketBatchUpdate to you |
If the URL is missing Relayer auth or StartExpiringPacketStream, P2C never starts.
1.1. Reusing your block-engine URL
Partners often register one URL for both bundles (your server → validator) and P2C (validator → your server). That is supported, but the single server must then expose the Relayer role as well as the Validator role, or P2C silently never starts while bundles keep working.
The full three-service table and the failure mode are documented once, on the setup page: Setup guide — same URL for bundles and P2C. You may also register a separate P2C URL that only implements Relayer + BlockEngineRelayer.
Once your endpoint is added, you receive transactions as PacketBatch (solana_perf::packet::PacketBatch) over the Jito packet gRPC protocol.
Rakurai will add partners' gRPC endpoints on-chain so you can receive updates from Rakurai nodes that have opted in. Using post-pack has two money paths — PSA first, then MCA.
2. Transaction / packet structure
For each transaction, the validator sends one PacketBatchUpdate with msg = batches:
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 = 0
2.1. meta fields
Meta and PacketFlags are the Jito 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
0.001 SOL as tip in your backrun bundle, — see Tips. Bundles that use post-pack confirmations receive an additional priority boost.
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 on Slack or Telegram; see 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 |
blocklisted_uuids | Endpoint UUIDs blocked via setPostPackConfirmationUuidBlocklist |
blocklisted_entries | Full merged entries whose uuid is blocklisted (url + uuid) |
active_entries | Merged admin + on-chain (admin wins on same URL), excluding blocklisted UUIDs — these are the endpoints receiving scheduler updates |
(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"}, {"url":"http://127.0.0.1:10000","uuid":"PostPackConfig1"} ], "onchain_entries": [], "blocklisted_uuids": ["PostPackConfig1"], "blocklisted_entries": [ {"url":"http://127.0.0.1:10000","uuid":"PostPackConfig1"} ], "active_entries": [ {"url":"http://127.0.0.1:20000","uuid":"PostPackConfig2"} ] }
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