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

# PropAMM quote

`POST /public/v1/evm/{chain_id}/pamm-quote`

Submits a signed quote-update transaction for a propAMM pool to Metric's partner builders. Ethereum (`1`) and BSC (`56`) are supported.

### Authentication

Authenticate with a Trading API key:

```
Authorization: Bearer $METRIC_TRADING_API_KEY
```

Any valid Trading API key can call this endpoint.

### Path parameters

| Name       | Type    | Description                                                                                                        |
| ---------- | ------- | ------------------------------------------------------------------------------------------------------------------ |
| `chain_id` | integer | `1` (Ethereum) or `56` (BSC). A chain Metric doesn't serve returns `404`. Any other supported chain returns `400`. |

### Request body

Send JSON. Hex values may include the `0x` prefix.

| Field                       | Type                         | Chains   | Description                                                                                                                                                                                                                                              |
| --------------------------- | ---------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tx`                        | hex string                   | Both     | Required. Signed quote-update transaction for the path's chain. Send `"0x"` to cancel the quote.                                                                                                                                                         |
| `blockNumber`               | integer                      | Both     | Required. Block for which the quote is valid. On BSC, it is the final valid block, and a live quote requires a value greater than `0`.                                                                                                                   |
| `replacementUuid`           | hex string, 16 bytes         | Both     | Required. Quote identifier.                                                                                                                                                                                                                              |
| `replacementSeqNumber`      | integer                      | Both     | Required. Must be greater than `0`. Increase it for each update using the same `replacementUuid`.                                                                                                                                                        |
| `disableCrossRegionSharing` | boolean                      | Both     | Optional. When `true`, the builder does not share the quote across regions.                                                                                                                                                                              |
| `responseTimeoutMs`         | integer, milliseconds        | Both     | Optional. How long to wait for builder answers before responding, measured from arrival. It can only lower the default of 500 ms. `0` responds as soon as the update is queued for every builder.                                                        |
| `maxTimestamp`              | integer, Unix seconds        | BSC      | Quote expiry. On a live BSC quote it can be at most five seconds ahead of the server clock. Required for BlockRazor. Without it, the quote reaches 48Club but skips BlockRazor. `0` or a millisecond value is rejected on both chains; only BSC uses it. |
| `quoteAddress`              | address                      | Ethereum | Address a taker must touch to trigger the quote. Ignored on BSC.                                                                                                                                                                                         |
| `poolAddresses`             | address\[]                   | Ethereum | Pools covered by the quote. Ignored on BSC.                                                                                                                                                                                                              |
| `assetPairs`                | hex string\[], 40 bytes each | Ethereum | Token pairs priced by this update. Each joins two 20-byte addresses. Ignored on BSC.                                                                                                                                                                     |

On Ethereum, always include as much information as you can: `quoteAddress`, `poolAddresses` and `assetPairs`. Builders use it to match the quote to a trade, and a quote they can't match is never included. A builder may not use every field, but the more you send, the more likely your quote is to be included.

Unknown fields are dropped without an error, so check field names carefully.

Send the same `tx`, `replacementUuid` and `replacementSeqNumber` to Metric and to any builder you also submit to directly.

#### Building delivery mechanics

| Chain           | Builder delivery                                                                                                                                                                                                                   |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Ethereum        | Metric applies the update to the simulated head block. It forwards it to Titan, Quasar, BuilderNet, Bombora and Eureka. A builder includes the update only with a trade against your pool. It places the update before that trade. |
| BSC: 48Club     | Receives the update over a quote stream. A newer quote replaces the earlier one. Send a higher `replacementSeqNumber` with the same `replacementUuid`. A cancel removes the quote.                                                 |
| BSC: BlockRazor | Receives the update as a bundle placed first in the block. This is paid inclusion. An accepted bundle can land and pay gas without a trade. BlockRazor cannot cancel a bundle. A live quote therefore needs `maxTimestamp`.        |

### Request

#### Ethereum

```bash
curl -X POST "https://api.metric.xyz/public/v1/evm/1/pamm-quote" \
  -H "Authorization: Bearer $METRIC_TRADING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tx": "0x02f8b10182…", "blockNumber": 23456789, "replacementUuid": "0x6f1c2b7e9a4d4c1e8b3a5d7f0e2c4a61", "replacementSeqNumber": 7, "quoteAddress": "0xF85AfbADeCC7F23Dd173ed706f07d9C7e2473e24", "poolAddresses": ["0xF85AfbADeCC7F23Dd173ed706f07d9C7e2473e24"] }'
