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.
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. |
When dstWalletAddress is omitted on redeem/sell, proceeds are delivered to
the caller’s evmEoa and no signature is currently needed.
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.
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
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
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
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' },
],
};
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
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).