Skip to main content
MEV transaction bundles are atomic collections of transactions submitted to block builders via relays like Flashbots. All transactions in a bundle execute sequentially in the same block, or the entire bundle is discarded.

Overview

Bundle provides type-safe construction and manipulation of MEV bundles for strategies like arbitrage, liquidations, and backrunning. Bundles guarantee atomic execution - either all transactions succeed in order, or none are included.
type BundleType = {
  // Ordered array of signed transaction bytes
  readonly transactions: readonly Uint8Array[];

  // Target block number (optional)
  readonly blockNumber?: BlockNumberType;

  // Minimum block timestamp (optional)
  readonly minTimestamp?: Uint256Type;

  // Maximum block timestamp (optional)
  readonly maxTimestamp?: Uint256Type;

  // Transaction hashes allowed to revert (optional)
  readonly revertingTxHashes?: readonly HashType[];
};

Quick Start

import * as Bundle from '@tevm/voltaire/Bundle';

// Create from signed transactions
const bundle = Bundle.from({
  transactions: [signedTx1, signedTx2],
  blockNumber: 18500000n,
});

// From hex strings
const bundleFromHex = Bundle.from({
  transactions: [
    "0x02f8...", // EIP-1559 tx
    "0x02f8...", // Another tx
  ],
  blockNumber: 18500000n,
});

API Reference

Constructors

MethodDescription
Bundle.from(value)Create bundle from transactions and options

Methods

MethodDescription
addTransaction(bundle, tx)Add a transaction to the bundle (immutable)
size(bundle)Get number of transactions in bundle
toHash(bundle, { keccak256 })Compute bundle hash
toFlashbotsParams(bundle)Convert to Flashbots RPC format

Bundle Options

PropertyTypeDescription
transactions(Uint8Array | string)[]Signed transactions in execution order
blockNumberbigintTarget block for inclusion
minTimestampbigintEarliest valid block timestamp
maxTimestampbigintLatest valid block timestamp
revertingTxHashesHash[]Tx hashes allowed to revert

Practical Examples

Sandwich Attack Bundle

import * as Bundle from '@tevm/voltaire/Bundle';

// Sandwich structure: frontrun -> victim -> backrun
const sandwichBundle = Bundle.from({
  transactions: [
    frontrunTx,  // Buy before victim
    victimTx,    // Target transaction
    backrunTx,   // Sell after victim
  ],
  blockNumber: targetBlock,
});

const params = Bundle.toFlashbotsParams(sandwichBundle);

Arbitrage with Revert Protection

import * as Bundle from '@tevm/voltaire/Bundle';
import { keccak256 } from '@tevm/voltaire/Keccak256';

// Allow first tx to revert (price check might fail)
const arbBundle = Bundle.from({
  transactions: [
    priceCheckTx,  // May revert if arb opportunity gone
    swapTx1,       // DEX A -> DEX B
    swapTx2,       // DEX B -> DEX A
  ],
  blockNumber: 18500000n,
  revertingTxHashes: [
    "0x..." + keccak256(priceCheckTx), // Allow this to revert
  ],
});

Time-Bounded Bundle

import * as Bundle from '@tevm/voltaire/Bundle';

// Bundle valid only within 12-second window
const timedBundle = Bundle.from({
  transactions: [tx1, tx2],
  blockNumber: 18500000n,
  minTimestamp: 1700000000n,
  maxTimestamp: 1700000012n, // 12 second window
});

Computing Bundle Hash

import * as Bundle from '@tevm/voltaire/Bundle';
import { keccak256 } from '@tevm/voltaire/Keccak256';

const bundle = Bundle.from({
  transactions: [tx1, tx2, tx3],
});

// Bundle hash is keccak256(concat(tx1Hash, tx2Hash, tx3Hash))
const bundleHash = Bundle.toHash(bundle, { keccak256 });
console.log("Bundle hash:", bundleHash);

Building Bundle Incrementally

import * as Bundle from '@tevm/voltaire/Bundle';

// Start with base transaction
let bundle = Bundle.from({
  transactions: [baseTx],
  blockNumber: 18500000n,
});

// Add transactions based on conditions
if (needsFrontrun) {
  bundle = Bundle.addTransaction(bundle, frontrunTx);
}

bundle = Bundle.addTransaction(bundle, mainTx);

if (needsBackrun) {
  bundle = Bundle.addTransaction(bundle, backrunTx);
}

console.log(`Bundle contains ${Bundle.size(bundle)} transactions`);

Error Handling

Bundle provides typed errors that extend the Voltaire error hierarchy:
import * as Bundle from '@tevm/voltaire/Bundle';
import {
  InvalidBundleError,
  MissingCryptoDependencyError
} from '@tevm/voltaire/Bundle';

try {
  // Empty bundle throws
  Bundle.from({ transactions: [] });
} catch (e) {
  if (e instanceof InvalidBundleError) {
    console.log(e.name);     // "InvalidBundleError"
    console.log(e.code);     // "INVALID_BUNDLE"
    console.log(e.message);  // "Bundle must contain at least one transaction"
    console.log(e.value);    // The invalid value
    console.log(e.expected); // What was expected
  }
}

try {
  // Invalid transaction format
  Bundle.from({
    transactions: [123], // Not Uint8Array or string
  });
} catch (e) {
  if (e instanceof InvalidBundleError) {
    console.log(e.name);    // "InvalidBundleError"
    console.log(e.message); // "Transaction must be Uint8Array or hex string"
  }
}

try {
  // Missing crypto dependency
  Bundle.toHash(bundle, {}); // Missing keccak256
} catch (e) {
  if (e instanceof MissingCryptoDependencyError) {
    console.log(e.name);     // "MissingCryptoDependencyError"
    console.log(e.code);     // "MISSING_CRYPTO_DEPENDENCY"
    console.log(e.message);  // "keccak256 not provided"
    console.log(e.expected); // "{ keccak256: (data: Uint8Array) => Uint8Array }"
  }
}

Error Types

ErrorExtendsWhen Thrown
InvalidBundleErrorValidationErrorInvalid bundle format or transaction data
MissingCryptoDependencyErrorValidationErrorRequired crypto function not provided

Flashbots RPC Format

The toFlashbotsParams method converts bundles to the format expected by Flashbots relays:
// Input bundle
const bundle = Bundle.from({
  transactions: [tx1, tx2],
  blockNumber: 18500000n,
  minTimestamp: 1700000000n,
  maxTimestamp: 1700000012n,
  revertingTxHashes: [hash1],
});

// Output format
const params = Bundle.toFlashbotsParams(bundle);
// {
//   txs: ["0x02f8...", "0x02f8..."],
//   blockNumber: "0x11a5b20",
//   minTimestamp: 1700000000,
//   maxTimestamp: 1700000012,
//   revertingTxHashes: ["0x..."]
// }