```

#### BSC

```bash
curl -X POST "https://api.metric.xyz/public/v1/evm/56/pamm-quote" \
  -H "Authorization: Bearer $METRIC_TRADING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tx": "0x02f8b13882…", "blockNumber": 63456789, "replacementUuid": "0x6f1c2b7e9a4d4c1e8b3a5d7f0e2c4a61", "replacementSeqNumber": 7, "maxTimestamp": 1790508442 }'
```

#### Cancel

Reuse the replacementUuid, increase the replacementSeqNumber and send an empty tx:

```bash
curl -X POST "https://api.metric.xyz/public/v1/evm/1/pamm-quote" \
  -H "Authorization: Bearer $METRIC_TRADING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tx": "0x", "blockNumber": 23456789, "replacementUuid": "0x6f1c2b7e9a4d4c1e8b3a5d7f0e2c4a61", "replacementSeqNumber": 8 }'
```

{% hint style="warning" %}
On BSC, a cancel only reaches 48Club. BlockRazor has no cancel operation. A bundle it has already accepted stays live until its `blockNumber` or `maxTimestamp` passes, even after a newer quote or cancel. The same signed transaction can only land once. An outdated quote can still land during that window. Keep `maxTimestamp` as short as your update cadence allows.
{% endhint %}

### Response

The endpoint returns 200 OK with a PammQuoteResponse, even if no builder accepts the update. Check accepted.

Metric responds once every builder has answered or the response deadline passes (500 ms from arrival, or your `responseTimeoutMs`). A builder that hasn't answered by then is reported as `pending`. Metric still delivers the update to it, but its final result isn't returned to you.

| Field                     | Description                                                                                                                               |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `accepted`                | `true` when at least one builder acknowledged the update by the response deadline.                                                        |
| `providers[].provider`    | Builder and endpoint, such as `titan:wss://…`, `club48:wss://pamm.48.club`, or `blockrazor:https://tokyo.builder.blockrazor.io`.          |
| `providers[].accepted`    | Whether this builder acknowledged the update.                                                                                             |
| `providers[].timestampNs` | Time the builder acknowledged the update, when reported.                                                                                  |
| `providers[].bundleHash`  | Accepted bundle hash. BlockRazor only.                                                                                                    |
| `providers[].pending`     | `true` when the builder had not answered by the response deadline. `accepted` is then `false` and there is no `error`. Omitted otherwise. |
| `providers[].error`       | Reason the update was not accepted. An error starting with `skipped:` means Metric did not submit to that builder.                        |

#### Ethereum example

```json
{
  "accepted": true,
  "providers": [
    {
      "provider": "titan:wss://eu.rpc.titanbuilder.xyz/ws/sendquoteupdate",
      "accepted": true,
      "timestampNs": 1790508439123456789
    },
    {
      "provider": "bombora:wss://ap-rpc.bombora.build/ws/sendquoteupdate",
      "accepted": false,
      "pending": true
    }
  ]
}
```

#### BSC example

```json
{
  "accepted": true,
  "providers": [
    {
      "provider": "club48:wss://pamm.48.club",
      "accepted": true,
      "timestampNs": 1790508439123456789
    },
    {
      "provider": "blockrazor:https://tokyo.builder.blockrazor.io",
      "accepted": true,
      "bundleHash": "0x9c1e…"
    }
  ]
}
```

On BSC, the BlockRazor entry reports a skip in two cases:

