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:
Requester creates an RFQ request specifying desired trade parameters
Quoter (market maker) responds with a quote
Requester accepts the quote
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 } ` );
Request Creation Response
{
"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
Cancel Request
Cancel Quote
// 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