circuit.toml
Copy
name = "my-polymarket-agent"
tagline = "Trades on Polymarket markets"
walletType = "ethereum"
allowedExecutionModes = ["auto"]
runtimeIntervalMinutes = 60
version = "0.0.1"
[startingAsset]
network = "ethereum:137"
address = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"
minimumAmount = "5000000" # 5 USDC (6 decimals)
Example
Copy
import { Agent, type AgentContext, type CurrentPosition } from "@circuitorg/agent-sdk";
const BUY_AMOUNT = 5; // USD to spend per cycle
// Polygon USDC address
const USDC = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174";
const CLOB_API = "https://clob.polymarket.com";
async function checkOrderBookLiquidity(tokenId: string, side: string, minUsd: number): Promise<boolean> {
/** Check that the CLOB order book has enough liquidity for our order. */
try {
const response = await fetch(`${CLOB_API}/book?token_id=${tokenId}`, { signal: AbortSignal.timeout(10000) });
if (!response.ok) return false;
const book = await response.json();
const orders = book[side === "BUY" ? "asks" : "bids"] || [];
let totalUsd = 0;
for (const order of orders) {
totalUsd += parseFloat(order.price) * parseFloat(order.size);
}
return totalUsd >= minUsd;
} catch {
return false;
}
}
interface MarketInfo {
tokenId: string;
question: string;
outcome: string;
price: string;
}
async function findLiquidSportsMarket(minLiquidityUsd: number): Promise<MarketInfo | null> {
/** Find the most liquid active sports market on Polymarket via the Gamma API. */
try {
const params = new URLSearchParams({
tag_slug: "sports",
limit: "10",
active: "true",
closed: "false",
archived: "false",
order: "volume24hr",
ascending: "false",
});
const response = await fetch(
`https://gamma-api.polymarket.com/events/pagination?${params}`,
{ signal: AbortSignal.timeout(10000) }
);
const { data: events = [] } = await response.json();
for (const event of events) {
for (const market of event.markets || []) {
if (!market.acceptingOrders) continue;
const clobTokenIds: string[] = JSON.parse(market.clobTokenIds || "[]");
const outcomes: string[] = JSON.parse(market.outcomes || "[]");
const outcomePrices: string[] = JSON.parse(market.outcomePrices || "[]");
if (!clobTokenIds.length || !outcomes.length) continue;
const price = outcomePrices.length ? parseFloat(outcomePrices[0]) : 0;
// Skip markets with extreme prices (thin liquidity on one side)
if (price < 0.10 || price > 0.90) continue;
const tokenId = clobTokenIds[0];
// Verify order book has enough ask liquidity for our buy
if (!(await checkOrderBookLiquidity(tokenId, "BUY", minLiquidityUsd))) continue;
return {
tokenId,
question: market.question || "Unknown",
outcome: outcomes[0],
price: outcomePrices.length ? outcomePrices[0] : "?",
};
}
}
} catch (e) {
console.error(`Failed to fetch markets: ${e}`);
}
return null;
}
async function run(agent: AgentContext): Promise<void> {
// 1. Redeem any settled positions
const redemption = await agent.platforms.polymarket.redeemPositions();
if (redemption.success && redemption.data) {
const redeemed = redemption.data.filter((r) => r.success);
if (redeemed.length > 0) {
await agent.log(`Redeemed ${redeemed.length} settled positions`);
for (const r of redeemed) {
if (r.position) {
await agent.log(` ${r.position.question} (${r.position.outcome}): $${r.position.valueUsd}`);
}
}
}
}
// 2. Check current positions and log Polymarket portfolio
const result = await agent.getCurrentPositions();
if (!result.success || !result.data) {
await agent.log(result.error || "Failed to get positions", { error: true });
return;
}
for (const pos of result.data.positions) {
if (pos.polymarketMetadata) {
const pm = pos.polymarketMetadata;
await agent.log(`${pm.question} [${pm.outcome}]: $${pm.valueUsd} (PnL: $${pm.pnlUsd})`);
}
}
// 3. Find USDC balance
const usdc = result.data.positions.find(
(p) => p.assetAddress.toLowerCase() === USDC
);
const usdcBalance = usdc ? parseFloat(usdc.currentQty) / 1e6 : 0;
await agent.log(`USDC balance: $${usdcBalance.toFixed(2)}`);
if (usdcBalance < BUY_AMOUNT) {
await agent.log("Not enough USDC to buy");
return;
}
// 4. Find the most liquid sports market (with order book liquidity check)
const market = await findLiquidSportsMarket(BUY_AMOUNT);
if (!market) {
await agent.log("No active sports markets with sufficient liquidity found");
return;
}
await agent.log(`Target market: ${market.question} [${market.outcome}] @ ${market.price}`);
// 5. Buy a position
const buy = await agent.platforms.polymarket.marketOrder({
tokenId: market.tokenId,
size: BUY_AMOUNT,
side: "BUY",
});
if (buy.success && buy.data) {
const info = buy.data.orderInfo;
if (info.totalPriceUsd) {
await agent.log(`Bought shares: $${info.totalPriceUsd} at $${info.priceUsd}/share (order ${info.orderId})`);
} else {
await agent.log(`Order placed: ${info.orderId} (${info.size} shares, ${info.side})`);
}
} else {
await agent.log(`Buy failed: ${buy.error}`, { error: true });
}
}
async function unwind(agent: AgentContext, positions: CurrentPosition[]): Promise<void> {
// Sell all Polymarket positions
const result = await agent.getCurrentPositions();
if (!result.success || !result.data) {
await agent.log("Failed to get positions for unwind", { error: true });
return;
}
let sold = 0;
for (const pos of result.data.positions) {
if (!pos.polymarketMetadata) continue;
const tokenId = pos.tokenId;
if (!tokenId) continue;
const shares = parseFloat(pos.currentQty) / 1e6; // Convert from raw units
if (shares <= 0) continue;
const sell = await agent.platforms.polymarket.marketOrder({
tokenId,
size: shares,
side: "SELL",
});
if (sell.success && sell.data) {
const pm = pos.polymarketMetadata;
await agent.log(`Sold ${pm.question} (${pm.outcome}): $${sell.data.orderInfo.totalPriceUsd}`);
sold++;
} else {
await agent.log(`Failed to sell position: ${sell.error}`, { error: true });
}
}
if (sold === 0) {
await agent.log("No Polymarket positions to sell");
}
// Redeem any settled positions
await agent.platforms.polymarket.redeemPositions();
}
const agent = new Agent({
runFunction: run,
unwindFunction: unwind,
});
export default agent.getExport();
Sample Output
Copy
Agent run started
Redeemed 1 settled positions
Will Spain win the 2026 FIFA World Cup? (Yes): $5.12
USDC balance: $12.34
Target market: Will Argentina win Copa America 2025? [Yes] @ 0.62
Bought shares: $5.00 at $0.62/share (order 8837291045)
Agent run completed
How It Works
- Redeem settled positions: Claims winnings from any resolved markets
- Log portfolio: Shows current Polymarket positions with PnL
- Check balance: Finds USDC balance from current positions
- Find market: Queries the Gamma API for liquid sports markets, filters by price range (0.10-0.90), and verifies order book depth via the CLOB API
- Buy shares: Places a market buy order on the best available market
- Unwind: Sells all open Polymarket positions and redeems settled ones
Notes
- Always check order book liquidity before calling
marketOrder. The CLOB will error if there aren’t enough asks/bids to fill your order. See Troubleshooting for details. - Polymarket uses USDC on Polygon (
ethereum:137). Set yourstartingAssetaccordingly. sizemeans different things for buys vs sells: USD amount for BUY, share count for SELL.- Token IDs are long numeric strings from Polymarket’s API — they identify a specific outcome (e.g., “Yes” or “No”) in a market.
- Use
redeemPositions()to claim winnings after markets resolve. It’s good practice to call it at the start of each run cycle.