> ## Documentation Index
> Fetch the complete documentation index at: https://docs.biconomy.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Stream prices

> Connect, subscribe, send signed ladders. Rewrite and Reprice, nonces and expiry, a runnable client.

You sign your ladder and send it over a WebSocket. Biconomy writes it onchain and pays the gas.

| Environment | WebSocket                           | Chains                                     |
| ----------- | ----------------------------------- | ------------------------------------------ |
| Staging     | `wss://propamm-staging.biconomy.io` | Base Sepolia (84532), mintable test tokens |
| Production  | `wss://propamm.biconomy.io`         | Base (8453), BNB Smart Chain (56)          |

## Connect and subscribe

One signer per connection. Ladders for several chains can share it.

```json theme={null}
{ "type": "subscribe", "data": { "type": "price-ledger", "mm": "0xYourSigner" } }
```

| Response                                               | Meaning                                                         |
| ------------------------------------------------------ | --------------------------------------------------------------- |
| `{ "type": "ack" }`                                    | accepted and stored                                             |
| `{ "type": "error", "code": "...", "message": "..." }` | rejected; your last accepted ladder stays live until it expires |

## Messages

| Type             | Mode    | Carries                                                                       | Send                                |
| ---------------- | ------- | ----------------------------------------------------------------------------- | ----------------------------------- |
| `price-ladder`   | Rewrite | sizes and prices                                                              | on every price change               |
| `offsets`        | Reprice | sizes, a discount per size, optional drift                                    | once, then when the shape changes   |
| `anchor`         | Reprice | the reference price                                                           | on every price change               |
| `board-controls` | both    | the protections, see [Risk and inventory](/propamm/makers/risk-and-inventory) | once, then when the settings change |

<img className="pa-fig pa-fig-wide-light" src="https://mintcdn.com/biconomy-73bb4454/40CFSb7K3hPGZ20b/images/propamm/modes-wide-light.svg?fit=max&auto=format&n=40CFSb7K3hPGZ20b&q=85&s=8a1c0fbed64e63c40e218d19df15bf2f" alt="Rewrite sends the whole ladder on every update. Reprice sends the ladder once, then a single price number on every update." width="900" height="170" data-path="images/propamm/modes-wide-light.svg" />

<img className="pa-fig pa-fig-wide-dark" src="https://mintcdn.com/biconomy-73bb4454/40CFSb7K3hPGZ20b/images/propamm/modes-wide-dark.svg?fit=max&auto=format&n=40CFSb7K3hPGZ20b&q=85&s=cf6ff81b2b2889c47c5a1b05a04186a8" alt="Rewrite sends the whole ladder on every update. Reprice sends the ladder once, then a single price number on every update." width="900" height="170" data-path="images/propamm/modes-wide-dark.svg" />

<img className="pa-fig pa-fig-narrow-light" src="https://mintcdn.com/biconomy-73bb4454/40CFSb7K3hPGZ20b/images/propamm/modes-light.svg?fit=max&auto=format&n=40CFSb7K3hPGZ20b&q=85&s=22b118ed1526866dc6cbe281ed8916b3" alt="Rewrite sends the whole ladder on every update. Reprice sends the ladder once, then a single price number on every update." width="480" height="290" data-path="images/propamm/modes-light.svg" />

<img className="pa-fig pa-fig-narrow-dark" src="https://mintcdn.com/biconomy-73bb4454/40CFSb7K3hPGZ20b/images/propamm/modes-dark.svg?fit=max&auto=format&n=40CFSb7K3hPGZ20b&q=85&s=6918920d1051aa5dab691723ae092fff" alt="Rewrite sends the whole ladder on every update. Reprice sends the ladder once, then a single price number on every update." width="480" height="290" data-path="images/propamm/modes-dark.svg" />

In Reprice mode each step prices at the anchor minus its discount.

| Offsets field       | Meaning                                                                | Example                |
| ------------------- | ---------------------------------------------------------------------- | ---------------------- |
| `offsetPpm`         | discount from the anchor, in parts per million                         | `1000` = 10 bps        |
| `driftPpmPerSecond` | extra discount per second since the anchor's `timestamp`; `0` disables | `10` = 1 bp per second |

