Skip to content
This repository was archived by the owner on May 11, 2026. It is now read-only.

Repository files navigation

Polymarket Integration Reference

A Next.js reference implementation for integrators to add Polymarket trading to their platforms. Works with any EIP-6963 compatible browser wallet.

This implementation demonstrates:

  • Geoblocking - Enforce Polymarket's geographic restrictions
  • Market Discovery - Surface liquid markets via Gamma API
  • Wallet Infrastructure - Deploy Polymarket-compatible Safe wallets
  • Builder Codes - Attribute orderflow to your integration
  • Trading - FAK market orders with automatic slippage
  • Fee Collection - Collect integrator fees on market orders
  • Position Management - Display user positions via Data API

Table of Contents

  1. Overview
  2. Quick Start
  3. Integration Guidelines
  4. Implementation Details
  5. Project Structure
  6. Environment Variables
  7. Key Dependencies

Overview

This repository serves as a technical reference for integrators who want to add Polymarket trading to their platforms. It implements the same patterns used by Polymarket.com, ensuring:

  • Seamless user experience - Users can log into Polymarket.com with the same wallet and see identical positions
  • Regulatory compliance - Same geoblocking rules as Polymarket.com
  • Reliable trading - FAK (Fill-And-Kill) market orders with automatic slippage
  • Revenue sharing - Fee collection infrastructure for integrators

Quick Start

Prerequisites

  1. Builder API Credentials from Polymarket

    • Visit polymarket.com/settings?tab=builder to obtain credentials
    • You'll need: API_KEY, SECRET, and PASSPHRASE
  2. Polygon RPC URL - Any Polygon mainnet RPC

  3. Browser Wallet - Any EIP-6963 compatible wallet (MetaMask, Rabby, Phantom, Bitget, OKX, Coinbase, etc.)

Installation

npm install

Environment Setup

Create .env.local:

# Polygon RPC endpoint
NEXT_PUBLIC_POLYGON_RPC_URL=https://polygon-rpc.com

# Builder credentials (from polymarket.com/settings?tab=builder)
POLYMARKET_BUILDER_API_KEY=your_builder_api_key
POLYMARKET_BUILDER_SECRET=your_builder_secret
POLYMARKET_BUILDER_PASSPHRASE=your_builder_passphrase

# Optional: Integrator fee collection
NEXT_PUBLIC_INTEGRATOR_FEE_ADDRESS=0x...  # Your fee receiving wallet
NEXT_PUBLIC_INTEGRATOR_FEE_BPS=50         # Fee in basis points (50 = 0.5%)

Run Development Server

npm run dev

Open http://localhost:3000


Integration Guidelines

Geoblocking

Integrators adhere to the same geoblocking rules as Polymarket.com.

Implementation:

// hooks/useGeoblock.ts
const response = await fetch("https://polymarket.com/api/geoblock");
const { blocked, country, region } = await response.json();

if (blocked) {
  // Disable trading UI, show informational message
  // Users can still VIEW markets, just not trade
}

Key Points:

  • Check geoblock status on app load
  • Prevent trading session initialization if blocked
  • Allow market viewing even when blocked (read-only)
  • Display clear messaging about restrictions

Files: hooks/useGeoblock.ts, components/GeoBlockedBanner.tsx, providers/TradingProvider.tsx

Docs: Geographic Restrictions

Polymarket Terms of Service


Market Discovery

Use Polymarket's Gamma API /events endpoint to surface liquid markets.

Canonical Pattern:

Polymarket uses hardcoded tag_id values for category filtering:

Category tag_id Description
Trending (none) All markets sorted by volume
Politics 2 Political markets
Finance 120 Financial markets
Crypto 21 Crypto + subcategories (BTC, ETH)
Sports 100639 Sports games
Tech 1401 Technology markets
Culture 596 Entertainment, pop culture
Geopolitics 100265 Geopolitical events

Implementation:

// constants/categories.ts - Hardcode official tag IDs
export const CATEGORIES = [
  { id: "trending", label: "Trending", tagId: null },
  { id: "politics", label: "Politics", tagId: 2 },
  { id: "finance", label: "Finance", tagId: 120 },
  { id: "crypto", label: "Crypto", tagId: 21 },
  { id: "sports", label: "Sports", tagId: 100639 },
  { id: "tech", label: "Tech", tagId: 1401 },
  { id: "culture", label: "Culture", tagId: 596 },
  { id: "geopolitics", label: "Geopolitics", tagId: 100265 },
];

// app/api/polymarket/markets/route.ts - Use tag_id for filtering
const url = `https://gamma-api.polymarket.com/events?tag_id=21&related_tags=true&closed=false&order=volume24hr&ascending=false`;

