diff --git a/terminal/src/app/api/copy-trading/run/route.ts b/terminal/src/app/api/copy-trading/run/route.ts
new file mode 100644
index 0000000..6ced8aa
--- /dev/null
+++ b/terminal/src/app/api/copy-trading/run/route.ts
@@ -0,0 +1,40 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { CopyTrader } from '@/lib/copy-trader';
+
+export async function GET(req: NextRequest) {
+ const { searchParams } = new URL(req.url);
+ const target = searchParams.get('target') || undefined;
+ const isTest = searchParams.get('test') === 'true';
+ const amountParam = searchParams.get('amount');
+
+ // Parse and validate trade amount (default: 5 USDC)
+ const tradeAmount = amountParam ? parseFloat(amountParam) : 5;
+ if (isNaN(tradeAmount) || tradeAmount <= 0) {
+ return NextResponse.json(
+ { error: 'Invalid trade amount. Must be a positive number.' },
+ { status: 400 }
+ );
+ }
+
+ try {
+ const trader = new CopyTrader();
+
+ if (isTest) {
+ const result = await trader.testExecution();
+ return NextResponse.json({ success: true, data: result });
+ }
+
+ const result = await trader.run(target, tradeAmount);
+
+ return NextResponse.json({
+ success: true,
+ data: result
+ });
+ } catch (error) {
+ console.error('Copy Trading execution failed:', error);
+ return NextResponse.json(
+ { error: 'Failed to run copy trader', details: error instanceof Error ? error.message : String(error) },
+ { status: 500 }
+ );
+ }
+}
diff --git a/terminal/src/app/copy-trading/README.md b/terminal/src/app/copy-trading/README.md
new file mode 100644
index 0000000..625ca9f
--- /dev/null
+++ b/terminal/src/app/copy-trading/README.md
@@ -0,0 +1,59 @@
+# Polymarket Copy Trading Bot
+
+## Overview
+The **Polymarket Copy Trading Bot** is an automated system designed to mimic the trades of profitable users on the Polymarket platform. By monitoring a target wallet, this system automatically executes identical trades (Buy/Sell) on the same markets, leveraging the insights of successful traders.
+
+## Features
+- **Leaderboard Integration:** View top traders by category (Politics, Sports, Crypto, etc.) and time period.
+- **One-Click Copy:** Easily select a trader from the leaderboard to start copying.
+- **Automated Execution:** Monitors the target wallet in real-time and executes trades instantly.
+- **Configurable Trade Amounts:** Set a fixed USDC amount ("n USDC to trade") for every copied trade, managing your risk independent of the target's trade size.
+- **Safety Mechanism:** Filters out large or risky trades based on configurable parameters (default: safe mode).
+
+## System Architecture
+
+### 1. Frontend (Next.js)
+- Located at `/copy-trading`.
+- Provides a dashboard to:
+ - Search/Select target wallets.
+ - View leaderboard statistics.
+ - Configure trade paramters (Amount per trade).
+ - View real-time logs of bot activity.
+
+### 2. Backend (Next.js API Routes)
+- **Monitoring Endpoint:** `/api/copy-trading/run`
+- **Logic:**
+ 1. Fetches recent trades from the target wallet using Polymarket's Gamma API.
+ 2. Compares trade timestamps to identify new activity.
+ 3. If a new trade is found, it constructs a mirroring order.
+
+### 3. Privy Integration (Security)
+We use **Privy** for secure, server-side wallet management and signing.
+- **Why Privy?** It allows the bot to sign transactions programmatically without exposing private keys in the frontend or requiring constant user confirmation (MetaMask popups).
+- **Implementation:**
+ - `src/lib/privy-client.ts`: Initializes the Privy client and manages the "Bot Wallet".
+ - **Server-Side Signing:** The bot generates a special `PrivySigner` (ethers.js compatible) that intercepts signing requests and delegates them to Privy's secure enclave.
+ - This ensures that your trading bot can run autonomously while your assets remain secure.
+
+## Usage Guide
+
+1. **Fund the Bot Wallet:**
+ - Go to the Copy Trading Terminal.
+ - Copy the "Bot Execution Wallet" address.
+ - Send **USDC (Polygon)** for trading and **MATIC** for gas fees to this address.
+
+2. **Select a Trader:**
+ - Browse the "Top Traders Leaderboard".
+ - Click "Copy" on a profitable trader, or manually enter a wallet address.
+
+3. **Configure Amount:**
+ - Enter the **"Trade Amount (USDC)"**.
+ - *Example:* If you set this to **10 USDC**, the bot will buy $10 worth of shares for every trade the target makes, regardless of whether the target traded $100 or $10,000.
+
+4. **Start the Bot:**
+ - Click **"START COPY BOT"**.
+ - Keep the terminal open to monitor logs.
+
+## Development Verification
+- **Test Market Fetching:** `node test-active-market.js`
+- **Test Order Execution:** `node test-live-order.mjs` (Requires funded bot wallet)
diff --git a/terminal/src/app/copy-trading/page.tsx b/terminal/src/app/copy-trading/page.tsx
new file mode 100644
index 0000000..15efee6
--- /dev/null
+++ b/terminal/src/app/copy-trading/page.tsx
@@ -0,0 +1,20 @@
+"use client";
+
+import CopyTradingTerminal from "@/components/CopyTradingTerminal";
+import Sidebar from "@/components/Sidebar";
+
+export default function CopyTradingPage() {
+ return (
+
+ {/* Sidebar Navigation */}
+
+
+
+
+ {/* Main Content */}
+
+
+
+
+ );
+}
diff --git a/terminal/src/components/CopyTradingTerminal.tsx b/terminal/src/components/CopyTradingTerminal.tsx
new file mode 100644
index 0000000..cce93b0
--- /dev/null
+++ b/terminal/src/components/CopyTradingTerminal.tsx
@@ -0,0 +1,683 @@
+"use client";
+
+import { useState, useEffect, useRef } from "react";
+import { Play, Square, Activity, Copy, Wallet, ExternalLink, DollarSign, TrendingUp, TrendingDown, Users, X, LayoutGrid, Circle } from "lucide-react";
+
+// interface ActiveTrader {
+// wallet: string;
+// tradeCount: number;
+// totalVolume: number;
+// markets: number;
+// recentTrades: Array<{
+// market: string;
+// side: string;
+// size: number;
+// price: number;
+// timestamp: number;
+// }>;
+// }
+
+import { LeaderboardCategory, LeaderboardTimePeriod, TraderLeaderboardEntry } from "@/types/polymarket";
+
+export default function CopyTradingTerminal() {
+ const [targetAddress, setTargetAddress] = useState("");
+ const [tradeAmount, setTradeAmount] = useState(5);
+ const [isRunning, setIsRunning] = useState(false);
+ const [logs, setLogs] = useState([]);
+ const [botWallet, setBotWallet] = useState("");
+
+ // Leaderboard State
+ const [leaderboard, setLeaderboard] = useState([]);
+ const [loadingLeaderboard, setLoadingLeaderboard] = useState(false);
+ const [leaderboardCategory, setLeaderboardCategory] = useState('OVERALL');
+ const [timePeriod, setTimePeriod] = useState('WEEK');
+ const [viewMode, setViewMode] = useState<'LIST' | 'BUBBLE'>('LIST');
+
+ const logsEndRef = useRef(null);
+ const intervalRef = useRef(null);
+
+ // Auto-scroll logs
+ useEffect(() => {
+ logsEndRef.current?.scrollIntoView({ behavior: "smooth" });
+ }, [logs]);
+
+ // Fetch leaderboard on mount and when filters change
+ useEffect(() => {
+ fetchLeaderboard();
+ }, [leaderboardCategory, timePeriod]);
+
+ // Cleanup on unmount
+ useEffect(() => {
+ return () => {
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current);
+ }
+ };
+ }, []);
+
+ const fetchLeaderboard = async () => {
+ setLoadingLeaderboard(true);
+ try {
+ const response = await fetch(`https://data-api.polymarket.com/v1/leaderboard?category=${leaderboardCategory}&timePeriod=${timePeriod}&limit=10`);
+ const data = await response.json();
+
+ if (Array.isArray(data)) {
+ setLeaderboard(data);
+ } else {
+ console.error('Leaderboard data is not an array:', data);
+ setLeaderboard([]);
+ }
+ } catch (error) {
+ console.error('Failed to fetch leaderboard:', error);
+ // addLog('Failed to fetch leaderboard', 'ERROR');
+ } finally {
+ setLoadingLeaderboard(false);
+ }
+ };
+
+ const openCopyTradeModalFromLeaderboard = (trader: TraderLeaderboardEntry) => {
+ setTargetAddress(trader.proxyWallet);
+ // Optionally scroll to top or highlight the configuration panel
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ };
+
+ const addLog = (message: string, level: 'INFO' | 'SUCCESS' | 'ERROR' | 'TRADE' = 'INFO') => {
+ const time = new Date().toLocaleTimeString([], { hour12: false });
+ const prefix = level === 'TRADE' ? '๐ฏ' : level === 'SUCCESS' ? 'โ
' : level === 'ERROR' ? 'โ' : 'โน๏ธ';
+ setLogs(prev => [...prev, `[${time}] ${prefix} ${message}`]);
+ };
+
+ const fetchBotWallet = async () => {
+ try {
+ addLog("Fetching bot wallet...");
+ const res = await fetch('/api/privy/wallet', { method: 'POST' });
+ const data = await res.json();
+ if (data.success && data.wallet) {
+ setBotWallet(data.wallet.address);
+ addLog(`Bot wallet loaded: ${data.wallet.address}`, 'SUCCESS');
+ return true;
+ } else {
+ addLog("Failed to load bot wallet", 'ERROR');
+ return false;
+ }
+ } catch (error) {
+ addLog(`Error loading bot wallet: ${error}`, 'ERROR');
+ return false;
+ }
+ };
+
+ const startBot = async () => {
+ if (!targetAddress) {
+ addLog("Please enter a target wallet address", 'ERROR');
+ return;
+ }
+
+ if (!targetAddress.match(/^0x[a-fA-F0-9]{40}$/)) {
+ addLog("Invalid wallet address format", 'ERROR');
+ return;
+ }
+
+ if (tradeAmount <= 0) {
+ addLog("Trade amount must be positive", 'ERROR');
+ return;
+ }
+
+ setIsRunning(true);
+ setLogs([]);
+
+ addLog("๐ Starting Copy Trading Bot...");
+ addLog(`Target: ${targetAddress}`);
+ addLog(`Trade Amount: ${tradeAmount} USDC`);
+
+ // Fetch bot wallet
+ const walletLoaded = await fetchBotWallet();
+ if (!walletLoaded) {
+ setIsRunning(false);
+ return;
+ }
+
+ addLog("Connecting to Polymarket...", 'SUCCESS');
+ addLog("Starting trade monitoring...");
+
+ // Start polling
+ pollForTrades();
+ };
+
+ const stopBot = () => {
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current);
+ intervalRef.current = null;
+ }
+ setIsRunning(false);
+ addLog("Bot stopped by user", 'INFO');
+ };
+
+ const pollForTrades = async () => {
+ // Initial check
+ await checkForTrades();
+
+ // Set up interval (every 5 seconds)
+ intervalRef.current = setInterval(async () => {
+ await checkForTrades();
+ }, 5000);
+ };
+
+ const checkForTrades = async () => {
+ try {
+ const res = await fetch(`/api/copy-trading/run?target=${targetAddress}&amount=${tradeAmount}`);
+ const data = await res.json();
+
+ if (!data.success) {
+ addLog(`Error: ${data.error || 'Unknown error'}`, 'ERROR');
+ return;
+ }
+
+ const result = data.data;
+
+ // Display all logs from backend (for new trades being executed)
+ if (result.logs && result.logs.length > 0) {
+ result.logs.forEach((log: string) => {
+ // Determine log level based on content
+ const level = log.includes('โ') ? 'ERROR' :
+ log.includes('โ
') || log.includes('๐ฏ') ? 'SUCCESS' :
+ 'INFO';
+ addLog(log, level);
+ });
+ }
+
+ // Always show latest trade info if available (both new and past trades)
+ if (result.latestParams) {
+ const trade = result.latestParams;
+
+ if (result.executed) {
+ // New trade was executed - already shown in logs above
+ addLog('โ'.repeat(50), 'INFO');
+ } else {
+ // Show latest trade found (already processed)
+ addLog(`๐ Latest Trade Monitored:`, 'INFO');
+ addLog(` Market: ${trade.title || trade.market_slug}`, 'INFO');
+ addLog(` Side: ${trade.side} | Outcome: ${trade.outcome}`, 'INFO');
+ addLog(` Size: ${trade.size} | Price: $${trade.price}`, 'INFO');
+ addLog(` Timestamp: ${new Date(trade.timestamp * 1000).toLocaleString()}`, 'INFO');
+ addLog(` Status: ${result.status}`, 'INFO');
+ addLog('โ'.repeat(50), 'INFO');
+ }
+ } else if (!result.executed) {
+ // No trades found at all
+ addLog(`๐ก Scanning... ${result.status}`, 'INFO');
+ }
+ } catch (error) {
+ addLog(`Connection error: ${error}`, 'ERROR');
+ }
+ };
+
+ return (
+
+
+
+ {/* Header */}
+
+
+ Copy Trading Bot
+
+
+ Automatically copy trades from any Polymarket wallet with configurable amounts
+
+
+
+ {/* Active Traders / Leaderboard Section */}
+
+
+
+ Top Traders Leaderboard
+
+
+
+
+ {/* View Toggle */}
+
+ setViewMode('LIST')}
+ className={`p-1.5 rounded transition-colors ${viewMode === 'LIST' ? 'bg-background shadow text-primary' : 'text-muted-foreground hover:text-foreground'}`}
+ title="List View"
+ >
+
+
+ setViewMode('BUBBLE')}
+ className={`p-1.5 rounded transition-colors ${viewMode === 'BUBBLE' ? 'bg-background shadow text-primary' : 'text-muted-foreground hover:text-foreground'}`}
+ title="Bubble Map"
+ >
+
+
+
+
+
setTimePeriod(e.target.value as LeaderboardTimePeriod)}
+ className="bg-secondary/50 border border-border rounded px-2 py-1 focus:outline-none focus:border-primary"
+ >
+ 24h
+ 7d
+ 30d
+ All Time
+
+
+
setLeaderboardCategory(e.target.value as LeaderboardCategory)}
+ className="bg-secondary/50 border border-border rounded px-2 py-1 focus:outline-none focus:border-primary"
+ >
+ Overall
+ Politics
+ Sports
+ Crypto
+ Culture
+ Tech
+ Finance
+
+
+
+ {loadingLeaderboard ? : 'Refresh'}
+
+
+
+
+ {loadingLeaderboard ? (
+
+
+
Loading leaderboard rankings...
+
+ ) : leaderboard.length === 0 ? (
+
+
No traders found for this category
+
+ ) : viewMode === 'BUBBLE' ? (
+
+
+ {/* Bubble Rendering Logic */}
+ {leaderboard.map((trader, idx) => {
+ // Scale size based on rank/volume concept - simpler to use rank for visual hierarchy in a top 10 list
+ // Rank 1 = Largest.
+ const maxBubbles = leaderboard.length;
+ // Size between 60px and 160px
+ const size = 160 - (idx * (100 / maxBubbles));
+ const isCopied = targetAddress.toLowerCase() === trader.proxyWallet.toLowerCase();
+ const isPositive = trader.pnl >= 0;
+
+ return (
+
openCopyTradeModalFromLeaderboard(trader)}
+ className={`rounded-full flex flex-col items-center justify-center cursor-pointer transition-all duration-300 hover:scale-110 hover:z-10 relative group border-2 ${isCopied
+ ? 'bg-green-500/20 border-green-500 shadow-[0_0_30px_rgba(74,222,128,0.3)] scale-105 z-10'
+ : isPositive
+ ? 'bg-gradient-to-br from-green-500/10 to-green-900/20 border-green-500/30 hover:border-green-400'
+ : 'bg-gradient-to-br from-red-500/10 to-red-900/20 border-red-500/30 hover:border-red-400'
+ }`}
+ style={{
+ width: `${size}px`,
+ height: `${size}px`,
+ }}
+ >
+ {/* Rank Badge */}
+
+ #{trader.rank}
+
+
+ {/* Profile Image or Initial */}
+ {trader.profileImage ? (
+
+ ) : (
+
+ {trader.userName?.charAt(0) || '?'}
+
+ )}
+
+ {/* Name */}
+
+ {trader.userName || 'Anonymous'}
+
+
+ {/* Stat */}
+
+ {isPositive ? '+' : ''}${trader.pnl.toLocaleString(undefined, { notation: "compact" })}
+
+
+ {/* Copied Label */}
+ {isCopied && (
+
+ COPIED
+
+ )}
+
+ );
+ })}
+
+
+ Size by Rank
+
+
+ ) : (
+
+
+
+
+ Rank
+ Trader
+ Volume
+ P&L
+ Action
+
+
+
+ {leaderboard.map((trader) => (
+
+
+ #{trader.rank}
+
+
+
+ {trader.profileImage ? (
+
+ ) : (
+
+ {trader.userName?.charAt(0) || '?'}
+
+ )}
+
+
+
+ {trader.userName || 'Anonymous'}
+
+ {trader.verifiedBadge && (
+ โ
+ )}
+
+
+ {trader.proxyWallet.substring(0, 6)}...{trader.proxyWallet.substring(38)}
+
+
+
+
+
+
+
+
+ ${trader.vol.toLocaleString(undefined, { maximumFractionDigits: 0 })}
+
+ = 0 ? 'text-green-400' : 'text-red-400'}`}>
+ {trader.pnl >= 0 ? '+' : ''}${trader.pnl.toLocaleString(undefined, { maximumFractionDigits: 0 })}
+
+
+ openCopyTradeModalFromLeaderboard(trader)}
+ className={`px-3 py-1.5 rounded text-xs font-bold border transition-colors flex items-center justify-center gap-1 min-w-[80px] ${targetAddress.toLowerCase() === trader.proxyWallet.toLowerCase()
+ ? 'bg-green-500/20 text-green-400 border-green-500/50 cursor-default'
+ : 'bg-primary/20 hover:bg-primary/30 text-primary border-primary/50'
+ }`}
+ >
+ {targetAddress.toLowerCase() === trader.proxyWallet.toLowerCase() ? (
+ <>
+
+ Copied
+ >
+ ) : (
+ 'Copy'
+ )}
+
+
+
+ ))}
+
+
+
+ )}
+
+
+ {/* Copy Trade Modal */}
+ {/* {showModal && selectedTrader && (
+
+
+
+
Start Copy Trading
+ setShowModal(false)}
+ className="p-2 hover:bg-secondary rounded transition-colors"
+ >
+
+
+
+
+
+
+ Target Wallet
+
+ {selectedTrader.wallet}
+
+
+
+
+
+
Trades
+
{selectedTrader.tradeCount}
+
+
+
Volume
+
${selectedTrader.totalVolume.toFixed(2)}
+
+
+
Markets
+
{selectedTrader.markets}
+
+
+
+ {selectedTrader.recentTrades.length > 0 && (
+
+
Recent Trades
+
+ {selectedTrader.recentTrades.map((trade, idx) => (
+
+
+
+ {trade.side}
+
+
+ {new Date(trade.timestamp * 1000).toLocaleString()}
+
+
+
{trade.market}
+
{trade.size.toFixed(1)} @ ${trade.price.toFixed(2)}
+
+ ))}
+
+
+ )}
+
+
+
Your Trade Amount (USDC)
+
+
+ setTradeAmount(parseFloat(e.target.value) || 0)}
+ min="0.01"
+ step="0.01"
+ className="w-full pl-10 pr-4 py-2 rounded bg-secondary/30 border border-border font-mono text-sm focus:border-primary focus:outline-none"
+ />
+
+
Amount to use for each copied trade
+
+
+
+ setShowModal(false)}
+ className="flex-1 px-4 py-2 rounded bg-secondary/50 hover:bg-secondary border border-border transition-colors"
+ >
+ Cancel
+
+
+ Start Copy Bot
+
+
+
+
+
+ )} */}
+
+ {/* Configuration Panel */}
+
+
+ {/* Target Configuration */}
+
+
+ Target Configuration
+
+
+
+ Target Wallet Address
+ setTargetAddress(e.target.value.trim())}
+ placeholder="0x..."
+ disabled={isRunning}
+ className="w-full px-4 py-2 rounded bg-secondary/30 border border-border font-mono text-sm focus:border-primary focus:outline-none disabled:opacity-50"
+ />
+
+
+
+
Trade Amount (USDC)
+
+
+ setTradeAmount(parseFloat(e.target.value) || 0)}
+ min="0.01"
+ step="0.01"
+ disabled={isRunning}
+ className="w-full pl-10 pr-4 py-2 rounded bg-secondary/30 border border-border font-mono text-sm focus:border-primary focus:outline-none disabled:opacity-50"
+ />
+
+
Amount to use for each copied trade
+
+
+
+ {isRunning ? (
+ <>
+
+ STOP BOT
+ >
+ ) : (
+ <>
+
+ START COPY BOT
+ >
+ )}
+
+
+
+ {/* Bot Wallet Info */}
+
+
+ Bot Execution Wallet
+
+
+
+
Wallet Address (Privy Server)
+
+
+ {botWallet || "Not initialized..."}
+
+
+
+
+ {botWallet && (
+
+ )}
+
+
+
+ โ ๏ธ Ensure this wallet has MATIC for gas and USDC for trading
+
+
+
+
+
+
+ {/* Logs Terminal */}
+
+
+
+ {logs.length === 0 ? (
+
+
+
Terminal Ready. Configure target and start bot.
+
+ ) : (
+ logs.map((log, i) => (
+
+ {log}
+
+ ))
+ )}
+
+
+
+
+
+
+ );
+}
diff --git a/terminal/src/lib/copy-trader.ts b/terminal/src/lib/copy-trader.ts
new file mode 100644
index 0000000..a02a5f5
--- /dev/null
+++ b/terminal/src/lib/copy-trader.ts
@@ -0,0 +1,265 @@
+import { PolymarketClient } from './polymarket-client';
+import { privy, getSystemWallet } from './privy-client';
+
+const DEFAULT_TARGET_WALLET = "0x7ec4ffce0be6d9b30bb6c962166cde129dba00ad";
+const POLYMARKET_EXCHANGE_ADDRESS = "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E";
+
+// Simple in-memory store for last processed timestamp
+let lastProcessedTimestamp = 0;
+
+export class CopyTrader {
+ private polyClient: PolymarketClient;
+ private botWallet?: { id: string; address: string };
+
+ constructor() {
+ this.polyClient = new PolymarketClient();
+ }
+
+ async run(targetAddress?: string, tradeAmount: number = 5) {
+ const target = targetAddress || DEFAULT_TARGET_WALLET;
+ const logs: string[] = [];
+
+ console.log(`[CopyTrader] Checking for new trades from ${target}...`);
+ console.log(`[CopyTrader] Trade amount configured: ${tradeAmount} USDC`);
+
+ logs.push(`๐ Checking for new trades from target wallet...`);
+ logs.push(` Target: ${target.substring(0, 10)}...${target.substring(target.length - 8)}`);
+ logs.push(` Trade Amount: ${tradeAmount} USDC`);
+
+ try {
+ // 1. Fetch recent trades from Target
+ logs.push(`๐ก Fetching recent trades from Polymarket API...`);
+ const trades = await this.polyClient.getUserTrades(target);
+
+ if (!trades || trades.length === 0) {
+ logs.push(`โ ๏ธ No trades found for this wallet`);
+ return { status: "No trades found", executed: false, logs };
+ }
+
+ logs.push(`โ
Found ${trades.length} trade(s) from target wallet`);
+
+ // 2. Sort by timestamp descending (newest first)
+ const latestTrade = trades[0];
+ logs.push(`๐ Analyzing latest trade...`);
+ logs.push(` Timestamp: ${new Date(latestTrade.timestamp * 1000).toLocaleString()}`);
+
+ // 3. Check if new
+ if (latestTrade.timestamp <= lastProcessedTimestamp) {
+ logs.push(`โญ๏ธ Trade already processed (timestamp: ${lastProcessedTimestamp})`);
+ logs.push(`๐ Latest Trade Info:`);
+ logs.push(` Market: ${latestTrade.title || latestTrade.market_slug}`);
+ logs.push(` Side: ${latestTrade.side} | Outcome: ${latestTrade.outcome}`);
+ logs.push(` Size: ${latestTrade.size} | Price: $${latestTrade.price}`);
+ return { status: "No new trades", executed: false, latestParams: latestTrade, logs };
+ }
+
+ console.log(`[CopyTrader] NEW TRADE DETECTED:`, latestTrade);
+ logs.push(`๐ฏ NEW TRADE DETECTED!`);
+ logs.push(` Side: ${latestTrade.side}`);
+ logs.push(` Market: ${latestTrade.title || latestTrade.market_slug || 'Unknown'}`);
+ logs.push(` Outcome: ${latestTrade.outcome}`);
+ logs.push(` Size: ${latestTrade.size}`);
+ logs.push(` Price: $${latestTrade.price}`);
+ if (latestTrade.asset) {
+ logs.push(` Asset ID: ${latestTrade.asset.substring(0, 20)}...`);
+ }
+
+ lastProcessedTimestamp = latestTrade.timestamp;
+ logs.push(`โ
Updated last processed timestamp: ${lastProcessedTimestamp}`);
+
+ // 4. Analyze/Filter Trade
+ // Example Policy: Only copy small trades < $1000 size for safety
+ // const size = parseFloat(latestTrade.size);
+ // if (size > 1000) {
+ // return { status: "Trade filtered (Size too large)", executed: false, trade: latestTrade };
+ // }
+
+ // 5. Execute Copy
+ // Get System Wallet
+ logs.push(`๐ Fetching bot wallet from Privy...`);
+ const botWallet = await getSystemWallet();
+ if (!botWallet) {
+ logs.push(`โ Bot wallet not available`);
+ throw new Error("Bot wallet not available");
+ }
+
+ // Store wallet for error messages
+ this.botWallet = botWallet;
+
+ console.log(`[CopyTrader] Executing trade with Bot Wallet: ${botWallet.address}`);
+ console.log(`[CopyTrader] Trade size: ${tradeAmount} USDC`);
+
+ logs.push(`โ
Bot Wallet Retrieved: ${botWallet.address}`);
+ logs.push(`๐ฐ Preparing to execute trade with ${tradeAmount} USDC`);
+
+ const txHash = await this.executeTrade(botWallet, latestTrade, tradeAmount, logs);
+
+ logs.push(`โ
TRADE EXECUTED SUCCESSFULLY!`);
+ logs.push(` TX Hash: ${txHash}`);
+
+ return {
+ status: "Copy Trade Executed",
+ executed: true,
+ txHash,
+ logs,
+ trade: {
+ market: latestTrade.market_slug,
+ side: latestTrade.side,
+ size: latestTrade.size,
+ price: latestTrade.price,
+ timestamp: latestTrade.timestamp,
+ outcome: latestTrade.outcome
+ }
+ };
+
+ } catch (error) {
+ console.error("[CopyTrader] Error:", error);
+ logs.push(`โ ERROR OCCURRED!`);
+
+ if (error instanceof Error) {
+ logs.push(` Message: ${error.message}`);
+ if (error.stack) {
+ const stackLines = error.stack.split('\n').slice(0, 3);
+ logs.push(` Stack: ${stackLines.join(' | ')}`);
+ }
+ } else {
+ logs.push(` Details: ${String(error)}`);
+ }
+
+ return {
+ status: "Error occurred",
+ executed: false,
+ logs,
+ error: error instanceof Error ? error.message : String(error)
+ };
+ }
+ }
+
+ private async executeTrade(wallet: { id: string, address: string }, trade: any, tradeAmount: number, logs: string[]) {
+ // Validate trade amount
+ if (tradeAmount <= 0) {
+ throw new Error("Trade amount must be positive");
+ }
+
+ logs.push(`โ๏ธ Initializing Polymarket client...`);
+
+ console.log(`[CopyTrader] Executing trade with Bot Wallet: ${wallet.address}`);
+ console.log(`[CopyTrader] Trade size: ${tradeAmount} USDC`);
+
+ // Import Privy signer creator
+ const { createPrivySigner } = await import('./privy-client');
+
+ // Create Privy signer for this wallet
+ logs.push(`๐ Creating Privy signer for wallet...`);
+ const signer = await createPrivySigner(wallet.id, wallet.address);
+ logs.push(`โ
Privy signer created`);
+
+ // Initialize Polymarket client with Privy signer
+ logs.push(`๐ Initializing Polymarket CLOB client...`);
+ await this.polyClient.initializeForWallet(wallet.address, signer);
+ logs.push(`โ
Polymarket client initialized`);
+
+ // Prepare order parameters
+ logs.push(`โ๏ธ Preparing order parameters...`);
+ logs.push(` Market: ${trade.title || trade.market_slug || 'Unknown'}`);
+ logs.push(` Side: ${trade.side}`);
+ if (trade.asset) {
+ logs.push(` Token ID: ${trade.asset.substring(0, 20)}...`);
+ }
+ logs.push(` Size: ${tradeAmount} USDC`);
+ logs.push(` Price: $${trade.price}`);
+
+ // Validate that we have the required asset ID
+ if (!trade.asset) {
+ logs.push(`โ Missing asset ID - cannot execute trade`);
+ throw new Error("Trade is missing asset ID");
+ }
+
+ // Execute the trade using the SDK
+ logs.push(`๐ Creating and signing order via Polymarket SDK...`);
+
+ try {
+ const response = await this.polyClient.postOrder({
+ tokenId: trade.asset,
+ conditionId: trade.conditionId, // Pass condition ID for proper market lookup
+ price: parseFloat(trade.price),
+ side: trade.side,
+ size: tradeAmount
+ });
+
+ // Check if order was actually created
+ if (response.orderID) {
+ logs.push(`โ
Order Posted Successfully!`);
+ logs.push(` Order ID: ${response.orderID}`);
+ logs.push(` TX Hash: ${response.transactionHash || 'Pending'}`);;
+ } else {
+ logs.push(`โ ๏ธ Order response received but no Order ID`);
+ logs.push(` Order ID: undefined`);
+ }
+
+ return response.transactionHash || response.orderID || "OrderPosted";
+ } catch (error: any) {
+ // Log the API error details
+ logs.push(`โ Order Submission Failed!`);
+
+ // Extract the specific error message from the API response
+ let errorMessage = 'Unknown error';
+
+ if (error.response?.data?.error) {
+ // This is the specific error from Polymarket API (e.g., "not enough balance / allowance")
+ errorMessage = error.response.data.error;
+ logs.push(` โ ERROR: ${errorMessage}`);
+ } else if (error.message) {
+ errorMessage = error.message;
+ logs.push(` Error: ${errorMessage}`);
+ } else {
+ logs.push(` Error: ${String(error)}`);
+ }
+
+ // Add helpful context for common errors
+ if (errorMessage.includes('balance') || errorMessage.includes('allowance')) {
+ logs.push(` ๐ก Your bot wallet needs USDC funding`);
+ logs.push(` ๐ Wallet: ${this.botWallet?.address || 'Unknown'}`);
+ logs.push(` ๐ฐ Send USDCe (Polygon) to this address`);
+ }
+
+ logs.push(` Order ID: undefined`);
+
+ throw error; // Re-throw to be caught by outer try-catch
+ }
+ }
+
+ async testExecution() {
+ console.log("[CopyTrader] Starting Manual Test Execution...");
+ // 1. Get System Wallet
+ const botWallet = await getSystemWallet();
+ if (!botWallet) {
+ throw new Error("Bot wallet not available");
+ }
+ console.log(`[Test] Using Bot Wallet: ${botWallet.address}`);
+
+ // 2. Create Dummy Trade (e.g. Yes on a popular market)
+ // Trump Inauguration Market Token ID (Example) or just a random one for testing signature
+ // Using a real Token ID is better to get a real error from Polymarket if funds are low
+ const TEST_TOKEN_ID = "21742633143463906290569050155826241533067272736897614382201930560761571152424"; // Random valid-looking ID
+
+ const testTrade = {
+ side: "BUY",
+ asset: TEST_TOKEN_ID,
+ size: "1",
+ price: "0.5",
+ market_slug: "test-market",
+ timestamp: Date.now(),
+ outcome: "Yes"
+ };
+
+ // 3. Execut
+ try {
+ const result = await this.executeTrade(botWallet, testTrade);
+ return { status: "Test Execution Attempted", result };
+ } catch (error: any) {
+ console.error("[Test] Execution Failed (Expected if no funds):", error);
+ return { status: "Test Execution Failed (Likely Insufficient Funds)", error: error.message };
+ }
+ }
+}
diff --git a/terminal/src/lib/polymarket-client.ts b/terminal/src/lib/polymarket-client.ts
new file mode 100644
index 0000000..9fffb86
--- /dev/null
+++ b/terminal/src/lib/polymarket-client.ts
@@ -0,0 +1,264 @@
+// src/lib/polymarket-client.ts
+import { ClobClient } from "@polymarket/clob-client";
+import * as ethersV5 from "ethers5"; // Use ethers v5 for Polymarket compatibility
+
+interface ApiCredentials {
+ key: string;
+ secret: string;
+ passphrase: string;
+}
+
+export class PolymarketClient {
+ private baseUrl = "https://data-api.polymarket.com";
+ private clobClient: ClobClient | null = null;
+ private credentials: Map = new Map();
+
+ /**
+ * Initialize the CLOB client with a wallet and handle API credentials
+ */
+ async initializeForWallet(walletAddress: string, signer: ethersV5.Wallet | ethersV5.providers.JsonRpcSigner) {
+ console.log(`[Polymarket] Initializing client for wallet ${walletAddress}...`);
+
+ // Step 1: Check if we have stored credentials
+ let apiCreds = this.credentials.get(walletAddress);
+
+ if (!apiCreds) {
+ // Check environment variables
+ if (
+ process.env.POLYMARKET_API_KEY &&
+ process.env.POLYMARKET_WALLET_ADDRESS === walletAddress
+ ) {
+ console.log(`[Polymarket] Using credentials from environment variables`);
+ apiCreds = {
+ key: process.env.POLYMARKET_API_KEY,
+ secret: process.env.POLYMARKET_API_SECRET!,
+ passphrase: process.env.POLYMARKET_API_PASSPHRASE!
+ };
+ this.credentials.set(walletAddress, apiCreds);
+ } else {
+ // Step 2: Create temporary client to derive credentials
+ console.log(`[Polymarket] Creating temporary client to derive credentials...`);
+ const tempClient = new ClobClient(
+ "https://clob.polymarket.com",
+ 137,
+ signer
+ );
+
+ // Step 3: Derive API credentials
+ console.log(`[Polymarket] Deriving API credentials (this may take a moment)...`);
+ const derivedCreds = await tempClient.createOrDeriveApiKey();
+
+ apiCreds = {
+ key: derivedCreds.key,
+ secret: derivedCreds.secret,
+ passphrase: derivedCreds.passphrase
+ };
+
+ this.credentials.set(walletAddress, apiCreds);
+
+ // Log credentials to save
+ console.log(`\n${"=".repeat(60)}`);
+ console.log(`๐ SAVE THESE POLYMARKET CREDENTIALS FOR: ${walletAddress}`);
+ console.log(`Add to your .env file:`);
+ console.log(`POLYMARKET_WALLET_ADDRESS=${walletAddress}`);
+ console.log(`POLYMARKET_API_KEY=${apiCreds.key}`);
+ console.log(`POLYMARKET_API_SECRET=${apiCreds.secret}`);
+ console.log(`POLYMARKET_API_PASSPHRASE=${apiCreds.passphrase}`);
+ console.log(`${"=".repeat(60)}\n`);
+ }
+ } else {
+ console.log(`[Polymarket] Using cached credentials for ${walletAddress}`);
+ }
+
+ // Step 4: Determine signature type and funder
+ // For Privy wallets (EOA), use signatureType = 0
+ const SIGNATURE_TYPE = 0; // EOA
+ const FUNDER_ADDRESS = walletAddress; // For EOA, funder is the wallet itself
+
+ // Step 5: Create the FINAL client with ALL required parameters
+ console.log(`[Polymarket] Creating authenticated CLOB client...`);
+ console.log(` Signature Type: ${SIGNATURE_TYPE} (EOA)`);
+ console.log(` Funder Address: ${FUNDER_ADDRESS}`);
+
+ this.clobClient = new ClobClient(
+ "https://clob.polymarket.com",
+ 137, // Polygon
+ signer,
+ apiCreds, // <-- CRITICAL: User API credentials
+ SIGNATURE_TYPE, // <-- CRITICAL: Must match wallet type
+ FUNDER_ADDRESS, // <-- CRITICAL: Where funds come from
+ undefined, // additional options
+ false // set default address
+ );
+
+ console.log(`[Polymarket] โ
Client initialized successfully with full authentication`);
+ }
+
+ /**
+ * Post an order to Polymarket
+ */
+ async postOrder(params: {
+ tokenId: string;
+ conditionId?: string;
+ price: number;
+ side: 'BUY' | 'SELL';
+ size: number;
+ }): Promise {
+ if (!this.clobClient) {
+ throw new Error("CLOB Client not initialized. Call initializeForWallet() first.");
+ }
+
+ console.log(`[Polymarket] Preparing order...`);
+ console.log(` Token ID: ${params.tokenId.substring(0, 20)}...`);
+ console.log(` Side: ${params.side}`);
+ console.log(` Size: ${params.size}`);
+ console.log(` Price: ${params.price}`);
+
+ try {
+ // Get market info to get tickSize and negRisk
+ let tickSize = "0.01"; // Default tick size
+ let negRisk = false; // Default negRisk
+
+ // Try to fetch market info using condition ID if available
+ if (params.conditionId) {
+ try {
+ console.log(`[Polymarket] Fetching market info using condition ID...`);
+ const market = await this.clobClient.getMarket(params.conditionId);
+
+ if (market) {
+ tickSize = market.minimum_tick_size || tickSize;
+ negRisk = market.neg_risk || negRisk;
+ console.log(`[Polymarket] Market info retrieved:`);
+ console.log(` Tick Size: ${tickSize}`);
+ console.log(` Neg Risk: ${negRisk}`);
+ }
+ } catch (error: any) {
+ console.warn(`[Polymarket] Could not fetch market info (using defaults):`, error.message);
+ console.log(` Using default Tick Size: ${tickSize}`);
+ console.log(` Using default Neg Risk: ${negRisk}`);
+ }
+ } else {
+ console.log(`[Polymarket] No condition ID provided, using default market settings`);
+ console.log(` Tick Size: ${tickSize}`);
+ console.log(` Neg Risk: ${negRisk}`);
+ }
+
+ // Import required types
+ const { Side, OrderType } = await import("@polymarket/clob-client");
+
+ // Create and post order with market options
+ console.log(`[Polymarket] Creating and posting order...`);
+ const response = await this.clobClient.createAndPostOrder(
+ {
+ tokenID: params.tokenId,
+ price: params.price,
+ size: params.size,
+ side: params.side === 'BUY' ? Side.BUY : Side.SELL
+ },
+ {
+ tickSize: tickSize,
+ negRisk: negRisk
+ },
+ OrderType.GTC // Good-Til-Cancelled
+ );
+
+ // Check if the response indicates an error
+ // The CLOB client doesn't throw on API errors, it returns them in the response
+ if (response.status && (response.status >= 400 || response.status < 200)) {
+ console.error(`[CLOB Client] โ API Error - Status ${response.status}`);
+
+ // Extract error message from response
+ const errorMessage = response.error || response.data?.error || response.statusText || 'Unknown error';
+ console.error(`[CLOB Client] Error: ${errorMessage}`);
+
+ // Create a proper error object with the API error details
+ const apiError: any = new Error(errorMessage);
+ apiError.response = {
+ status: response.status,
+ statusText: response.statusText,
+ data: { error: errorMessage }
+ };
+
+ throw apiError;
+ }
+
+ console.log("[Polymarket] โ
Order posted successfully!");
+ console.log(" Order ID:", response.orderID);
+ console.log(" Status:", response.status);
+
+ return response;
+ } catch (error: any) {
+ console.error("[Polymarket] โ Failed to post order:", error);
+
+ // Provide helpful error messages
+ if (error.message?.includes("Invalid signature") || error.message?.includes("L2 auth not available")) {
+ console.error("\n๐ก TIP: This usually means:");
+ console.error(" - Wrong signature type (should be 0 for Privy EOA wallets)");
+ console.error(" - Wrong funder address");
+ console.error(" - API credentials don't match the wallet");
+ } else if (error.message?.includes("Unauthorized") || error.message?.includes("Invalid api key")) {
+ console.error("\n๐ก TIP: API credentials are invalid or expired");
+ console.error(" - Delete credentials from .env and let them regenerate");
+ } else if (error.message?.includes("balance") || error.message?.includes("allowance") || error.message?.includes("not enough")) {
+ console.error("\n๐ก INSUFFICIENT BALANCE ERROR");
+ console.error(" โ Your wallet doesn't have enough USDC to execute this trade");
+ console.error(" ๐ To fix this:");
+ console.error(" 1. Get your bot wallet address from the logs above");
+ console.error(" 2. Send USDCe (Polygon) to that address");
+ console.error(" 3. Approve Polymarket contract to spend your USDC");
+ console.error(" ๐ฐ Minimum recommended: $20-50 USDC for testing");
+
+ // Throw a more user-friendly error
+ throw new Error("Insufficient USDC balance or allowance. Please fund your bot wallet with USDCe on Polygon and approve the Polymarket contract.");
+ }
+
+ throw error;
+ }
+ }
+
+ /**
+ * Get user trades (no auth needed for public data)
+ */
+ async getUserTrades(walletAddress: string, limit: number = 20): Promise {
+ const url = `${this.baseUrl}/trades?limit=${limit}&user=${walletAddress}`;
+
+ try {
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ console.error(`Polymarket API Error (${response.status}): ${errorText}`);
+ throw new Error(`Polymarket API Error: ${response.statusText}`);
+ }
+
+ const data = await response.json();
+
+ console.log("[Polymarket] Raw API response sample:", data[0]);
+
+ // Map to unified format
+ return data.map((t: any) => ({
+ side: t.side,
+ size: parseFloat(t.size),
+ price: parseFloat(t.price),
+ timestamp: t.timestamp,
+ market_slug: t.slug || t.market,
+ asset: t.asset || t.asset_id,
+ conditionId: t.conditionId, // Include condition ID for market lookup
+ title: t.title,
+ outcome: t.outcome,
+ transactionHash: t.transactionHash || t.transaction_hash
+ }));
+
+ } catch (error) {
+ console.error("Failed to fetch Polymarket trades:", error);
+ throw error;
+ }
+ }
+}
+
+export const createPolymarketClient = () => new PolymarketClient();
\ No newline at end of file
diff --git a/terminal/src/lib/privy-client.ts b/terminal/src/lib/privy-client.ts
new file mode 100644
index 0000000..e1a12a6
--- /dev/null
+++ b/terminal/src/lib/privy-client.ts
@@ -0,0 +1,86 @@
+import { PrivyClient } from '@privy-io/server-auth';
+import * as ethersV5 from 'ethers5'; // v5 for Polymarket
+import fs from 'fs';
+import path from 'path';
+
+if (!process.env.PRIVY_APP_ID || !process.env.PRIVY_APP_SECRET) {
+ throw new Error('Missing Privy configuration');
+}
+
+export const privy = new PrivyClient(
+ process.env.PRIVY_APP_ID,
+ process.env.PRIVY_APP_SECRET
+);
+
+const WALLET_FILE = path.join(process.cwd(), 'bot-wallet.json');
+
+export async function getSystemWallet() {
+ // 1. Check if we already have a wallet saved
+ if (fs.existsSync(WALLET_FILE)) {
+ try {
+ const data = JSON.parse(fs.readFileSync(WALLET_FILE, 'utf-8'));
+ if (data.id && data.address) {
+ return data;
+ }
+ } catch (e) {
+ console.error("Error reading wallet file:", e);
+ }
+ }
+
+ // 2. Create new if not exists
+ try {
+ console.log("Creating new Privy Server Wallet for Bot...");
+ const wallet = await privy.walletApi.create({ chainType: 'ethereum' });
+
+ // Save to file
+ fs.writeFileSync(WALLET_FILE, JSON.stringify({
+ id: wallet.id,
+ address: wallet.address,
+ chainType: wallet.chainType,
+ createdAt: new Date().toISOString()
+ }, null, 2));
+
+ return { id: wallet.id, address: wallet.address };
+ } catch (error) {
+ console.error("Failed to create system wallet:", error);
+ return null;
+ }
+}
+
+/**
+ * Create an ethers v5 Signer from a Privy wallet
+ * This uses Privy's signing methods under the hood
+ */
+export async function createPrivySigner(walletId: string, walletAddress: string): Promise {
+ // Create a custom signer that uses Privy's API for signing
+ const provider = new ethersV5.providers.JsonRpcProvider('https://polygon-rpc.com');
+
+ const signer = new ethersV5.VoidSigner(walletAddress, provider);
+
+ // Override the _signTypedData method to use Privy
+ (signer as any)._signTypedData = async (domain: any, types: any, value: any) => {
+ console.log('[PrivySigner] Signing typed data with Privy...');
+
+ // @ts-ignore
+ const signatureResponse = await privy.walletApi.ethereum.signTypedData({
+ walletId: walletId,
+ typedData: {
+ domain,
+ types,
+ primaryType: Object.keys(types).find(key => key !== 'EIP712Domain') || 'Order',
+ message: value
+ }
+ });
+
+ // Extract signature from response
+ const signature = typeof signatureResponse === 'string'
+ ? signatureResponse
+ : (signatureResponse as any).signature || String(signatureResponse);
+
+ console.log('[PrivySigner] Signature received from Privy');
+ return signature;
+ };
+
+ // Cast to JsonRpcSigner to match Polymarket SDK expectations
+ return signer as unknown as ethersV5.providers.JsonRpcSigner;
+}
diff --git a/terminal/src/types/polymarket.ts b/terminal/src/types/polymarket.ts
new file mode 100644
index 0000000..ef52677
--- /dev/null
+++ b/terminal/src/types/polymarket.ts
@@ -0,0 +1,26 @@
+export type LeaderboardCategory =
+ | 'OVERALL'
+ | 'POLITICS'
+ | 'SPORTS'
+ | 'CRYPTO'
+ | 'CULTURE'
+ | 'MENTIONS'
+ | 'WEATHER'
+ | 'ECONOMICS'
+ | 'TECH'
+ | 'FINANCE';
+
+export type LeaderboardTimePeriod = 'DAY' | 'WEEK' | 'MONTH' | 'ALL';
+
+export type LeaderboardOrderBy = 'PNL' | 'VOL';
+
+export interface TraderLeaderboardEntry {
+ rank: string;
+ proxyWallet: string;
+ userName?: string;
+ vol: number;
+ pnl: number;
+ profileImage?: string;
+ xUsername?: string;
+ verifiedBadge?: boolean;
+}
diff --git a/terminal/test-active-market.js b/terminal/test-active-market.js
new file mode 100644
index 0000000..949b8fc
--- /dev/null
+++ b/terminal/test-active-market.js
@@ -0,0 +1,77 @@
+// Quick script to fetch active Polymarket markets
+const fetch = require('node-fetch');
+
+async function getActiveMarkets() {
+ try {
+ console.log('Fetching active markets from Polymarket...\n');
+
+ // Use gamma API for better market data
+ const response = await fetch('https://gamma-api.polymarket.com/markets?limit=20&active=true');
+ const markets = await response.json();
+
+ if (!markets || markets.length === 0) {
+ console.log('No markets found');
+ return;
+ }
+
+ console.log(`Found ${markets.length} active markets:\n`);
+ console.log('='.repeat(80));
+
+ markets.slice(0, 5).forEach((market, idx) => {
+ console.log(`\n${idx + 1}. ${market.question}`);
+ console.log(` Market Slug: ${market.slug || market.market_slug}`);
+ console.log(` Condition ID: ${market.condition_id || market.conditionId}`);
+ console.log(` Active: ${market.active}`);
+ console.log(` End Date: ${market.end_date_iso || 'N/A'}`);
+
+ if (market.tokens && market.tokens.length > 0) {
+ console.log(` Tokens:`);
+ market.tokens.forEach(token => {
+ console.log(` - ${token.outcome}: ${token.token_id}`);
+ console.log(` Price: $${token.price || 'N/A'}`);
+ });
+ } else if (market.outcomes && Array.isArray(market.outcomes)) {
+ console.log(` Outcomes: ${market.outcomes.join(', ')}`);
+ }
+ console.log('-'.repeat(80));
+ });
+
+ // Show a recommended market for testing
+ if (markets.length > 0) {
+ const testMarket = markets[0];
+ console.log('\n\n๐ฏ RECOMMENDED TEST MARKET:');
+ console.log('='.repeat(80));
+ console.log(`Question: ${testMarket.question}`);
+ console.log(`Market Slug: ${testMarket.slug || testMarket.market_slug}`);
+ console.log(`Condition ID: ${testMarket.condition_id || testMarket.conditionId}`);
+ console.log(`End Date: ${testMarket.end_date_iso || 'N/A'}`);
+
+ if (testMarket.tokens && testMarket.tokens.length > 0) {
+ const token = testMarket.tokens[0];
+ console.log(`\nโ
Test Order Parameters (Copy this):`);
+ console.log(`{`);
+ console.log(` tokenId: "${token.token_id}",`);
+ console.log(` conditionId: "${testMarket.condition_id || testMarket.conditionId}",`);
+ console.log(` side: "BUY",`);
+ console.log(` price: ${token.price || 0.5},`);
+ console.log(` size: 1`);
+ console.log(`}`);
+ } else if (testMarket.clobTokenIds && testMarket.clobTokenIds.length > 0) {
+ console.log(`\nโ
Test Order Parameters (Copy this):`);
+ console.log(`{`);
+ console.log(` tokenId: "${testMarket.clobTokenIds[0]}",`);
+ console.log(` conditionId: "${testMarket.condition_id || testMarket.conditionId}",`);
+ console.log(` side: "BUY",`);
+ console.log(` price: 0.5,`);
+ console.log(` size: 1`);
+ console.log(`}`);
+ }
+ }
+
+ } catch (error) {
+ console.error('Error fetching markets:', error.message);
+ console.error('Stack:', error.stack);
+ }
+}
+
+getActiveMarkets();
diff --git a/terminal/test-live-order.mjs b/terminal/test-live-order.mjs
new file mode 100644
index 0000000..4ede268
--- /dev/null
+++ b/terminal/test-live-order.mjs
@@ -0,0 +1,95 @@
+// Test script to execute a real order on an active Polymarket market
+import { PolymarketClient } from './src/lib/polymarket-client.js';
+import { getSystemWallet, createPrivySigner } from './src/lib/privy-client.js';
+
+async function testLiveOrder() {
+ console.log('๐งช TESTING LIVE ORDER EXECUTION');
+ console.log('='.repeat(80));
+
+ try {
+ // Step 1: Get bot wallet
+ console.log('\n๐ Step 1: Getting bot wallet...');
+ const wallet = await getSystemWallet();
+ if (!wallet) {
+ throw new Error('Failed to get bot wallet');
+ }
+ console.log(`โ
Bot Wallet: ${wallet.address}`);
+
+ // Step 2: Create Privy signer
+ console.log('\n๐ Step 2: Creating Privy signer...');
+ const signer = await createPrivySigner(wallet.id, wallet.address);
+ console.log('โ
Signer created');
+
+ // Step 3: Initialize Polymarket client
+ console.log('\n๐ Step 3: Initializing Polymarket client...');
+ const polyClient = new PolymarketClient();
+ await polyClient.initializeForWallet(wallet.address, signer);
+ console.log('โ
Polymarket client initialized');
+
+ // Step 4: Prepare test order
+ console.log('\n๐ Step 4: Preparing test order...');
+ const testOrder = {
+ tokenId: "13018971792603393862521629014128024641178044618205296260525674415067194037856",
+ conditionId: "0x49a20c7523c271099008f3ef9a31521263b24d637959852a391e6b4697b1a437",
+ side: "BUY",
+ price: 0.004,
+ size: 1 // $1 USDC
+ };
+
+ console.log('๐ Order Details:');
+ console.log(` Market: Trump deportation 1-1.25M`);
+ console.log(` Side: ${testOrder.side}`);
+ console.log(` Price: $${testOrder.price}`);
+ console.log(` Size: ${testOrder.size} USDC`);
+ console.log(` Token ID: ${testOrder.tokenId.substring(0, 20)}...`);
+ console.log(` Condition ID: ${testOrder.conditionId}`);
+
+ // Step 5: Execute order
+ console.log('\n๐ Step 5: Executing order...');
+ console.log('โณ This may take a moment...\n');
+
+ const response = await polyClient.postOrder(testOrder);
+
+ // Step 6: Show results
+ console.log('\n' + '='.repeat(80));
+ console.log('๐ ORDER EXECUTION RESULT');
+ console.log('='.repeat(80));
+ console.log('Response:', JSON.stringify(response, null, 2));
+
+ if (response.orderID) {
+ console.log('\nโ
SUCCESS! Order was created:');
+ console.log(` Order ID: ${response.orderID}`);
+ console.log(` Status: ${response.status || 'Pending'}`);
+ if (response.transactionHash) {
+ console.log(` TX Hash: ${response.transactionHash}`);
+ }
+ } else {
+ console.log('\nโ ๏ธ Order response received but no Order ID');
+ }
+
+ } catch (error) {
+ console.error('\n' + '='.repeat(80));
+ console.error('โ TEST FAILED');
+ console.error('='.repeat(80));
+ console.error('Error:', error.message);
+
+ if (error.response) {
+ console.error('API Response:', error.response);
+ }
+
+ // Provide helpful debugging info
+ if (error.message?.includes('Unauthorized')) {
+ console.error('\n๐ก TIP: API credentials issue - try regenerating them');
+ } else if (error.message?.includes('balance') || error.message?.includes('insufficient')) {
+ console.error('\n๐ก TIP: Insufficient USDC balance in wallet');
+ console.error(' Deposit USDCe on Polygon to:', wallet?.address);
+ } else if (error.message?.includes('orderbook')) {
+ console.error('\n๐ก TIP: Market orderbook issue - try a different market');
+ }
+
+ process.exit(1);
+ }
+}
+
+// Run the test
+testLiveOrder();