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.
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 |
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.
GET /polymarket/escrow/order/: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 /polymarket/escrow/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 status.
POST /polymarket/escrow/cancel/:orderId
Response:
{
"jsonrpc": "2.0",
"id": "1",
"result": {
"success": true,
"message": "Order cancelled and refund initiated",
"orderId": "0x...",
"refundTxHash": "0x..."
}
}
Delete Escrow Order
Cancel an escrow order and remove it from the database. Only callable for orders in pending status.
DELETE /polymarket/escrow/order/:orderId
Response:
{
"jsonrpc": "2.0",
"id": "1",
"result": {
"success": true,
"message": "Order cancelled and deleted",
"orderId": "0x...",
"refundTxHash": "0x..."
}
}
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 be at least 0.01% of order size to prevent dust.
Q: How do I become an affiliate?
Contact [email protected] 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.