Skip to main content

Overview

The Request for Quote (RFQ) system enables direct negotiation between traders for large orders. This is ideal for institutional traders who need better execution than public order books can provide.

How RFQ Works

The RFQ flow involves four steps between two parties:
  1. Requester creates an RFQ request specifying desired trade parameters
  2. Quoter (market maker) responds with a quote
  3. Requester accepts the quote
  4. Quoter approves the order, executing the trade
Quoters must be whitelisted to participate in RFQ. Contact Polymarket to get whitelisted as a market maker.

Prerequisites

For Requesters:
  • Standard CLOB client credentials
  • USDC balance (for buy requests) or token shares (for sell requests)
For Quoters:
  • Whitelisted address on the CLOB
  • Sufficient inventory to provide liquidity
  • Automated quote monitoring system (recommended)

Complete RFQ Example

This example demonstrates the full RFQ workflow with two parties:

Setup

First, initialize clients for both the requester and quoter:
import { ClobClient, Side, Chain, ApiKeyCreds } from "@polymarket/clob-client";
import { ethers } from "ethers";
import { config as dotenvConfig } from "dotenv";

dotenvConfig();

// Initialize requester
const requesterWallet = new ethers.Wallet(process.env.REQUESTER_PK!);
const requesterCreds: ApiKeyCreds = {
  key: process.env.REQUESTER_API_KEY!,
  secret: process.env.REQUESTER_SECRET!,
  passphrase: process.env.REQUESTER_PASS_PHRASE!,
};

// Initialize quoter
const quoterWallet = new ethers.Wallet(process.env.QUOTER_PK!);
const quoterCreds: ApiKeyCreds = {
  key: process.env.QUOTER_API_KEY!,
  secret: process.env.QUOTER_SECRET!,
  passphrase: process.env.QUOTER_PASS_PHRASE!,
};

const chainId = parseInt(process.env.CHAIN_ID || Chain.POLYGON.toString()) as Chain;
const host = process.env.CLOB_API_URL || "https://clob.polymarket.com";

const requesterClob = new ClobClient(host, chainId, requesterWallet, requesterCreds);
const quoterClob = new ClobClient(host, chainId, quoterWallet, quoterCreds);

// Get RFQ clients
const requesterClient = requesterClob.rfq;
const quoterClient = quoterClob.rfq;

Step 1: Create RFQ Request

The requester initiates the RFQ by specifying what they want to trade:
const REQUEST_PARAMS = {
  tokenID: "34097058504275310827233323421517291090691602969494795225921954353603704046623",
  price: 0.50,        // Desired price per token
  side: Side.BUY,     // BUY or SELL
  size: 40,           // Number of tokens
  tickSize: "0.01" as const,
};

console.log("[Step 1] Creating RFQ request...");
console.log(`  Token ID: ${REQUEST_PARAMS.tokenID}`);
console.log(`  Side: ${REQUEST_PARAMS.side}`);
console.log(`  Size: ${REQUEST_PARAMS.size}`);
console.log(`  Price: ${REQUEST_PARAMS.price}`);

const rfqRequestResponse = await requesterClient.createRfqRequest(
  {
    tokenID: REQUEST_PARAMS.tokenID,
    price: REQUEST_PARAMS.price,
    side: REQUEST_PARAMS.side,
    size: REQUEST_PARAMS.size,
  },
  { tickSize: REQUEST_PARAMS.tickSize }
);

if (rfqRequestResponse.error) {
  throw new Error(`Request creation failed: ${rfqRequestResponse.error}`);
}

const requestId = rfqRequestResponse.requestId!;
console.log(`✓ Request created: ${requestId}`);
{
  "requestId": "0x1a2b3c4d...",
  "tokenID": "34097058504275310827233323421517291090691602969494795225921954353603704046623",
  "side": "BUY",
  "size": "40",
  "price": "0.50",
  "status": "OPEN",
  "createdAt": "2024-03-04T12:00:00Z"
}

Step 2: Create Quote

The quoter responds with their quote. For a BUY request, the quoter sells (SELL side):
// For BUY request: quoter provides SELL quote
// For SELL request: quoter provides BUY quote
const QUOTE_PARAMS = {
  tokenID: "34097058504275310827233323421517291090691602969494795225921954353603704046623",
  price: 0.50,        // Quote price
  side: Side.SELL,    // Opposite of request side
  size: 40,           // Must match request size
  tickSize: "0.01" as const,
};

