Back to home

Documentation

Maker API · v1

Integrate external market makers: stream RFQs over WebSocket, return quotes, and compete on price against Convallax's internal quoter.

← Protocol overview

Flow

  1. Your service opens a persistent WebSocket to the Convallax relay (same host/port as REST).
  2. When an end-user submits POST /rfq, the relay broadcasts one rfq message to every connected maker.
  3. Before timeoutMs elapses (see env), reply with quote referencing the same broadcastId.
  4. The relay merges your quote with Convallax's internal model quote (and every other maker) and selects the winning price for the taker rule below.
  5. If you win, the taker accepts via POST /execute; the relay signs an on-chain settlement order and the user's wallet calls fill() on ConvallaxRFQSettlement.

Endpoints

GET http://HOST:PORT/maker/v1/status

JSON: protocol version + count of sockets (for ops).

WS ws://HOST:PORT/maker/v1/ws
Optional: ?apiKey=… when MAKER_API_KEY is set server-side.
POST http://HOST:PORT/rfq

Same envelope the dashboard sends: EIP-712 typed-data signature (signatureEncoding: "eip712"), relay verifies signer = payload.wallet, plus nested payload. Response includes best quote plus meta:

{
  "success": true,
  "quote": {
    "quote_id": "b2c98e2c-…",
    "rfq_id": "rfq-…",
    "side": "buy",
    "price": 0.182,
    "size": 100,
    "fairValue": 0.175,
    "spread_bps": 412,
    "greeks": { "delta": 0.62, "gamma": -0.01, "vega": 0.004, "theta": -0.002 },
    "expires_in_ms": 2000
  },
  "meta": {
    "broadcastId": "…",
    "rfqDeadlineMs": 800,
    "makersConnected": 1,
    "quotesReceivedExternal": 1,
    "winningSource": "external",
    "winningMakerId": "mm1-demo"
  }
}

Relay → maker

First frame after connect: {"type":"connected",…}. Each RFQ:

{
  "type": "rfq",
  "broadcastId": "6d2c7b3a-…",
  "deadlineIso": "2026-05-08T14:03:09.881Z",
  "timeoutMs": 800,
  "envelope": {
    "signature": "0x…",
    "signatureEncoding": "eip712",
    "payload": {
      "version": 2,
      "rfqId": "rfq-…",
      "wallet": "0x…",
      "createdAt": "2026-05-08T14:03:09.081Z",
      "market": {
        "conditionId": "0x…",
        "yesTokenId": "12345…",
        "question": "Will …?"
      },
      "option": {
        "optionType": "call",
        "strikeBps": 50,
        "strike": 0.5,
        "expiry": "May 31",
        "expiryMs": 1717189200000,
        "tauDays": 21.5,
        "sigmaL": 2,
        "currentYesPrice": 0.48,
        "isResolutionExpiry": false
      },
      "trade": { "side": "buy", "size": 100 }
    }
  }
}

Maker → relay

{
  "type": "quote",
  "broadcastId": "6d2c7b3a-…",
  "makerId": "your-firm-mm1",
  "quote": {
    "quote_id": "client-generated-uuid",
    "rfq_id": "same-as-envelope.payload.rfqId",
    "side": "buy",
    "price": 0.18,
    "size": 100,
    "fairValue": 0.175,
    "spread_bps": 420,
    "greeks": { "delta": 0.61, "gamma": -0.01, "vega": 0.004, "theta": -0.002 },
    "expires_in_ms": 5000
  }
}
  • quote.rfq_id must match the RFQ. quote.side mirrors the wallet's aggressor direction (buy ↔ user lifts the offer, sell ↔ user hits the bid).
  • quote.size must be ≥ half of payload trade.size (soft liquidity check until partial fills ship).
  • price is quoted per one unit of the option notion in 0–1 probability-dollars, consistent with Convallax's RFQ pane.

Winner selection

Among valid quotes matching rfq_id + side: if the user is buy, the lowest price wins; if sell, the highest wins. Quotes outside (0,1) or mis-sized are rejected.

Environment variables (relay)

VariableMeaning
MAKER_RFQ_TIMEOUT_MSWait window for outbound maker quotes after each inbound RFQ (50–30000).
MAKER_API_KEYIf non-empty, require ?apiKey= on the WebSocket URL.
RFQ_ALLOWED_ORIGINSCORS allowlist for browser POST routes (comma-separated).

MM1 starter (Node)

Drop into a standalone repo pointed at localhost or your staging URL; replace the placeholder pricer with your inventory + model stack.

const WebSocket = require("ws");
const url = process.env.CONVALLAX_WS || "ws://localhost:3001/maker/v1/ws";
const qs = process.env.MAKER_API_KEY ? "?apiKey=" + encodeURIComponent(process.env.MAKER_API_KEY) : "";
const ws = new WebSocket(url + qs);

ws.on("open", () => console.log("maker connected"));
ws.on("message", (raw) => {
  const msg = JSON.parse(raw.toString());
  if (msg.type !== "rfq") return;

  const p = msg.envelope.payload;
  const px = Number(p.option?.currentYesPrice ?? 0.48) + 0.005; // placeholder
  ws.send(JSON.stringify({
    type: "quote",
    broadcastId: msg.broadcastId,
    makerId: "MM1-demo",
    quote: {
      quote_id: crypto.randomUUID(),
      rfq_id: p.rfqId,
      side: p.trade.side === "sell" ? "sell" : "buy",
      price: Math.min(0.99, Math.max(0.01, px)),
      size: Number(p.trade.size) || 1,
      fairValue: px,
      spread_bps: 100,
      greeks: { delta: 0.5, gamma: 0, vega: 0.01, theta: 0 },
      expires_in_ms: 60_000
    }
  }));
});
Versioning: this page documents protocolVersion 1. Breaking WS or quote schema changes bump the documented version alongside server hello payloads.