SearchersProtocol

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.