API Flow:

Client request: /api/polymarket/markets?tag_id=21
                         |
Gamma API: /events?tag_id=21&related_tags=true&closed=false

Filtering for Quality:

const validMarkets = markets.filter((market) => {
  if (market.acceptingOrders === false) return false;
  
  const liquidity = parseFloat(market.liquidity || "0");
  if (liquidity < 5000) return false;
  
  const prices = JSON.parse(market.outcomePrices);
  const hasTradeablePrice = prices.some((p) => {
    const price = parseFloat(p);
    return price >= 0.05 && price <= 0.95;
  });
  
  return hasTradeablePrice;
});

Files: app/api/polymarket/markets/route.ts, hooks/useMarkets.ts, utils/gamma.ts, constants/categories.ts

Docs: Gamma API Reference


Wallet Infrastructure

Deploy Polymarket proxy wallets (Safes) on behalf of users and use the relayer for gasless transactions.

Benefits:

  • Seamless integration - Users see same positions on Polymarket.com
  • Gasless transactions - Via builder relayer
  • Efficiency - Batch transactions

RelayClient Initialization

The RelayClient handles Safe deployment, token approvals, and CTF operations (splitting, merging, redeeming positions). It requires your builder credentials via a BuilderConfig:

// hooks/useRelayClient.ts
import { BuilderConfig } from "@polymarket/builder-signing-sdk";
import { RelayClient } from "@polymarket/builder-relayer-client";

// Remote signing keeps builder credentials server-side
const builderConfig = new BuilderConfig({
  remoteBuilderConfig: {
    url: "/api/polymarket/sign", // Your signing endpoint
  },
});

const relayClient = new RelayClient(
  "https://relayer.polymarket.com",
  137, // Polygon chain ID
  ethersSigner,
  builderConfig
);

Safe Address Derivation

Safe addresses are deterministic - derived from the user's EOA address. This means you can know the Safe address before it's deployed:

// hooks/useSafeDeployment.ts
import { deriveSafe } from "@polymarket/builder-relayer-client/dist/builder/derive";
import { getContractConfig } from "@polymarket/builder-relayer-client/dist/config";

const config = getContractConfig(137); // Polygon
const safeAddress = deriveSafe(eoaAddress, config.SafeContracts.SafeFactory);

Safe Deployment

For new users, deploy their Safe before they can trade:

// Check if already deployed
const deployed = await relayClient.getDeployed(safeAddress);

if (!deployed) {
  // Prompts user to sign - gasless deployment via builder relayer
  const response = await relayClient.deploy();
  const result = await response.wait();
  console.log("Safe deployed at:", result.proxyAddress);
}

Files: hooks/useSafeDeployment.ts, hooks/useRelayClient.ts

Docs: Builder Relayer Client


Builder Codes

Use builder codes for orderflow attribution and access to the relayer.

Remote Signing Architecture

Builder credentials should stay server-side. The BuilderConfig supports remote signing, where the client requests signatures from your server:

┌─────────────────────────────────────────────────────────────┐
│  Client (Browser)                                           │
│  ┌─────────────────────────────────────────────────────────┐│
│  │ BuilderConfig({ remoteBuilderConfig: { url: "/sign" }}) ││
│  │                         │                               ││
│  │                         ↓                               ││
│  │   SDK needs signature → POST /api/polymarket/sign       ││
│  └─────────────────────────────────────────────────────────┘│
└──────────────────────────────│──────────────────────────────┘
                               ↓
┌──────────────────────────────────────────────────────────────┐
│  Server (Next.js API Route)                                  │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │ buildHmacSignature(secret, timestamp, method, path, body)││
│  │                         │                                ││
│  │                         ↓                                ││
│  │ Returns: { POLY_BUILDER_SIGNATURE, POLY_BUILDER_TIMESTAMP,│ │
│  │           POLY_BUILDER_API_KEY, POLY_BUILDER_PASSPHRASE } │ │
│  └─────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘

Server-Side Signing Endpoint

// app/api/polymarket/sign/route.ts
import { buildHmacSignature, BuilderApiKeyCreds } from "@polymarket/builder-signing-sdk";

const BUILDER_CREDENTIALS: BuilderApiKeyCreds = {
  key: process.env.POLYMARKET_BUILDER_API_KEY!,
  secret: process.env.POLYMARKET_BUILDER_SECRET!,
  passphrase: process.env.POLYMARKET_BUILDER_PASSPHRASE!,
};

