Polymarket
A prediction market on the Polygon chain. The exchange object places orders and reads order books, orders, the balance and the account's fills through Polymarket's CLOB (central limit order book) API, reads market information through the Gamma API, and reads positions and market trades through the data API; redeem, merge and split are submitted as on-chain transactions through Polymarket's gas-free relayer. Everything is REST polling, with no market data or order push.
Names and Configuration
exchange.GetName(): Futures_Polymarket (Prediction market); aliases: Futures_Polymarket.
| Field | Required | Description |
|---|---|---|
| Deposit Wallet Address | Yes | The Polymarket deposit wallet (POLY_1271 smart-contract wallet) address, i.e. the wallet your Polymarket funds are deposited in; it must be a valid 0x address or startup fails. Orders are placed with it as the maker, positions and account fills are queried for it, and redeem/merge/split require it to be deployed on chain. |
| Wallet Private Key | Yes | Private key for API authentication and order signing. Email users: export from reveal.magic.link/polymarket; Browser wallet users: use your MetaMask private key (keep it safe and never share) |
| Builder API Key | Yes | The Polymarket Builder API Key. (more below the table) |
| Builder API Secret | Yes | The Polymarket Builder API Secret, used with BuilderKey and BuilderPassphrase for relayer authentication. |
| Builder API Passphrase | Yes | The Polymarket Builder API Passphrase, used with BuilderKey and BuilderSecret for relayer authentication. |
The rest of the longer descriptions in the table:
- Builder API Key: It and
BuilderSecretandBuilderPassphraseare all required (startup fails if any is missing) and authenticate to the relayer for redeem, merge and split; the CLOB API credentials used for orders are derived from the private key automatically and do not use these three.
Trading Pairs and Contract Codes
Pair examples: will-btc-100k_USDC
The trading pair is the market's identifier (slug) plus _USDC; the quote currency can only be USDC. The slug is case-sensitive, and exchange.SetCurrency() keeps it as given without upper-casing. Use the slug of the market, not of the event: every candidate of a multi-outcome event (e.g. "who will win the election") is a separate binary market with its own slug.
| Contract code | Description |
|---|---|
Yes | The outcome "Yes"; most markets have the two outcomes Yes and No. |
No | The outcome "No". |
Up | An outcome of up/down markets (such as short-term crypto up/down markets); the other one is Down. |
Down | An outcome of up/down markets. |
Prediction markets have no perpetual/delivery distinction and no default contract: the contract code is the name of one of the market's outcomes, and every market has exactly two outcomes (sports and similar markets may use team names). Spaces in an outcome name are replaced with - (the outcome "Donald Trump" is written Donald-Trump), so a full instrument code looks like will-btc-100k_USDC.Yes. Outcome names that themselves contain - or . are written as they are (the outcome "Non-Fed" is Non-Fed, "U.S. Fed" is U.S.-Fed). Orders and market data match the outcome name case-insensitively, but exchange.GetPositions() compares instrument codes exactly and redeem matches the position's instrument code, so copy the spelling returned by exchange.GetMarkets() and exchange.GetPositions().
exchange.SetContractType() only checks the form (not empty, no whitespace or .); swap and similar codes are not rejected on the spot but fail with "outcome not found" on later calls.
Finding instrument codes: exchange.GetMarkets() has one row per outcome, keyed by the full instrument code; exchange.IO("gamma", "/markets/slug/<market slug>") shows a market's outcomes, and exchange.IO("gamma", "/events/slug/<event slug>") lists the markets of an event.
The Symbol (exchange-native code) of each exchange.GetMarkets() row is that outcome's CLOB token ID; it is for cross-referencing the exchange API only and cannot be used as a contract code.
Exchange-specific IO Commands
| Command | Arguments | Description | Returns |
|---|---|---|---|
proxyWalletAddress | — | Get the configured deposit wallet address (EIP-55 checksum form). | The address string. |
redeem | symbol[, wait] | After the market resolves, redeem the deposit wallet's shares of that market for USDC (both outcomes at once, gas-free via the relayer). (more below the table) | With wait true, the relayer transaction record after confirmation (state is STATE_CONFIRMED); with false, {"transactionID": "..."} at once, without waiting or refreshing the balance cache. (more below the table) |
merge | symbol[, amount[, wait]] | Merge equal amounts of both outcomes' shares back into USDC (gas-free via the relayer). (more below the table) | As for redeem. |
split | symbol, amount[, wait] | Split USDC into equal amounts of both outcomes' shares (gas-free via the relayer). (more below the table) | As for redeem. |
batchOrders | orders | Batch order placement; orders is an array of 1-15 order objects, all signed and submitted in one request and not written to the trade log. (more below the table) | The raw exchange response array, one item per entry of orders (accepted items carry an orderID, rejected items an errorMsg). When only some orders are rejected the array is still returned and each rejected item is also logged as an error; when all are rejected or the response is malformed, the call fails and returns null. |
markets_cursor | [string] | Query or set the paging cursor of exchange.GetMarkets() (an empty string clears it, back to the first page); next advances the cursor to the next page; see the notes for usage. | The current cursor string (after setting or advancing); an empty string after advancing past the last page. next fails if no page has been fetched at the current cursor yet. |
tickers_cursor | [string] | Like markets_cursor, for exchange.GetTickers(); the two cursors are independent. | As for markets_cursor. |
base | [url] / kind, url | Without arguments, returns the current CLOB API address; with one URL, switches the CLOB address (used for orders, the balance, account fills and order books); this is what exchange.GetBase() and exchange.SetBase() do. (more below the table) | A string without arguments or with one URL (the current address or the address before the change); null for the kind, url form. |
api | method, path[, params[, raw]] | Sends a request to the CLOB, L2-signed by default; for GET the params become the query string, for other methods the JSON body; path may be a full URL (e.g. a data API address); a raw JSON object (or JSON object string) is merged into params, any other string is sent verbatim as the query string or body, and false sends the request unsigned. | The raw exchange response (JSON is parsed); an error and null when the request fails. |
gamma | path | Sends an unsigned GET request to the Gamma API (market information); path includes the query string, e.g. (more below the table) | The raw exchange response (JSON is parsed); null when the request fails. |
clob | path | Sends an unsigned GET request to the CLOB API, e.g. exchange.IO("clob", "/book?token_id=..."); use exchange.IO("api", ...) for private endpoints that need a signature. | The raw exchange response (JSON is parsed); null when the request fails. |
data | path | Sends an unsigned GET request to the data API (positions, market trades), e.g. exchange.IO("data", "/trades?market=<conditionId>"); the address follows exchange.IO("base", "data", url). | The raw exchange response (JSON is parsed); null when the request fails. |
The rest of the longer descriptions in the table:
redeem:symbolmust exactly equal the instrument code of a position inexchange.GetPositions()that is redeemable (redeemableistruein the position's raw data) and holds more than 0 shares; the deposit wallet must be deployed on chain.waitdefaults totrue; the relayer is polled every 2 seconds for up to about 2 minutes, and the CLOB balance cache is refreshed after confirmation. An object{symbol, wait}is also accepted.redeemreturns: A transaction reported as failed by the relayer (STATE_FAILED,STATE_INVALID), a wait timeout or a failed cache refresh is an error whose message carries thetransactionID; in the last two cases the transaction has been submitted (a timed-out one may still land later), so checkexchange.GetPositions()andexchange.GetAccount()before retrying. After a wait timeout, calling the same operation on the same market again fails without submitting until that transaction is confirmed or has failed.merge:symbolis the instrument code of either outcome of the market;amountis the number of shares (truncated to 6 decimals), and 0 or omitted takes the smaller of the two outcome positions.waitis as forredeem. An object{symbol, amount, wait}is also accepted.split:symbolis the instrument code of either outcome of the market;amountis the USDC amount, greater than 0 (truncated to 6 decimals), givingamountshares of each outcome.waitis as forredeem. An object{symbol, amount, wait}is also accepted.batchOrders: Each object hassymbol(full instrument code),side(buy/sell),price(0 or below is a market order),amount(shares), and an optionaloptionobject with the same keys as the order parameters (orderType,expiration,postOnly,deferExec,slippage; see the notes).base:exchange.IO("base", kind, url)switches the address of a service, wherekindisgamma(market information and lists),data(positions and market trades),relayer(redeem, merge, split) orclob(the CLOB address, as in the one-argument form). The one-argument form accepts only addresses starting withhttp://orhttps://; anything else fails.gamma:exchange.IO("gamma", "/markets/slug/will-btc-100k"); the address followsexchange.IO("base", "gamma", url). The command name may also be a full service address with the path as the second argument, e.g.exchange.IO("https://gamma-api.polymarket.com", "/markets/slug/will-btc-100k").
For the common command currency see exchange.IO.
Common Usage
Find a market with the Gamma API and read its order book and recent trades
javascript
function main() {
// The open market with the largest 24h volume (/markets lists markets, /events events)
var path = "/markets?limit=1&closed=false&order=volume24hr&ascending=false"
var list = exchange.IO("gamma", path)
var m = exchange.IO("gamma", "/markets/slug/" + list[0].slug)
// outcomes and clobTokenIds are JSON strings in the Gamma response
var outcomes = JSON.parse(m.outcomes)
var tokenIds = JSON.parse(m.clobTokenIds)
// Instrument code: market slug + _USDC. + outcome name (spaces replaced with -)
var symbol = m.slug + "_USDC." + outcomes[0].replace(/ /g, "-")
Log("Symbol:", symbol, "tick:", m.orderPriceMinTickSize, "min size:", m.orderMinSize)
var depth = exchange.GetDepth(symbol)
Log("Best bid:", depth.Bids[0], "best ask:", depth.Asks[0])
// The raw order book of the same outcome: the CLOB /book endpoint, by token ID
var book = exchange.IO("clob", "/book?token_id=" + tokenIds[0])
Log("Raw book tick_size:", book.tick_size, "bid levels:", book.bids.length)
// Recent trades of the market: the data API, by conditionId
var trades = exchange.IO("data", "/trades?market=" + m.conditionId + "&limit=5")
for (var i = 0; i < trades.length; i++) {
Log(trades[i].outcome, trades[i].side, trades[i].price, trades[i].size)
}
}
Page through the market list with markets_cursor
javascript
function main() {
exchange.IO("markets_cursor", "") // back to the first page
var all = {}
for (var page = 1; page <= 5; page++) {
// Each call returns the page at the current cursor (about 100 markets, one row per
// outcome) without moving the cursor
var markets = exchange.GetMarkets()
for (var k in markets) {
all[k] = markets[k]
}
Log("Page", page, ":", Object.keys(markets).length, "instruments")
// Advance to the next page; an empty string after the last page
if (exchange.IO("markets_cursor", "next") === "") {
break
}
}
Log("Total", Object.keys(all).length, "instruments")
}
Read the deposit wallet's raw positions and open orders
javascript
function main() {
var wallet = exchange.IO("proxyWalletAddress")
// The data API: raw position rows (with fields such as redeemable and conditionId)
var rows = exchange.IO("data", "/positions?user=" + wallet + "&sizeThreshold=0")
Log("Position rows:", rows.length)
// A private CLOB endpoint, L2-signed by default
var r = exchange.IO("api", "GET", "/data/orders")
Log("Open orders:", r.count, "on this page:", r.data.length)
}
Split and merge, turning USDC into shares of both outcomes and back
javascript
function main() {
var symbol = "will-btc-100k_USDC.Yes" // either outcome of the market
// Split 10 USDC into 10 shares each of Yes and No, waiting for the relayer to confirm
var r = exchange.IO("split", symbol, 10)
Log("Split:", r ? r.state : GetLastError())
// Merge: without an amount, the smaller of the two outcome positions is merged
r = exchange.IO("merge", symbol)
Log("Merge:", r ? r.state : GetLastError())
// Without waiting for confirmation: the transactionID comes back at once
r = exchange.IO("split", {symbol: symbol, amount: 5, wait: false})
Log("Submitted:", r ? r.transactionID : GetLastError())
}
Redeem all resolved positions
javascript
function main() {
var done = {}
var positions = exchange.GetPositions()
for (var i = 0; i < positions.length; i++) {
var p = positions[i]
var slug = p.Symbol.split("_USDC.")[0]
// Info.redeemable true means the market has resolved; both outcomes of a market are
// redeemed together, so call it once per market
if (!p.Info.redeemable || p.Amount <= 0 || done[slug]) {
continue
}
var r = exchange.IO("redeem", p.Symbol)
Log("Redeem", p.Symbol, r ? r.state : GetLastError())
done[slug] = true
}
}
Submit several orders at once with batchOrders
javascript
function main() {
var r = exchange.IO("batchOrders", [
{symbol: "will-btc-100k_USDC.Yes", side: "buy", price: 0.3, amount: 10},
{symbol: "will-btc-100k_USDC.No", side: "buy", price: 0.6, amount: 10,
option: {postOnly: true}}
])
// One response item per order: accepted ones carry orderID, rejected ones errorMsg;
// null when all are rejected
for (var i = 0; r && i < r.length; i++) {
Log(i, r[i].errorMsg ? "Rejected: " + r[i].errorMsg : "orderID: " + r[i].orderID)
}
}
Function Support
Not supported (the call fails with an error and returns null): exchange.ModifyOrder, exchange.SetMarginLevel, exchange.GetFundings, exchange.CreateConditionOrder, exchange.ModifyConditionOrder, exchange.CancelConditionOrder, exchange.GetConditionOrder, exchange.GetConditionOrders, exchange.GetHistoryConditionOrders.
Served through a fallback:
exchange.GetRecords: The exchange has no K-line endpoint; bars are synthesized by the docker from trades. The first call covers only the few bars spanned by recent trades, and more accumulate with later calls.
Margin, Account and Simulation
Margin mode: There is no margin or leverage: shares are paid for in full in USDC, and margin commands such as exchange.IO("cross", ...) fail.
Position mode: Positions are long only: the shares of each outcome form one position (Type long); to bet against an outcome, buy the other one. Price is the average entry price, Profit is the exchange's PnL (cashPnl) and Margin is the position's current market value; redeemable being true in the position's raw data means the market has resolved and can be redeemed with redeem. There are no close directions: orders placed after exchange.SetDirection() sets closebuy or closesell, or with closebuy/closesell as the order side, are rejected; selling simply sells shares you hold.
Account: Funds and positions are held in the deposit wallet (the ProxyWalletAddress option); the deposit wallet is both maker and signer of orders, which the private key signs with POLY_1271 (signature type 3). Balance of exchange.GetAccount() is the available USDC reported by the CLOB, Equity is Balance plus the market value of all positions, UPnL is the sum of the positions' cashPnl, and FrozenBalance is always 0.
Simulation: There is no simulated trading; every call uses real funds on the Polygon mainnet.
Notes
- Prices are USDC per share, between 0 and 1, truncated to the market's price step (
TickSizeofexchange.GetMarkets(): 0.1/0.01/0.001/0.0001); a price above 1 is sent as 1 minus one step. Quantities are shares, truncated to 2 decimals; the minimum order size isMinQty(commonly 5 shares), which is not pre-checked, leaving rejection to the exchange. - Market orders (price
-1, ortype: "market"given in the order parameters, which ignores the price): the CLOB only has limit orders, so the connector walks the opposite side of the book to the level that covers the order quantity (the last level if the book is too thin) and sends that level's price × (1 ± slippage) as the limit price, with a default slippage of 0.01; an empty opposite side is an error. Buy and sell quantities are both in shares. The result is still a GTC order and any unfilled part stays on the book; to fill immediately or cancel, pass{tif: "ioc"}(FAK) or{tif: "fok"}as the last argument, e.g.exchange.CreateOrder(symbol, "buy", -1, 10, {tif: "ioc"}). - Other order parameters go in the last object argument, e.g.
exchange.CreateOrder(symbol, "buy", price, amount, {orderType: "GTD", expiration: 1830000000})(the old forms"buy;{\"orderType\":\"GTD\"}"and"buy;slippage=0.02"still work):orderType(GTCby default,GTD,FOK,FAK),expiration(required for GTD, Unix seconds, more than 60 seconds ahead; an error for non-GTD orders),postOnly(GTC/GTD only),deferExec(passed to the exchange as is),slippage(market-order slippage). exchange.GetOrders()andexchange.GetPositions()without a symbol cover all markets;exchange.GetHistoryOrders()without a symbol covers only the current instrument, and an empty string covers all.exchange.GetHistoryOrders()is built from the account's trade records: one row per fill (status filled; an order filled in several trades appears several times), and canceled or never-filled orders do not appear;sincemay be milliseconds or seconds, and at most 300 rows are returned whenlimitis not given.exchange.GetTicker()reads the outcome's own order book and last trade price;HighandLoware the current best ask and best bid (there is no 24-hour high/low), andVolumeis the 24-hour volume of the whole market (both outcomes).exchange.GetTickers()comes from the Gamma market list, with the second outcome's prices derived from the first as1 - x.exchange.GetMarkets()andexchange.GetTickers()return one page (about 100 markets) per call. With no cursor set, they return only the first page of the Gamma market list; a call returns the page at the current cursor without moving it (internal queries such asexchange.SetContractType()do not advance it either); after a page has been fetched,exchange.IO("markets_cursor", "next")(ortickers_cursor) advances to the next page, and after the last page the cursor returns to an empty string. To page through:exchange.IO("markets_cursor", ""), then loop overexchange.GetMarkets()andexchange.IO("markets_cursor", "next")until it returns an empty string. A cursor can also be set directly, e.g. thenext_cursorof a Gamma/markets/keysetresponse.