Skip to content

emudoteth/polypocket

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

172 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

PolyPocket ๐Ÿซง

Live License: MIT Next.js Polymarket

The whole prediction market, in your pocket.

Live prediction markets, real-time order book depth, and wallet-native trading โ€” built directly on the Polymarket CLOB API.

โ†’ polypocket.vercel.app

Built as a proof-of-concept DevRel integration. No paid API keys. Non-custodial โ€” your wallet, your keys.


Stack: Next.js 14 (Pages Router) ยท ethers.js v5 ยท Recharts ยท Vercel Serverless


Architecture

Browser                          Vercel Serverless (Node.js)
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
hooks/useWallet.js               pages/api/events.js    โ†’ Gamma API
  โ””โ”€ window.ethereum + ethers    pages/api/event.js     โ†’ Gamma API
                                 pages/api/markets.js   โ†’ Gamma API
components/                      pages/api/book.js      โ†’ CLOB API
  MarketCard.jsx                 pages/api/prices-history.js โ†’ CLOB API
  MarketDetail.jsx
  Portfolio.jsx
  TradeModal.jsx โ”€โ”€deeplinkโ”€โ”€โ†’ polymarket.com
  WalletButton.jsx

lib/clob.js  โ† SERVER ONLY (Node.js crypto)

All Polymarket API calls go through serverless proxies โ€” both APIs lack CORS headers for browser origins.


Wallet Connection

Uses bare window.ethereum + ethers.js v5. No RainbowKit, no wagmi, no WalletConnect needed.

const provider = new ethers.providers.Web3Provider(window.ethereum, 'any');
const signer   = provider.getSigner();
// Pass signer directly to ClobClient (server-side) or sign typed data client-side

Works with: MetaMask, Brave Wallet, Coinbase Wallet, Rabby, any EIP-1193 extension.


API Reference

Gamma API (Events / Markets)

Base: https://gamma-api.polymarket.com

Endpoint Purpose
GET /events?limit=N&order=volume24hr&ascending=false Paginated events, sorted by 24h volume
GET /events?tag_slug=politics&limit=N Events filtered by tag
GET /events/{id} Single event with all markets

CLOB API (Order Book / Prices)

Base: https://clob.polymarket.com

Endpoint Purpose
GET /book?token_id={TOKEN_ID} Order book depth for one outcome token
GET /prices-history?market={TOKEN_ID}&interval={1h|6h|1d|1w|1m|max}&fidelity=100 Price history
POST /order Place signed limit order (requires L2 HMAC auth)

Data API (Portfolio / Leaderboard)

Base: https://data-api.polymarket.com

Endpoint Purpose
GET /positions?user={PROXY_WALLET}&limit=50 User's open positions
GET /v1/leaderboard?category=OVERALL&limit=20 Global leaderboard

Gotchas & Hard-Won Lessons

1. @polymarket/clob-client is Node.js only

The clob-client uses crypto, stream, http, https, os from Node.js. These don't exist in the browser. Never import lib/clob.js from a component. Import it only from pages/api/* routes.

// โœ… Fine โ€” runs in Node.js
// pages/api/place-order.js
import { placeOrder } from '../../lib/clob';

// โŒ Crashes browser โ€” do not do this
// components/TradeModal.jsx
import { placeOrder } from '../lib/clob';

Next.js 14 removed automatic Node.js polyfills. Webpack resolve.fallback: { crypto: false } stubs the module but causes runtime TypeErrors when the code actually calls crypto.createHmac().

2. Don't use RainbowKit / wagmi for this stack

  • wagmi@1.x removed useSigner (it existed in v0.x only). Any code calling useSigner() from wagmi 1.x throws TypeError: useSigner is not a function.
  • RainbowKit requires a WalletConnect project ID (free at cloud.walletconnect.com) or you get WebSocket 401 noise in the console.
  • The wagmi/RainbowKit provider stack adds ~400KB and several peer-dep version constraints.
  • Simpler: window.ethereum + ethers.js v5 directly. Zero provider wrapping, no hook version drift.

3. market param for prices-history = TOKEN ID, not conditionId

// โœ… Correct
GET /prices-history?market=38397507750621893057346880033441136112987238933685677349709401910643842844855

// โŒ Wrong โ€” conditionId won't return history
GET /prices-history?market=0x1234...conditionId

