> ## Documentation Index
> Fetch the complete documentation index at: https://docs.circuit.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Polymarket Agent

> A sports prediction market agent that finds liquid markets and buys shares on Polymarket.

<Tip>
  Scaffold this example locally and test it with the CLI:

  ```bash theme={null}
  circuit new --name my-polymarket-agent --language python --template polymarket
  cd my-polymarket-agent
  circuit run    # execute a run cycle
  circuit unwind # test the unwind logic
  ```
</Tip>

### circuit.toml

```toml circuit.toml theme={null}
name = "Example Polymarket Agent"
tagline = "Trades on Polymarket markets"
category = "prediction"
imageUrl = "https://cdn.circuit.org/agents/default"
walletType = "ethereum"
allowedExecutionModes = ["auto", "manual"]
runtimeIntervalMinutes = 60

[startingAsset]
network = "ethereum:137" # Polygon
address = "0xc011a7e12a19f7b1f670d46f03b03f3342e82dfb" # pUSD on Polygon
minimumAmount = "5000000" # 5 pUSD (6 decimals)
```

### Example

<CodeGroup>
  ```python Python theme={null}
  import json
  import requests
  from circuit_sdk import AgentContext, SessionPortfolio

  BUY_AMOUNT = 5  # USD to spend per cycle

  # Polygon pUSD address
  PUSD = "0xc011a7e12a19f7b1f670d46f03b03f3342e82dfb"

  CLOB_API = "https://clob.polymarket.com"

  def check_order_book_liquidity(token_id: str, side: str, min_usd: float) -> bool:
      """Check that the CLOB order book has enough liquidity for our order."""
      try:
          response = requests.get(
              f"{CLOB_API}/book",
              params={"token_id": token_id},
              timeout=10,
          )
          if not response.ok:
              return False

          book = response.json()
          orders = book.get("asks" if side == "BUY" else "bids", [])

          total_usd = 0
          for order in orders:
              price = float(order["price"])
              size = float(order["size"])
              total_usd += price * size

          return total_usd >= min_usd
      except Exception:
          return False

  def find_liquid_sports_market(agent: AgentContext, min_liquidity_usd: float) -> dict | None:
      """Find the most liquid active sports market on Polymarket via the Gamma API."""
      try:
          response = requests.get(
              "https://gamma-api.polymarket.com/events/pagination",
              params={
                  "tag_slug": "sports",
                  "limit": 10,
                  "active": "true",
                  "closed": "false",
                  "archived": "false",
                  "order": "volume24hr",
                  "ascending": "false",
              },
              timeout=10,
          )
          events = response.json().get("data", [])

          for event in events:
              for market in event.get("markets", []):
                  if not market.get("acceptingOrders"):
                      continue

                  clob_token_ids = json.loads(market.get("clobTokenIds", "[]"))
                  outcomes = json.loads(market.get("outcomes", "[]"))
                  outcome_prices = json.loads(market.get("outcomePrices", "[]"))

                  if not clob_token_ids or not outcomes:
                      continue

                  price = float(outcome_prices[0]) if outcome_prices else 0

                  # Skip markets with extreme prices (thin liquidity on one side)
                  if price < 0.10 or price > 0.90:
                      continue

                  token_id = clob_token_ids[0]

                  # Verify order book has enough ask liquidity for our buy
                  if not check_order_book_liquidity(token_id, "BUY", min_liquidity_usd):
                      continue

                  return {
                      "tokenId": token_id,
                      "question": market.get("question", "Unknown"),
                      "outcome": outcomes[0],
                      "price": outcome_prices[0] if outcome_prices else "?",
                  }

      except Exception as e:
          agent.log(f"Failed to fetch markets: {e}", error=True)

      return None

  def run(agent: AgentContext) -> None:
      # 1. Redeem this agent's settled positions (filter own portfolio, pass explicit ids)
      redeemable_token_ids = [
          pos.polymarket_metadata.token_id
          for pos in agent.portfolio.positions
          if pos.polymarket_metadata
          and pos.polymarket_metadata.is_redeemable
          and pos.polymarket_metadata.token_id
      ]
      if redeemable_token_ids:
          redemption = agent.platforms.polymarket.redeem_positions({"tokenIds": redeemable_token_ids})
          if redemption.success and redemption.data:
              redeemed = [r for r in redemption.data if r.success]
              if redeemed:
                  agent.log(f"Redeemed {len(redeemed)} settled positions")
                  for r in redeemed:
                      if r.position:
                          agent.log(f"  {r.position.question} ({r.position.outcome}): ${r.position.value_usd}")

      # 2. Read holdings and log Polymarket portfolio
      for pos in agent.portfolio.positions:
          if pos.polymarket_metadata:
              pm = pos.polymarket_metadata
              agent.log(f"{pm.question} [{pm.outcome}]: ${pm.value_usd} (PnL: ${pm.pnl_usd})")

      # 3. Find pUSD balance (pUSD cash lives in balances, not positions)
      pusd = next(
          (b for b in agent.portfolio.balances if b.token_address.lower() == PUSD),
          None,
      )
      pusd_balance = float(pusd.amount_raw) / 1e6 if pusd else 0
      agent.log(f"pUSD balance: ${pusd_balance:.2f}")

      if pusd_balance < BUY_AMOUNT:
          agent.log("Not enough pUSD to buy")
          return

      # 4. Find the most liquid sports market (with order book liquidity check)
      market = find_liquid_sports_market(agent, min_liquidity_usd=BUY_AMOUNT)
      if not market:
          agent.log("No active sports markets with sufficient liquidity found")
          return

      agent.log(f"Target market: {market['question']} [{market['outcome']}] @ {market['price']}")

      # 5. Buy a position
      buy = agent.platforms.polymarket.market_order({
          "tokenId": market["tokenId"],
          "size": BUY_AMOUNT,
          "side": "BUY",
      })

      if buy.success and buy.data:
          info = buy.data.order_info
          if info.total_price_usd:
              agent.log(f"Bought shares: ${info.total_price_usd} at ${info.price_usd}/share (order {info.order_id})")
          else:
              agent.log(f"Order placed: {info.order_id} ({info.size} shares, {info.side})")
      else:
          agent.log(f"Buy failed: {buy.error}", error=True)

  def unwind(agent: AgentContext, portfolio: SessionPortfolio) -> None:
      # Sell all Polymarket positions
      sold = 0
      for pos in portfolio.positions:
          pm = pos.polymarket_metadata
          if not pm:
              continue

          token_id = pm.token_id
          if not token_id:
              continue

          shares = float(pm.formatted_shares)  # Human-readable share count
          if shares <= 0:
              continue

          sell = agent.platforms.polymarket.market_order({
              "tokenId": token_id,
              "size": shares,
              "side": "SELL",
          })

          if sell.success and sell.data:
              agent.log(f"Sold {pm.question} ({pm.outcome}): ${sell.data.order_info.total_price_usd}")
              sold += 1
          else:
              agent.log(f"Failed to sell position: {sell.error}", error=True)

      if sold == 0:
          agent.log("No Polymarket positions to sell")

      # Redeem this agent's settled positions
      redeemable_token_ids = [
          pos.polymarket_metadata.token_id
          for pos in portfolio.positions
          if pos.polymarket_metadata
          and pos.polymarket_metadata.is_redeemable
          and pos.polymarket_metadata.token_id
      ]
      if redeemable_token_ids:
          agent.platforms.polymarket.redeem_positions({"tokenIds": redeemable_token_ids})

  ```

  ```typescript TypeScript theme={null}
  import type { AgentContext, SessionPortfolio } from "circuit:sdk";

  const BUY_AMOUNT = 5; // USD to spend per cycle

  // Polygon pUSD address
  const PUSD = "0xc011a7e12a19f7b1f670d46f03b03f3342e82dfb";

  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(agent: AgentContext, 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) {
      await agent.log(`Failed to fetch markets: ${e}`, { error: true });
    }

    return null;
  }

  export async function run(agent: AgentContext): Promise<void> {
    // 1. Redeem this agent's settled positions (filter own portfolio, pass explicit ids)
    const redeemableTokenIds = agent.portfolio.positions.flatMap((pos) => {
      const pm = pos.polymarketMetadata;
      return pm?.isRedeemable && pm.tokenId ? [pm.tokenId] : [];
    });

    if (redeemableTokenIds.length > 0) {
      const redemption = await agent.platforms.polymarket.redeemPositions({
        tokenIds: redeemableTokenIds,
      });
      if (redemption.success && Array.isArray(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. Read holdings and log Polymarket portfolio
    for (const pos of agent.portfolio.positions) {
      if (pos.polymarketMetadata) {
        const pm = pos.polymarketMetadata;
        await agent.log(`${pm.question} [${pm.outcome}]: $${pm.valueUsd} (PnL: $${pm.pnlUsd})`);
      }
    }

    // 3. Find pUSD balance (pUSD cash lives in balances, not positions)
    const pusd = agent.portfolio.balances.find(
      (b) => b.tokenAddress.toLowerCase() === PUSD
    );
    const pusdBalance = pusd ? parseFloat(pusd.amountRaw) / 1e6 : 0;
    await agent.log(`pUSD balance: $${pusdBalance.toFixed(2)}`);

    if (pusdBalance < BUY_AMOUNT) {
      await agent.log("Not enough pUSD to buy");
      return;
    }

    // 4. Find the most liquid sports market (with order book liquidity check)
    const market = await findLiquidSportsMarket(agent, 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 && "orderInfo" in 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 });
    }
  }

  export async function unwind(agent: AgentContext, portfolio: SessionPortfolio): Promise<void> {
    // Sell all Polymarket positions
    let sold = 0;
    for (const pos of portfolio.positions) {
      const pm = pos.polymarketMetadata;
      if (!pm) continue;

      const tokenId = pm.tokenId;
      if (!tokenId) continue;

      const shares = parseFloat(pm.formattedShares); // Human-readable share count
      if (shares <= 0) continue;

      const sell = await agent.platforms.polymarket.marketOrder({
        tokenId,
        size: shares,
        side: "SELL",
      });

      if (sell.success && sell.data && "orderInfo" in sell.data) {
        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 this agent's settled positions
    const redeemableTokenIds = portfolio.positions.flatMap((pos) => {
      const pm = pos.polymarketMetadata;
      return pm?.isRedeemable && pm.tokenId ? [pm.tokenId] : [];
    });
    if (redeemableTokenIds.length > 0) {
      await agent.platforms.polymarket.redeemPositions({ tokenIds: redeemableTokenIds });
    }
  }

  ```
</CodeGroup>

### Sample Output

```text theme={null}
Agent run started
Redeemed 1 settled positions
  Will Spain win the 2026 FIFA World Cup? (Yes): $5.12
pUSD 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
```

```text theme={null}
Agent unwind started
Sold 8.06 shares of token 123456... (order 9912345678)
Redeemed 1 settled positions
Agent unwind completed
```

### How It Works

1. **Redeem settled positions**: Filters the agent's own portfolio for redeemable positions and claims their winnings
2. **Log portfolio**: Shows current Polymarket positions with PnL
3. **Check balance**: Finds pUSD balance from current positions
4. **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
5. **Buy shares**: Places a market buy order on the best available market
6. **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](../troubleshooting#polymarket-issues) for details.
* Polymarket uses pUSD on Polygon (`ethereum:137`) as trading collateral. Set your `startingAsset` accordingly.
* `size` means 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({ tokenIds })` to claim winnings after markets resolve — filter your portfolio for `isRedeemable` positions and pass their token IDs. It's good practice to do this at the start of each run cycle.
