- TypeScript
- Python
- API
Given a market, read its outcome token IDs:
const yesTokenId = market.outcomes.yes.tokenId!;
const noTokenId = market.outcomes.no.tokenId!;
Given a market, read its outcome token IDs:
if market.outcomes.yes.token_id is None or market.outcomes.no.token_id is None:
raise RuntimeError("Market token IDs not found")
yes_token_id = market.outcomes.yes.token_id
no_token_id = market.outcomes.no.token_id
Given a market object, its outcome token IDs are stored as a JSON-encoded
array:Parse the array, then select the outcome you want to trade:
{
"clobTokenIds": "[\"<yes_token_id>\", \"<no_token_id>\"]"
}
TOKEN_ID="<yes_token_id>"
Limit Orders
A limit order specifies the price at which you are willing to trade and can rest on the book until it fills, expires, or you cancel it. Use one when price control matters more than immediate execution. A limit order also defines how long any unfilled amount remains active:| Lifetime | Behavior | Use when |
|---|---|---|
| Good Till Cancelled (GTC) | Remains active until it fills or you cancel it. | The order has no deadline. |
| Good Till Date (GTD) | Remains active until the expiration time you specify. | The order should expire before a known event. |
GTD orders expire one minute before their stated expiration as a security
threshold. To set an effective lifetime of N seconds, use
now + 60 + N. In
addition, the expiration must be at least 3 minutes in the future — orders
expiring sooner are rejected — so the minimum effective lifetime is about two
minutes.Place a Limit Order
- TypeScript
- Python
- API
Given a
SecureClient, place the limit order and check its
response:1
Place the Order
First, call Both examples buy
placeLimitOrder() with the price and number of shares. The
price must follow the market’s minimum price increment, and the size must
meet its minimum order size. Omit expiration for a GTC order or provide a
Unix timestamp in seconds for a GTD order.import { OrderSide } from "@polymarket/client";
const response = await client.placeLimitOrder({
tokenId: yesTokenId,
side: OrderSide.BUY,
price: "0.52",
size: "10",
});
// response: OrderResponse
import { OrderSide } from "@polymarket/client";
const expiration = Math.floor(Date.now() / 1000) + 60 + 60 * 60;
const response = await client.placeLimitOrder({
tokenId: yesTokenId,
side: OrderSide.BUY,
price: "0.52",
size: "10",
expiration,
});
// response: OrderResponse
10 shares at a price of 0.52 USD per share. The GTD
order has an effective lifetime of one hour.2
Check the Response
Then, check An accepted response includes the order ID and one of these statuses. If
the order fills fully or partially, the
A rejected order returns
response.ok to determine whether the CLOB accepted the order:if (response.ok) {
console.log(response.orderId, response.status);
console.log(response.tradeIds, response.transactionsHashes);
} else {
console.error(response.code, response.message);
}
tradeIds collection identifies
the resulting trades. When available, transactionsHashes contains their
transaction hashes:| Status | Description |
|---|---|
live | The order is resting on the book. |
matched | The order matched immediately with resting liquidity. |
delayed | The order is marketable but subject to a matching delay. |
ok: false with a code and message.Given an
AsyncSecureClient, place the limit order and check
its response. The synchronous SecureClient provides the same method.1
Place the Order
First, call Both examples buy
place_limit_order() with the price and number of shares. The
price must follow the market’s minimum price increment, and the size must
meet its minimum order size. Omit expiration for a GTC order or provide a
Unix timestamp in seconds for a GTD order.response = await client.place_limit_order(
token_id=yes_token_id,
side="BUY",
price="0.52",
size="10",
)
# response: OrderResponse
import time
expiration = int(time.time()) + 60 + 60 * 60
response = await client.place_limit_order(
token_id=yes_token_id,
side="BUY",
price="0.52",
size="10",
expiration=expiration,
)
# response: OrderResponse
10 shares at a price of 0.52 USD per share. The GTD
order has an effective lifetime of one hour.2
Check the Response
Then, check An accepted response includes the order ID and one of these statuses. If
the order fills fully or partially, the
A rejected order returns
response.ok to determine whether the CLOB accepted the order:if response.ok:
print(response.order_id, response.status)
print(response.trade_ids, response.transactions_hashes)
else:
print(response.code, response.message)
trade_ids collection identifies
the resulting trades. When available, transactions_hashes contains their
transaction hashes:| Status | Description |
|---|---|
live | The order is resting on the book. |
matched | The order matched immediately with resting liquidity. |
delayed | The order is marketable but subject to a matching delay. |
ok=False with a code and message.Build the limit order from its price and size, then sign and submit it:
1
Read the Market Context
First, fetch the current order book to read the market’s trading
constraints and exchange type:Where:
curl "https://clob.polymarket.com/book?token_id=$TOKEN_ID"
{
"asset_id": "<yes_token_id>",
"bids": [{ "price": "0.50", "size": "40" }],
"asks": [{ "price": "0.54", "size": "30" }],
"min_order_size": "5",
"tick_size": "0.01",
"neg_risk": false
}
min_order_sizeis the minimum number of shares the CLOB accepts for an order.neg_riskindicates whether the market belongs to a negative-risk group and therefore uses the Neg Risk Exchange contract when signing and submitting the order.
2
Calculate the Order Amounts
Then, choose a price that conforms to
The two sides map the price and size differently:
tick_size and a share quantity that
meets min_order_size. Use the tick size to select the required precision:| Tick size | Price decimals | Size decimals | Amount decimals |
|---|---|---|---|
0.1 | 1 | 2 | 3 |
0.01 | 2 | 2 | 4 |
0.005 | 3 | 2 | 5 |
0.0025 | 4 | 2 | 6 |
0.001 | 3 | 2 | 5 |
0.0001 | 4 | 2 | 6 |
If your integration stores
tick_size for later orders, listen for the
tick_size_change event on the market
stream and replace the stored value
when it changes. The CLOB rejects an order whose price does not conform to the
market’s current tick size.- For a BUY,
makerAmountisprice × sizeUSD andtakerAmountis the number of shares. - For a SELL,
makerAmountis the number of shares andtakerAmountisprice × sizeUSD.
- Express the price using no more than Price decimals.
- Round the share quantity down to Size decimals.
- Calculate the USD amount. If it exceeds Amount decimals, round it up first to Amount decimals + 4, then down to Amount decimals.
3
Validate and Encode the Amounts
Next, verify that the rounded share quantity still meets
min_order_size,
then convert both amounts to six-decimal integers.For a BUY of 10 shares at 0.52, the USD amount is 5.20 and the share
quantity is above the minimum of 5. The encoded values are:{
"makerAmount": "5200000",
"takerAmount": "10000000"
}
4
Select the Exchange and Signing Path
Then, use the
Then resolve the remaining placeholders for the account’s wallet:
neg_risk value returned by the order book in the first step
to select the Exchange contract used as the EIP-712 verifying contract:| Market | exchange_address |
|---|---|
| Standard | 0xE111180000d2663C0091e4f400237545B87B996B |
| Negative risk | 0xe2222d279d744050d28e00520010520000310F59 |
| Wallet | signature_type | maker_address | order_signer_address |
|---|---|---|---|
| Deposit Wallet | 3 | Deposit Wallet address | Deposit Wallet address |
| Proxy Wallet | 1 | Proxy Wallet address | Account signer address |
| Safe Wallet | 2 | Safe Wallet address | Account signer address |
| EOA | 0 | EOA address | EOA address |
5
Create the Order Typed Data
Next, create the EIP-712 typed data for the selected wallet:Where:
{
"domain": {
"name": "Polymarket CTF Exchange",
"version": "2",
"chainId": 137,
"verifyingContract": "<exchange_address>"
},
"types": {
"Order": [
{ "name": "salt", "type": "uint256" },
{ "name": "maker", "type": "address" },
{ "name": "signer", "type": "address" },
{ "name": "tokenId", "type": "uint256" },
{ "name": "makerAmount", "type": "uint256" },
{ "name": "takerAmount", "type": "uint256" },
{ "name": "side", "type": "uint8" },
{ "name": "signatureType", "type": "uint8" },
{ "name": "timestamp", "type": "uint256" },
{ "name": "metadata", "type": "bytes32" },
{ "name": "builder", "type": "bytes32" }
],
"TypedDataSign": [
{ "name": "contents", "type": "Order" },
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "uint256" },
{ "name": "verifyingContract", "type": "address" },
{ "name": "salt", "type": "bytes32" }
]
},
"primaryType": "TypedDataSign",
"message": {
"contents": {
"salt": "479249096354",
"maker": "<maker_address>",
"signer": "<order_signer_address>",
"tokenId": "<yes_token_id>",
"makerAmount": "5200000",
"takerAmount": "10000000",
"side": 0,
"signatureType": 3,
"timestamp": "<unix_milliseconds>",
"metadata": "0x0000000000000000000000000000000000000000000000000000000000000000",
"builder": "0x0000000000000000000000000000000000000000000000000000000000000000"
},
"name": "DepositWallet",
"version": "1",
"chainId": 137,
"verifyingContract": "<maker_address>",
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
}
{
"domain": {
"name": "Polymarket CTF Exchange",
"version": "2",
"chainId": 137,
"verifyingContract": "<exchange_address>"
},
"types": {
"Order": [
{ "name": "salt", "type": "uint256" },
{ "name": "maker", "type": "address" },
{ "name": "signer", "type": "address" },
{ "name": "tokenId", "type": "uint256" },
{ "name": "makerAmount", "type": "uint256" },
{ "name": "takerAmount", "type": "uint256" },
{ "name": "side", "type": "uint8" },
{ "name": "signatureType", "type": "uint8" },
{ "name": "timestamp", "type": "uint256" },
{ "name": "metadata", "type": "bytes32" },
{ "name": "builder", "type": "bytes32" }
]
},
"primaryType": "Order",
"message": {
"salt": "479249096354",
"maker": "<maker_address>",
"signer": "<order_signer_address>",
"tokenId": "<yes_token_id>",
"makerAmount": "5200000",
"takerAmount": "10000000",
"side": 0,
"signatureType": 1,
"timestamp": "<unix_milliseconds>",
"metadata": "0x0000000000000000000000000000000000000000000000000000000000000000",
"builder": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
}
sideis0for a BUY and1for a SELL.saltis a fresh random value for each order. Because the request body serializes it as a JSON number, JavaScript integrations should keep it withinNumber.MAX_SAFE_INTEGER.
6
Sign the Order
Then, sign the typed data with the account signer. Deposit Wallets require
the raw signature to be wrapped for ERC-7739 validation. Proxy Wallets,
Safe Wallets, and EOAs submit the standard signature returned by signing
the Exchange
Order typed data directly.See the example below using Viem:import { privateKeyToAccount } from "viem/accounts";
const signer = privateKeyToAccount(process.env.SIGNER_PRIVATE_KEY);
const innerSignature = await signer.signTypedData(typedData);
const signature = wrapDepositWalletSignature(typedData, innerSignature);
import { privateKeyToAccount } from "viem/accounts";
const signer = privateKeyToAccount(process.env.SIGNER_PRIVATE_KEY);
const signature = await signer.signTypedData(typedData);
import { concatHex, encodeAbiParameters, keccak256, toHex } from "viem";
const ORDER_TYPE =
"Order(uint256 salt,address maker,address signer,uint256 tokenId,uint256 makerAmount,uint256 takerAmount,uint8 side,uint8 signatureType,uint256 timestamp,bytes32 metadata,bytes32 builder)";
const EIP712_DOMAIN_TYPE =
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)";
function wrapDepositWalletSignature(typedData, innerSignature) {
const order = typedData.message.contents;
const exchangeDomain = typedData.domain;
const appDomainSeparator = keccak256(
encodeAbiParameters(
[
{ type: "bytes32" },
{ type: "bytes32" },
{ type: "bytes32" },
{ type: "uint256" },
{ type: "address" },
],
[
keccak256(toHex(EIP712_DOMAIN_TYPE)),
keccak256(toHex(exchangeDomain.name)),
keccak256(toHex(exchangeDomain.version)),
BigInt(exchangeDomain.chainId),
exchangeDomain.verifyingContract,
],
),
);
const contentsHash = keccak256(
encodeAbiParameters(
[
{ type: "bytes32" },
{ type: "uint256" },
{ type: "address" },
{ type: "address" },
{ type: "uint256" },
{ type: "uint256" },
{ type: "uint256" },
{ type: "uint8" },
{ type: "uint8" },
{ type: "uint256" },
{ type: "bytes32" },
{ type: "bytes32" },
],
[
keccak256(toHex(ORDER_TYPE)),
BigInt(order.salt),
order.maker,
order.signer,
BigInt(order.tokenId),
BigInt(order.makerAmount),
BigInt(order.takerAmount),
order.side,
order.signatureType,
BigInt(order.timestamp),
order.metadata,
order.builder,
],
),
);
return concatHex([
innerSignature,
appDomainSeparator,
contentsHash,
toHex(ORDER_TYPE),
toHex(ORDER_TYPE.length, { size: 2 }),
]);
}
7
Submit the Order
Choose how long the unfilled order should remain active in the final request
body. For a GTC order, set
orderType to "GTC" and expiration to
"0". For a GTD order, set orderType to "GTD" and expiration to a
Unix timestamp in seconds.