console.log("\n[Step 2] Creating quote...");
console.log(`  Quote price: ${QUOTE_PARAMS.price}`);

const rfqQuoteResponse = await quoterClient.createRfqQuote(
  {
    requestId: requestId,
    tokenID: QUOTE_PARAMS.tokenID,
    price: QUOTE_PARAMS.price,
    side: QUOTE_PARAMS.side,
    size: QUOTE_PARAMS.size,
  },
  { tickSize: QUOTE_PARAMS.tickSize }
);

if (rfqQuoteResponse.error) {
  throw new Error(`Quote creation failed: ${rfqQuoteResponse.error}`);
}

const quoteId = rfqQuoteResponse.quoteId!;
console.log(`✓ Quote created: ${quoteId}`);
{
  "quoteId": "0x5e6f7g8h...",
  "requestId": "0x1a2b3c4d...",
  "tokenID": "34097058504275310827233323421517291090691602969494795225921954353603704046623",
  "side": "SELL",
  "size": "40",
  "price": "0.50",
  "status": "ACTIVE",
  "expiresAt": "2024-03-04T12:05:00Z"
}

Step 3: Accept Quote

The requester reviews and accepts the quote:
const EXPIRATION_SECONDS = 3600; // 1 hour

console.log("\n[Step 3] Accepting quote...");

const acceptResult = await requesterClient.acceptRfqQuote({
  requestId: requestId,
  quoteId: quoteId,
  expiration: Math.floor(Date.now() / 1000) + EXPIRATION_SECONDS,
});

console.log("✓ Quote accepted");
console.log(acceptResult);
{
  "success": true,
  "requestId": "0x1a2b3c4d...",
  "quoteId": "0x5e6f7g8h...",
  "status": "ACCEPTED",
  "signature": "0x9a8b7c...",
  "expiration": 1709560800
}

Step 4: Approve Order

Finally, the quoter approves the order to execute the trade:
console.log("\n[Step 4] Approving order...");

const approveResult = await quoterClient.approveRfqOrder({
  requestId: requestId,
  quoteId: quoteId,
  expiration: Math.floor(Date.now() / 1000) + EXPIRATION_SECONDS,
});

console.log("✓ Order approved and executed");
console.log(approveResult);
{
  "success": true,
  "requestId": "0x1a2b3c4d...",
  "quoteId": "0x5e6f7g8h...",
  "orderID": "0xabc123...",
  "status": "MATCHED",
  "matchedAmount": "40",
  "executionPrice": "0.50",
  "transactionHash": "0xdef456..."
}

Complete Working Example

Here’s the full executable script:
import { ClobClient, Side, Chain, ApiKeyCreds } from "@polymarket/clob-client";
import { ethers } from "ethers";
import { config as dotenvConfig } from "dotenv";

dotenvConfig();

