Type/to search
Built-in Functions
Structures
Built-in Variables and Constants
Conventions
Exchanges
Capability Matrix
Centralized Exchanges
Decentralized Exchanges (DEX)
Prediction Markets
Polymarket
Securities and Futures
On-chain & Custom

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.

FieldRequiredDescription
Deposit Wallet AddressYesThe 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 KeyYesPrivate 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 KeyYesThe Polymarket Builder API Key. (more below the table)
Builder API SecretYesThe Polymarket Builder API Secret, used with BuilderKey and BuilderPassphrase for relayer authentication.
Builder API PassphraseYesThe 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 BuilderSecret and BuilderPassphrase are 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 codeDescription
YesThe outcome "Yes"; most markets have the two outcomes Yes and No.
NoThe outcome "No".
UpAn outcome of up/down markets (such as short-term crypto up/down markets); the other one is Down.
DownAn 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
CommandArgumentsDescriptionReturns
proxyWalletAddress—Get the configured deposit wallet address (EIP-55 checksum form).The address string.
redeemsymbol[, 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)
mergesymbol[, amount[, wait]]Merge equal amounts of both outcomes' shares back into USDC (gas-free via the relayer). (more below the table)As for redeem.
splitsymbol, amount[, wait]Split USDC into equal amounts of both outcomes' shares (gas-free via the relayer). (more below the table)As for redeem.
batchOrdersordersBatch 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, urlWithout 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.
apimethod, 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.
gammapathSends 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.
clobpathSends 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.
datapathSends 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: symbol must exactly equal the instrument code of a position in exchange.GetPositions() that is redeemable (redeemable is true in the position's raw data) and holds more than 0 shares; the deposit wallet must be deployed on chain. wait defaults to true; 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.
  • redeem returns: 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 the transactionID; in the last two cases the transaction has been submitted (a timed-out one may still land later), so check exchange.GetPositions() and exchange.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: symbol is the instrument code of either outcome of the market; amount is the number of shares (truncated to 6 decimals), and 0 or omitted takes the smaller of the two outcome positions. wait is as for redeem. An object {symbol, amount, wait} is also accepted.
  • split: symbol is the instrument code of either outcome of the market; amount is the USDC amount, greater than 0 (truncated to 6 decimals), giving amount shares of each outcome. wait is as for redeem. An object {symbol, amount, wait} is also accepted.
  • batchOrders: Each object has symbol (full instrument code), side (buy/sell), price (0 or below is a market order), amount (shares), and an optional option object 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, where kind is gamma (market information and lists), data (positions and market trades), relayer (redeem, merge, split) or clob (the CLOB address, as in the one-argument form). The one-argument form accepts only addresses starting with http:// or https://; anything else fails.
  • gamma: exchange.IO("gamma", "/markets/slug/will-btc-100k"); the address follows exchange.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 (TickSize of exchange.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 is MinQty (commonly 5 shares), which is not pre-checked, leaving rejection to the exchange.
  • Market orders (price -1, or type: "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 (GTC by 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() and exchange.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; since may be milliseconds or seconds, and at most 300 rows are returned when limit is not given.
  • exchange.GetTicker() reads the outcome's own order book and last trade price; High and Low are the current best ask and best bid (there is no 24-hour high/low), and Volume is 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 as 1 - x.
  • exchange.GetMarkets() and exchange.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 as exchange.SetContractType() do not advance it either); after a page has been fetched, exchange.IO("markets_cursor", "next") (or tickers_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 over exchange.GetMarkets() and exchange.IO("markets_cursor", "next") until it returns an empty string. A cursor can also be set directly, e.g. the next_cursor of a Gamma /markets/keyset response.