# General

#### Endpoint

[https://rpc.titanbuilder.xyz](https://rpc.titanbuilder.xyz/)

This URL uses AWS geo-routing to find the best RPC to send to. We have found this can sometimes route requests to the wrong region, so we have also added specific geo-location URLs. We reccomend you use these.

* **Europe:** [eu.rpc.titanbuilder.xyz](https://eu.rpc.titanbuilder.xyz)
* **United States:** [us.rpc.titanbuilder.xyz](https://us.rpc.titanbuilder.xyz)
* **Asia:** [ap.rpc.titanbuilder.xyz](https://ap.rpc.titanbuilder.xyz)

#### Testnet

<https://rpc-hoodi.titanbuilder.xyz/>

#### Block Building Algorithm

Our builder runs multiple sorting algorithms concurrently that compete against each other to produce the most valuable block possible.

Sorting of bundles and transactions is not necessarily ranked by effective gas price, and as such top-of-block execution is not guaranteed. Based on how transactions interact with each other and the current state, including transactions with lower effective gas prices before higher paying ones may yield more valuable blocks, which is what our algorithms optimise for.

We will never unbundle a bundle, and will never broadcast any bundles or private transactions to the public mempool.

#### Priority Queues

We often get asked about priority queues. We used to have these but have since removed them as we now get through all bundles without needing one. There is no priority system.

#### Coinbase

**Address:** [titanbuilder.eth](https://etherscan.io/address/0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97) \[`0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97`]


# API

In this section you will find the list of all our currently supported API commands, including CURL and response examples.

**Rate limit: 50 requests/sec**&#x20;


# eth\_sendBundle

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_sendBundle",
  "params": [
    {
      txs,                   // Array[String], A list of signed transactions to execute in an atomic bundle, list can be empty for bundle cancellations
      blockNumber,           // (Optional) String, a hex-encoded block number for which this bundle is valid. Default, current block number
      revertingTxHashes,     // (Optional) Array[String], A list of tx hashes that are allowed to revert or be discarded
      droppingTxHashes,      // (Optional) Array[String], A list of tx hashes that are allowed to be discarded, but may not revert
      replacementUuid,       // (Optional) String, 16 byte string that can be used to replace or cancel this bundle
      refundPercent,         // (Optional) Number, the percentage (from 0 to 99) of the  ETH reward of the last transaction, or the transaction specified by refundIndex, that should be refunded back to the ‘refundRecipient’
      refundRecipient,       // (Optional) Address, the address that will receive the ETH refund. Default, sender of the first transaction in the bundle
      replacementSeqNumber,  // (Optional) Number, monotonically increasing sequence for bundles sharing the same replacementUuid. Later bundles must have a higher sequence or they are dropped. If 0 or omitted, ordering falls back to builder receive time
      minTimestamp,          // (Optional) Number, the minimum slot timestamp for which this bundle is valid, in seconds since the unix epoch 
    }
  ]
}
```

**N.B.:** If the `refundPercent` field is set, the builder will construct a refund transaction automatically. However, if the refund amount does not cover the cost of the transaction (i.e., `gas_used * base_fee`), the bundle will be discarded.\
‍

### **Refund Example**

**Consider the following Bundle:**

* TXN 1 - User swap (Base fee: 50 Gwei, Piority fee 3 Gwei, Gas: 280k)
* TXN 2 - Backrun (Base fee: 50 Gwei, Priority fee 100 Gwei, Gas: 150k)
* `refundPercent`: 90%
* Current block base fee: 50 Gwei

**Calculation:**

* ETH reward of the last transaction in the bundle = (150k x 100 Gwei) = 15,000 Gwei
* ETH reward after transfer transaction fees = 15000 - (21k x 50 Gwei) = 13,950 Gwei
* Refund amount = 0.9 x 13,950 Gwei = 12,555 Gwei‍

### **Sponsored Bundles**

Our builder supports Sponsored Bundles. If we receive a bundle that fails with `LackOfFundForGasLimit` error, we will automatically send the ETH required to cover the gas fees and value transfer for the transaction to succeed.

The caveat here is that the bundle must of course increase the builder balance, as we will need to recoup this sponsoring cost with the bundle’s execution.

For further details, see our [substack article](https://titanbuilder.substack.com/p/titan-tech-teatime-1).

## **CURL example**

```json
curl -s --data '{"jsonrpc": "2.0","id": "1","method": "eth_sendBundle","params": [{"txs": ["0x12…ab","0x34..cd"], "blockNumber": "0x102286B","replacementUuid": "abcd1234"}]}' -H "Content-Type: application/json" -X POST https://rpc.titanbuilder.xyz
```

## **Response example**

```javascript
{"result":{"bundleHash":"0x164d7d41f24b7f333af3b4a70b690cf93f636227165ea2b699fbb7eed09c46c7"},"error":null,"id":1}
```

## Bundle Hash

Here is the algorithm for determining the bundle hash:

```rust
#[derive(Hash, Serialize, Deserialize)]
pub struct RawBundle {
   #[serde(default)]
    pub block_number: U64,
    #[serde(default)]
    pub txs: Vec<Bytes>,
    pub reverting_tx_hashes: Option<Vec<String>>,
    pub dropping_tx_hashes: Option<Vec<String>>,
    pub replacement_uuid: Option<String>,
    pub refund_percent: Option<u64>,
    pub refund_recipient: Option<Address>,
    pub refund_tx_hashes: Option<Vec<String>>,
}

pub fn bundle_hash(bundle: &RawBundle) -> B256 {
    let mut hasher = wyhash::WyHash::default();
    let mut bytes = [0u8; 32];
    for i in 0..4 {
        bundle.hash(&mut hasher);
        let hash = hasher.finish();
        bytes[(i * 8)..((i + 1) * 8)].copy_from_slice(&hash.to_be_bytes());
    }

    B256::from(bytes)
}
```


# eth\_cancelBundle

```javascript
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_cancelBundle",
  "params": [
    {
      replacementUuid, // String, to uniquely identify submission
    }
  ]
}
```

**N.B.:** the `replacementUuid` must have been set when the bundle was submitted.

## **CURL example**

```javascript
curl -s --data '{"jsonrpc": "2.0","id": "1","method": "eth_cancelBundle", "params": [{"replacementUuid": "abcd1234"}]}' -H "Content-Type: application/json" -X POST https://rpc.titanbuilder.xyz
```

## **Response example**

```javascript
{"result":200,"error":null,"id":1}
```

**N.B.:** We cannot guarantee that the bundle will be canceled if the cancellation is submitted within 4 seconds of the final relay submission.


# eth\_sendRawTransaction

or eth\_sendPrivateRawTransaction

```javascript
{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "eth_sendRawTransaction”,
  "params": ["0x…4b"],  // Signed raw tx hex
}
```

## **CURL example**

```javascript
curl -s --data '{"jsonrpc": "2.0","id": "1","method": "eth_sendRawTransaction","params": ["0x…12"]}' -H "Content-Type: application/json" -X POST https://rpc.titanbuilder.xyz
```

## **Response example**

```javascript
{"result":200,"error":null,"id":1}
```


# eth\_sendPrivateTransaction

```js
{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "eth_sendPrivateTransaction",
  "params": [
    {
      tx,               // Signed raw tx hex
    }
  ]‍
```

## **CURL example**

```js
curl -s --data '{"jsonrpc": "2.0","id": "1","method": "eth_sendPrivateTransaction","params": [{"tx": "0x12…ab"}]}' -H "Content-Type: application/json" -X POST https://rpc.titanbuilder.xyz
```

## ‍**Response example**

```js
{"result":200,"error":null,"id":1}‍
```

Note: we do not support `maxBlockNumber`


# eth\_sendEndOfBlockBundle

This endpoint requires the X-Flashbots-Signature header to be included.

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_sendEndOfBlockBundle",
  "params": [
    {
      txs,                   // Array[String], A list of signed transactions to execute in an atomic bundle, list can be empty for bundle cancellations
      blockNumber,           // (Optional) String, a hex-encoded block number for which this bundle is valid. Default, current block number
      revertingTxHashes,     // (Optional) Array[String], A list of tx hashes that are allowed to revert or be discarded     
      targetPools,           // Array[String], A list of pool addresses that this bundle is targeting
      replacementUuid,       // (Optional) String, any arbitrary string that can be used to replace or cancel this bundle
      replacementSeqNumber,  // (Optional) Number, monotonically increasing sequence for bundles sharing the same replacementUuid. Later bundles must have a higher sequence or they are dropped. If 0 or omitted, ordering falls back to builder receive time.
    }
  ]
}
```

After building a block, any end-of-block bundle that contains a target pool that has been modified will be simulated at the end-of-block state. Successfully simulated bundles are then added to the block.\
\
Note: end-of-block bundles are handled with the same atomicity and revert guarantees as normal bundles.&#x20;


# eth\_sendBlobs

Originators no longer need to guess the number of blobs to send, ensuring optimal utilisation of the 6-blob limit per block.

```javascript
{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "eth_sendBlobs”,
  "params": [
    {
      txs,            // Array[String], A list of blob transactions. One transaction per blob permutation.
      maxBlockNumber, // (Optional) String, a hex-encoded string representing the block number of the last block in which the transactions should be included.
    }
}
```

### Background

Blob pools currently present a challenge to external blob originators, as they function somewhat like a black box. Typically, only a single transaction can be sent, necessitating a guess on the number of blobs to include. Here is an example of where this could be problematic:

*Originator A sends a transaction with 6 blobs, but there is a higher paying transaction with 1 blob already in the mempool. Due to the 6-blob per block limit, no blobs from Originator A will be posted.*

### Functionality

The `eth_sendBlobs` endpoint enables the sending of all permutations of blob transactions from a single sender. This is made possible because these blob transactions enter a custom blob pool that allows for multiple transactions with the same nonce. These blobs are then sorted individually to generate the optimal combination and added to our blocks.

## **Example**

If you have 6 blobs to post, you would send a transaction from the same sender with the same nonce containing different permutations of blobs. This means you would end up sending up to 6 transactions, each containing a different number of blobs:

```js
curl -s --data '{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "eth_sendBlobs",
  "params": [{
    "txs": [
      "0x12...ab1",  // Transaction with 1 blob
      "0x34...cd2",  // Transaction with 2 blobs
      "0x56...ef3",  // Transaction with 3 blobs
      "0x78...gh4",  // Transaction with 4 blobs
      "0x9a...ij5",  // Transaction with 5 blobs
      "0xbc...kl6"   // Transaction with 6 blobs
    ]
  }]
}' -H "Content-Type: application/json" -X POST https://rpc.titanbuilder.xyz
```

## ‍**Response example**

```js
{"result":200,"error":null,"id":1}‍
```


# PropAMMs

pAMM liquidity is now available in every Titan block.

### PropAMM Router

`0x4ddf368080cd7946db5b459ad591c350158175e1`&#x20;

More information on how to interact with it and all PropAMMs [here](https://github.com/lambdaclass/propamm-router-contracts).

### Currently live in Titan blocks:

<table><thead><tr><th width="146">Maker</th><th width="590">Contract</th></tr></thead><tbody><tr><td>Fermi</td><td><p>Oracle: <code>0x26e5A56f807d4C937B0b815266B135F09B4Bf312</code></p><p>Router: <code>0x5979458912F80B96d30D4220af8E2e4925A33320</code></p></td></tr><tr><td>Kipseli</td><td>Oracle: <code>0x5CDbE59400Cc2EFDCC2B54acca4a99FE00dD588c</code> <br>Router: <code>0x342b8458161137d0203605Fa51E4363c1445ADCD</code></td></tr><tr><td>Bebop</td><td>Oracle: <code>0xBC60639345dFa607d73b74e88C2d54D8B8AD7Cc3</code> <br>Router: <code>0xdb13ad0fcd134e9c48f2fdaea8f6751a0f5349ca</code></td></tr><tr><td>Metric</td><td>Oracle: <code>0x28d9CCEDf1B7ac9B3F090f4F0292837dE87c1D39</code></td></tr></tbody></table>

All PropAMMs are required to implement [this](https://github.com/lambdaclass/propamm-router-contracts/blob/main/src/interfaces/IPropAMM.sol) interface.

### Overview

A Proprietary AMM (PropAMM \[pAMM]) is an onchain pool in which prices are continuously updated by a market maker. Instead of relying on a passive inventory curve, makers stream signed quote updates to the builder ensuring prices are updated in real-time.

The pool is a passive Ethereum contract, enabling anyone to swap against it directly without an offchain layer. Once a maker update is included, taker interactions settle onchain like any other AMM interaction, and can be composed into normal swap paths, solver transactions, arbitrage bundles, and aggregator routes. This is the key difference to current Ethereum RFQ-style institutional liquidity: pAMM liquidity sits directly inside an onchain venue rather than behind a separate offchain layer.

Titan enables pAMMs on Ethereum through a mechanism that grants power over transaction sequencing to applications also known as Application-Controlled Execution (ACE). Makers stream quote updates directly to the builder and receive deterministic priority relating to their own pool updates. Takers submit bundles routing through pAMM liquidity, and the builder automatically sequences them against current pAMM state, re-simulating submissions on every quote update to ensure orders are filled at the latest available price.

### Why pAMMs

A large share of Ethereum DEX volume trades at prices that are stale (12 seconds old) relative to centralised markets. Whenever onchain prices update more slowly than what market makers deem fair value, a divergence is created showing up as value leakage for routers, solvers, end users, and atomic searchers routing through stale intermediary liquidity.

To quantify the impact we analysed 4.77M swaps across 25 ETH/BTC pools on Ethereum between Feb–Mar 2026. The results showed that 84% of router volume, 73% of non-cex-dex-contract volume, and 66% of atomic-searcher volume executed below Binance mid. Users swapping greater than $10k can expect negative markouts of \~6bps on average and double digit negative markouts for p25.

The value proposition of a pAMM lies in its ability to compete for flow with more competitive pricing. A maker can quote closer to the live market because the builder constantly re-evaluates the latest price updates and sequence taker flow against the latest applicable state, all before the block lands. For makers, this collapses the adverse-selection window that makes onchain quoting more costly on slower chains. For takers, this raises execution quality on the routes that consistently leak the most value.

The pAMM model has been validated on faster, lower-cost chains, such as Solana where pAMMs now make up the majority of major-pair spot volume. Ethereum's slot time and gas cost make a naive port of this approach unworkable as by the time the next taker arrives, the last onchain update is already stale.

Builder level ACE enables Ethereum to offer pAMM pricing without sacrificing guarantees around economic security, decentralization, liveness and censorship resistance.

### What Titan offers with ACE

Historically, If a maker posts a quote too early the price may be stale by the time the taker flow lands, allowing takers to benefit from any mispricing. Alternatively, if takers are forced to choose a route before the final block state is known they would have to route against a stale price, leaking value to makers.

Titan solves this at the builder layer:

* Makers stream low-latency quote updates to the builder
* Stale quote updates are replaced, so only the latest applicable quote is considered
* When a taker trades against a pAMM, the latest quote update is guaranteed to be ranked before it in the block
* Makers can configure freshness protection so takers are only eligible for inclusion against sufficiently recent quote state
* Takers can submit multiple candidate routes through pAMM and constant-function market makers (CFMM) liquidity
* The builder evaluates candidates against live state at build time and re-evaluates on every pAMM quote update right up until the point of inclusion

This gives pAMMs on Ethereum a new set of advantages. Rather than relying on public propagation and protocol level ordering behaviour, the maker gets an explicit builder-enforced priority rule around its own pool. That priority applies both to rank in the block and to the timing relationship between quote and taker receipts. For takers, the builder becomes the place where pAMM and CFMM pricing can be compared against the latest available state.&#x20;

The result is a more deterministic execution environment for active liquidity, that has the potential to offer tighter quotes than any CEX or propAMM chain. For this reason, we soon expect Ethereum to offer the industry’s most competitive prices on major pairs.

### Integration paths

There are three main integration surfaces:

1. Makers stream quote updates to Titan through the pAMM quote WebSocket. Quotes can land every block, or only when there is taker flow, depending on the maker session configuration.
2. Takers submit candidate bundles through the normal bundle path. A candidate can route through existing CFMM liquidity, pAMM liquidity, or both. The builder evaluates all candidates against the freshest available pAMM state.
3. Takers can also consume the pAMM state stream when makers opt in. The stream publishes flattened Ethereum state override objects, making it easier for searchers, solvers, and aggregators to simulate pAMM routes locally before submission.

The following pages describe the maker and taker interfaces in more detail.

<br>


# Makers

pAMM Quote WebSocket

> This endpoint is reserved for trading firms operating a prop AMM on Ethereum who want to land quote updates in Titan blocks. Access is API-key gated, please reach out if you want access.

### Behavioural properties

#### Maker priority

The builder provides two defences against being picked off on latency.

**Taker ordering.** When a taker trade against the pool is included in a block, the latest applicable quote update is guaranteed to be placed before it in the same block.

**Maker freshness protection.** A per-session freshness buffer `b`, set at onboarding, can be applied to takers routing against your pool. A taker is only eligible to trade against a quote update if the taker was received by the builder at least `b` before that quote update was received:

```
taker_recv + b < quote_update_recv
```

For example, with `b = 50ms`, a quote update received at time `T` can only be sequenced against takers received before `T - 50ms`.

This reduces exposure to toxic takers attempting to snipe stale quotes, while also protecting makers in case of quote streaming failures mid-slot.

#### Fast replacements and cancellations

Quotes sent through this endpoint take the fastest path to the builders. We have cores dedicated to simulating and applying these bundles efficiently. Cancellations are more consistent due to stricter block overwrite rules.

#### Conditional inclusion

Quote transactions can either land every block, or only when a taker in the block trades against the pool. The builder evaluates each candidate tx against every live quote and, in conditional mode, includes the latest quote update immediately before any taker - quotes that have no takers never go onchain.

### Authentication

Authentication uses an API key shared directly when access is granted. Specify it in the `Authorization` header when establishing the WebSocket connection.

### Endpoints

Use the following regional endpoints to get the lowest latency between your quote publisher and our clusters. Higher-performance URLs (direct access) are available on request.

| Region           | Endpoint                                           |
| ---------------- | -------------------------------------------------- |
| `eu-central-1`   | `wss://eu.rpc.titanbuilder.xyz/ws/sendquoteupdate` |
| `ap-northeast-1` | `wss://ap.rpc.titanbuilder.xyz/ws/sendquoteupdate` |
| `us-east-1`      | `wss://us.rpc.titanbuilder.xyz/ws/sendquoteupdate` |

### Submit quote (`ws/sendquoteupdate`)

```proto
message PWebsocketQuoteUpdateV1Args {
  // RLP-encoded quote-update tx bytes. Empty = cancel the replacement_uuid.
  bytes tx = 1;
  // single block this quote is valid for
  uint64 block_number = 2;
  // 16-byte quote identifier (raw UUID bytes).
  bytes replacement_uuid = 3;
  // monotonic per uuid (>0)
  uint64 replacement_seq_number = 4;
  // quotes will not be shared between builder regions
  bool disable_cross_region_sharing = 5;
  // Asset pairs for quoting: each element is 40 bytes (two 20-byte ERC20 token
  // addresses concatenated). Designates which pair prices are influenced by the update.
  repeated bytes asset_pairs = 6;
  // Quote address taker has to touch (20-byte address) in order to receive a quote. 
  bytes quote_address = 7; 
  // Pool addresses (20-byte address) for which this quote is needed if takers want to swap
  repeated bytes pool_addresses = 8;
}
```

`replacement_uuid` must be set and exactly 16 bytes. `replacement_seq_number` must be set and strictly greater than the previous value seen under the same `replacement_uuid` and cannot be 0. Messages violating either constraint are dropped.

We recommend setting `disable_cross_region_sharing` to `true`. Otherwise we broadcast your quotes internally across regions, which adds latency you do not control. For best results, run edge nodes next to each of our regional clusters and send directly to the closest.

### Cancel quotes

Send a `PWebsocketQuoteUpdateV1Args` with `tx` empty, `replacement_uuid` set to the quote you want to pull, and a strictly greater `replacement_seq_number` than the last update under that uuid.

### Response

```proto
message PWebsocketQuoteUpdateV1Response {
  // Quote identifier being acknowledged.
  bytes replacement_uuid = 1;
  uint64 replacement_seq_number = 2;
  // RPC receive UNIX nanos.
  uint64 timestamp = 3;
  // Empty on success. Populated with error detail on failure.
  string error = 4;
}
```

Match responses to submits on `(replacement_uuid, replacement_seq_number)`. Use `timestamp` minus your local send-time to measure end-to-end latency.

### Example (Rust)

```rust
// Generate the `pb` module from the .proto above using prost-build.

use std::io::ErrorKind;
use tungstenite::{client::IntoClientRequest, stream::MaybeTlsStream, Message as WsMessage};
use prost::Message;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut req = "wss://eu.rpc.titanbuilder.xyz/ws/sendquoteupdate".into_client_request()?;
    req.headers_mut().insert("Authorization", "YOUR_API_KEY".parse()?);

    let (mut ws, _) = tungstenite::connect(req)?;

    if let MaybeTlsStream::Plain(s) = ws.get_mut() {
        s.set_nodelay(true)?;
        s.set_nonblocking(true)?;
    }

    let mut buf = Vec::with_capacity(4096);
    let mut seq: u64 = 0;

    loop {
        seq += 1;
        let quote = pb::PWebsocketQuoteUpdateV1Args {
            tx: vec![/* RLP-encoded signed quote-update tx */],
            block_number: 1234,
            replacement_uuid: vec![/* 16 bytes */],
            replacement_seq_number: seq,
            disable_cross_region_sharing: true,
            asset_pairs: vec![/* 40-byte token0||token1 concatenations */],
        };

        buf.clear();
        quote.encode(&mut buf)?;

        match ws.send(WsMessage::Binary(buf.into())) {
            Ok(()) => {}
            Err(tungstenite::Error::Io(e)) if e.kind() == ErrorKind::WouldBlock => {}
            Err(e) => return Err(e.into()),
        }

        match ws.read() {
            Ok(WsMessage::Binary(bytes)) => {
                let _resp = pb::PWebsocketQuoteUpdateV1Response::decode(&*bytes)?;
                // latency = match on (replacement_uuid, replacement_seq_number)
            }
            Ok(_) => {}
            Err(tungstenite::Error::Io(e)) if e.kind() == ErrorKind::WouldBlock => {}
            Err(e) => return Err(e.into()),
        }
    }
}
```

### Pricing

Quote updates submitted through this endpoint do not pay priority fees.

Instead, makers are charged a volume-based fee on filled trades. Fees are configured on a per-pair basis. Pricing may differ across assets depending on market structure and quoting behaviour, though fees across makers are the same.

During the initial testing phase, fee discounts are active while we collect data on spreads, fill quality, and realised markouts across different markets. We expect to move to a more stable and publicly documented fee schedule once the system has been sufficiently tested and pricing can be calibrated from production data. Please reach out directly for more information on current pricing.

### Permissionless Access

We're also exploring opening these guarantees up permissionlessly. For example, we are exploring supporting this initiative: <https://github.com/flashbots/priority-update-registry>


# Takers

Routing Against pAMM Liquidity

> Audience: searchers, solvers, and aggregators routing flow or arbitrage paths through pAMM liquidity in Titan blocks.

### Multi-candidate bundle submission

Send us N candidate bundles for the same trade, each routing differently. At build time we evaluate every candidate against live state, including the freshest streamed quote from every pAMM, and include whichever gives the best outcome.

* Construct your normal path through CFMM liquidity.
* Construct additional variants where one or more hops route through pAMMs.
* Submit all variants. We evaluate each against current state at build time and re-evaluate on every pAMM quote update until inclusion.

Use the same bundle submission path you already use.

### pAMM state stream

Takers can consume a JSON stream of pAMM state overrides for makers that opt in to publishing their state. The stream is intended for searchers, solvers, and dex aggregators that want fresher routing inputs for pAMM liquidity before submitting candidate bundles.

The stream is maker opt-in. Makers choose whether to publish state, and configure the update thresholds and delay applied before state is emitted.

Some maker streams may require permissioned access. For example, a maker may only allow whitelisted takers such as dex aggregators to consume their stream. Please reach out if there are pAMM quotes you want access to that are not currently available.

### pAMM stream addresses

Current expected top-level stream addresses:

| Protocol  | Top-level stream address                     | Simulator call target                                        | Oracle touched in `state_override`           |
| --------- | -------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------- |
| FermiSwap | `0xb1076fe3ab5e28005c7c323bac5ac06a680d452e` | `quote(...)` on `0x5979458912F80B96d30D4220af8E2e4925A33320` | `0xb1076fe3ab5e28005c7c323bac5ac06a680d452e` |
| Kipseli   | `0x5cdbe59400cc2efdcc2b54acca4a99fe00dd588c` | `quote(...)` on `0x71e790dd841c8A9061487cb3E78C288E75cE0B3d` | `0x5cdbe59400cc2efdcc2b54acca4a99fe00dd588`  |
| bopAMM    | `0x160141a205f5ddcf096ba3f48b7ed21eb52c62ea` | `quote(...)` on `0xdb13ad0fcd134e9c48f2fdaea8f6751a0f5349ca` | `0xBC60639345dFa607d73b74e88C2d54D8B8AD7Cc3` |

The public no-auth stream currently exposes FermiSwap, Kipseli and bopAMM.

#### Endpoints

| Region         | WebSocket endpoint                                   | JSON-RPC endpoint                 |
| -------------- | ---------------------------------------------------- | --------------------------------- |
| eu-central-1   | `wss://eu.rpc.titanbuilder.xyz/ws/pamm_quote_stream` | `https://eu.rpc.titanbuilder.xyz` |
| ap-northeast-1 | `wss://ap.rpc.titanbuilder.xyz/ws/pamm_quote_stream` | `https://ap.rpc.titanbuilder.xyz` |
| us-east-1      | `wss://us.rpc.titanbuilder.xyz/ws/pamm_quote_stream` | `https://us.rpc.titanbuilder.xyz` |

#### Format

Each WebSocket message is JSON. Every other top-level key is a pAMM quote stream address. The value is an object with a `stateOverride` field.

```json
{
  "slot": 14285824,
  "blockNumber": 25051224,
  "timestamp": 1778253913749564761,
  "0xb1076fe3ab5e28005c7c323bac5ac06a680d452e": {
    "stateOverride": {
      "0x1038c87766e36d1925889e6f26d10e0012d50fed": {
        "balance": "0x0",
        "nonce": "0x1",
        "stateDiff": {
          "0x156b1d71de08fed89d0fce38008e2b9d03a8998077e394b20597ef3d148f5ebc": "0x000000000000000000000000000000000000000000000000000000017e405801"
        }
      }
    }
  }
}

```

`stateOverride` uses the same structure as the Ethereum eth\_call State Override Set.

The keys inside `stateOverride` are Ethereum account addresses. Each account override may include:

* balance&#x20;
* nonce&#x20;
* code&#x20;
* state&#x20;
* stateDiff

`stateDiff` maps storage slot keys to overridden storage values.

The object can be passed directly as the third parameter to `eth_call` or `eth_simulateV1`.

If a taker has access to multiple pAMM feeds, Titan merges all maker updates into a single flat `stateOverride` object keyed by account address.

#### Latest state over JSON-RPC

Takers that do not want to maintain a WebSocket connection can request the latest flattened pAMM state view over JSON-RPC.

Data API Endpoint [https://rpc.titanbuilder.xyz/data](https://rpc.titanbuilder.xyz/) (Regional endpoints are also supported)

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "titan_getPammStateOverrides",
  "params": []
}
```

Response:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "blockNumber": "0x16f3a10",
    ...
  }
}
```

#### Example (Python)

```python
import asyncio
import json
import urllib.request
import websockets

WS_URI = "wss://eu.rpc.titanbuilder.xyz/ws/pamm_quote_stream"
RPC_URL = "https://eu.rpc.titanbuilder.xyz/data"


def get_pamm_state_overrides():
    body = json.dumps({
        "jsonrpc": "2.0",
        "id": 1,
        "method": "titan_getPammStateOverrides",
        "params": [],
    }).encode()
    req = urllib.request.Request(
        RPC_URL, data=body, headers={"Content-Type": "application/json"}
    )
    with urllib.request.urlopen(req, timeout=10) as resp:
        return json.loads(resp.read())["result"]


async def main():
    # One-shot snapshot.
    snapshot = get_pamm_state_overrides()
    print(int(snapshot["blockNumber"], 16), snapshot["stateOverrides"])
    
    # Live updates.
    async with websockets.connect(WS_URI) as ws:
        async for message in ws:
            update = json.loads(message)
            block_number = int(update["block_number"])
            state_overrides = update["state_override"]
            print(block_number, state_overrides)


asyncio.run(main())
```

### pAMM price level

Takers can also consume a live pAMM price level stream. Unlike the state override stream, this stream is already quoted by Titan and grouped per pAMM.

Order book levels are available in two variants. `Simulated` quotes are derived from EVM simulations using synthesized taker transactions. The quoted sizes are distributed according to a geometric progression to provide coverage across a range of trade sizes.

To improve quote granularity, we also generate `Interpolated` levels between the simulated quotes. These intermediate levels are computed using linear spline interpolation, providing a more convenient set of quote sizes at the cost of a small approximation error.

Each message is a complete snapshot. Consumers should keep the newest message and treat older messages as superseded.

Endpoints are the same as pAMM state stream just use the `/ws/pamm_price_levels` path.

**Latest price levels over websocket**

Each WebSocket message is JSON. The pamms array contains one ladder per pAMM.

```json
{
  "slot": 14581462,
  "blockNumber": 25345763,
  "timestamp": 1781801564588230787,
  "pamms": [
    {
      "pamm": "0x5979458912f80b96d30d4220af8e2e4925a33320",
      "pairs": [
        {
          "tokenIn": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",
          "tokenOut": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
          "orderBook": [
            {
              "amountIn": "0x989680",
              "amountOut": "0x174b67393",
              "variant": "Simulated"
            },
            {
              "amountIn": "0xaa810a",
              "amountOut": "0x1a0781260",
              "variant": "Interpolated"
            },
            ...
          ],
        },
        ...
      ],
    },
    ...
  ]
}
```

**You can also query latest price levels over JSON-RPC**

Request:

```json
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "titan_getPammPriceLevels",
    "params": []
}
```

Both methods return the same response shape:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "slot": 14581462,
    ...
  }
}
```

### pAMM quote helpers

Titan exposes two JSON-RPC quote helpers backed by the latest pAMM price level snapshot.

titan\_getPammQuoteVenue mirrors the LambdaClass [router](https://github.com/lambdaclass/propamm-router-contracts/blob/main/src/PropAMMRouter.sol) style `quoteVenueV1` and `quoteV1` flow.

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "titan_getPammQuoteVenue",
  "params": [
    "0xVenue",
    "0xTokenIn",
    "0xTokenOut",
    "0xAmountIn"
  ]
}
```

`titan_getPammQuote` scans all pAMMs in the latest snapshot and returns the best quote plus the pAMM that produced it.

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "titan_getPammQuote",
  "params": [
    "0xTokenIn",
    "0xTokenOut",
    "0xAmountIn"
  ]
}
```

Both methods return the same response shape:

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tokenIn": "0x..12",
    "tokenOut": "0x..34",
    "amountIn": "0xde0b6b3a7640000",
    "amountOut": "0xd09dc300",
    "pamm": "0x..56",
    "router": "0x..78",
    "blockNumber": 25051224,
    "slot": 14285824,
    "timestamp": 1778253913749564761
  }
}
```

### On-chain routing

On-chain routing is another integration path for pAMM liquidity. This is not a separate Titan endpoint, but a router or solver-side pattern that can be used alongside the bundle flow above.

The idea is to move route selection into the execution transaction. Instead of submitting a single pre-selected path, the transaction could carry multiple candidates per hop. For a route from asset A to asset B to asset C, the A → B hop could have several candidate venues, and the B → C hop could have several candidate venues. Some candidates may route through standard AMM liquidity, while others may route through pAMMs.

Because execution happens onchain, the router evaluates the candidate set against the exact execution state and selects the best valid combination. This is especially attractive for pAMM routing because quote state can change between path construction and inclusion. If a maker update lands earlier in the same block, the router sees the updated state when the taker transaction executes.


# Bundle Tracing

### Introduction

This feature aims to provide MEV searchers with in-depth insights into why their bundles were or were not included in blocks. This is an early version of the feature, and we welcome your feedback on additional data points you'd find useful.

> **Note**: The bundle trace will be ready approximately 5 minutes after your bundle has been sent.&#x20;
>
> Rate limit: 50 requests/sec.

### Using `titan_getBundleStats` Endpoint

To get the status of your bundle, use the `titan_getBundleStats` API endpoint. Below is an example request and response:

#### Example Request

```bash
curl -X POST -H 'content-type: application/json' -d '{"method": "titan_getBundleStats", "params": [{"bundleHash": "0x...123"}], "jsonrpc": "2.0", "id": 1}' https://stats.titanbuilder.xyz
```

#### Example Response

```json
{
  "jsonrpc":"2.0",
  "result": {
    "status":"SimulationFail",
    "builderPayment":"0",
    "builderPaymentWhenIncluded": "0",
    "error":"BundleRevert. Reverting Hash: 0x…456"
  },
  "id":1
}
```

### Understanding Bundle Statuses

The `status` field in the response can have multiple values, each of which provides specific information about the bundle:

1. **Received**: Your bundle was received by the RPC endpoint but arrived too late to be added to the pool.
2. **Invalid**: Your bundle could be invalid for various reasons. For example, invalid RLP encoding, incorrect block number, already mined nonces, incorrect Chain ID, etc... To troubleshoot, ensure that the transactions within the bundle can be simulated using an `estimateGas` call to your node, and that the block number is either greater than the current block or set to 0 (we will default it to the current block if set to 0).
3. **SimulationFail**: This status arises from one of two scenarios, both evaluated during top-of-block simulation:
   1. Transaction revert: One or more transactions, not listed in the reverting transaction hashes, reverted.
   2. Builder payment: The net builder payment, computed as the difference between the builder's balance post-bundle and pre-bundle, is less than or equal to zero.
4. **SimulationPass:** Your bundle passed top-of-block simulation but was not selected for inclusion. This could be due to:
   1. Bundle was sent too late to be considered for inclusion.
   2. Insufficient bribe: While our algorithms don't solely sort by bribe, they do aim to maximise total block value. In 95% of cases, increasing your bribe should solve the issue.
   3. Late submission: This is less likely but possible if the bundle is sent very late into the slot, e.g., around `prev_slot + 12s`.
5. **IncludedInBlock**: Your bundle was considered by at least one of our sorting algorithms but wasn't selected for submission to the relay. This is likely because another algorithm produced a more valuable block that excluded your bundle. `builderPaymentWhenIncluded` reflects the builder payment of your bundle inside the block it was included in.
6. **Submitted**: Your bundle was included in at least one block submitted to a relay. It's important to note here that this does not mean your bundle was submitted in the winning block, just that it was submitted at least once.


# Builder Public Keys

Our builder will submit only using the following public keys:

```asm
0x807a81a9873359323966feb80fcc52b6049888f885d58134bf52bf9825f89aabc723bb5bacd1d8f4dea6322a6d166535
0x8216e00e1dc8e15c362ce8083ad01feeb04688dd3a18998a37db1c3c8b641372398c504e1aca2cbddd87c4075482b42f
0x8226fb149bfe7b4967ffe82ecb9084ffd5bbf0303de0b88f68fdd8297cdffe80f611fa27bc05506b4fba12e2eb5bc5a5
0x8509ecb595da0eda2c6fced4e287f0510a2c2dba5f80ee930503ef86e268d808a6df25e397177da06cd479771ce66840
0x8527d16cf01edcea2cbb05e27f5f61b184578ea27c4015e4533e4cebd6d53297bd004c14fe3f247a468361bf781e0069
0x88857150299287cedfbadea1ee3fb7ac121f1e4e16bef44b4a7bad35432973c4009efb90394facca3fdc0759ba70f93f
0x88a53ec4422f50238def5696446e06acd076f94d73c76e51449cf82eb5312dcb8a845a6c3199ce35de3f2b0441deb76b
0x8898a80ec199dbe15a57e3ceb51389ded413d6a2ebaaac0330af7effcdd33bba70318339ec9cbc1253b30d463c4999c4
0x8a85062118e29045e8b199723d9af519b0c071d9e7586fa89733e798536d4637f8545a169012cc1b76005d9453603273
0x8b39e8f6ad0a7d2c9e893459d76ac1bc7884d5343324f7639cc883590e8914c2edb59c3751e4b4c31d466baec718d440
0x910f20173e75cc036eb232cf2081043d62c028c3ddae53d5cccdbdd1213a9830c5143c431ee9e401a4d3432f3c23d18c
0x946fcf348bbf1044a3eaa3d27f1d01397cfc4d27495a949447c95ea7ec41db9f5c4fe3b867a937623685a0462996df09
0x95c8cc31f8d4e54eddb0603b8f12d59d466f656f374bde2073e321bdd16082d420e3eef4d62467a7ea6b83818381f742
0xa32aadb23e45595fe4981114a8230128443fd5407d557dc0c158ab93bc2b88939b5a87a84b6863b0d04a4b5a2447f847
0xa7a713e0275e888a06b4a95b5c0b16557cf25fe431269f54bc898287081dc4a25110edbafa643ea1b86c4319c8d9977e
0xa9d0a0f9059972d775a45d8377768ff20234de91a6fbacba5737ff8803807c38021b5863e5084869e695ef1d6c2fdaef
0xae2ffc6986c9a368c5ad2d51f86db2031d780f6ac9b2348044dea4e3a75808b566c935099de8b1a1609db322f2110e7a
0xb0b0de6ba411630193eed5450f82a76d66501e9838fe5fbe98d4873b5de677b3a20a360302fbe094adae63bc63e4ded4
0xb26f96664274e15fb6fcda862302e47de7e0e2a6687f8349327a9846043e42596ec44af676126e2cacbdd181f548e681
0xb435dd63a14675ea11e4eaca6bd640c70e68843cf4c8bfe3bbaf3a7ebedc3fc53d80050e58748ec3d088a15113c6d4c6
0xb47963246adef02cd3e61cbb648c04fd99b05e28a616aef3aa7fb688c17b10d1ce9662b61a600efbdd110e93d62d5144
0xb4a435cf816291596fe2e405651ec8b6c80b9cc34dace3c83202ca489a833756c9a0672ebdc17f23d9d43163db1caa5d
0xb4ce6162ff05198a2a3273c3cc94615e67bff9751d0febe14c2b20a7555221f6edd61c33f128c80b758f522343c72ea1
0xb67eaa5efcfa1d17319c344e1e5167811afbfe7922a2cf01c9a361f465597a5dc3a5472bd98843bac88d2541a78eab08
0xb9ce2753254979122cf137288efe471791893a9cf6abcd192f33515f6ca778507a51bcab84542562efc244001a7b4c55
```

Any public key not included in the above list should be considered unrelated to our builder.