async function main() {
  // ============================================
  // Setup: Initialize both clients
  // ============================================
  const requesterWallet = new ethers.Wallet(process.env.REQUESTER_PK!);
  const quoterWallet = new ethers.Wallet(process.env.QUOTER_PK!);
  
  const chainId = parseInt(process.env.CHAIN_ID || Chain.POLYGON.toString()) as Chain;
  const host = process.env.CLOB_API_URL || "https://clob.polymarket.com";
  
  console.log("=" .repeat(60));
  console.log("RFQ Full Flow");
  console.log("=" .repeat(60));
  console.log(`Requester: ${await requesterWallet.getAddress()}`);
  console.log(`Quoter: ${await quoterWallet.getAddress()}`);
  console.log(`Chain ID: ${chainId}`);
  console.log("=" .repeat(60));
  
  const requesterCreds: ApiKeyCreds = {
    key: process.env.REQUESTER_API_KEY!,
    secret: process.env.REQUESTER_SECRET!,
    passphrase: process.env.REQUESTER_PASS_PHRASE!,
  };
  
  const quoterCreds: ApiKeyCreds = {
    key: process.env.QUOTER_API_KEY!,
    secret: process.env.QUOTER_SECRET!,
    passphrase: process.env.QUOTER_PASS_PHRASE!,
  };
  
  const requesterClob = new ClobClient(host, chainId, requesterWallet, requesterCreds);
  const quoterClob = new ClobClient(host, chainId, quoterWallet, quoterCreds);
  
  const requesterClient = requesterClob.rfq;
  const quoterClient = quoterClob.rfq;
  
  // ============================================
  // Step 1: Create RFQ Request
  // ============================================
  console.log("\n[Step 1] Creating RFQ request...");
  
  const rfqRequestResponse = await requesterClient.createRfqRequest(
    {
      tokenID: "34097058504275310827233323421517291090691602969494795225921954353603704046623",
      price: 0.50,
      side: Side.BUY,
      size: 40,
    },
    { tickSize: "0.01" }
  );
  
  if (rfqRequestResponse.error || !rfqRequestResponse.requestId) {
    throw new Error(`Request creation failed: ${rfqRequestResponse.error}`);
  }
  
  const requestId = rfqRequestResponse.requestId;
  console.log(`✓ Request created: ${requestId}`);
  
  // ============================================
  // Step 2: Create Quote
  // ============================================
  console.log("\n[Step 2] Creating quote...");
  
  const rfqQuoteResponse = await quoterClient.createRfqQuote(
    {
      requestId: requestId,
      tokenID: "34097058504275310827233323421517291090691602969494795225921954353603704046623",
      price: 0.50,
      side: Side.SELL,
      size: 40,
    },
    { tickSize: "0.01" }
  );
  
  if (rfqQuoteResponse.error || !rfqQuoteResponse.quoteId) {
    throw new Error(`Quote creation failed: ${rfqQuoteResponse.error}`);
  }
  
  const quoteId = rfqQuoteResponse.quoteId;
  console.log(`✓ Quote created: ${quoteId}`);
  
  // ============================================
  // Step 3: Accept Quote
  // ============================================
  console.log("\n[Step 3] Accepting quote...");
  
  const acceptResult = await requesterClient.acceptRfqQuote({
    requestId: requestId,
    quoteId: quoteId,
    expiration: Math.floor(Date.now() / 1000) + 3600,
  });
  
  console.log("✓ Quote accepted");
  console.log(acceptResult);
  
  // ============================================
  // Step 4: Approve Order
  // ============================================
  console.log("\n[Step 4] Approving order...");
  
  const approveResult = await quoterClient.approveRfqOrder({
    requestId: requestId,
    quoteId: quoteId,
    expiration: Math.floor(Date.now() / 1000) + 3600,
  });
  
  console.log("✓ Order approved and executed");
  console.log(approveResult);
  
  // ============================================
  // Summary
  // ============================================
  console.log("\n" + "=".repeat(60));
  console.log("RFQ FLOW COMPLETED SUCCESSFULLY!");
  console.log("=".repeat(60));
  console.log(`Request ID: ${requestId}`);
  console.log(`Quote ID:   ${quoteId}`);
  console.log("=".repeat(60));
}

main().catch(error => {
  console.error("\n✗ Error in RFQ flow:", error);
  if (error.response) {
    console.error("Response:", error.response.data);
  }
  process.exit(1);
});

Querying RFQ Data

Get Active Requests

const requests = await requesterClient.getRfqRequests({
  status: "OPEN",
  limit: 10,
});

console.log("Active requests:", requests);

Get Quotes for a Request

const quotes = await requesterClient.getRfqQuotes({
  requestId: requestId,
});

console.log("Available quotes:", quotes);

Get Best Quote

const bestQuote = await requesterClient.getBestRfqQuote({
  requestId: requestId,
});

console.log("Best available quote:", bestQuote);

Canceling RFQ Operations

// Requester cancels an RFQ request
const cancelResult = await requesterClient.cancelRfqRequest({
  requestId: requestId,
});

console.log("Request canceled:", cancelResult);

Error Handling

{
  "error": "Unauthorized quoter",
  "message": "Address is not whitelisted for RFQ"
}
Solution: Contact Polymarket to get whitelisted as a market maker.
{
  "error": "Quote expired",
  "message": "Quote is no longer valid"
}
Solution: Request a new quote from the quoter.
{
  "error": "Insufficient balance",
  "message": "Quoter does not have enough tokens"
}
Solution: Reduce the order size or find another quoter.

Best Practices

Set Reasonable Expirations

Use 1-hour expirations for standard trades. Shorter for time-sensitive operations.

Validate Quotes

Always verify quote parameters match your request before accepting.

Monitor Quote Quality

Compare RFQ quotes against public order book prices.

Handle Timeouts

Implement timeout logic for quotes that aren’t responded to quickly.

Next Steps

RFQ Guide

Learn advanced RFQ strategies and best practices

RFQ Client API

Detailed API reference for RFQ operations

Market Orders

Fast execution for smaller orders

WebSocket Streaming

Monitor RFQ events in real-time

Build docs developers (and LLMs) love