Token IDs are the clobTokenIds values from the event's market object (always JSON strings).

4. API response fields are JSON strings, not parsed objects

const market = event.markets[0];

// โŒ This crashes
market.clobTokenIds[0]

// โœ… Parse first
JSON.parse(market.clobTokenIds)[0]    // e.g. "38397507..."
JSON.parse(market.outcomePrices)[0]   // e.g. "0.73"
JSON.parse(market.outcomes)[0]        // e.g. "Yes"

5. Both Polymarket APIs lack CORS headers

clob.polymarket.com and gamma-api.polymarket.com don't send Access-Control-Allow-Origin for browser requests. All API calls must go through server-side proxies (Vercel API routes in this project).

6. Vercel build cache can serve stale chunks

After code changes, Vercel may serve a cached bundle with the old chunk hash. The symptom: browser throws errors for code you've already fixed. Fix: hard refresh (Cmd+Shift+R) or clear site data. The deployed chunk hash changing in the Network tab confirms the new build is live.

7. ClobClient constructor for EOA wallets

// 4-arg form (v4.x default) โ€” works for proxy wallets
new ClobClient(host, chainId, signer, creds)

// 6-arg form โ€” required for EOA (externally owned account) signing
new ClobClient(host, chainId, signer, creds, 0, funderAddress)
//                                             โ†‘ SignatureType.EOA = 0
//                                                      โ†‘ your wallet address

8. L2 API credentials (HMAC)

Polymarket uses a two-layer auth model:

  • L1 โ€” EIP-712 signed message proves wallet ownership โ†’ returns L2 API key + secret + passphrase
  • L2 โ€” Every CLOB request is HMAC-signed with the L2 secret

ClobClient.createOrDeriveApiKey() handles L1 and returns L2 creds. Cache them in localStorage โ€” re-deriving requires a wallet signature every time.

9. Neg-risk markets

Multi-outcome markets where probabilities sum to 1 (e.g. "Which party wins the Senate?") use a different contract (NegRiskCTFExchange). The enableOrderBook and negRisk flags on the event indicate this. The clob-client handles the routing, but be aware the token structure differs.

10. US geoblock

const res = await fetch('https://polymarket.com/api/geoblock');
const { restricted } = await res.json(); // true for US IPs

Show a warning in the UI before allowing trades from restricted regions.


Local Development

npm install
npm run dev   # http://localhost:3000

Environment variables (optional):

# None required for read-only browsing
# For WalletConnect mobile QR: NEXT_PUBLIC_WC_PROJECT_ID=your_id_here

Trading Flow (Current)

The trade modal collects price/size and opens polymarket.com/event/{slug} for actual execution. This is intentional โ€” the CLOB signing flow requires server-side @polymarket/clob-client but the EIP-712 signature must come from the user's wallet, which creates a round-trip complexity outside scope for this MVP.

To implement real order placement:

  1. Server: GET /api/build-order?tokenID=&price=&size=&side= โ†’ returns EIP-712 typed data
  2. Browser: signer._signTypedData(domain, types, value) โ†’ signed order
  3. Browser: POST /api/submit-order with signed order + funder address
  4. Server: add L2 HMAC headers, forward to POST https://clob.polymarket.com/order

Known Data Quality Issues

Leaderboard: Spam / Zero-Volume Entries