## Common fields

| Field                 | Meaning                                                                                  |
| --------------------- | ---------------------------------------------------------------------------------------- |
| `mm`                  | your signing address                                                                     |
| `provider`            | your provider contract (`price-ladder` and `offsets`)                                    |
| `tokenIn`, `tokenOut` | the direction; the reverse direction is a separate ladder                                |
| `nonce`               | increases with every message, see below                                                  |
| `expiresAt`           | unix seconds, at most 1 hour ahead                                                       |
| `signature`           | EIP-712 by `mm`, domain `PropAMMExecutor` version `2`, verifying contract = the executor |
| `chainId`             | the chain the ladder lives on                                                            |

| Unit  | Rule                                                                     | Example                                                               |
| ----- | ------------------------------------------------------------------------ | --------------------------------------------------------------------- |
| size  | cumulative, `tokenIn` smallest units                                     | 2 WETH = `2000000000000000000`                                        |
| price | 1e18-scaled `tokenOut` per `tokenIn`, smallest units, decimals folded in | WETH (18) to USDC (6) at 2,000 = `2000 * 1e18 * 10^(6-18)` = `2000e6` |

Exact structs and wire formats are in the [Message reference](/propamm/reference/messages).

## Nonces and expiry

| Message          | Nonce sequence                    | Typical `expiresAt` | Resets taken steps |
| ---------------- | --------------------------------- | ------------------- | ------------------ |
| `price-ladder`   | depth, shared with `offsets`      | 10 to 30 s          | yes                |
| `offsets`        | depth, shared with `price-ladder` | up to 1 hour        | yes                |
| `anchor`         | its own                           | 10 to 30 s          | no                 |
| `board-controls` | its own                           | none                | no                 |

A nonce at or below the last accepted one returns `STALE_NONCE`. Unix milliseconds work as nonces.

## Runnable client

Reprice mode on Base Sepolia: `offsets` once per direction, then an `anchor` per direction every second. Replace `yourPrice()` with your engine's price in raw units.