* The request had no maxTimestamp: "accepted": false, "error": "skipped: maxTimestamp is required for Blockrazor, which cannot cancel a bundle".
* The request was a cancel: "skipped: blockrazor has no cancel operation".

An acknowledgement is not inclusion. Measure fills, not acknowledgements.

### Timing

* Metric responds within 500 ms of arrival, or sooner if you set `responseTimeoutMs`. Builders that haven't answered by then are reported as `pending`.
* Each builder has three seconds to acknowledge. Metric keeps delivering a `pending` update in the background until then.
* An update with less than 500 ms of that time left can be dropped before it's sent. This applies to the Ethereum builders and 48Club. You only see such an update as `pending`.
* Slow builder connections reconnect automatically.
* Ethereum: the simulated-head update runs in the background.
* BlockRazor:
  * Metric sends one request at a time per endpoint. A push that arrives while a request is in flight waits and is sent next. Each `replacementUuid` keeps only its newest waiting push. An older waiting push from the same `replacementUuid` is never sent and reports skipped: superseded by a newer quote before it was sent. A cancel removes a waiting push the same way. A request already in flight can't be recalled.
  * A push older than one already seen for its `replacementUuid` is ignored (skipped: older than a quote already seen for this stream). A higher `blockNumber` always counts as newer. For 30 s after a cancel, a live quote on the same `replacementUuid` with a `replacementSeqNumber` at or below the cancel's is ignored.
  * Start a new `replacementUuid` for each block, or never restart `replacementSeqNumber` under one `replacementUuid`. BlockRazor works better with the second: a new `replacementUuid` doesn't replace the previous block's waiting push, so that push is still sent first.
  * Resending an already accepted bundle (same tx, blockNumber and maxTimestamp) returns the earlier result without sending it again. Resending a quote whose earlier copy is still waiting or being sent reports skipped: this quote is already queued or being sent.
  * Up to 256 pushes can wait per endpoint. When the queue is full, a push from a `replacementUuid` with nothing waiting reports skipped: parked queue full. A waiting push whose `maxTimestamp` passes is dropped (skipped: maxTimestamp passed while queued).
  * If BlockRazor already holds the bundle (already known or bundle already exist), Metric reports the update as accepted.
  * After a transport error, a timeout, an auth failure, a 429 or a 5xx, Metric pauses that endpoint for 500 ms to 30 s. If BlockRazor asks Metric to wait (Retry-After), Metric waits up to 30 s. Pushes that arrive during the pause report skipped: backing off for …ms more.

### Errors

Errors return an `ApiError` body:

```json
{ "error": "" }
```

| Status | When                                                                                                                                                                                                                                                                                                                                                                                           |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | The JSON is malformed, the `chain_id` is a chain Metric serves other than `1` or `56`, or a value is malformed. This includes malformed hex, wrong field lengths, `replacementSeqNumber` of `0`, or a transaction signed for another chain. On BSC, a live quote cannot have `blockNumber: 0`. `maxTimestamp` cannot be `0` or milliseconds, or, on a live BSC quote, over five seconds ahead. |
| `401`  | The Trading API key is missing or invalid.                                                                                                                                                                                                                                                                                                                                                     |
| `404`  | Metric doesn't serve the `chain_id` (`Unknown chainId`).                                                                                                                                                                                                                                                                                                                                       |
| `415`  | The `Content-Type: application/json` header is missing.                                                                                                                                                                                                                                                                                                                                        |
| `422`  | A required field is missing or has the wrong type.                                                                                                                                                                                                                                                                                                                                             |
| `429`  | Rate limit exceeded.                                                                                                                                                                                                                                                                                                                                                                           |
| `503`  | Maker submission is disabled for this chain, or no builder is configured for it. Every `503` returns the same body, `Service temporarily unavailable`, so the cases can't be told apart.                                                                                                                                                                                                       |

`400` messages name the failing field or value. Body-parsing errors (malformed JSON, `415` and `422`) return a plain-text body instead of `ApiError`.
