Skip to main content

surfpool_types/
jito_bundles.rs

1//! Wire types for the Jito `simulateBundle` JSON-RPC method.
2//!
3//! These mirror the schema implemented in jito-foundation/jito-solana
4//! (`rpc-client-api/src/bundles.rs` upstream). They are vendored here rather
5//! than imported from `solana-rpc-client-api` because the Jito-specific bundle
6//! types only exist in Jito's fork of the Solana monorepo, not in mainline
7//! `solana-rpc-client-api`. Keeping the wire format byte-identical to Jito's
8//! reference implementation lets clients written against Jito's spec target
9//! Surfpool with no JSON-shape adjustments.
10//!
11//! `RpcSimulateBundleResult` and friends are the public surface; the rest are
12//! sub-shapes referenced from it.
13
14use serde::{Deserialize, Serialize};
15use solana_account_decoder_client_types::{UiAccount, UiAccountEncoding};
16use solana_clock::Slot;
17use solana_commitment_config::{CommitmentConfig, CommitmentLevel};
18use solana_rpc_client_api::response::RpcBlockhash;
19use solana_signature::Signature;
20use solana_transaction_error::TransactionError;
21use solana_transaction_status::{
22    UiLoadedAddresses, UiTransactionEncoding, UiTransactionReturnData, UiTransactionTokenBalance,
23};
24use thiserror::Error;
25
26/// Request payload for `simulateBundle`. Carries the encoded transactions; the
27/// per-tx pre/post-account hints + flags live in the optional config below.
28#[derive(Debug, Default, Clone, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct RpcBundleRequest {
31    pub encoded_transactions: Vec<String>,
32}
33
34/// Per-tx account-fetch hint. Mirrors `solana_rpc_client_api::config::
35/// RpcSimulateTransactionAccountsConfig` (which `simulateTransaction` already
36/// uses) — vendored to keep the bundles module self-contained.
37#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
38#[serde(rename_all = "camelCase")]
39pub struct RpcSimulateTransactionAccountsConfig {
40    /// Optional output encoding for the returned account data. Only
41    /// `UiAccountEncoding::Base64` is supported by `simulateBundle`.
42    pub encoding: Option<UiAccountEncoding>,
43    /// Pubkeys whose state to return alongside the simulation result.
44    pub addresses: Vec<String>,
45}
46
47#[derive(Debug, Default, Clone, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct RpcSimulateBundleConfig {
50    /// Per-tx pre-execution account snapshot hints. When provided MUST have
51    /// the same length as `RpcBundleRequest.encoded_transactions`. Omitting
52    /// the field (or sending an empty array) is allowed — the server treats
53    /// it as "no snapshots requested for any tx", equivalent to sending
54    /// `vec![None; bundle_len]`. Mismatched non-empty lengths are rejected
55    /// with `invalid_params`.
56    #[serde(default)]
57    pub pre_execution_accounts_configs: Vec<Option<RpcSimulateTransactionAccountsConfig>>,
58    /// Per-tx post-execution account snapshot hints. Same shape and
59    /// "omitted = no snapshots" rules as `pre_execution_accounts_configs`.
60    #[serde(default)]
61    pub post_execution_accounts_configs: Vec<Option<RpcSimulateTransactionAccountsConfig>>,
62    /// Encoding the transactions are submitted in. Only `Base64` is supported
63    /// — the server rejects any other value with `invalid_params`. Matches
64    /// Jito's reference simulateBundle, which also enforces base64 only.
65    pub transaction_encoding: Option<UiTransactionEncoding>,
66    /// Which bank to simulate against. Surfpool always treats this as
67    /// `Tip`-equivalent (the working SVM); accepted for API compatibility.
68    pub simulation_bank: Option<SimulationSlotConfig>,
69    /// Skip signature verification. Required when `replace_recent_blockhash`
70    /// is true (the resigned blockhash invalidates any pre-existing sig).
71    #[serde(default)]
72    pub skip_sig_verify: bool,
73    /// Replace each tx's recent blockhash with the bank's current latest
74    /// blockhash. Useful for replaying historical transactions.
75    #[serde(default)]
76    pub replace_recent_blockhash: bool,
77}
78
79#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
80#[serde(rename_all = "camelCase")]
81pub enum SimulationSlotConfig {
82    Commitment(CommitmentConfig),
83    Slot(Slot),
84    Tip,
85}
86
87impl Default for SimulationSlotConfig {
88    fn default() -> Self {
89        Self::Commitment(CommitmentConfig {
90            commitment: CommitmentLevel::Confirmed,
91        })
92    }
93}
94
95#[derive(Serialize, Deserialize, Clone, Debug)]
96#[serde(rename_all = "camelCase")]
97pub struct RpcSimulateBundleResult {
98    pub summary: RpcBundleSimulationSummary,
99    pub transaction_results: Vec<RpcSimulateBundleTransactionResult>,
100}
101
102#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
103#[serde(rename_all = "camelCase")]
104pub enum RpcBundleSimulationSummary {
105    /// At least one tx in the bundle errored. `error` carries the typed cause
106    /// (with the offending signature if known) and `tx_signature` is the
107    /// signature of the first failing tx.
108    Failed {
109        error: RpcBundleExecutionError,
110        tx_signature: Option<String>,
111    },
112    /// Every tx in the bundle simulated cleanly.
113    Succeeded,
114}
115
116#[derive(Error, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
117pub enum RpcBundleExecutionError {
118    #[error("The bank has hit the max allotted time for processing transactions")]
119    BankProcessingTimeLimitReached,
120    #[error("Error locking bundle because a transaction is malformed")]
121    BundleLockError,
122    #[error("Bundle execution timed out")]
123    BundleExecutionTimeout,
124    #[error("The bundle exceeds the cost model")]
125    ExceedsCostModel,
126    #[error("Invalid pre or post accounts")]
127    InvalidPreOrPostAccounts,
128    #[error("PoH record error: {0}")]
129    PohRecordError(String),
130    #[error("Tip payment error: {0}")]
131    TipError(String),
132    #[error("A transaction in the bundle failed to execute: [signature={0}, error={1}]")]
133    TransactionFailure(Signature, String),
134}
135
136/// Per-transaction simulation outcome inside a bundle. Matches the wire shape
137/// Jito-Solana returns from `simulateBundle`. Fields are `Option` because not
138/// every backend populates every enrichment. Surfpool currently populates
139/// `err`, `logs`, `pre/post_execution_accounts`, `units_consumed`, and
140/// `replacement_blockhash`. The remaining fields — `return_data`, `fee`,
141/// `pre/post_balances`, `pre/post_token_balances`, `loaded_addresses`,
142/// `loaded_accounts_data_size` — are uniformly `None` from this backend.
143/// This is the same gap that already exists on the single-tx
144/// `simulateTransaction` path's bundle-only fields; closing it requires
145/// piping richer metadata through `ProfileResult` and is tracked for a
146/// follow-up PR. Wire-format clients should treat `None` as "not provided
147/// by this server" rather than "field unsupported".
148#[derive(Serialize, Deserialize, Clone, Debug)]
149#[serde(rename_all = "camelCase")]
150pub struct RpcSimulateBundleTransactionResult {
151    pub err: Option<TransactionError>,
152    pub logs: Option<Vec<String>>,
153    pub pre_execution_accounts: Option<Vec<UiAccount>>,
154    pub post_execution_accounts: Option<Vec<UiAccount>>,
155    pub units_consumed: Option<u64>,
156    pub loaded_accounts_data_size: Option<u32>,
157    pub return_data: Option<UiTransactionReturnData>,
158    pub replacement_blockhash: Option<RpcBlockhash>,
159    pub fee: Option<u64>,
160    pub pre_balances: Option<Vec<u64>>,
161    pub post_balances: Option<Vec<u64>>,
162    pub pre_token_balances: Option<Vec<UiTransactionTokenBalance>>,
163    pub post_token_balances: Option<Vec<UiTransactionTokenBalance>>,
164    pub loaded_addresses: Option<UiLoadedAddresses>,
165}