```ts theme={null}
import WebSocket from "ws";
import { privateKeyToAccount } from "viem/accounts";

const ENDPOINT = "wss://propamm-staging.biconomy.io";
const CHAIN_ID = 84532;
const EXECUTOR = "0x000000fFA5f8Ae192Ab65204f9B7E062CbF4e05D"; // same address on every chain
const PROVIDER = "0xYourProviderContract";
const TOKEN_IN = "0x8b414aD7005EeFd315aF2A16538885Eae229bab7";  // MockWETH, 18 decimals
const TOKEN_OUT = "0xAbbdbbbd6d56593A9c5656c06cB30D61E4a544Df"; // MockUSDC, 18 decimals

const account = privateKeyToAccount(process.env.MM_SIGNER_KEY as `0x${string}`);
const domain = { name: "PropAMMExecutor", version: "2", chainId: CHAIN_ID, verifyingContract: EXECUTOR } as const;
const types = {
  Rung: [{ name: "size", type: "uint256" }, { name: "offsetPpm", type: "uint256" }],
  OffsetLadder: [
    { name: "mm", type: "address" }, { name: "provider", type: "address" },
    { name: "tokenIn", type: "address" }, { name: "tokenOut", type: "address" },
    { name: "rungs", type: "Rung[]" }, { name: "nonce", type: "uint256" },
    { name: "expiresAt", type: "uint256" }, { name: "driftPpmPerSecond", type: "uint256" },
  ],
  Anchor: [
    { name: "mm", type: "address" }, { name: "tokenIn", type: "address" },
    { name: "tokenOut", type: "address" }, { name: "price", type: "uint256" },
    { name: "nonce", type: "uint256" }, { name: "timestamp", type: "uint256" },
    { name: "expiresAt", type: "uint256" },
  ],
} as const;

const now = () => BigInt(Math.floor(Date.now() / 1000));
// bigint fields go on the wire as decimal strings; chainId stays a number.
const frame = (o: unknown) => JSON.stringify(o, (_, v) => (typeof v === "bigint" ? v.toString() : v));

function yourPrice(): bigint {
  return 2000n * 10n ** 18n; // 2000 tokenOut per tokenIn, both 18 decimals
}

async function sendOffsets(ws: WebSocket) {
  const message = {
    mm: account.address, provider: PROVIDER, tokenIn: TOKEN_IN, tokenOut: TOKEN_OUT,
    rungs: [
      { size: 1n * 10n ** 18n, offsetPpm: 1000n },  // first 1 tokenIn at 10 bps below the anchor
      { size: 3n * 10n ** 18n, offsetPpm: 2500n },  // next 2 tokenIn at 25 bps below
    ],
    nonce: BigInt(Date.now()),          // depth nonce, shared with price-ladder
    expiresAt: now() + 1800n,           // the shape lives 30 minutes
    driftPpmPerSecond: 10n,             // widen 1 bp per second of anchor age
  };
  const signature = await account.signTypedData({ domain, types, primaryType: "OffsetLadder", message });
  ws.send(frame({ type: "offsets", payload: { ...message, signature, chainId: CHAIN_ID } }));
}

async function sendAnchor(ws: WebSocket) {
  const t = now();
  const message = {
    mm: account.address, tokenIn: TOKEN_IN, tokenOut: TOKEN_OUT,
    price: yourPrice(),
    nonce: BigInt(Date.now()),          // anchor nonce, its own sequence
    timestamp: t,                       // drift counts from here
    expiresAt: t + 15n,                 // the ladder stops quoting 15 s after this if no newer anchor lands
  };
  const signature = await account.signTypedData({ domain, types, primaryType: "Anchor", message });
  ws.send(frame({ type: "anchor", payload: { ...message, signature, chainId: CHAIN_ID } }));
}

const ws = new WebSocket(ENDPOINT);
ws.on("message", (m) => console.log(m.toString()));
ws.on("open", async () => {
  ws.send(JSON.stringify({ type: "subscribe", data: { type: "price-ledger", mm: account.address } }));
  await sendOffsets(ws);                                   // once, then when sizes, offsets or drift change
  setInterval(() => void sendAnchor(ws), 1000);            // the price, every second
});
ws.on("close", () => process.exit(1));                     // let a supervisor restart for a clean reconnect
```

For Rewrite, sign `PriceLadder` (`levels` of `{ size, price }`, `primaryType: "PriceLadder"`) on every tick and send it as `type: "price-ladder"`. A client that samples a pricing curve into a ladder is in [`curve-maker-reference.ts`](https://github.com/bcnmy/propamm-protocol-docs/blob/main/docs/examples/curve-maker-reference.ts). Each direction of a pair is a separate ladder.

## Common errors

| Code                                                                 | Fix                                                                              |
| -------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `INVALID_SIGNATURE`                                                  | domain `PropAMMExecutor`, version `2`, the right `chainId`, the executor address |
| `STALE_NONCE`                                                        | nonces must increase per ladder                                                  |
| `UPDATE_EXPIRED`                                                     | `expiresAt` is in the past; check clock skew and that it is in seconds           |
| `UNREGISTERED_MARKET_MAKER`, `UNSUPPORTED_PAIR`, `PROVIDER_MISMATCH` | signer, pair or provider differ from your registration                           |
| `NOT_SUBSCRIBED`                                                     | send the subscribe frame first                                                   |
| `RATE_LIMITED`                                                       | over 300 messages per second on one connection                                   |

Full list in the [Message reference](/propamm/reference/messages#responses-and-error-codes).

## Limits

| Limit                   | Value                        |
| ----------------------- | ---------------------------- |
| Messages per connection | 300 per second               |
| Frame size              | 64 KB                        |
| Steps per ladder        | 20                           |
| `expiresAt`             | at most 1 hour ahead         |
| Idle timeout            | 60 s without inbound traffic |
