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

# Polymarket Signing Guide

> How to produce the `signatureByEvmEoa` EIP-712 signature for Polymarket workflow endpoints.

Some Polymarket workflow endpoints accept a `signatureByEvmEoa` field — an
EIP-712 signature produced by the user's home EOA that authorizes the request.
This page describes the typed-data schemas, the signing flow, and a working
viem example.

## When a signature is required

| Endpoint                                    | Signature requirement                                                                                                                                         |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /workflows/polymarket/placeOrder`     | **Always required.** The signature gates every direct order.                                                                                                  |
| `POST /workflows/polymarket/redeemPosition` | **Required when** the request includes `dstWalletAddress` (proceeds going to a wallet other than the EOA). Omit `dstWalletAddress` to skip signing — for now. |
| `POST /workflows/polymarket/sellPosition`   | Same as `redeemPosition`.                                                                                                                                     |
| `POST /workflows/polymarket/withdraw`       | **Always required.** The signature authorizes the destination address.                                                                                        |

When `dstWalletAddress` is omitted on redeem/sell, proceeds are delivered to
the caller's `evmEoa` and no signature is currently needed.

<Warning>
  **Upcoming change.** `signatureByEvmEoa` + `expiration` will soon be required
  on **every** `redeemPosition` and `sellPosition` request, regardless of
  whether `dstWalletAddress` is set. Today the signature is only enforced when
  `dstWalletAddress` is present so existing integrations keep working — but
  partners should plan to sign every redeem/sell call ahead of the cutover.
</Warning>

## Expiration window

`expiration` is a Unix timestamp in **seconds**. The backend rejects:

* expirations in the past,
* expirations more than **300 seconds** (5 minutes) in the future,
* non-integer or non-finite values.

Compute it close to send time, e.g. `Math.floor(Date.now() / 1000) + 60`.

## Typed-data schemas

All three schemas use the same EIP-712 domain shape — `version: '1'`,
`chainId: 137` (Polygon). Only the domain `name` and the `primaryType` change.

Address- and chainId-shaped fields are signed as `string` so absent optionals
can be represented as the empty string `""`. When an optional is omitted from
the request body, sign it as `""` (not `"0x0000..."` or `"0"`).

### `PlaceOrder` — used by `placeOrder`

```ts theme={null}
const domain = {
  name: 'BoxPolymarketPlaceOrder',
  version: '1',
  chainId: 137,
};

const types = {
  PlaceOrder: [
    { name: 'tokenID', type: 'string' },
    { name: 'side', type: 'string' },
    { name: 'orderType', type: 'string' },
    { name: 'amount', type: 'uint256' },
    { name: 'tickSize', type: 'string' },
    { name: 'negRisk', type: 'bool' },
    { name: 'slippage', type: 'uint256' },
    { name: 'expiration', type: 'uint256' },
  ],
};
```

### `RedeemPosition` — used by `redeemPosition`

```ts theme={null}
const domain = {
  name: 'BoxPolymarketRedeemPosition',
  version: '1',
  chainId: 137,
};

const types = {
  RedeemPosition: [
    { name: 'conditionId', type: 'string' },
    { name: 'assetId', type: 'string' },
    { name: 'dstTokenAddress', type: 'string' },
    { name: 'dstTokenChainId', type: 'string' },
    { name: 'dstWalletAddress', type: 'string' },
    { name: 'expiration', type: 'uint256' },
  ],
};
```

### `SellPosition` — used by `sellPosition`

```ts theme={null}
const domain = {
  name: 'BoxPolymarketSellPosition',
  version: '1',
  chainId: 137,
};

const types = {
  SellPosition: [
    { name: 'proxyWallet', type: 'string' },
    { name: 'tokenID', type: 'string' },
    { name: 'side', type: 'string' },
    { name: 'orderType', type: 'string' },
    { name: 'tickSize', type: 'string' },
    { name: 'negRisk', type: 'bool' },
    { name: 'amount', type: 'uint256' },
    { name: 'slippage', type: 'uint256' },
    { name: 'dstTokenAddress', type: 'string' },
    { name: 'dstTokenChainId', type: 'string' },
    { name: 'dstWalletAddress', type: 'string' },
    { name: 'expiration', type: 'uint256' },
  ],
};
```

### `Withdraw` — used by `withdraw`

```ts theme={null}
const domain = {
  name: 'BoxPolymarketWithdraw',
  version: '1',
  chainId: 137,
};

const types = {
  Withdraw: [
    { name: 'dstTokenAddress', type: 'string' },
    { name: 'dstTokenChainId', type: 'string' },
    { name: 'dstWalletAddress', type: 'string' },
    { name: 'expiration', type: 'uint256' },
  ],
};
```

## Building the message

A few field-specific rules apply when assembling the `message` object:

* **Amount scaling.** For `PlaceOrder` and `SellPosition`, `amount` is the USD
  amount scaled to 6 decimals: `BigInt(Math.floor(amount * 1_000_000))`. The
  request body still carries the un-scaled number (e.g. `2.5`); the scaling
  applies only to the signed value.
* **uint256 fields.** `slippage` and `expiration` must be `bigint` when signing
  (`BigInt(slippage)`, `BigInt(expiration)`).
* **Empty-string optionals.** Map omitted optionals to `""`:
  * `dstToken` absent → `dstTokenAddress: ""`, `dstTokenChainId: ""`.
  * `proxyWallet` absent (sell) → `proxyWallet: ""`.
* **Chain ID as string.** When `dstToken` is present, sign
  `dstTokenChainId: String(dstToken.chainId)`.

## Example — sign and submit a `placeOrder`

```ts theme={null}
import { privateKeyToAccount } from 'viem/accounts';

const account = privateKeyToAccount(privateKey);

const orderRequest = {
  side: 'BUY',
  tokenID: '902348896171994436737091508776752646...',
  orderType: 'FOK',
  amount: 2,
  tickSize: '0.01',
  negRisk: true,
  slippage: 100,
};

const expiration = Math.floor(Date.now() / 1000) + 60; // 60s in the future

const signatureByEvmEoa = await account.signTypedData({
  domain: {
    name: 'BoxPolymarketPlaceOrder',
    version: '1',
    chainId: 137,
  },
  types: {
    PlaceOrder: [
      { name: 'tokenID', type: 'string' },
      { name: 'side', type: 'string' },
      { name: 'orderType', type: 'string' },
      { name: 'amount', type: 'uint256' },
      { name: 'tickSize', type: 'string' },
      { name: 'negRisk', type: 'bool' },
      { name: 'slippage', type: 'uint256' },
      { name: 'expiration', type: 'uint256' },
    ],
  },
  primaryType: 'PlaceOrder',
  message: {
    tokenID: orderRequest.tokenID,
    side: orderRequest.side,
    orderType: orderRequest.orderType,
    amount: BigInt(Math.floor(orderRequest.amount * 1_000_000)),
    tickSize: orderRequest.tickSize,
    negRisk: orderRequest.negRisk,
    slippage: BigInt(orderRequest.slippage),
    expiration: BigInt(expiration),
  },
});

await fetch('https://api-v2.swaps.xyz/api/workflows/polymarket/placeOrder', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': API_KEY,
  },
  body: JSON.stringify({
    evmEoa: account.address,
    orderRequest,
    expiration,
    signatureByEvmEoa,
  }),
});
```

The same pattern applies to `redeemPosition` and `sellPosition` — swap the
domain `name`, `primaryType`, and `types` for the matching schema and include
the additional `dstTokenAddress`, `dstTokenChainId`, and `dstWalletAddress`
fields (using `""` when their request-body counterpart is omitted).