The /v1/leaderboard endpoint returns unfiltered data from the Data API. This includes bot accounts and spam usernames with $0 volume that have somehow made it onto the leaderboard (e.g. "2121212121212121212121212" at rank #5 with Vol: $0K).

Root cause: Polymarket's leaderboard API does not enforce minimum volume or username sanity. Entries with volume: 0 (or near-zero) and nonsensical names (all-numeric, repeated characters) appear legitimately in the response.

Observed example:

#5  2121212121212121212121212   Vol: $0K

Recommended client-side fix:

const leaderboard = raw
  .filter(entry => parseFloat(entry.volume || 0) >= 100)   // drop $0 / dust entries
  .filter(entry => !/^[\d\s]{6,}$/.test(entry.name || '')); // drop all-numeric spam names

Status: Not fixed yet โ€” currently displayed as-is from the API. Worth filtering before shipping.

Leaderboard: Volume Field is Opaque

The volume field returned by /v1/leaderboard does not correspond to 24h volume or any documented time window. It appears to be a cumulative all-time figure, but Polymarket does not define it in their public docs.

// What the API returns
{ name: "trader123", volume: 48293.12, ... }

// What it is NOT:
//   โŒ 24h volume  โ€” that's event.volume24hr on the Gamma API
//   โŒ 7d volume
//   โŒ any labeled time period

// What it probably is:
//   โœ… All-time total trading volume for that wallet (undocumented)

Implication: Don't label this as "24h Volume" or "Recent Volume" in the UI. "Vol" or "All-time Vol" is the safest label until Polymarket documents the field. The displayed $XXK / $XXM formatting is correct โ€” the ambiguity is in the time period, not the unit.

Category Emojis are Hardcoded

The emoji icons next to each category tag (๐Ÿ—ณ๏ธ Politics, ๐Ÿ† Sports, ๐Ÿฆ Iran, etc.) are defined in a static lookup map in pages/index.jsx and components/MarketCard.jsx:

const tagEmoji = slug => ({
  politics:'๐Ÿ—ณ๏ธ', sports:'๐Ÿ†', crypto:'๐Ÿ”ฎ', finance:'๐Ÿ“ˆ',
  iran:'๐Ÿฆ', geopolitics:'๐ŸŒ', tech:'๐Ÿ’ป', culture:'๐ŸŽญ',
  economy:'๐Ÿ’ฐ', 'climate-science':'๐ŸŒฆ๏ธ', elections:'๐Ÿ—ณ๏ธ',
  entertainment:'๐ŸŽฌ', nfl:'๐Ÿˆ', nba:'๐Ÿ€',
}[slug] || '๐Ÿซง'); // fallback

The Gamma API returns tag slugs (tag_slug: "politics") but no emoji or icon metadata. Any new tag from the API that isn't in this map falls back to ๐Ÿซง. To support new tags properly, this map needs manual updates.

"Person AN" / "Person CX" โ€” Placeholder Candidate Names

For prediction markets where candidates haven't been officially announced, Polymarket uses anonymized placeholder names in their API response: Person AN, Person BX, Person CX, etc.

// What the API returns for "Republican Presidential Nominee 2028"
outcomes: '["Will Donald Trump win?","Will Person AN win?","Will Person CX win?"]'

These are real Polymarket market entries โ€” not a bug in the integration. They represent real liquidity on unknown/unannounced candidates.

UI handling: Placeholder outcomes (/^Person [A-Z]+$/) are deprioritised โ€” sorted to the end of the card so named candidates show first. They still appear in the full market detail view.

Mobile Wallet Connection โ€” window.ethereum is Unavailable

Standard browser extensions (MetaMask, Brave Wallet, Rabby) inject window.ethereum into the page. On mobile browsers (Safari, Chrome), no such injection happens โ€” window.ethereum is undefined, so any call to connect() silently fails or errors with no user feedback.

What we observed:

  • "Connect Wallet" button appeared functional but did nothing on mobile Safari
  • No error was thrown, no modal appeared โ€” completely silent failure
  • The bug only surfaced when testing on a real device, not in browser DevTools mobile simulation

The fix applied:

// Detect missing wallet before attempting connection
function hasEthereum() {
  return typeof window !== 'undefined' && !!window.ethereum;
}

// Show a deep link instead of a broken button
if (!hasEthereum()) {
  return (
    <a href="https://metamask.app.link/dapp/polypocket.vercel.app">
      ๐ŸฆŠ Get MetaMask
    </a>
  );
}

metamask.app.link/dapp/<url> opens the MetaMask mobile app and navigates to your dapp inside MetaMask's built-in browser, where window.ethereum IS available.

Alternatives for real mobile wallet support:

  • WalletConnect v2 โ€” QR code + deep link flow, works cross-app. Requires a free project ID from cloud.walletconnect.com. Add as NEXT_PUBLIC_WC_PROJECT_ID env var.
  • Coinbase Wallet SDK โ€” similar mobile deep link approach, no project ID needed
  • RainbowKit โ€” wraps both of the above with a polished modal UI, but adds ~400KB and wagmi dependency complexity (see gotcha #2)

About

๐Ÿซง Live Polymarket prediction markets in your pocket โ€” Next.js + ethers.js CLOB API integration

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors