Protocol
The searcher.proto definition and the format of the transaction bytes.
Generate a client for your language from searcher.proto. The node has no gRPC reflection, so tools like grpcurl need this file.
searcher.proto
syntax = "proto3";
package inertia.searcher.v1;
// Every call authenticates with the API key in the `x-api-key` metadata.
service Searcher {
// Streams the transactions and bundles on their way to the validators, batched. Requires orderflow access.
rpc SubscribeOrderflow(SubscribeOrderflowRequest) returns (stream Bundles);
// Bids on streamed bundles and streams back what happened to every bid. Requires intake access.
rpc SubmitBundles(stream Bundles) returns (stream BundleUpdate);
}
message SubscribeOrderflowRequest {
// 1 to 1024 32-byte pubkeys, static or loaded from a lookup table. A bundle matches if any of its transactions
// touches one of them.
repeated bytes accounts = 1;
}
// Streamed: the signature of the first transaction and all transactions of the bundle, one for a plain transaction.
// Submitted: the signature of the streamed bundle bid on and only the searcher's own transactions, which have to
// transfer the tip to one of the tip accounts. Both together may not exceed 5 transactions.
message Bundle {
bytes signature = 1;
repeated bytes transactions = 2;
}
message Bundles {
repeated Bundle bundles = 1;
}
message BundleUpdate {
// Signature of the streamed bundle the bid is on.
bytes signature = 1;
// Signature of the first transaction of the bid.
bytes bid_signature = 2;
oneof update {
Outbid outbid = 3;
Rejected rejected = 4;
Sent sent = 5;
}
}
// Another bid tips more, or the bid did not tip more than the winning one when it arrived.
message Outbid {
uint64 tip = 1;
}
enum Reason {
REASON_UNSPECIFIED = 0;
// The signature is not held for bidding, it is unknown or its auction is closed.
NOT_HELD = 1;
// No transactions, or more than 5 together with the streamed bundle.
BUNDLE_SIZE = 2;
INVALID = 3;
UNVERIFIED = 4;
// A transaction was already seen on its way to the validators. Bids carry the searcher's own transactions only.
DUPLICATE = 5;
// No transfer to a tip account.
NO_TIP = 6;
// A transaction failed a validator rule, named in the detail.
REJECTED = 7;
// The bid queue was full.
DROPPED = 8;
// The bid won but no validator took the bundle.
UNDELIVERABLE = 9;
}
message Rejected {
Reason reason = 1;
string detail = 2;
}
// The bid won and its bundle was sent to the validator.
message Sent {
uint64 tip = 1;
}
Transaction format
Every entry in transactions is one serialized Solana VersionedTransaction. These are the bytes you base64-encode for the sendTransaction RPC call. The message can be legacy, v0 or v1.
In Rust, bincode parses the bytes into solana_transaction::versioned::VersionedTransaction, and serializes your bid transactions the same way:
use solana_transaction::versioned::VersionedTransaction;
let transaction: VersionedTransaction = bincode::deserialize(&bytes)?;
let bytes = bincode::serialize(&transaction)?;
This works with bincode 1 and solana-transaction 4.3 with the serde feature, which parses all three message formats.