bandicam.2025-08-28.12-34-48-269.online-video-cutter.com.mp4
bandicam.2025-08-28.13-05-01-952.mp4
competi-app/
│
├── .env # Contains private key & wallet address
├── .env.local
├── package.json
├── tsconfig.json
├── tailwind.config.js
├── postcss.config.js
│
├── public/
│
├── src/
│ ├── pages/
│ │ ├── market/[id].tsx # Single market details & place bet
| | ├── markets/index.tsx # Shows the market cards
│ │ ├── cashout.tsx # Cashout positions page
│ │ └── api/ # API routes (server-side logic)
│ │ ├── markets/
│ │ │ ├── index.ts # GET markets list (Gamma)
│ │ │ └── [id].ts # GET single market details (Gamma)
| | ├── session #Create token and url
| | | ├── create.ts #Create jwt token and url
│ │ ├── positions.ts # GET user positions (CLOB data API)
│ │ ├── cashout.ts # POST to cashout (CLOB order)
│ │ ├── placeOrder.ts # POST to place bet (CLOB order)
│ │ └── trades.ts # GET recent trades (CLOB data API)
│ │
│ ├── components/
│ │ ├── PriceChart.tsx
│ │ ├── RelatedMarketSection.tsx
│ │
│ ├── lib/ # API client wrappers & utils
│ │ ├── gammaClient.ts # Wrap Gamma API calls
│ │ ├── clobClient.ts # Wrap CLOB API calls
│ │ ├── auth.ts # Wallet signing/auth helpers
| | ├── redis.ts
│ │ └── config.ts # Reads values from .env
│ │
│ ├── hooks/ # React hooks for live data
│ │ ├── useMarketHistory.ts
│ │ ├── useMarketData.ts
│ │ ├── usePositions.ts
│ │ ├── useCashout.ts
│ │ ├── usePlaceOrder.ts
| | ├── useLivePrices.ts
| | ├── useRelatedMarkets.ts
│ │
│ ├── styles/
│ │ └── globals.css
│ │
│
├── bot/ # NEW: Discord bot service
│ ├── commands/ # Slash command files
│ │ ├── placebet.js
│ │ └── cashout.js
│ ├── index.js # Main Discord bot (was src/index.js)
│ ├── deploy-commands.js # Registers slash commands
│ └── package.json # Bot deps (discord.js, dotenv, etc.)
Flow Diagram:
[Discord Bot Command (/cashout or /placebet)]
↓
Backend: POST /api/session/create (Next.js API route)
↓
Generates short-lived token (JWT) tied to Discord user + wallet
↓
Returns pop-out URL with token → Discord bot sends link to user
↓
User clicks link → opens Next.js page (cashout.tsx or market/[id].tsx)
↓
Page loads → reads token from query string
↓
Uses hook (usePositions, useMarketData) → calls Next.js API routes
↓
API routes use lib/gammaClient.ts and lib/clobClient.ts to call external Polymarket APIs
↓
Data returned → UI renders
↓
When user clicks "Cashout" or "Place Bet"
↓
Frontend POSTs to /api/cashout or /api/placeOrder (with token + params)
↓
API route looks up wallet from token → calls CLOB API (real price)
↓
Order executed → response sent to frontend
-
Discord bot sends /cashout or /placebet → This triggers a POST to api/session/create in Next.js backend.
-
/api/session/create → Generates a short-lived JWT with discordId and wallet address. → Returns both a token and a URL (like http://localhost:3000/cashout?token=...). → example response shows this.
-
User clicks the link → Browser opens Next.js page (cashout.tsx or market/[id].tsx) with the token in the query string.
-
Page loads → Reads the token from the query string. → Uses hooks (usePositions, useMarketData) to call Next.js API routes.
-
Next.js API routes → Inside /api/positions, /api/markets, etc., we use gammaClient.ts and clobClient.ts to hit the Polymarket APIs (Gamma for market data, CLOB for orders).
-
User clicks "Cashout" or "Place Bet" → Frontend calls /api/cashout or /api/placeOrder with the token and details.
-
Backend verifies token → Extracts wallet + Discord ID from JWT. → Calls CLOB API to execute the trade.
-
Order executes → Response sent back to the frontend.
Before:
Discord bot was sending a price along with the cashout request.
That price could be stale because it was based on an old snapshot of the orderbook.
If the market moved in those seconds, the cashout could fail or give a worse fill.
Now:
✅ Gets live market price using client.getOrderBook(tokenID) - fetches real-time bid/ask prices
✅ Calculates sell price with slippage protection: bestBid * (1 - slippageBps/10000)
✅ Specific Position Cashout: Can target exact marketId + wallet
✅ Real-time Values: Each API call gets current market data
✅ Token ID Integration: Properly maps positions to tradeable tokens
✅ currentBestBid: 0.925 - real-time market rate
✅ recommendedSellPrice: 0.91575 - 99% of live price for execution
Before:
The bet price could be outdated
Now:
It queries the CLOB API for the current lowest ask price (cheapest someone is willing to sell at) at the moment the bet is placed
No stale prices are sent from the frontend → server always decides the price in real time.
Key API used here:
CLOB API again, but this time for buying (lowest ask) instead of selling (highest bid).
I used CLOB WebSocket (NEXT_PUBLIC_CLOB_WS_URL) for real-time price and liquidity changes.
Frontend updates every time the orderbook changes.
And I used Gamma API (NEXT_PUBLIC_GAMMA_API_URL) to fetch market listings and details.
- Cashout is accurate → always sells at the highest bid price from CLOB.
- Bet pricing is accurate → always buys at the lowest ask price from CLOB.
- Frontend no longer sends price → avoids stale or outdated prices.
CashoutButton click → useCashout() → POST /api/cashout?tokenId=123 → lib/clobClient.cashoutPosition("123") → Polymarket CLOB API → return success → update UI
-
CLOB API (https://clob.polymarket.com)
Purpose: Live trading engine (Central Limit Order Book).
What it does:
Returns current orderbook (live buy/sell offers).
Places buy or sell orders (bets and cashouts).
Matches trades at the best available price right now.
Use in the project:
For placing bets at the lowest ask price.
For cashouts at the highest bid price. -
CLOB WebSocket (wss://ws-subscriptions-clob.polymarket.com/ws)
Purpose: Live updates to avoid refreshing the page.
What it does:
Streams real-time orderbook changes.
Can instantly update displayed prices and cashout values. -
Data API (https://data-api.polymarket.com)
Purpose: Historical & analytical data.
What it gives:
Past price movements (candlestick data).
Volume over time.
Trade history.
Use in the project:
Displaying market trends in a graph.
PRIVATE_KEY=your_private_key
WALLET_ADDRESS=your_wallet_address
JWT_SECRET=your_jwt_secret
REDIS_URL=redis://127.0.0.1:6379
DISCORD_TOKEN=your_discord_token
DISCORD_CLIENT_ID=your_discord_client
NEXT_PUBLIC_GAMMA_API_URL=https://gamma-api.polymarket.com
NEXT_PUBLIC_CLOB_API_URL=https://clob.polymarket.com
NEXT_PUBLIC_CLOB_WS_URL=wss://ws-subscriptions-clob.polymarket.com/ws
NEXT_PUBLIC_DATA_API_URL=https://data-api.polymarket.com
NEXT_PUBLIC_USE_MOCK=false
POLYGON_RPC_URL=https://polygon-rpc.com
WALLET_PRIVATE_KEY=your_wallet_address
JWT_SECRET=your_jwt_secret
REDIS_URL=redis://127.0.0.1:6379
fork clone https://github.com/mahmoodalisha/competi-app.git
cd competi-app
npm run dev
cd bot
node deploy-commands.js
node index.js
- Place a /placebet command
- Enter a market id
- Receive a url like this:
- Session created! Click here: http://localhost:3000/market/516706?token=eyJhbGciOiJ
- Visit the page to place a bet and see the live graph
GET http://localhost:3000/api/positions?wallet=yourwalletaddress
Response:
{
"success": true,
"wallet": "your wallet address",
"positions": [
{
"marketId": "516706",
"outcome": "NO",
"size": 300,
"price": 0.35,
"tokenID": "81104637750588840860328515305303028259865221573278091453716127842023614249200",
"wallet": "your wallet address",
"timestamp": 1755934989091
},
{
"marketId": "516717",
"outcome": "YES",
"size": 500,
"price": 0.55,
"tokenID": "84933668601774689515031739413415177606677344300836824135590506442377653851020",
"wallet": "your wallet address",
"timestamp": 1755935059743
},
{
"marketId": "516720",
"outcome": "NO",
"size": 365,
"price": 0.35,
"tokenID": "13310920295352519429188380245880301405446543407852746139009983343885307875580",
"wallet": "your wallet address",
"timestamp": 1755935099653
}
],
"openOrders": [
{
"marketId": "516706",
"outcome": "NO",
"size": 300,
"price": 0.35,
"tokenID": "81104637750588840860328515305303028259865221573278091453716127842023614249200",
"wallet": "your wallet address",
"timestamp": 1755934989091
},
{
"marketId": "516717",
"outcome": "YES",
"size": 500,
"price": 0.55,
"tokenID": "84933668601774689515031739413415177606677344300836824135590506442377653851020",
"wallet": "your wallet address",
"timestamp": 1755935059743
},
{
"marketId": "516720",
"outcome": "NO",
"size": 365,
"price": 0.35,
"tokenID": "13310920295352519429188380245880301405446543407852746139009983343885307875580",
"wallet": "your wallet address",
"timestamp": 1755935099653
}
],
"source": "redis"
}
POST http://localhost:3000/api/placeOrders
Request Body:
{
"marketId": "516720",
"outcome": "NO",
"size": 365,
"price": 0.35
}
Response: {
"success": true,
"order": {
"marketId": "516720",
"outcome": "NO",
"size": 365,
"price": 0.35,
"tokenID": "13310920295352519429188380245880301405446543407852746139009983343885307875580",
"wallet": "wallet address",
"timestamp": 1755935099653
}
}
POST http://localhost:3000/api/cashout
Request Body: {
"wallet": "your wallet address",
"marketId": "516720",
"fullCashout": true
}
Response: {
"success": true,
"message": "Live market data retrieved successfully",
"position": {
"marketId": "516720",
"outcome": "NO",
"size": 365,
"tokenID": "13310920295352519429188380245880301405446543407852746139009983343885307875580"
},
"liveMarketData": {
"currentBestBid": 0.001,
"recommendedSellPrice": 0.00099,
"estimatedCashoutValue": 0.36135,
"timestamp": "2025-08-23T08:40:54.837Z"
},
"instructions": "Use CLOB TypeScript client to execute the actual sell order"
}
Gamma API gives all the market metadata: question, outcomes (Yes/No), start/end date, etc. When we place a bet, the backend sends an order to CLOB with that marketId and the selected outcome (Yes or No).The price is what we want to buy at, and amount is how much we’re betting.
-
Backend doesn’t need to know which market the user is interacting with in Discord ahead of time.
-
The bot then sends a request to backend (or generates a session token URL) that’s tied to that Discord ID.
-
Backend workflow:
- Receive session token from frontend (generated via Discord ID).
- Look up the wallet address associated with that Discord ID.
- Use the token + request body (amount, price, and for cashout, position ID) to place the order. ⦁ Market ID in placeOrders: comes from the Discord bot’s context, not something the frontend or backend decides. ⦁ Handle the request with token + wallet + amount/price and send it to Polymarket/CLOB.
Gamma API (Market Data)
Base URL: https://gamma-api.polymarket.com
-
List Markets
Endpoint: GET /markets
Example:
https://gamma-api.polymarket.com/markets?limit=50&offset=0&active=true -
Get Single Market Details
Endpoint: GET /markets/{marketId} -
Historical Price Data (Candles / Timeseries)
Endpoints: GET /markets/{marketId}/candles
Or under “Pricing and Books”: timeseries endpoints
Purpose: Build charts/trend visualizations -
Fetch Events (Market Grouping) Endpoint: GET /events
Example:
https://gamma-api.polymarket.com/events?active=true
Use for: Displaying grouped markets by event, sorting, tags, etc.
CLOB API (Trading & Cashout)
Base URLs:
REST: https://clob.polymarket.com
WebSocket: wss://ws-subscriptions-clob.polymarket.com/ws/
Data-only endpoints (e.g., positions): https://data-api.polymarket.com
The Discord bot already has the user’s wallet details from their session When we click “place bet” through Discord, it doesnt pass a private key in the request body. Instead, the backend looks up the wallet/session for that Discord user, signs the order server-side, and sends it to the CLOB API.
1.Discord bot → /api/session/create
- ✅ Generates a 10 minutes token mapped to Discord user and wallet.
- ✅ Sends URL with token back to user. This ensures only the Discord user can see their own positions.
2.User clicks URL → frontend reads token
- ✅ Token stored in query params.
- ✅ Frontend calls backend APIs passing this token.
3.Fetch positions → /api/positions
- ✅ Uses token to verify user → fetch wallet address → query Polymarket API.
- ✅ Returns all open positions for that wallet.
- ✅ Can be updated with WebSocket subscription for constantly updating cashout values.
4.One-click cashout → /api/cashout
- ✅ Token validated → wallet resolved → order signed server-side → sent to CLOB.
- ✅ Only backend handles wallet private key — frontend never sees it.
5.Place bet → /api/placeOrders
- ✅ Works the same way: token → wallet → sign → send order.
- ✅ Allows the same Discord flow for placing trades.
Video of the Live graph:
Recording.2025-08-18.120623.mp4
