HadesDocs Partner panel

Bundles

A bundle is an ordered list of signed transactions that land together, in the order given, in one block, or not at all.

Access

The partner panel shows your key, your bundle limit and the bundles you sent, and resets the key.

Send the key in the Authorization header of every request:

Authorization: Bearer <your key>

Without a valid key the bundle methods answer as if they did not exist (-32601).

eth_sendBundle

eth_sendMevBundle is the same method under another name. Both are served by every RPC endpoint.

curl -s https://rpc.hades.build \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $HADES_KEY" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "eth_sendBundle",
    "params": [{
      "txs": ["0x02f8...", "0x02f8..."],
      "maxBlockNumber": <current block + 10>,
      "revertingTxHashes": []
    }]
  }'
{"jsonrpc":"2.0","id":1,"result":"0x8a3b...41c0"}

Replace <current block + 10> with a block number, or omit maxBlockNumber for current block + 100.

Parameters

One object:

Field Type Required Description
txs array of hex strings yes Signed raw transactions, in execution order. At most 2 by default, higher on request.
maxBlockNumber number no Last block the bundle may land in, as a plain JSON number, not hex. Must be after the current block and at most 1,000 blocks ahead. Defaults to the current block + 100.
revertingTxHashes array of hashes no Hashes of transactions in txs that may revert. A revert of any other transaction drops the bundle.

Result

The bundle hash: keccak256 of the transaction hashes, in order, followed by maxBlockNumber (or its default) as 8 big-endian bytes. The same transactions sent with another maxBlockNumber are another bundle.

Limits

Rule Value
Transactions per bundle 2 by default, higher on request
Average gas price at least 0.05 gwei
Size 1 MiB of raw transactions
Gas the sum of the gas limits is at most the block gas limit

The average gas price is sum(gasUsed × effectiveGasPrice) / sum(gasUsed) over the bundle's transactions, and exactly 0.05 gwei passes. A single transaction may pay no tip as long as the bundle as a whole meets the floor.

Each transaction must pass the usual transaction pool checks (signature, chain id, intrinsic gas, size up to 128 KiB, a nonce not below the sender's current one). Blob transactions are not accepted.

Errors

Refusals are JSON-RPC errors with code -38000 and a message naming the cause:

Message Cause
bundle has no transactions txs is empty
bundle has more than N transactions more transactions than your limit
bundle gas exceeds the block gas limit the gas limits add up to more than a block
bundle target block is not after the head maxBlockNumber has passed
bundle target block is more than 1000 blocks ahead maxBlockNumber is too far
revertingTxHashes holds a hash not in txs an entry of revertingTxHashes is not one of txs
bundle already exist the same bundle was already sent
too many pending bundles for this partner too many of your bundles are still pending; wait for them to land or expire
bundle average gas price is below 0.05 gwei the bundle pays under the floor
tx N reverted and is not in revertingTxHashes transaction N reverted without being allowed to
tx N: ... transaction N failed to decode or validate
bundle simulation failed: ... the bundle could not be simulated
bundle pool is full temporary: send again later
bundle simulation unavailable temporary: send again later
bundle simulation interrupted by an interpreter fallback, retry temporary: send again

A string in txs that is not 0x-prefixed or has an odd length, or a hex maxBlockNumber, is -32602 invalid argument 0: .... Valid hex that is not a transaction is -38000 tx N: decode: ....

Examples

Each example sends two signed transactions, tx1 and tx2, as one bundle with the key in the HADES_KEY environment variable.

Go

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	tx1, tx2 := "0x02f8...", "0x02f8..." // signed raw transactions
	body, _ := json.Marshal(map[string]any{
		"jsonrpc": "2.0",
		"id":      1,
		"method":  "eth_sendBundle",
		"params": []any{map[string]any{
			"txs": []string{tx1, tx2}, // no maxBlockNumber: current block + 100
		}},
	})
	req, _ := http.NewRequest("POST", "https://rpc.hades.build", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+os.Getenv("HADES_KEY"))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	var out struct {
		Result string          `json:"result"`
		Error  json.RawMessage `json:"error"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		panic(err)
	}
	fmt.Println(out.Result, string(out.Error))
}

Rust

[dependencies]
reqwest = { version = "0.12", features = ["blocking", "json"] }
serde_json = "1"
use serde_json::{json, Value};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (tx1, tx2) = ("0x02f8...", "0x02f8..."); // signed raw transactions
    let body = json!({
        "jsonrpc": "2.0",
        "id": 1,
        "method": "eth_sendBundle",
        "params": [{ "txs": [tx1, tx2] }] // no maxBlockNumber: current block + 100
    });
    let out: Value = reqwest::blocking::Client::new()
        .post("https://rpc.hades.build")
        .bearer_auth(std::env::var("HADES_KEY")?)
        .json(&body)
        .send()?
        .json()?;
    println!("{out}");
    Ok(())
}

Python

import os
import requests

tx1, tx2 = "0x02f8...", "0x02f8..."  # signed raw transactions
resp = requests.post(
    "https://rpc.hades.build",
    headers={"Authorization": f"Bearer {os.environ['HADES_KEY']}"},
    json={
        "jsonrpc": "2.0",
        "id": 1,
        "method": "eth_sendBundle",
        "params": [{"txs": [tx1, tx2]}],  # no maxBlockNumber: current block + 100
    },
    timeout=10,
)
print(resp.json())

JavaScript

Save as bundle.mjs and run with node bundle.mjs (Node.js 18 or later; an ES module, for the top-level await). Server side only: keep the key out of browsers.

const [tx1, tx2] = ["0x02f8...", "0x02f8..."]; // signed raw transactions

const resp = await fetch("https://rpc.hades.build", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.HADES_KEY}`,
  },
  body: JSON.stringify({
    jsonrpc: "2.0",
    id: 1,
    method: "eth_sendBundle",
    params: [{ txs: [tx1, tx2] }], // no maxBlockNumber: current block + 100
  }),
});
console.log(await resp.json());