For AI agents & builders

PoolPot's contracts are immutable and permissionless: an agent with a funded wallet on Base can buy tickets, collect the pool's prizes and claim its share directly on-chain — no API key, no account. The machine-readable version of this page is /llms.txt.

Contracts on Base (chainId 8453)

Limits, verified in the contracts: 11 tickets per join transaction, 150 ids per collect (the site uses 100), 60 drawings per claimMany, 25 plans per fundMany, auto-buy tip at most $0.50/day, plans up to 365 days.

1 · Read the current drawing

import { createPublicClient, http, parseAbi } from "viem";
import { base } from "viem/chains";

const client = createPublicClient({ chain: base, transport: http() });

const JACKPOT = "0x3bAe643002069dBCbcd62B1A4eb4C4A397d042a2";
const FACTORY = "0x039894BA3a66DAD20c58939EfD021663244B0A59";

const jackpotAbi = parseAbi([
  "function currentDrawingId() view returns (uint256)",
  "struct DrawingState { uint256 prizePool; uint256 ticketPrice; uint256 edgePerTicket; uint256 referralWinShare; uint256 referralFee; uint256 globalTicketsBought; uint256 lpEarnings; uint256 drawingTime; uint256 winningTicket; uint8 ballMax; uint8 bonusballMax; address payoutCalculator; bool jackpotLock; }",
  "function getDrawingState(uint256 _drawingId) view returns (DrawingState)",
]);
const factoryAbi = parseAbi(["function roundOf(uint256 d) view returns (address)"]);

const drawingId = await client.readContract({
  address: JACKPOT, abi: jackpotAbi, functionName: "currentDrawingId",
});
const s = await client.readContract({
  address: JACKPOT, abi: jackpotAbi, functionName: "getDrawingState", args: [drawingId],
});
// s.ticketPrice is USDC base units (6 decimals; 1_000_000 = $1)
// s.drawingTime is unix seconds (daily 17:00 UTC); s.jackpotLock true = closed
// s.ballMax / s.bonusballMax bound your picks
const round = await client.readContract({
  address: FACTORY, abi: factoryAbi, functionName: "roundOf", args: [drawingId],
}); // address(0) until the first buyer of the day; join() opens it lazily

2 · Buy tickets: approve, then join

Cost is exact: n × ticketPrice, where n = your picks + quick picks (1–11 per call). A Pick is five strictly ascending normals within ballMax plus a bonusball within bonusballMax. The contract accepts joins until the drawing locks (jackpotLock, around the 17:00 UTC draw); the site stops 60 seconds before drawingTime so a transaction cannot be mined too late — do the same.

import { parseAbi } from "viem";

const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const erc20Abi = parseAbi(["function approve(address spender, uint256 value) returns (bool)"]);
const factoryAbi = parseAbi([
  "struct Pick { uint8[5] normals; uint8 bonusball; }",
  "function join(uint256 d, Pick[] picks, uint16 quickPicks, uint256 maxCost) returns (uint256[])",
]);

// 1 quick pick: n = 0 own picks + 1 quick pick, cost = 1 * ticketPrice
const cost = s.ticketPrice; // exact, in USDC base units

await walletClient.writeContract({
  address: USDC, abi: erc20Abi, functionName: "approve",
  args: [FACTORY, cost], // approve the exact cost, no more
});
await walletClient.writeContract({
  address: FACTORY, abi: factoryAbi, functionName: "join",
  args: [drawingId, [], 1, cost], // maxCost = cost: reverts CostAboveMax if the price moved
});
// Max 11 tickets per call (factory.maxJoin()). Your own picks may duplicate a
// combination the pool already holds; quick picks never do. USDC is pulled from
// msg.sender and the tickets are credited to msg.sender.

3 · After settlement: collect, then claim

A drawing is settled once currentDrawingId() moves past it. Discover the pool's winning ticket ids from chain (TicketNFT ids owned by the round, tiered by Jackpot.getTicketTierIds); the public Megapot Data API (https://api.megapot.io/v1, proxied at /api/megapot/…) is a convenience — e.g. /wallets/{round}/wins/rounds/{id} — but the chain is the truth and the API's claimed flag can lag a fresh collect.

const roundAbi = parseAbi([
  "function collect(uint256[] ids) returns (uint256)",
  "function bountyFor(uint256 credited, uint256 count) pure returns (uint256)",
]);

// ids: this drawing's winning ticket ids still owned by the round, strictly
// ascending, at most 150 per call (the site sends 100). Anyone may call —
// no tickets required. The caller is paid min(credited * 0.25%, $0.25 per id
// collected); the rest fills the pot for everyone.
await walletClient.writeContract({
  address: round, abi: roundAbi, functionName: "collect", args: [ids],
});
const roundAbi = parseAbi([
  "function claimable(address u) view returns (uint256)",
  "function claim() returns (uint256)",
]);
const factoryAbi = parseAbi(["function claimMany(uint256[] ds) returns (uint256)"]);

const owed = await client.readContract({
  address: round, abi: roundAbi, functionName: "claimable", args: [me],
});
if (owed > 0n) {
  await walletClient.writeContract({ address: round, abi: roundAbi, functionName: "claim" });
}
// Or across up to 60 settled drawings in one transaction (pays msg.sender only):
await walletClient.writeContract({
  address: FACTORY, abi: factoryAbi, functionName: "claimMany", args: [[173n, 174n]],
});
// Claims never expire; claiming again after a later collect pays the difference.

Auto-buy plans

  • factory.createAutoBuy(fixedPicks, quickPicksPerDay, daysTotal, tipPerDay, maxEscrow) clones an escrow wallet for the caller. Escrow = tickets/day × days × ticketPrice + days × tipPerDay, pulled from the caller after an exact USDC approval; reverts CostAboveMax above maxEscrow.
  • AutoBuyWallet.fund(drawingId) is callable by anyone, once per drawing: it buys the day's tickets (credited to the plan's owner) and pays tipPerDay (≤ $0.50) to a non-owner caller. A failed day is not consumed. factory.fundMany(wallets, drawingId) funds up to 25 plans, all tips to the caller; size gas with factory.fundGasFloor(ticketsPerDay).
  • Only the plan's owner can close() it, refunding the whole remaining escrow at any time.

Safety notes

  • Base mainnet only, chainId 8453. Simulate every transaction before sending.
  • USDC has 6 decimals ($1 = 1,000,000). Approve exact amounts only, to the contract you are calling.
  • Never send ETH to these contracts, and never transfer USDC directly to the factory — it is ownerless with no sweep, so a direct transfer is unrecoverable.
  • Payouts always go to msg.sender (claim, collect bounty, fund tips); no call pulls USDC from anyone but msg.sender.
  • An InsufficientGas revert means the gas limit was too low for the guarded batch — resend with more (the site adds 50% headroom).

Questions: hello@poolpot.fun