Repository navigation
Polymarket
Typed Polymarket prediction-market client for Deno,
Bun, Node.js and Cloudflare Workers. It covers Gamma market discovery (no
auth), CLOB trading with secp256k1/EIP-712 order signing (L1/L2 auth),
portfolio positions and value, and gasless split, merge and redeem. An
alternative to the official @polymarket/clob-client for the endpoints it
covers.
One Polymarket client covers both APIs, matching how a Gamma market's
clobTokenIds feed directly into CLOB calls:
-
Gamma (
gamma-api.polymarket.com) —getMarkets()(legacy offset or keyset cursor pagination) andgetMarketBySlug(). No credentials needed. -
CLOB (
clob.polymarket.com) —getVersion()/getTickSize()/getNegRisk()/getOrderbook()(public),deriveApiCredentials()(one-time L1-signed onboarding), and L2 HMAC-authenticatedgetBalance()(USDC collateral, or one outcome token's shares),getOpenOrders()/getFills()(cursor-paginated),submitOrder()/submitOrders()(single/bulk, up to 15 per request, auto-chunked),cancelOrder()/cancelOrders()/cancelMarketOrders()/cancelAllOrders(). -
Data API (
data-api.polymarket.com) —getPositions()andgetPortfolioValue(): the public, read-only portfolio view keyed by wallet address (mark-to-market value and PnL). No signing; defaults toauth.funder, reads any wallet viauser. -
Relayer (
relayer-v2.polymarket.com) —split()/merge()/redeem(), gasless collateral split/merge/redeem via a signed EIP-191 proxy meta-transaction. Requiresauth.relayerApiKey/auth.relayerApiKeyAddress, a credential separate from the CLOB's L2apiCredentials.
submitOrder()/submitOrders()/split()/merge()/redeem() all resolve
to the same OrderResult shape (a slippage field included — 0 for
SPLIT/MERGE/REDEEM, the requested-vs-actual price gap for a filled
BUY/SELL); order() is a single entry point that dispatches to whichever
of them a request.action of 'BUY'/'SELL'/'SPLIT'/'MERGE'/'REDEEM'
calls for.
Construct with auth.privateKey (and auth.funder for order placement) to
use the CLOB; omit auth entirely for Gamma-only, credential-free market
data (getOrderbook() and the Data API reads need no credentials either —
though the Data API reads default to auth.funder when no user is given).
submitOrder() and deriveApiCredentials() sign for real: this client
holds your wallet's raw private key in memory for the lifetime of the
instance and produces genuine secp256k1 ECDSA signatures over EIP-712 typed
data — the same cryptographic operation a browser wallet performs, not a
delegated call to one. The key is consumed once at construction, stored in
a true (non-enumerable) private field, and never logged, returned, or
included in a thrown error. This is the first connect in this repo with a
non-@tundralibs dependency — @noble/curves
and @noble/hashes (audited,
zero-further-dependency, pure JS/TS) — because Web Crypto implements neither
secp256k1 ECDSA nor Keccak-256, both of which EIP-712 signing genuinely
requires.
A wallet private key is an irreversible-loss-capable secret, unlike a
revocable API key. Treat auth.privateKey accordingly: never commit it,
prefer a wallet dedicated to this integration over one holding significant
funds, and review PolymarketSigner.ts/PolymarketOrder.ts yourself before
trusting this client with real money.
split()/merge()/redeem() move real on-chain collateral through a
signed EIP-191 meta-transaction relayed by Polymarket's Relayer — review
PolymarketRelayer.ts/PolymarketAbi.ts yourself before trusting this
client with these calls, the same way you would for order signing.
-
WebSocket market/user feeds (
wss://ws-subscriptions-clob.polymarket.com) are real-time push channels, a different integration shape than this repo's REST-only connects; whether/how to add one is an open decision, not yet built. - Data API activity/leaderboard/holders feeds — only the portfolio
half (
/positions,/value) is wired up, viagetPositions()/getPortfolioValue().
Your tests don't need to fake HTTP. Have your code take a Polymarket
instance and pass in a stand-in that returns the shapes from
@tundraconnect/polymarket/schemas or throws a real PolymarketError:
import { PolymarketError } from '@tundraconnect/polymarket/errors';
const outage = new PolymarketError('SERVICE_UNAVAILABLE', { status: 503 });
console.log(outage.code);To exercise the client itself against a fake transport, subclass it and
reassign the protected _fetch.
| Topic | Description |
|---|---|
| API | Client configuration and endpoint methods |
| Errors | Error codes and diagnostic metadata |
| Schemas | Public Guardian schemas and inferred types |
Deno:
deno add jsr:@tundraconnect/polymarketBun:
bunx jsr add @tundraconnect/polymarketNode.js:
npx jsr add @tundraconnect/polymarketimport { Polymarket } from '@tundraconnect/polymarket';
// Gamma only — no wallet needed.
const gamma = new Polymarket({});
const { markets } = await gamma.getMarkets({ closed: false, limit: 20 });
const market = markets[0];
console.log(market?.question, market?.clobTokenIds);
// CLOB trading.
const clob = new Polymarket({
auth: {
type: 'CUSTOM',
privateKey: Deno.env.get('CONNECTOR_POLYMARKET_PRIVATE_KEY')!,
funder: Deno.env.get('CONNECTOR_POLYMARKET_FUNDER')!,
},
});
await clob.deriveApiCredentials();
const balance = await clob.getBalance();
console.log('USDC available:', balance.balance);
// Portfolio: open orders and fills (CLOB), positions and value (Data API).
const { data: openOrders } = await clob.getOpenOrders();
const positions = await clob.getPositions();
console.log(
openOrders.length,
'resting orders,',
positions.length,
'positions',
);
const tokenId = market!.clobTokenIds[0]!;
const result = await clob.submitOrder({
tokenId,
side: 'BUY',
price: 0.55,
shares: 9.0,
orderType: 'FAK',
});
if (result.filled) {
console.log(
`filled: spent ${result.makingAmount} for ${result.takingAmount} shares`,
);
console.log('slippage vs. the requested price:', result.slippage);
} else if (result.noMatch) {
console.log('no resting liquidity matched — nothing was spent');
}
// Kill switch: every resting order, all markets, one request.
await clob.cancelAllOrders();
// order() dispatches BUY/SELL/SPLIT/MERGE/REDEEM to the right call above and
// always returns the same OrderResult shape.
await clob.order({
action: 'BUY',
tokenId,
price: 0.55,
shares: 9.0,
orderType: 'FAK',
});MIT. Part of Tundra Connect, typed vendor API clients for Deno, Bun, Node.js and Cloudflare Workers.
- Algolia
- AzureBlob
- CloudflareDNS
- CloudflareEmail
- CloudflareSaaS
- CloudflareTurnstile
- CoinGecko
- Discord
- DodoPayments
- GCS
- GoogleAnalytics
- GoogleCertificateManager
- GoogleWebRisk
- Kalshi
- ntfy
- OpenExchange
- OpenWeatherMap
- PayPal
- Polymarket
- Razorpay
- Resend
- S3
- SendGrid
- Sentry
- Slack
- Stripe
- Telegram
- Twilio
- UpstashRedis
- URLhaus