export async function POST(request: NextRequest) {
  const { method, path, body } = await request.json();
  const timestamp = Date.now().toString();

  const signature = buildHmacSignature(
    BUILDER_CREDENTIALS.secret,
    parseInt(timestamp),
    method,
    path,
    body
  );

  return NextResponse.json({
    POLY_BUILDER_SIGNATURE: signature,
    POLY_BUILDER_TIMESTAMP: timestamp,
    POLY_BUILDER_API_KEY: BUILDER_CREDENTIALS.key,
    POLY_BUILDER_PASSPHRASE: BUILDER_CREDENTIALS.passphrase,
  });
}

Client-Side Configuration

// hooks/useClobClient.ts (and useRelayClient.ts)
const builderConfig = new BuilderConfig({
  remoteBuilderConfig: { url: "/api/polymarket/sign" },
});

Files: app/api/polymarket/sign/route.ts, hooks/useClobClient.ts, hooks/useRelayClient.ts

Security Note: This reference implementation exposes builder credentials (API key + passphrase) to the client via the /api/polymarket/sign endpoint. For production deployments, implement one of:

  • Proxy pattern - Server makes all CLOB/Relay requests, credentials never reach client
  • Auth token validation - Require authenticated session before returning credentials

Docs: Builder Program | CLOB Authentication


User API Credentials

Each user needs their own CLOB API credentials (separate from your Builder credentials).

Important Distinction:

  • Builder credentials - Your integrator credentials for order attribution
  • User API credentials - User-specific credentials for placing their orders

Implementation:

Users either have existing credentials (if they've traded before) or need new ones created:

// hooks/useUserApiCredentials.ts
import { ClobClient } from "@polymarket/clob-client";

// Create a temporary client (no credentials needed) to derive/create user creds
const tempClient = new ClobClient(
  "https://clob.polymarket.com",
  137,
  ethersSigner
);

// Try to derive existing credentials first
const existingCreds = await tempClient.deriveApiKey().catch(() => null);

if (existingCreds?.key && existingCreds?.secret && existingCreds?.passphrase) {
  // User has traded before - reuse their credentials
  return existingCreds;
}

// New user - create fresh credentials (prompts signature)
const newCreds = await tempClient.createApiKey();
return newCreds; // { key, secret, passphrase }

Flow:

User connects wallet
         ↓
    [Has existing credentials?]
         │
    Yes ──┴──> deriveApiKey() - deterministic from signature
         │
        No
         ↓
    createApiKey() - creates new credentials (prompts signature)

Files: hooks/useUserApiCredentials.ts

Docs: API Keys


Token Approvals

Approve Polymarket contracts to spend USDC.e and outcome tokens (ERC-1155).

Required Approvals:

Token Spender Contract Purpose
USDC.e CTF Contract Splitting collateral into outcome tokens
USDC.e Neg Risk Adapter Neg risk market collateral
USDC.e CTF Exchange Standard market orders
USDC.e Neg Risk CTF Exchange Neg risk market orders
CTF (ERC-1155) CTF Exchange Selling outcome tokens
CTF (ERC-1155) Neg Risk CTF Exchange Selling neg risk tokens
CTF (ERC-1155) Neg Risk Adapter Redeeming neg risk positions

Batched Execution:

All approvals can be batched into a single transaction using the RelayClient:

// utils/approvals.ts
import { OperationType, SafeTransaction } from "@polymarket/builder-relayer-client";

const createAllApprovalTxs = (): SafeTransaction[] => {
  const txs: SafeTransaction[] = [];
  
  // USDC.e approvals (ERC-20)
  for (const spender of USDC_SPENDERS) {
    txs.push({
      to: USDC_E_CONTRACT,
      operation: OperationType.Call,
      data: encodeFunctionData({
        abi: erc20Abi,
        functionName: "approve",
        args: [spender, MAX_UINT256],
      }),
      value: "0",
    });
  }
  
  // Outcome token approvals (ERC-1155)
  for (const spender of OUTCOME_TOKEN_SPENDERS) {
    txs.push({
      to: CTF_CONTRACT,
      operation: OperationType.Call,
      data: encodeFunctionData({
        abi: erc1155Abi,
        functionName: "setApprovalForAll",
        args: [spender, true],
      }),
      value: "0",
    });
  }
  
  return txs;
};

// hooks/useTokenApprovals.ts - Execute all approvals in one tx
const approvalTxs = createAllApprovalTxs();
await relayClient.execute(approvalTxs, "Set token approvals");

Files: hooks/useTokenApprovals.ts, utils/approvals.ts


Authenticated CLOB Client

Create an authenticated ClobClient for placing orders on behalf of users.

Key Parameters:

// hooks/useClobClient.ts
const clobClient = new ClobClient(
  "https://clob.polymarket.com",  // CLOB API URL
  137,                             // Chain ID (Polygon)
  ethersSigner,                    // User's ethers signer
  userApiCredentials,              // { key, secret, passphrase }
  2,                               // signatureType = 2 for Safe funder
  safeAddress,                     // User's Safe (derived from EOA)
  undefined,                       // Placeholder (unused)
  false,                           // Placeholder (unused)
  builderConfig                    // For order attribution
);

Parameter Details:

Parameter Value Purpose
signatureType 2 Indicates the signer is a Safe funder (not direct EOA)
safeAddress Derived address The Safe that holds funds and executes trades
builderConfig Your config Attaches builder code to all orders

Files: hooks/useClobClient.ts


Trading

Use FAK (Fill-And-Kill) market orders with automatic slippage.

Order Types:

Type Use Case Description
OrderType.FAK Market orders Fill-And-Kill - fills available, cancels rest
OrderType.GTC Limit orders Good-Til-Cancelled

Market Order Flow:

For FAK market orders, omit the price field - the SDK calculates the optimal price based on orderbook depth:

// hooks/useClobOrder.ts
const marketOrder: UserMarketOrder = {
  tokenID,
  amount,  // $ amount for BUY, shares for SELL
  side,
  feeRateBps: 0,
  // NOTE: Don't provide 'price', SDK calculates from orderbook
};

const response = await clobClient.createAndPostMarketOrder(
  marketOrder,
  { negRisk },
  OrderType.FAK
);

Error Handling:

FAK orders will fail if there's no liquidity on the orderbook:

if (error.message.includes("no orders found to match")) {
  // No buyers/sellers available - market is illiquid
  // User should wait or use a limit order instead
}

Files: hooks/useClobOrder.ts, constants/trading.ts

Docs: Order Placement | CLOB Client


Fee Collection

Collect integrator fees separately after successful market orders.

Flow:

┌─────────────────────────────────────────────────────────┐
│  1. User places market order                            │
│     └─> FAK order with automatic slippage               │
├─────────────────────────────────────────────────────────┤
│  2. Poll for order confirmation                         │
│     └─> Check order status every 500ms (30s timeout)    │
├─────────────────────────────────────────────────────────┤
│  3. After order is MATCHED/FILLED                       │
│     └─> Execute fee transfer from Safe to integrator    │
└─────────────────────────────────────────────────────────┘

Implementation:

// hooks/useOrderStatus.ts - Poll for confirmation
const pollOrderStatus = async (clobClient, orderId) => {
  while (Date.now() - startTime < ORDER_POLL_TIMEOUT_MS) {
    const order = await clobClient.getOrder(orderId);
    
    if (order.status === "MATCHED") {
      return { isFullyFilled: true, sizeFilled: order.size_matched };
    }
    
    await sleep(ORDER_POLL_INTERVAL_MS);
  }
};

// hooks/useFeeCollection.ts - Transfer fee
const collectFee = async (relayClient, orderValueUsdc) => {
  const feeAmount = orderValueUsdc * (INTEGRATOR_FEE_BPS / 10000);
  
  const feeTransferTx = createUsdcTransferTx({
    recipient: INTEGRATOR_FEE_ADDRESS,
    amount: parseUnits(feeAmount.toFixed(6), 6),
  });
  
  await relayClient.execute([feeTransferTx], "Collect integrator fee");
};

Configuration:

# .env.local
NEXT_PUBLIC_INTEGRATOR_FEE_ADDRESS=0x...  # Your wallet
NEXT_PUBLIC_INTEGRATOR_FEE_BPS=50         # 0.5% fee

Files: hooks/useOrderStatus.ts, hooks/useFeeCollection.ts, hooks/useClobOrder.ts, constants/config.ts

Docs: CLOB Client


Position Management

Use Polymarket's Data API for position tracking.

// hooks/useUserPositions.ts
const DATA_API = "https://data-api.polymarket.com";

const fetchPositions = async (safeAddress: string) => {
  const response = await fetch(
    `${DATA_API}/positions?user=${safeAddress}`
  );
  return response.json();
};

Benefits:

  • Same API as Polymarket.com
  • Highly available

Files: hooks/useUserPositions.ts

Docs: Data API Reference


IP Whitelists

Note for Production: Contact Polymarket to whitelist your order-posting servers. This ensures:

  • No rate limiting from general order posting limits
  • Reliable order execution during high-volume periods

Implementation Details

Provider Architecture

┌─────────────────────────────────────────┐
│         WagmiProvider (v3)              │  ← Wallet connection
│  ┌───────────────────────────────────┐  │
│  │       QueryProvider               │  │  ← React Query
│  │  ┌─────────────────────────────┐  │  │
│  │  │     WalletProvider          │  │  │  ← Ethers + Viem
│  │  │  ┌───────────────────────┐  │  │  │
│  │  │  │  TradingProvider      │  │  │  │  ← Geoblock + Trading
│  │  │  │    App Components     │  │  │  │
│  │  │  └───────────────────────┘  │  │  │
│  │  └─────────────────────────────┘  │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

Trading Session Flow

User Connects Wallet
         ↓
    Check Geoblock
         ↓
    [BLOCKED?] ──Yes──> Show Banner, Disable Trading
         │
        No
         ↓
┌────────────────────────────────────────────────────┐
│  Initialize Trading Session                         │
├────────────────────────────────────────────────────┤
│  1. Initialize RelayClient (builder config)        │
│  2. Derive Safe address from EOA                   │
│  3. Deploy Safe if needed                          │
│  4. Get/Create User API Credentials                │
│  5. Set token approvals (batch)                    │
│  6. Initialize authenticated ClobClient            │
└────────────────────────────────────────────────────┘
         ↓
    Ready to Trade
         ↓
┌────────────────────────────────────────────────────┐
│  Market Order Flow                                  │
├────────────────────────────────────────────────────┤
│  1. Get market price + calculate slippage          │
│  2. Place FAK order                                │
│  3. Poll for order confirmation                    │
│  4. If filled: collect integrator fee              │
│  5. Update UI                                      │
└────────────────────────────────────────────────────┘

Project Structure

wallet-integration/
├── app/
│   ├── api/
│   │   └── polymarket/
│   │       ├── sign/route.ts          # Remote signing endpoint
│   │       └── markets/route.ts       # Gamma API proxy
│   ├── layout.tsx
│   └── page.tsx
│
├── providers/
│   ├── index.tsx                      # Provider composition
│   ├── WagmiProvider.tsx              # Wagmi v3 config
│   ├── WalletProvider.tsx             # Wallet abstraction
│   └── TradingProvider.tsx            # Trading + geoblock state
│
├── hooks/
│   ├── useGeoblock.ts                 # Geoblock API check
│   ├── useMarkets.ts                  # Gamma API market discovery
│   ├── useTradingSession.ts           # Session orchestration
│   ├── useRelayClient.ts              # RelayClient init
│   ├── useSafeDeployment.ts           # Safe deployment
│   ├── useTokenApprovals.ts           # Token approvals
│   ├── useClobClient.ts               # Authenticated CLOB client
│   ├── useClobOrder.ts                # Order placement + fee collection
│   ├── useOrderStatus.ts              # Order confirmation polling
│   ├── useFeeCollection.ts            # Fee transfer logic
│   └── useUserPositions.ts            # Position tracking
│
├── components/
│   ├── GeoBlockedBanner.tsx           # Geoblock UI
│   ├── Header/                        # Wallet connection
│   ├── TradingSession/                # Session UI
│   └── Trading/
│       └── Markets/
│           ├── CategoryTabs.tsx       # Category navigation
│           ├── MarketCard.tsx         # Market display
│           └── index.tsx              # Markets list
│
├── constants/
│   ├── api.ts                         # API URLs
│   ├── config.ts                      # Chain, fees, session
│   ├── trading.ts                     # Slippage, polling
│   ├── categories.ts                  # Market categories
│   ├── tokens.ts                      # Contract addresses
│   └── validation.ts                  # Order validation
│
└── utils/
    ├── approvals.ts                   # Approval utilities
    ├── transfer.ts                    # USDC transfer utils
    └── session.ts                     # Session persistence

Environment Variables

# Required
NEXT_PUBLIC_POLYGON_RPC_URL=https://polygon-rpc.com
POLYMARKET_BUILDER_API_KEY=your_key
POLYMARKET_BUILDER_SECRET=your_secret
POLYMARKET_BUILDER_PASSPHRASE=your_passphrase

# Optional: Fee Collection
NEXT_PUBLIC_INTEGRATOR_FEE_ADDRESS=0x...
NEXT_PUBLIC_INTEGRATOR_FEE_BPS=50

Key Dependencies

Package Purpose
@polymarket/clob-client Order placement, credentials
@polymarket/builder-relayer-client Safe deployment, approvals
@polymarket/builder-signing-sdk Builder HMAC signatures
wagmi Wallet connection
viem Ethereum interactions
ethers Signing (Polymarket SDK compat)
@tanstack/react-query Server state
next Framework

Resources


Support

Questions? Email builder@polymarket.com


License

MIT

About

No description, website, or topics provided.

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages