Skip to main content
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:
FieldTypeDescription
orderIdstringUnique 32-byte order identifier (0x-prefixed hex)
payerstringAddress paying the fee
feeAmountstringFee amount in USDC (6 decimals)
deadlinenumberUnix timestamp when authorization expires
signaturestringEIP-712 signature from payer

Additional Required Fields

When feeAuth is present, these fields are also required:
FieldTypeDescription
payerAddressstringMust match feeAuth.payer
signerAddressstringAddress 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
  },
});
OptionTypeDefaultDescription
feeBpsnumber25Fee in basis points (0.25%)
deadlineSecondsnumber3600How long fee auth is valid
affiliatestring0x0...0Affiliate address for fee split
escrowAddressstring(mainnet)Escrow contract address

Error Codes

Fee escrow-specific error codes:
CodeNameDescription
2001MISSING_FEE_ESCROW_FIELDSfeeAuth present but missing payerAddress or signerAddress
2002INVALID_PAYER_ADDRESSpayerAddress doesn’t match feeAuth.payer
2003FEE_TOO_LOWFee amount below minimum threshold
2004DEADLINE_EXPIREDFee authorization has expired
2005PULL_FEE_FAILEDOn-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

FunctionDescription
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:
  1. Monitors order status - Checks Polymarket for fill status every 5 minutes
  2. Distributes on fill - Calls distribute() when orders are fully or partially filled
  3. Refunds on cancel - Calls refund() when orders are cancelled
Order Lifecycle:

   ┌─────────────┐     ┌─────────────────┐     ┌──────────────┐
   │   pending   │────▶│ partially_filled │────▶│  distributed │
   └─────────────┘     └─────────────────┘     └──────────────┘
          │                    │
          │                    │ (cancel)
          ▼                    ▼
   ┌───────────────────────────────────────┐
   │              refunded                 │
   └───────────────────────────────────────┘

Distribution Timing

EventTiming
Order placedFee pulled immediately
Order filledDistribution within 5 minutes
Order cancelledRefund within 5 minutes
Partial fillProportional distribution within 5 minutes
Old orders (>24h) not foundAutomatic 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:
ParameterTypeDescription
payerstringFilter by payer address
signerstringFilter by signer address
statusstringFilter by status (pending, distributed, refunded, cancelled)
limitnumberMax results to return (default: 50, max: 100)
offsetnumberPagination 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

StatusDescription
pendingFee pulled, order placed, awaiting fill or cancel
distributedOrder filled, fee distributed to platform/affiliate
refundedOrder cancelled, fee refunded to payer
cancelledOrder 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.