> ## Documentation Index
> Fetch the complete documentation index at: https://docs.domeapi.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Fee Escrow

> Optional fee collection with the Order Router

<Warning>
  **BETA - DO NOT USE IN PRODUCTION**

  This 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.
</Warning>

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.

<Note>
  Fee Escrow is an **opt-in feature**. Orders without fee authorization work exactly as before.
</Note>

## Endpoints Quick Reference

| Method   | Endpoint                         | Description                                                                |
| -------- | -------------------------------- | -------------------------------------------------------------------------- |
| `POST`   | `/v1/polymarket/placeOrder`      | [Place order with fee escrow](#rest-endpoint)                              |
| `GET`    | `/v1/polymarket/orders/:orderId` | [Get order status](#get-escrow-order)                                      |
| `GET`    | `/v1/polymarket/orders`          | [List escrow orders](#list-escrow-orders)                                  |
| `DELETE` | `/v1/polymarket/orders/:orderId` | [Cancel order and refund](#cancel-escrow-order)                            |
| `POST`   | `/v1/polymarket/affiliate`       | [Set affiliate address](/order-router/user-settings#set-affiliate-address) |
| `GET`    | `/v1/polymarket/fees`            | [Get fee configuration](/order-router/user-settings)                       |
| `PUT`    | `/v1/polymarket/fees`            | [Update fee configuration](/order-router/user-settings)                    |

## 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`:

```typescript theme={null}
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

```typescript theme={null}
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

```json theme={null}
{
  "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

```json theme={null}
{
  "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:

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
// 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.

```bash theme={null}
# 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:**

```json theme={null}
{
  "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:

1. **Server-side config** (from API key settings) - highest priority
2. **Client-provided** (from `params.affiliate` in request)
3. **Default affiliate** (environment variable)
4. **Zero address** (all fees go to Dome)

<Tip>
  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.
</Tip>

## 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

```json theme={null}
{
  "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:

```typescript theme={null}
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

| 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

<Note>
  Distribution and refund transactions are executed by the Dome operator wallet. Users don't need to take any action after order placement.
</Note>

## 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:**

```json theme={null}
{
  "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:**

```json theme={null}
{
  "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:**

```json theme={null}
{
  "success": true,
  "escrowOrderId": "0x...",
  "polymarketOrderId": "abc123",
  "status": "cancelled",
  "refunded": "12500",
  "refundTxHash": "0x..."
}
```

<Note>
  The `POST /orders/:orderId/cancel` endpoint is also available for backward compatibility but `DELETE /orders/:orderId` is preferred.
</Note>

### 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

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "1",
  "error": {
    "code": -32000,
    "message": "Order not found"
  }
}
```

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "1",
  "error": {
    "code": -32001,
    "message": "Cannot cancel order with status: distributed"
  }
}
```

## Backward Compatibility

Orders without `feeAuth` work exactly as before:

```typescript theme={null}
// 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):

1. **Server-side config** - Affiliate address set via API key settings (`POST /v1/polymarket/affiliate`)
2. **Client-provided** - `affiliateAddress` parameter in the order request
3. **Default affiliate** - Environment-configured default (if any)
4. **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](mailto: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.
