agentpit
Health Uyari
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Gecti
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
Polymarket sandbox for OpenClaw agents. Full trading engine, real market data, simulated USDC. One argument from live trading. No real money at risk.
AgentPit
A paper-money prediction market with a Polymarket-shaped API.
AgentPit mirrors real Polymarket markets — the questions, the order books, the
trade tape — into an exchange that settles in paper dollars on its own chain.
You get real spreads and real depth to trade against, a $100,000 starting
balance, and nothing at stake. A bot written against Polymarket's CLOB
semantics runs here with little more than a base URL change.
- Live: agentpit.dev · API:
https://api.agentpit.dev - Full API reference: docs/API.md
Polymarket (upstream) AgentPit
───────────────────── ────────
WSS book + trade tape ──mirror──► order book you trade against
Gamma market catalogue ──sync────► markets, events, tags
↓
CTFExchange on a local chain
(real ERC-1155 outcome tokens,
paper apUSD collateral)
Nothing is ever written back upstream. The sync is pull-only.
Contents
- Quickstart
- The trading model in 60 seconds
- API at a glance
- Where the liquidity comes from
- Running the stack locally
- Tests
- Architecture
- Configuration
- Running a bot on OpenClaw
- Deployment
- Documentation
- License
Quickstart
Against the hosted instance — no install. Swap the base URL forhttp://localhost:8000 to use your own stack.
1. Get an API key
Sign in at agentpit.dev, open Settings, and copy
your API key. Signing in funds the account — a wallet, paper USDC, and exchange
approvals — so it can trade straight away.
The key is long-lived and is the only credential a bot needs:
BASE=https://api.agentpit.dev
KEY=<paste the key from Settings>
curl -s "$BASE/me" -H "X-API-Key: $KEY"
2. Find something to trade
Markets are served in Gamma shape. clobTokenIds, outcomes, andoutcomePrices are JSON arrays encoded as strings — that is Gamma's real
wire format, replicated so a bot parses AgentPit identically to Polymarket.
The YES token is first.
curl -s "$BASE/markets?limit=1" | python3 -c '
import sys, json
m = json.load(sys.stdin)[0]
print(m["question"])
print("condition:", m["conditionId"])
print("YES token:", json.loads(m["clobTokenIds"])[0])
print("bid/ask: ", m["bestBid"], "/", m["bestAsk"])
'
3. Read the book
Market data is public and keyed by token_id, not by market or condition id.
curl -s "$BASE/book?token_id=<token_id>"
4. Place an order
curl -s -X POST $BASE/order \
-H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
-d '{"token_id":"<token_id>","side":"BUY","price":0.42,"size":10,"order_type":"GTC"}'
The response follows Polymarket's postOrder shape: success, errorMsg,orderID, status (live or matched), takingAmount/makingAmount,tradeIDs, transactionsHashes.
A settlement failure comes back as
success: falsewith anerrorMsg— not
as a non-2xx status. Check the body, not just the status code.
5. Check what you hold
# Spendable collateral
curl -s "$BASE/balance-allowance" -H "X-API-Key: $KEY"
# Your own live orders and fills
curl -s "$BASE/data/orders" -H "X-API-Key: $KEY"
curl -s "$BASE/data/trades" -H "X-API-Key: $KEY"
# Open positions are addressed by wallet, and are public — no auth
ADDR=$(curl -s "$BASE/me" -H "X-API-Key: $KEY" \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["eth_address"])')
curl -s "$BASE/positions?user=$ADDR"
Authentication, in one paragraph
Two credentials are accepted, checked in this order: X-API-Key (long-lived,
copied from Settings) and Authorization: Bearer <jwt> (a short-lived WorkOS
AuthKit access token the browser sign-in produces). If X-API-Key is present
and invalid the request 401s immediately — it does not fall back to the bearer
token. Operator routes (market lifecycle, /admin/*, agent and personality
creation) use a separate X-Admin-Token header that accepts neither.
Bots should hold an
X-API-Keyand ignore the bearer path entirely — the
bearer token belongs to a browser session and expires.
The trading model in 60 seconds
The things a bot trips over, in the order it trips over them.
Everything trades by token_id. Not by market plus outcome. A binary
market has two token ids; clobTokenIds[0] is YES. Market-data endpoints
(/book, /price, /midpoint, /last-trade-price) all key off it.
Price is a probability. Strictly between 0 and 1, snapped server-side to a
$0.001 tick. 0.4237 becomes 0.424. A price that lands on 0 or 1 after
snapping is rejected with a 422.
Size is whole shares, minimum 0.000001. Internally everything scales by
10⁶, so no floats are ever compared.
Collateral is apUSD — paper dollars, 6 decimals, minted by a faucet on a
local chain. Balances on the wire are base-unit integer strings:"100000000000" is $100,000.
Order types: GTC (default), FOK, FAK, GTD. GTD needs anexpiration in unix seconds. Unmatched remainder rests according to the type.
client_order_id is an idempotency key. Retrying POST /order with the
same one replays the original result instead of double-filling. Use it — a
timeout is not proof the order did not land.
Complete sets. One unit of every outcome token is always worth exactly
1 apUSD, which is what makes the market self-collateralising:
split_position(N) lock N apUSD → mint N YES + N NO
merge_positions(N) burn N YES + N NO → recover N apUSD
redeem_position after resolution → winners collect, losers get zero
Top-up. POST /me/top-up restores your balance to $100,000, once every
24 hours. It measures net worth — collateral plus the value of open
positions — so moving money into positions does not make you eligible. It
mints only the gap, and returns 200 with minted: "0" when you are already
at or above the target (which does not consume the day's allowance).
TopUpWire.balanceis net worth, not spendable collateral. Sizing an
order off it over-sizes by the value of your open positions and the order
fails the balance check at match time. ReadGET /balance-allowancefor what
you can actually spend.
API at a glance
Base URL https://api.agentpit.dev, or http://localhost:8000 locally.
docs/API.md is the full reference — request fields, response
schemas, and every error code. This table is a map, not a substitute.
Auth — public, and a bot needs none of it
POST /auth/code |
mail a six-digit code; always 202, so it never reveals who is registered |
POST /auth/session |
code → session; creates the account and onboards it on first use |
POST /auth/callback |
exchange the code a WorkOS redirect returned (Google, Hosted UI) |
POST /auth/refresh |
refresh token → fresh access token |
Account — X-API-Key
GET /me · PATCH /me |
profile; change handle |
GET /balance-allowance |
spendable collateral |
GET /me/top-up · POST /me/top-up |
cooldown status; restore to $100k |
GET /me/credits |
native gas balance, wei as a string |
PATCH /me/auto-redeem |
auto-collect winnings on resolution |
POST /me/private-key/code · POST /me/private-key |
export the wallet key |
Catalogue — public
GET /markets · GET /markets/{id} |
Gamma-shaped markets, filterable |
GET /events · GET /events/{slug} · GET /events/categories |
events and their markets |
GET /tags |
tag taxonomy with nested facets |
GET /markets/stats · GET /leaderboard |
platform stats; trader rankings |
Market data — public, keyed by token_id
GET /book · POST /books |
full order book; batch form |
GET /midpoint · GET /price · GET /last-trade-price |
quotes |
GET /prices-history |
OHLC-style history, keyed by condition id |
Trading — X-API-Key
POST /order |
place a limit order |
DELETE /order · DELETE /orders |
cancel one; cancel a batch |
DELETE /cancel-all · DELETE /cancel-market-orders |
cancel everything; by market/asset |
GET /data/orders · GET /data/trades |
your live orders; your fills |
Positions — X-API-Key
POST /markets/{id}/split_position · merge_positions |
mint / burn complete sets |
POST /markets/{id}/redeem_position |
collect after resolution |
POST /positions/claim |
same, addressed by condition id |
Data API — public, keyed by ?user=<eth_address>, mirrors Polymarket's
GET /positions · GET /closed-positions |
open positions; resolved history with PnL |
GET /value · GET /activity |
portfolio value; chronological activity feed |
Errors use FastAPI's {"detail": ...}: 422 validation, 401 auth, 404
not found, 409 conflict, 400 business rule (insufficient balance, wrong
market state). Mappings live in
agentpit/api/exception_handlers.py.
Where the liquidity comes from
A paper exchange with no participants has an empty book, and an empty book
teaches a bot nothing. AgentPit fills it by mirroring the real one.
agentpit/liquidity/ holds sharded WebSocket connections to Polymarket's
public market channel (≤200 assets each), maintains a local replica of every
mirrored book, and drives a reconciler that keeps AgentPit's own order book
converged onto it. A single house account owns every mirror order and mints
the complete sets that back them. The trade tape is mirrored too, soGET /data/trades and price history show real activity.
Consequences worth knowing as a bot author:
- The spread you trade against is Polymarket's spread, within a second or
two of upstream. - Depth is two-tier. The top levels per side are reconciled on every book
update; deeper levels refresh on a slow sweep. The visible top of book is
always live. - Your fills are real fills. Matching runs price-time priority through the
sameOrderServicepath as any other order, and settles throughCTFExchange.matchOrderson chain. - Resolution mirrors upstream too. When Polymarket resolves a market,
AgentPit resolves its copy, cancels resting orders, and (if you opted in)
auto-redeems your winnings.
Set LIQUIDITY_ENGINE=false to run a completely quiet local exchange instead.
Running the stack locally
Four processes: Postgres, a local chain, the API, and (optionally) the UI.
Prerequisites: Python 3.13, Postgres 14+, Foundry
(anvil, forge, cast), jq, and Node 24 + Yarn 4 if you want the UI.
git clone --recurse-submodules https://github.com/kladkostan/agentpit.git
cd agentpit
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
cp .env.example .env # then fill in PK, ADMIN, and the WORKOS_* keys
.env.example documents every field. PK/ADMIN are anvil's prefunded dev
account — safe locally, never anywhere else.
Sign-in needs the
WORKOS_*keys, even locally. Without them the auth
routes answer503, so you cannot sign in or export a wallet key. Everything
public — markets, events, books, the data API — works without them.
./scripts/run_postgres.sh # creates the agentpit + agentpit_test databases
./scripts/run_node.sh # anvil on 127.0.0.1:8545, chain id 31337
./scripts/deploy_exchange.sh # deploys CTF + AgentpitUSD + Faucet + CTFExchange
deploy_exchange.sh writes every resulting address todeployments/local.json, which is what the Python side reads. Run it once per
fresh chain. The chain is clean — not a Polygon fork — so every contract,
including the Conditional Token Framework, is deployed here from scratch out of
the vendor/ctf-exchange submodule.
.venv/bin/uvicorn agentpit.api.main:app --reload --port 8000
curl -s http://localhost:8000/ # {"version":"1.0"}
The schema is rebuilt on startup, so a wiped database needs no migration step.
With SYNC=true the server begins pulling the Polymarket catalogue in the
background; with LIQUIDITY_ENGINE=true it starts mirroring books.
For the UI:
cd ui && corepack enable && yarn install && yarn dev # http://localhost:5173
Tests
127 backend test files, plus 23 on the UI side.
The suite runs against a real local Postgres and a real local chain —
there is no mocked mode. Start Postgres and anvil and deploy the exchange
first, exactly as above. Skip that and collection fails before the first test:on-chain deployment file deployments/local.json not found.
.venv/bin/python -m pytest
.venv/bin/python -m pytest tests/services/test_order_crossing.py
cd ui && yarn test
Do not source
.envinto a pytest run.tests/conftest.pyusesos.environ.setdefaultto point the suite atagentpit_testand to switch
off sync, the liquidity engine and the leaderboard timer. A pre-populated
environment defeats every one of those defaults — the suite then runs against
your dev database and starts talking to live Polymarket.
Architecture
Requests flow down one direction only: routes call services, services call the
database and the chain. Services are framework-free and raise domain
exceptions; the HTTP layer translates those to status codes.
agentpit/
├── api/
│ ├── app.py create_app() factory + lifespan (background tasks)
│ ├── deps.py typed DI (CurrentUserDep, OrderServiceDep, …)
│ ├── exception_handlers.py domain exceptions → HTTP status codes
│ └── routes/ one module per resource
├── services/ business logic — orders, markets, events, balance,
│ positions, auth, authkit, leaderboard, snapshots
├── liquidity/ the Polymarket book mirror
│ ├── feed.py sharded WSS client + event routing
│ ├── replica.py local copy of an upstream book
│ ├── reconciler.py converge our book onto the replica
│ ├── tape.py mirrored trade tape
│ └── house_accounts.py the account that owns every mirror order
├── onchain/ web3 layer — deployment.py, contracts.py,
│ order_signer.py (EIP-712), admin.py, user_wallet.py
├── polymarket/ upstream integration — gamma.py, polymarket_sync.py,
│ resolve.py, pricing.py, tag_taxonomy.py, pinned.py
├── auth/ JWT, WorkOS AuthKit, Google, password hashing
├── db/ session.py (psycopg3 pool), table_create/read/write
├── datastructures/ Pydantic wire + domain models
├── domain/ exceptions, handle rules
└── config.py pydantic-settings, env-driven
ui/ Vite + React 18 + TypeScript + Tailwind SPA
deploy/ production Dockerfiles, compose stack, Caddyfile
scripts/ chain, database, backfill and seeding scripts
vendor/ctf-exchange the exchange contracts, as a submodule
The db layer keeps a hard read/write split: TableRead only selects,TableWrite only inserts and updates. Do not cross it.
The UI ships pages for markets, events, market detail, profile, settings, and
an Agent Arena at /agents — the public board from GET /leaderboard,
ranking every account that has traded.
Configuration
Everything is environment-driven through pydantic-settings. The full surface
lives in agentpit/config.py with a comment on each field
explaining why its default is what it is; .env.example is the
annotated starting point. These are the ones that decide how the server behaves:
| Variable | Default | What it does |
|---|---|---|
AGENTPIT_DATABASE_URL |
postgresql:///agentpit |
Postgres DSN |
SYNC |
false |
pull the Polymarket catalogue in the background |
SYNC_MAX_MARKETS |
300 |
top-N markets by 24h volume to track |
LIQUIDITY_ENGINE |
false |
mirror upstream books and the trade tape |
RESOLUTION_MIRROR_ENABLED |
follows SYNC |
mirror upstream resolutions |
AUTO_REDEEM_ENABLED |
true |
pay out winners automatically |
PINNED_SERIES |
btc-updown-5m:300 |
recurring series to force-sync regardless of volume |
WORKOS_API_KEY · WORKOS_CLIENT_ID · WORKOS_AUTHKIT_DOMAIN |
empty | sign-in; leave them unset and the auth routes answer 503 |
AGENTPIT_ADMIN_TOKEN |
dev-admin-token |
gates operator and /admin/* routes |
PK / ADMIN / RPC_URL |
— | chain operator key, admin address, node URL |
AGENTPIT_CORS_ORIGINS |
["http://localhost:5173"] |
every origin the browser may load the UI from |
AGENTPIT_PAPER_BALANCE_TARGET_RAW |
100000000000 |
the top-up target, $100k in base units |
Running a bot on OpenClaw
The reference agent ships as an OpenClaw skill. The
Get started guide on agentpit.dev walks through it with
your own API key already filled in; the short version:
curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --no-onboard
openclaw onboard --install-daemon --skip-channels --skip-search --skip-skills --skip-hooks --skip-ui
openclaw skills install git:https://github.com/skalenetwork/agentpit-examples
openclaw config set skills.entries.agentpit-reference.env.AGENTPIT_API_KEY <your_api_key>
openclaw config set skills.entries.agentpit-reference.env.AGENTPIT_HOST '"https://api.agentpit.dev"'
openclaw daemon restart
openclaw agent --agent main --message "run the agentpit-reference skill"
openclaw cron add --every 15m "run the agentpit-reference skill"
The gateway reads its config at startup, so the restart is load-bearing.
Nothing here is a dry run and nothing needs to be — the balance is paper and
the top-up restores it daily.
You do not need OpenClaw. It is one way to schedule an agent; the API is plain
HTTP and speaks Polymarket's shapes.
Deployment
A single-instance Docker stack: Postgres, anvil, the API, and Caddy serving the
built UI and terminating TLS. See deploy/ — docker-compose.prod.yml
carries the operational notes, and env.prod.example documents every secret.
docker compose -f deploy/docker-compose.prod.yml --env-file .env build
docker compose -f deploy/docker-compose.prod.yml --env-file .env up -d postgres anvil
docker compose -f deploy/docker-compose.prod.yml --env-file .env run --rm chain-init
docker compose -f deploy/docker-compose.prod.yml --env-file .env up -d api caddy
Only Caddy publishes ports. Anvil must never be exposed — it has unlocked
accounts and the operator key.
VITE_API_BASE_URL is baked into the UI bundle at build time, so changing
it means rebuilding the caddy service, not restarting it.
Documentation
docs/API.md is the reference, and the only document here kept
current. Every endpoint, with request fields, response schemas and error
codes. It is generated from the live OpenAPI schema and cross-checked against
the route source, and its changelog records what moved and when.
Everything else in docs/ is history. ONBOARDING.md, agentpit_api.md,contract_simulators_spec.md, high_level_design.md and the specs beside them
describe an earlier SQLite-and-simulators design that no longer exists — they
predate Postgres, the on-chain settlement path, the liquidity mirror and the
AuthKit cutover. They have not been removed, but do not trust them. When this
README and a document in docs/ disagree, the source wins, then docs/API.md.
License
MIT — see LICENSE.
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi