BETA - DO NOT USE IN PRODUCTIONThis feature is currently in beta testing. The API and contracts may change without notice. Do not use fee escrow in production applications until this warning is removed.
The Fee Escrow feature allows platforms to collect fees on orders placed through the Order Router. Fees are held in escrow and distributed upon order fills, with automatic refunds on cancellations.
Fee Escrow is an opt-in feature. Orders without fee authorization work exactly as before.
Endpoints Quick Reference
| Method | Endpoint | Description |
|---|
POST | /v1/polymarket/placeOrder | Place order with fee escrow |
GET | /v1/polymarket/orders/:orderId | Get order status |
GET | /v1/polymarket/orders | List escrow orders |
DELETE | /v1/polymarket/orders/:orderId | Cancel order and refund |
POST | /v1/polymarket/affiliate | Set affiliate address |
GET | /v1/polymarket/fees | Get fee configuration |
PUT | /v1/polymarket/fees | Update fee configuration |
How It Works
1. User signs a fee authorization (EIP-712)
2. SDK includes fee auth in order request
3. Dome server pulls fee to escrow contract
4. Order placed on Polymarket
5. On fill: Fee distributed to platform + affiliate
6. On cancel: Remaining fee refunded to user
Prerequisites
@dome-api/sdk version 2.0.0 or later
- USDC.e approval for the escrow contract on Polygon
- Platform integration with Dome’s fee system
Quick Start
Using the SDK
Use PolymarketRouterWithEscrow instead of PolymarketRouter:
import { PolymarketRouterWithEscrow } from '@dome-api/sdk';
const router = new PolymarketRouterWithEscrow({
apiKey: process.env.DOME_API_KEY,
chainId: 137,
escrow: {
feeBps: 25, // 0.25% fee
},
});
// Place orders - fee escrow is automatic
const order = await router.placeOrder(
{
userId: 'user-123',
marketId: '104173...',
side: 'buy',
size: 100,
price: 0.50,
signer,
},
credentials
);
// Response includes escrow info
console.log(order.escrowOrderId); // Unique escrow order ID
console.log(order.feePulled); // true
console.log(order.feeAmount); // "12500" (0.0125 USDC for $50 order)
console.log(order.pullFeeTxHash); // "0x..." transaction hash
Skip Escrow for Specific Orders
const order = await router.placeOrder(
{
userId: 'user-123',
marketId: '104173...',
side: 'buy',
size: 100,
price: 0.50,
signer,
skipEscrow: true, // Skip fee escrow for this order
},
credentials
);
REST Endpoint
If using the REST API directly, include feeAuth in your request:
Request with Fee Authorization
{
"jsonrpc": "2.0",
"method": "placeOrder",
"id": "unique-request-id",
"params": {
"signedOrder": {
"salt": "123456789",
"maker": "0x...",
"signer": "0x...",
"taker": "0x0000000000000000000000000000000000000000",
"tokenId": "104173...",
"makerAmount": "50000000",
"takerAmount": "100000000",
"expiration": "0",
"nonce": "0",
"feeRateBps": "0",
"side": "BUY",
"signatureType": 0,
"signature": "0x..."
},
"orderType": "GTC",
"credentials": {
"apiKey": "your-polymarket-api-key",
"apiSecret": "your-polymarket-api-secret",
"apiPassphrase": "your-polymarket-passphrase"
},
"clientOrderId": "unique-client-order-id",
"feeAuth": {
"orderId": "0x...",
"payer": "0x...",
"feeAmount": "12500",
"deadline": 1704067200,
"signature": "0x..."
},
"payerAddress": "0x...",
"signerAddress": "0x..."
}
}
Response with Escrow Info
{
"jsonrpc": "2.0",
"id": "unique-request-id",
"result": {
"success": true,
"orderId": "order-id-from-polymarket",
"clientOrderId": "unique-client-order-id",
"status": "LIVE",
"orderHash": "0x...",
"transactionHashes": ["0x..."],
"metadata": {
"region": "us-east-1",
"latencyMs": 125,
"timestamp": 1704067200000
},
"escrowOrderId": "0x...",
"feePulled": true,
"feeAmount": "12500",
"pullFeeTxHash": "0x..."
}
}
Fee Authorization Structure
The feeAuth object contains:
| Field | Type | Description |
|---|
orderId | string | Unique 32-byte order identifier (0x-prefixed hex) |
payer | string | Address paying the fee |
feeAmount | string | Fee amount in USDC (6 decimals) |
deadline | number | Unix timestamp when authorization expires |
signature | string | EIP-712 signature from payer |
Additional Required Fields
When feeAuth is present, these fields are also required:
| Field | Type | Description |
|---|
payerAddress | string | Must match feeAuth.payer |
signerAddress | string | Address that signed the order |
EIP-712 Signing
The fee authorization uses EIP-712 typed structured data:
const domain = {
name: 'DomeFeeEscrow',
version: '1',
chainId: 137,
verifyingContract: '0x989876083eD929BE583b8138e40D469ea3E53a37',
};
const types = {
FeeAuthorization: [
{ name: 'orderId', type: 'bytes32' },
{ name: 'payer', type: 'address' },
{ name: 'feeAmount', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
};
const value = {
orderId: '0x...',
payer: '0x...',
feeAmount: BigInt('12500'),
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
};
const signature = await signer.signTypedData(domain, types, value);
SDK Utilities
The SDK provides utilities for working with fee escrow:
Generate Order ID
import { generateOrderId, parseUsdc } from '@dome-api/sdk';
const orderId = generateOrderId({
chainId: 137,
userAddress: '0x...',
marketId: '104173...',
side: 'buy',
size: parseUsdc(50), // $50 in USDC
price: 0.50,
timestamp: Date.now(),
});
Create Fee Authorization
import { createFeeAuthorization, calculateFee, parseUsdc } from '@dome-api/sdk';
const orderSize = parseUsdc(50); // $50
const feeAmount = calculateFee(orderSize, BigInt(25)); // 0.25% = $0.125
const feeAuth = createFeeAuthorization(
orderId,
payerAddress,
feeAmount,
3600 // 1 hour deadline
);
Sign Fee Authorization
import { signFeeAuthorization } from '@dome-api/sdk';
const signedAuth = await signFeeAuthorization(
signer,
'0x989876083eD929BE583b8138e40D469ea3E53a37', // escrow contract
feeAuth,
137 // chainId
);
Escrow Configuration
Configure escrow settings when initializing the router:
const router = new PolymarketRouterWithEscrow({
apiKey: process.env.DOME_API_KEY,
chainId: 137,
escrow: {
feeBps: 25, // Fee in basis points (25 = 0.25%)
deadlineSeconds: 3600, // Authorization validity (default: 1 hour)
affiliate: '0x...', // Optional affiliate address for fee sharing
},
});
| Option | Type | Default | Description |
|---|
feeBps | number | 25 | Fee in basis points (0.25%) |
deadlineSeconds | number | 3600 | How long fee auth is valid |
affiliate | string | 0x0...0 | Affiliate address for fee split |
escrowAddress | string | (mainnet) | Escrow contract address |
Affiliate Configuration
Affiliates can earn a share of fees collected through the escrow system. There are two ways to configure affiliate addresses:
Option 1: Per-Request (SDK)
Pass the affiliate address when initializing the router or per-order:
// Set default affiliate for all orders
const router = new PolymarketRouterWithEscrow({
apiKey: process.env.DOME_API_KEY,
chainId: 137,
escrow: {
feeBps: 25,
affiliate: '0xYourAffiliateAddress...',
},
});
// Or pass per-order via REST API in request body
{
"params": {
...
"affiliate": "0xYourAffiliateAddress..."
}
}
Option 2: Server-Side (API Key Config)
For a persistent affiliate address tied to your API key, use the User Settings API. This ensures all orders from your API key automatically use your affiliate address, regardless of what the SDK sends.
# Set affiliate address for your API key
curl -X POST https://api.domeapi.com/v1/polymarket/affiliate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"affiliateAddress": "0xYourAffiliateAddress..."
}'
Response:
{
"success": true,
"message": "Affiliate address updated successfully",
"affiliateAddress": "0x...",
"note": "Changes are live immediately via Redis"
}
Priority Order
When resolving the affiliate address, the system uses this priority:
- Server-side config (from API key settings) - highest priority
- Client-provided (from
params.affiliate in request)
- Default affiliate (environment variable)
- Zero address (all fees go to Dome)
Server-side affiliate configuration is recommended for affiliates who want to ensure they always receive their share, as it cannot be overridden by the client.
Error Codes
Fee escrow-specific error codes:
| Code | Name | Description |
|---|
2001 | MISSING_FEE_ESCROW_FIELDS | feeAuth present but missing payerAddress or signerAddress |
2002 | INVALID_PAYER_ADDRESS | payerAddress doesn’t match feeAuth.payer |
2003 | FEE_TOO_LOW | Fee amount below minimum threshold |
2004 | DEADLINE_EXPIRED | Fee authorization has expired |
2005 | PULL_FEE_FAILED | On-chain pullFee() transaction failed |
Error Response Example
{
"jsonrpc": "2.0",
"id": "unique-request-id",
"error": {
"code": 2004,
"message": "DEADLINE_EXPIRED",
"data": {
"reason": "Fee authorization deadline has passed"
}
}
}
Escrow Contract
The fee escrow contract is deployed on Polygon mainnet:
Address: 0x989876083eD929BE583b8138e40D469ea3E53a37
Contract Functions
| Function | Description |
|---|
pullFee(feeAuth, affiliate) | Pull fee from user to escrow |
distribute(orderId, amount) | Distribute fee on order fill |
refund(orderId) | Refund remaining fee on cancel |
USDC Approval
Users must approve the escrow contract to spend their USDC.e:
const USDC_ADDRESS = '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174';
const ESCROW_ADDRESS = '0x989876083eD929BE583b8138e40D469ea3E53a37';
// Approve escrow contract to spend USDC
await usdcContract.approve(ESCROW_ADDRESS, ethers.constants.MaxUint256);
Fee Distribution
After an order is placed with fee escrow, the Dome server automatically handles fee distribution and refunds based on order status.
How Distribution Works
The server runs a background process that:
- Monitors order status - Checks Polymarket for fill status every 5 minutes
- Distributes on fill - Calls
distribute() when orders are fully or partially filled
- Refunds on cancel - Calls
refund() when orders are cancelled
Order Lifecycle:
┌─────────────┐ ┌─────────────────┐ ┌──────────────┐
│ pending │────▶│ partially_filled │────▶│ distributed │
└─────────────┘ └─────────────────┘ └──────────────┘
│ │
│ │ (cancel)
▼ ▼
┌───────────────────────────────────────┐
│ refunded │
└───────────────────────────────────────┘
Distribution Timing
| Event | Timing |
|---|
| Order placed | Fee pulled immediately |
| Order filled | Distribution within 5 minutes |
| Order cancelled | Refund within 5 minutes |
| Partial fill | Proportional distribution within 5 minutes |
| Old orders (>24h) not found | Automatic refund |
Partial Fills
For partially filled orders, fees are distributed proportionally:
- If 50% of an order fills → 50% of fee is distributed
- Remaining fee stays in escrow until fill or cancel
- On cancel, remaining fee is refunded to user
Distribution and refund transactions are executed by the Dome operator wallet. Users don’t need to take any action after order placement.
Order Management Endpoints
The following REST endpoints are available for managing escrow orders.
Get Escrow Order
Retrieve details of a specific escrow order by ID (escrowOrderId or polymarketOrderId).
GET /v1/polymarket/orders/:orderId
Response:
{
"jsonrpc": "2.0",
"id": "1",
"result": {
"order": {
"orderId": "0x...",
"payer": "0x...",
"signer": "0x...",
"feeAmount": "12500",
"affiliate": "0x...",
"status": "pending",
"polymarketOrderId": "abc123",
"pullFeeTxHash": "0x...",
"distributeTxHash": null,
"refundTxHash": null,
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
}
}
List Escrow Orders
Retrieve a list of escrow orders with optional filters.
GET /v1/polymarket/orders
Query Parameters:
| Parameter | Type | Description |
|---|
payer | string | Filter by payer address |
signer | string | Filter by signer address |
status | string | Filter by status (pending, distributed, refunded, cancelled) |
limit | number | Max results to return (default: 50, max: 100) |
offset | number | Pagination offset (default: 0) |
Response:
{
"jsonrpc": "2.0",
"id": "1",
"result": {
"orders": [
{
"orderId": "0x...",
"payer": "0x...",
"signer": "0x...",
"feeAmount": "12500",
"affiliate": "0x...",
"status": "pending",
"polymarketOrderId": "abc123",
"pullFeeTxHash": "0x...",
"distributeTxHash": null,
"refundTxHash": null,
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
}
Cancel Escrow Order
Cancel an escrow order and initiate a refund. Only callable for orders in pending or open status.
DELETE /v1/polymarket/orders/:orderId
Response:
{
"success": true,
"escrowOrderId": "0x...",
"polymarketOrderId": "abc123",
"status": "cancelled",
"refunded": "12500",
"refundTxHash": "0x..."
}
The POST /orders/:orderId/cancel endpoint is also available for backward compatibility but DELETE /orders/:orderId is preferred.
Order Status Values
| Status | Description |
|---|
pending | Fee pulled, order placed, awaiting fill or cancel |
distributed | Order filled, fee distributed to platform/affiliate |
refunded | Order cancelled, fee refunded to payer |
cancelled | Order manually cancelled via cancel endpoint |
Error Responses
{
"jsonrpc": "2.0",
"id": "1",
"error": {
"code": -32000,
"message": "Order not found"
}
}
{
"jsonrpc": "2.0",
"id": "1",
"error": {
"code": -32001,
"message": "Cannot cancel order with status: distributed"
}
}
Backward Compatibility
Orders without feeAuth work exactly as before:
// No feeAuth = no fee escrow
const order = await router.placeOrder(
{
userId: 'user-123',
marketId: '104173...',
side: 'buy',
size: 100,
price: 0.50,
signer,
// No feeAuth, payerAddress, or signerAddress
},
credentials
);
// Response has no escrow fields
console.log(order.escrowOrderId); // undefined
FAQ
Q: Is fee escrow required?
No, it’s opt-in. Orders without feeAuth work normally.
Q: What happens if the order is cancelled?
The fee is refunded to the payer via the escrow contract.
Q: What’s the minimum fee?
Fees must meet both requirements:
- At least 0.01% of order size (to prevent dust)
- At least $0.01 USDC minimum
Q: How is the affiliate address resolved?
When processing an order, the system resolves the affiliate address using this priority (highest first):
- Server-side config - Affiliate address set via API key settings (
POST /v1/polymarket/affiliate)
- Client-provided -
affiliateAddress parameter in the order request
- Default affiliate - Environment-configured default (if any)
- Zero address - If none of the above, all fees go to Dome
Server-side configuration always takes priority, ensuring platforms receive their fee share regardless of what the client sends.
Q: How do I become an affiliate?
Contact support@domeapi.com to set up affiliate fee sharing.
Q: Can I use a different escrow contract?
Custom escrow contracts can be configured - contact support for enterprise setups.