> For the complete documentation index, see [llms.txt](https://docs.titanbuilder.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.titanbuilder.xyz/propamms/takers.md).

# 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.

1. Construct your normal path through CFMM liquidity.
2. Construct additional variants where one or more hops route through pAMMs.
3. 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

Top-level stream addresses currently published on the public no-auth stream:

| Protocol       | Top-level stream address                     | Price-level `pamm` address                   |
| -------------- | -------------------------------------------- | -------------------------------------------- |
| FermiSwap      | `0x5979458912f80b96d30d4220af8e2e4925a33320` | `0x5979458912f80b96d30d4220af8e2e4925a33320` |
| bopAMM (Bebop) | `0xb09aaa8933626d7e4c48d65dad2d77021cfbca9a` | `0xb09aaa8933626d7e4c48d65dad2d77021cfbca9a` |
| Kipseli        | `0xc9a956b5196dfe110debe8781857bc8b97d32091` | `0x71e790dd841c8a9061487cb3e78c288e75ce0b3d` |
| Tempest        | `0x00000003f1ec2379e79f58e12ec6c4f51ee92149` | `0x00000003f1ec2379e79f58e12ec6c4f51ee92149` |
| ElZorro        | `0xcf211b4dd0d2be5c173ea57bcf938fc61d1d3bd3` | `0xcf211b4dd0d2be5c173ea57bcf938fc61d1d3bd3` |
| Stelaxis       | `0x77047af6cd8f7f84d96a020a2833d24916b75fde` | `0x77047af6cd8f7f84d96a020a2833d24916b75fde` |

Each top-level key is the quote address the maker publishes under. Makers can opt in or out, change their quote address, or stop quoting temporarily, so keys may appear or disappear over time. Treat unknown keys as valid pAMM state rather than errors.

#### Endpoints

pAMM data is served from the regional data hosts. The general `rpc.titanbuilder.xyz` hosts do not serve pAMM data.

| Region | State stream (WebSocket)                              | Price levels (WebSocket)                              | JSON-RPC                                |
| ------ | ----------------------------------------------------- | ----------------------------------------------------- | --------------------------------------- |
| EU     | `wss://eu.data.titanbuilder.xyz/ws/pamm_quote_stream` | `wss://eu.data.titanbuilder.xyz/ws/pamm_price_levels` | `https://eu.data.titanbuilder.xyz/data` |
| US     | `wss://us.data.titanbuilder.xyz/ws/pamm_quote_stream` | `wss://us.data.titanbuilder.xyz/ws/pamm_price_levels` | `https://us.data.titanbuilder.xyz/data` |
| AP     | `wss://ap.data.titanbuilder.xyz/ws/pamm_quote_stream` | `wss://ap.data.titanbuilder.xyz/ws/pamm_price_levels` | `https://ap.data.titanbuilder.xyz/data` |

Connect to the region closest to you. Permissioned streams authenticate with an API key in the `Authorization` header.

#### Format

Each WebSocket message is JSON. `slot`, `blockNumber`, and `timestamp` are reserved metadata fields:

| Field         | Type   | Description                                                                                     |
| ------------- | ------ | ----------------------------------------------------------------------------------------------- |
| `slot`        | number | Beacon slot of the block being built. The block timestamp is `1606824023 + 12 * slot`.          |
| `blockNumber` | number | Execution block number the state targets.                                                       |
| `timestamp`   | number | Titan snapshot time in nanoseconds. This is not the block timestamp; use `slot` for block time. |

Every other top-level key is a pAMM stream address. Its value is an object with a `stateOverride` field. A message carries only the pAMMs whose state changed.

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

`stateOverride` uses the same structure as the Ethereum `eth_call` State Override Set. Its keys are account addresses, and each account override may include `balance`, `nonce`, `code`, `state`, and `stateDiff`. `stateDiff` maps storage slot keys to overridden storage values.

The `stateOverride` object can be passed directly as the third parameter to `eth_call`, or as `stateOverrides` in an `eth_simulateV1` block state call. To simulate against several pAMMs at once, merge their `stateOverride` objects into a single object keyed by account address.

#### Latest state over JSON-RPC

Takers that do not want to maintain a WebSocket connection can request the latest pAMM state over JSON-RPC on the regional data endpoint (see Endpoints).

Request:

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

The result is a snapshot of every pAMM you have access to, keyed by stream address like the WebSocket messages:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "slot": 14285824,
    "blockNumber": "0x17e4458",
    "0x5979458912f80b96d30d4220af8e2e4925a33320": { "stateOverride": { ... } },
    "0xb09aaa8933626d7e4c48d65dad2d77021cfbca9a": { "stateOverride": { ... } }
  }
}
```

Unlike WebSocket messages, `blockNumber` is a hex string, there is no `timestamp`, and `slot`/`blockNumber` are the latest values across the included pAMMs.

#### Example (Python)

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

import websockets

WS_URI = "wss://eu.data.titanbuilder.xyz/ws/pamm_quote_stream"
RPC_URL = "https://eu.data.titanbuilder.xyz/data"
METADATA = {"slot", "blockNumber", "timestamp"}


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"]


def state_overrides(message):
    return {k: v["stateOverride"] for k, v in message.items() if k not in METADATA}


async def main():
    # One-shot snapshot.
    snapshot = get_pamm_state_overrides()
    print(int(snapshot["blockNumber"], 16), list(state_overrides(snapshot)))

    # Live updates.
    async with websockets.connect(WS_URI) as ws:
        async for message in ws:
            update = json.loads(message)
            print(update["blockNumber"], list(state_overrides(update)))


asyncio.run(main())
```

### pAMM price levels

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

Order book levels come in two variants. `Simulated` levels are derived from EVM simulations of synthesized taker transactions, with sizes spaced in a geometric progression to cover a range of trade sizes. `Interpolated` levels sit between the simulated ones and are computed by linear spline interpolation, giving finer-grained sizes at the cost of a small approximation error.

Each message is a complete snapshot. Keep the newest message and treat older ones as superseded.

Use the `/ws/pamm_price_levels` path on the regional data hosts (see Endpoints).

#### Latest price levels over WebSocket

Each WebSocket message is JSON. The `pamms` array contains one ladder per pAMM, identified by its price-level `pamm` address (see pAMM stream addresses).

```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" }
          ]
        }
      ]
    }
  ]
}
```

#### Latest price levels over JSON-RPC

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

The `result` has the same shape as a WebSocket message.

### pAMM quote helpers

Titan exposes two JSON-RPC quote helpers on the regional data endpoints, backed by the latest price level snapshot.

`titan_getPammQuoteVenue` quotes one pAMM. It mirrors the LambdaClass router `quoteVenueV1` / `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": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
    "tokenOut": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
    "amountIn": "0xde0b6b3a7640000",
    "amountOut": "0x9fb79b90",
    "pamm": "0x00000003f1ec2379e79f58e12ec6c4f51ee92149",
    "router": "0x4ddf368080cd7946db5b459ad591c350158175e1",
    "blockNumber": 26078723,
    "slot": 15317324,
    "timestamp": 1790631904288995296
  }
}
```

### On-chain routing

On-chain routing is another integration path for pAMM liquidity. It 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 carries 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, others through pAMMs.

Because execution happens on-chain, the router evaluates the candidate set against the exact execution state and selects the best valid combination. This suits pAMM routing well 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.
