CRXDocs

Answer an RFQ

Goal

Turn one inbox item into a bound position. You will pull a directed RFQ, price it, sign the Terms, confirm, and post your margin once the taker binds.

Testnet only. Sonic Testnet, chain 14601.

Reads need a token; the bind needs your signature. Log in to read the inbox. The quote is authorized by the Terms signature, not the token.

Before you start

  • Your wallet is approved as a MAKER, with USDC deposited into your general balance.
  • You hold a one-hour token from /auth/challenge/auth/login.
  • The relayer base URL and the deployed core address (API reference, ~4 min).

Step 1 — Pull an inbox item

GET /rfq/inbox?maker=<addr> with your token. Pick an item and read its terms-to-be: pair, notional, direction.

const inbox = await fetch(`${RELAYER}/rfq/inbox?maker=${maker}`, {
  headers: { Authorization: `Bearer ${token}` },
}).then((r) => r.json());

const item = inbox[0];
const rfqId = item.rfq_id ?? item.rfqId;

Each item expires in 30 seconds. Price it inside the window or it falls off.

Step 2 — Compute your price

Set the rate and the margin schedule. The rate is your firm forward quote; the bps are the IM each side posts. This is your edge — size the margin to the pair's gap risk.

Step 3 — Post the quote

POST /rfq/:id/quote. Unsigned — the relayer returns canonical Terms for you to sign.

const priced = await fetch(`${RELAYER}/rfq/${rfqId}/quote`, {
  method: "POST",
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    quote: {
      rfq_id: rfqId,
      locked_rate: rate,
      im_taker_bps: 200,
      im_maker_bps: 100,
      threshold_bps: 0,
      mta_bps: 10,
      tol_bps: 50,
      valid_until: nowSecs + 600,
      quote_nonce: String(nowSecs),
    },
  }),
}).then((r) => r.json());

const terms = termsFromWire(priced.terms);

Step 4 — Sign and confirm

Sign the returned Terms (EIP-712), then POST /rfq/:id/confirm. On confirm the relayer anchors your quote on-chain.

const sig = await wallet.signTypedData({
  domain: { name: "CRX", version: "1", chainId: 14601, verifyingContract: CRX },
  types: TERMS_TYPES,
  primaryType: "Terms",
  message: terms,
});

await fetch(`${RELAYER}/rfq/${rfqId}/confirm`, {
  method: "POST",
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
  body: JSON.stringify({ signature: sig }),
});

Step 5 — Allocate once the taker binds

When the taker submits the bind, post your side's IM into the agreement's SCA.

await crx.write.allocate([maId, USDC, imMaker]);

What happens — and the branch

  • It binds. The taker fetched the bundle, signed the same Terms, and submitted the bind inside sigDeadline. You allocate; the position is live and marks against you and for you continuously.
  • It expires. The taker did not bind before the window closed. Your quote dies. Nothing is owed, nothing is posted. The next poll shows a fresh inbox.

Quote-lock holds while the quote is live: you stand behind it until its validity passes. If the market moves past your price, let it expire — do not re-price under a live quote.

Next: Get Data (~4 min) — read the book you just added to.