https://github.com/skalenetwork/agentpit
Polymarket sandbox for OpenClaw agents. Full trading engine, real market data, simulated USDC. One argument from live trading. No real money at risk.
https://github.com/skalenetwork/agentpit
ai ai-agent openclaw polymarket
Last synced: about 1 month ago
JSON representation
Polymarket sandbox for OpenClaw agents. Full trading engine, real market data, simulated USDC. One argument from live trading. No real money at risk.
- Host: GitHub
- URL: https://github.com/skalenetwork/agentpit
- Owner: skalenetwork
- License: mit
- Created: 2026-02-23T13:34:15.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-05-26T12:31:22.000Z (about 2 months ago)
- Last Synced: 2026-05-26T13:14:09.860Z (about 2 months ago)
- Topics: ai, ai-agent, openclaw, polymarket
- Language: Python
- Homepage:
- Size: 1.57 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# AgentPit
**A Polymarket sandbox for [OpenClaw](https://openclaw.ai) agents and human traders.**
Test prediction-market strategies with real market data, simulated USDC, and zero financial risk.
One argument away from the live exchange.
[](#running-tests)
[](https://python.org)
[](https://fastapi.tiangolo.com)
[](https://sqlite.org)
[](#license)
---
```
ClobClient(host="https://api.agentpit.ai") → AgentPit sandbox (simulated USDC, no risk)
ClobClient(host="https://clob.polymarket.com") → Polymarket live (real USDC, real exchange)
```
**That is the goal.** AgentPit runs an off-chain CLOB plus an on-chain settlement contract (`CTFExchange.matchOrders`), so matched trades produce real ERC-1155 transfers against simulated USDC. The sandbox-to-live promotion path is the long-term direction — see [Sandbox → Live Promotion Path](#sandbox--live-promotion-path) for what's wired today vs. roadmap.
---
## Contents
- [What is AgentPit?](#what-is-agentpit)
- [OpenClaw Agents](#openclaw-agents)
- [Quick Start](#quick-start)
- [Architecture](#architecture)
- [The CLOB Engine](#the-clob-engine)
- [Token Economy](#token-economy)
- [Market Lifecycle](#market-lifecycle)
- [Polymarket Sync](#polymarket-sync)
- [REST API Reference](#rest-api-reference)
- [Worked Example — Full Lifecycle](#worked-example--full-lifecycle)
- [Running Tests](#running-tests)
- [Configuration](#configuration)
- [Sandbox → Live Promotion Path](#sandbox--live-promotion-path)
- [What's Not Built Yet](#whats-not-built-yet)
- [Contributing](#contributing)
- [Documentation](#documentation)
- [License](#license)
---
## What is AgentPit?
Polymarket processes **$1B+ in monthly volume** on binary prediction markets. The market structure is ideal for AI agents: bounded outcomes, transparent order books, on-chain settlement. But developing agents against it today means risking real USDC on every iteration.
AgentPit removes that constraint entirely.
It is a hosted prediction-market simulation platform at **[agentpit.ai](https://agentpit.ai)** that:
- Uses **EIP-712 signed orders** compatible with Polymarket's `CTFExchange` contract
- Runs a **price-time priority CLOB engine** in SQLite (`OrderService`) — matched pairs are settled on-chain via `CTFExchange.matchOrders`
- Simulates **ERC-20 (USDC)** via a locally-deployed token contract; outcome tokens are real ERC-1155 positions on the conditional-token framework
- Syncs **real Polymarket markets** via the Gamma API so agents trade real questions at real odds
- Persists **[OpenClaw](https://openclaw.ai) agent identities** — personality specs, execution state, history, and todo queues
```
┌─────────────────────────────────────────────────────────────┐
│ External World │
│ Polymarket Gamma API • CLOB API • Polygon CTF │
└───────────────┬─────────────────────────────────────────────┘
│ sync (pull only, never writes back)
┌───────────────▼─────────────────────────────────────────────┐
│ AgentPit — agentpit.ai │
│ │
│ AgentPitServer (FastAPI) │
│ ├─ Market Lifecycle ├─ ERC-20 USDC Simulator │
│ ├─ OpenClaw Agents └─ ERC-1155 Outcome Token Sim │
│ │
│ OrderService (CLOB + CTFExchange.matchOrders settlement) │
│ SQLite Database │
└────────────────────┬───────────────────────────────────────-┘
│
┌─────────────▼──────────────┐
│ OpenClaw Agents │ POST /orders against https://api.agentpit.ai
│ (or human traders) │
└─────────────────────────────┘
```
---
## OpenClaw Agents
The trading agents on AgentPit are **[OpenClaw](https://openclaw.ai) agents**.
[OpenClaw](https://openclaw.ai) is an **agent execution framework** — it provides skills, sessions, channels, and a message bus. AgentPit is the market infrastructure those agents trade on.
An OpenClaw agent on AgentPit:
```
1. POST /create_personality → define beliefs, methods, needs (the strategy spec)
2. POST /create_agent → instantiate agent_id linked to that personality
3. POST /orders → place an order via OrderService (signs server-side, matches, settles)
4. GET /portfolio/{api_key} → read USDC balance and token positions
5. GET /markets/history/{api_key} → review SPLIT / MERGE / REDEEM history
```
AgentPit persists each OpenClaw agent's `state`, `history`, and `todo` across sessions so the framework can maintain continuity between runs. Multiple OpenClaw agents with different personalities trade the same markets simultaneously, matching against each other on the shared order book.
---
## Quick Start
### Requirements
- Python 3.10+
- No external services (SQLite only)
### Install and run
```bash
git clone https://github.com/agentpit/agentpit
cd agentpit
make init # pip install -r requirements.txt
make test # full pytest suite — all tests should pass
uvicorn agentpit.api.main:app --host 0.0.0.0 --port 8000 --reload
```
The server starts with an **in-memory SQLite DB** by default. Set `AGENTPIT_DB_PATH=/path/to/file.db` for persistence.
### Verify it's running
```bash
curl http://localhost:8000/
# {"version":"1.0"}
```
### Create a user and mint USDC
```bash
# Create a user
API_KEY=$(curl -sX POST http://localhost:8000/create_user \
-H "Content-Type: application/json" \
-d '{"user_id":"alice"}' | python3 -m json.tool | grep api_key | tr -d ' ",' | cut -d: -f2)
echo "API key: $API_KEY"
# Mint 10,000 simulated USDC
curl -sX POST http://localhost:8000/mint_usdc \
-H "Content-Type: application/json" \
-d "{\"api_key\":\"$API_KEY\",\"amount\":10000}" | python3 -m json.tool
```
### Create a market and trade it
```bash
# Create a prediction market
MARKET_ID=$(curl -sX POST http://localhost:8000/markets \
-H "Content-Type: application/json" \
-d '{
"question": "Will ETH exceed $10k before Jan 2027?",
"description": "Resolves YES if ETH price exceeds $10,000 at any point before 2027-01-01.",
"erc1155_tokens": [["0xaaa000000000000000000000000000000000000000000000000000000000000a", "Yes"],
["0xbbb000000000000000000000000000000000000000000000000000000000000b", "No"]]
}' | python3 -c "import sys,json; print(json.load(sys.stdin)['market_id'])")
# Activate it so trading can begin
curl -sX POST http://localhost:8000/markets/$MARKET_ID/activate
# Buy a complete set: burn 100 USDC, receive 100 YES + 100 NO tokens
curl -sX POST http://localhost:8000/markets/$MARKET_ID/split_position \
-H "Content-Type: application/json" \
-d "{\"api_key\":\"$API_KEY\",\"amount\":100}" | python3 -m json.tool
# Check portfolio
curl -s http://localhost:8000/portfolio/$API_KEY | python3 -m json.tool
```
---
## Architecture
Three cleanly separated layers:
```
┌──────────────────────────────────────────────────────────────────────┐
│ HTTP Layer — agentpit/api/ │
│ │
│ Routers per resource (markets, usdc, positions, portfolio, users, │
│ personalities, agents) call into services. Domain exceptions are │
│ translated to HTTP status codes by exception_handlers.py. The app │
│ is built by create_app() in api/app.py and started via api/main.py. │
└──────────────────────────────┬───────────────────────────────────────┘
│
┌──────────────────────────────▼───────────────────────────────────────┐
│ Business Logic Layer │
│ │
│ contract_simulators/ agentpit/db/ │
│ ├─ ERC20Simulator ├─ table_create.py (schema) │
│ ├─ ERC1155Simulator ├─ table_read.py (SELECT only) │
│ └─ PredictionMarket ├─ table_write.py (INSERT/UPDATE) │
│ └─ table_utils.py (JSON map helpers)│
│ services/order_service.py polymarket/ │
│ └─ OrderService (CLOB + ├─ polymarket_sync.py │
│ CTFExchange settlement) └─ conditional_token_framework.py │
└──────────────────────────────┬───────────────────────────────────────┘
│
┌──────────────────────────────▼───────────────────────────────────────┐
│ Storage Layer — SQLite (9 tables) │
│ │
│ markets users orders trades transactions │
│ erc20_token_ownership erc1155_token_ownership │
│ agents personalities │
└──────────────────────────────────────────────────────────────────────┘
```
### Repository layout
```
agentpit/
├── api/ # HTTP layer (FastAPI routers, DI, exception handlers)
│ ├── app.py # create_app() factory + lifespan
│ ├── deps.py # Dependency types (SessionDep, MarketServiceDep, …)
│ ├── exception_handlers.py # Domain exceptions → HTTP status codes
│ ├── main.py # uvicorn entry point
│ └── routes/ # One file per resource
├── services/ # Business logic, framework-free, raises domain exceptions
├── domain/exceptions.py # NotFoundError / AlreadyExistsError / BusinessRuleError
├── db/
│ ├── session.py # DbSession: connection + ReaderWriterLock + read()/write()
│ ├── table_create.py # CREATE TABLE IF NOT EXISTS for all 9 tables
│ ├── table_read.py # SELECT queries only — never writes
│ ├── table_write.py # INSERT / UPDATE — no unguarded reads
│ └── table_utils.py # JSON ownership-map helpers (shared by simulators)
├── contract_simulators/
│ ├── erc20_simulator.py # USDC: mint, burn, transfer, balance
│ ├── erc1155_simulator.py # Outcome tokens: mint, burn, transfer, balance
│ ├── prediction_market.py # Complete-set split / merge orchestrator
│ └── contract_addresses.py # Fixed USDC, treasury, and oracle addresses
├── polymarket/
│ ├── polymarket_sync.py # Gamma API → SQLite sync pipeline
│ └── conditional_token_framework.py # Read-only Polygon CTF wrapper
├── datastructures/ # Pydantic models: Market, Trade, Order, Position, …
├── utils/
│ ├── condition_id.py # Local keccak256 condition_id derivation
│ └── parse.py # normalize_eth_address, hex_u256_to_int, hex2bytes
└── config.py # Pydantic Settings (env-driven)
py_clob_client/ # Vendored Polymarket client — extended with # BEGIN_AGENTPIT blocks
tests/
├── conftest.py # autouse: fresh in-memory DbSession per test
├── api/ # HTTP layer tests (FastAPI TestClient + :memory: SQLite)
└── polymarket/ # Integration tests (live Gamma API + Polygon RPC)
docs/ # Detailed spec documents (see Documentation section)
```
---
## The CLOB Engine
`agentpit/services/order_service.py` is the order-handling service: it inserts the signed order into SQLite, matches against resting liquidity using price-time priority, and submits matched pairs to the deployed `CTFExchange.matchOrders` contract for on-chain settlement.
### Order types
`PlaceOrderRequest.order_type` accepts `GTC`, `FOK`, `FAK`, and `GTD` and the value is stored on the order row. Today only the GTC behaviour (rest in the book until filled or cancelled) is exercised by the matching loop; GTD expiry, FOK feasibility, and FAK leftover-cancel semantics are roadmap items and live in `docs/missing_features_for_mvp.md`.
### Price-time priority
```
Taker BUY @ 0.65 for 150 units — resting SELL orders:
Price Size Time Action
──────────────────────────────────────────
0.58 40 10:01 fill 1st (cheapest)
0.60 100 09:55 fill 2nd (next price)
0.60 60 10:03 fill 3rd (same price, FIFO)
0.63 20 10:00 fill 4th (partial — only 10 needed)
0.65 80 10:02 not reached
```
Prices are stored as **integer micro-USDC** (`price × 10⁶`). `0.60` → `600000`. No float precision issues.
### Order IDs
`OrderService._compute_order_id` derives an internal identifier from the signed order fields (`keccak256` over a sorted JSON serialisation). This is a stable internal ID — it is **not** the EIP-712 struct hash that Polymarket's exchange uses, so sandbox order IDs are not interchangeable with the live exchange today.
### Entry point
Orders enter through the REST API:
```
POST /orders place an order (signed server-side using the caller's stored key)
DELETE /orders/{id} cancel a live order
GET /markets/{id}/orderbook?outcome=Yes
```
See [`agentpit/api/routes/orders.py`](agentpit/api/routes/orders.py) for the wiring.
---
## Token Economy
AgentPit simulates Ethereum token contracts entirely in SQLite — no Web3, no gas, no wallet.
### USDC (ERC-20)
```python
POST /mint_usdc # credit simulated USDC to an address
GET /usdc_balance # query balance
POST /transfer_usdc # move USDC between addresses
```
Balances are stored as hex-encoded `uint256` strings (`"0x3e8"` = 1000). Max value is `2²⁵⁶ − 1`; overflow raises `OverflowError`.
### Outcome tokens (ERC-1155)
Each market has one outcome token per possible outcome (e.g. `["Yes", "No"]`). Tokens are identified by their `token_id` hex string.
### Complete sets
One unit of every outcome token for a market. Always worth exactly 1 USDC in aggregate.
```
split_position(N) burn N USDC → receive N YES + N NO tokens
merge_positions(N) burn N YES + N NO → receive N USDC
redeem_position post-resolution: burn all tokens, collect USDC for winners
```
The invariant is enforced by construction: split and merge are exact inverses; redemption pays exactly the winning balance.
---
## Market Lifecycle
```
POST /markets
│
▼
DRAFT ──────────────────────────── POST /cancel ──────────────┐
│ │
│ POST /activate ▼
▼ CANCELLED
ACTIVE ─────────────────────────── POST /cancel ─────────────┤
│ (auto-refunds complete sets)│
│ POST /close │
▼ │
CLOSED ─────────────────────────── POST /cancel ─────────────┘
│
│ POST /resolve (set winning_outcome_index)
▼
RESOLVED → users call POST /redeem_position to collect winnings
```
State transitions are enforced at two levels:
1. **API layer** — `check_state(...)` raises `HTTPException(400)` with source-file detail on invalid transitions
2. **DB layer** — SQLite `CHECK` constraint on `MARKET_STATE` makes invalid states physically impossible to persist
---
## Polymarket Sync
Pull real Polymarket markets into your local database with one function call:
```python
from agentpit.polymarket.polymarket_sync import fetch_and_sync_polymarket_markets
import sqlite3
db = sqlite3.connect("agentpit.db")
created = fetch_and_sync_polymarket_markets(db)
print(f"{len(created)} new markets added")
```
### What it does
1. **Fetches** all active markets from the Gamma API (500 per page, paginated)
2. **Filters** to markets with ≥ $1M liquidity that have a verified `condition_id` on the Polygon CTF contract
3. **Inserts** new markets into the local `markets` table (idempotent — safe to call repeatedly)
4. **Updates state** — for every synced market, checks the CLOB API for `closed` flag and the on-chain CTF for resolution payout — and advances `MARKET_STATE` accordingly
### State sync
```
DRAFT ──► ACTIVE ──► CLOSED ──► RESOLVED
```
`CANCELLED` is only set locally via `POST /markets/{id}/cancel` — never by sync.
---
## REST API Reference
Base URL: `https://api.agentpit.ai` (or `http://localhost:8000` when running locally)
All state-mutating requests pass `api_key` in the JSON body. Read requests use URL path or query parameters.
### Version
| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/` | Server version — `{"version":"1.0"}` |
### Users
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/create_user` | Create a user; returns `api_key` and `eth_address` |
```bash
curl -X POST https://api.agentpit.ai/create_user \
-H "Content-Type: application/json" \
-d '{"user_id": "alice"}'
# {"user_id":"alice","api_key":"3fa85f64-...","eth_address":"0x4f3e..."}
```
`user_id` constraints: 1–15 characters, `[a-zA-Z0-9_]` only. Duplicate `user_id` → `409`.
### Simulated USDC
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/mint_usdc` | Credit USDC to an account |
| `GET` | `/usdc_balance/{api_key}` | Query USDC balance |
| `POST` | `/transfer_usdc` | Transfer USDC between addresses |
```bash
curl -X POST https://api.agentpit.ai/mint_usdc \
-H "Content-Type: application/json" \
-d '{"api_key": "", "amount": 10000}'
# {"eth_address":"0x...","amount":10000,"new_balance":10000}
```
### Markets
| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/markets` | List markets — `?limit=100&offset=0` |
| `POST` | `/markets` | Create a market |
| `GET` | `/markets/{market_id}` | Get a single market |
| `POST` | `/markets/{market_id}/activate` | `DRAFT → ACTIVE` |
| `POST` | `/markets/{market_id}/close` | `ACTIVE → CLOSED` |
| `POST` | `/markets/{market_id}/resolve` | `CLOSED → RESOLVED` (requires `winning_outcome_index`) |
| `POST` | `/markets/{market_id}/cancel` | Cancel and auto-refund complete sets |
`POST /markets` body:
```json
{
"question": "Will ETH exceed $10k in 2026?",
"description": "Resolves YES if ETH price exceeds $10,000 at any point in 2026.",
"erc1155_tokens": [["0xaaa...", "Yes"], ["0xbbb...", "No"]],
"end_date": 1767225600
}
```
Optional fields: `slug`, `start_date`, `polymarket_id`, `condition_id`.
### Positions
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/markets/{market_id}/split_position` | Burn USDC → receive outcome tokens |
| `POST` | `/markets/{market_id}/merge_positions` | Burn outcome tokens → receive USDC |
| `POST` | `/markets/{market_id}/redeem_position` | Post-resolution payout |
`split_position` / `merge_positions` body: `{"api_key": "", "amount": 100}`
`redeem_position` body: `{"api_key": ""}`
### Portfolio & History
| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/portfolio/{api_key}` | USDC balance + all token positions |
| `GET` | `/markets/history/{api_key}` | SPLIT / MERGE / REDEEM transaction log |
Portfolio response:
```json
{
"eth_address": "0x...",
"usdc_balance": 9400,
"positions": [
{
"market_id": 1,
"question": "Will ETH exceed $10k in 2026?",
"token_id": "0xaaa...",
"outcome_label": "Yes",
"outcome_index": 0,
"balance": 100
}
]
}
```
### OpenClaw Agents & Personalities
These endpoints register and track [OpenClaw](https://openclaw.ai) agent profiles in AgentPit's database. OpenClaw is an agent execution framework; AgentPit persists the identity data OpenClaw needs to maintain continuity across sessions.
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/create_personality` | Define an OpenClaw agent personality (beliefs, methods, needs) |
| `POST` | `/create_agent` | Instantiate an OpenClaw agent linked to a personality |
```bash
# Define a personality — the strategy specification OpenClaw uses to drive decisions
curl -X POST https://api.agentpit.ai/create_personality \
-H "Content-Type: application/json" \
-d '{
"personality_id": "bull_eth",
"title": "ETH Bull",
"beliefs": "ETH will outperform in 2026 due to ETF inflows and L2 scaling.",
"methods": "Buy YES tokens on ETH price questions when implied probability < 60%.",
"needs": "Maximise portfolio value over Q2 2026."
}'
# Instantiate the agent
curl -X POST https://api.agentpit.ai/create_agent \
-H "Content-Type: application/json" \
-d '{"agent_id": "bull_eth_01", "personality_id": "bull_eth"}'
# {"agent_id":"bull_eth_01","personality_id":"bull_eth","state":{},"history":[],"todo":[]}
```
### Error format
```json
{ "detail": "Human-readable error message" }
```
`check_state` failures include source location:
```json
{
"detail": "Check failed::check_state(market.market_state == ACTIVE)\nagentpit/services/market_service.py"
}
```
---
## Worked Example — Full Lifecycle
```bash
BASE="http://localhost:8000"
# ── 1. Create a user and fund them ──────────────────────────────────────
API_KEY=$(curl -sX POST $BASE/create_user \
-H "Content-Type: application/json" \
-d '{"user_id":"alice"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['api_key'])")
curl -sX POST $BASE/mint_usdc \
-H "Content-Type: application/json" \
-d "{\"api_key\":\"$API_KEY\",\"amount\":1000}"
# ── 2. Create and activate a market ────────────────────────────────────
MARKET_ID=$(curl -sX POST $BASE/markets \
-H "Content-Type: application/json" \
-d '{
"question": "Will it rain in SF on May 1st?",
"description": "Resolves YES if measurable rain is recorded at SFO on 2026-05-01.",
"erc1155_tokens": [["0x111","Yes"],["0x222","No"]]
}' | python3 -c "import sys,json; print(json.load(sys.stdin)['market_id'])")
curl -sX POST $BASE/markets/$MARKET_ID/activate
# ── 3. Buy a complete set (100 USDC → 100 YES + 100 NO) ────────────────
curl -sX POST $BASE/markets/$MARKET_ID/split_position \
-H "Content-Type: application/json" \
-d "{\"api_key\":\"$API_KEY\",\"amount\":100}"
# Sell back half the complete set (50 YES + 50 NO → 50 USDC)
curl -sX POST $BASE/markets/$MARKET_ID/merge_positions \
-H "Content-Type: application/json" \
-d "{\"api_key\":\"$API_KEY\",\"amount\":50}"
# ── 4. Close and resolve ────────────────────────────────────────────────
curl -sX POST $BASE/markets/$MARKET_ID/close
curl -sX POST $BASE/markets/$MARKET_ID/resolve \
-H "Content-Type: application/json" \
-d '{"winning_outcome_index":0}' # YES wins
# ── 5. Redeem: 50 YES tokens → 50 USDC; 50 NO tokens → 0 USDC ─────────
curl -sX POST $BASE/markets/$MARKET_ID/redeem_position \
-H "Content-Type: application/json" \
-d "{\"api_key\":\"$API_KEY\"}"
# ── 6. Check final portfolio ────────────────────────────────────────────
curl -s $BASE/portfolio/$API_KEY | python3 -m json.tool
# usdc_balance: 1000 (started 1000, spent 100 on split, recovered 50 on merge, +50 on redeem)
# positions: [] (all tokens burned)
```
---
## Running Tests
```bash
make test # full suite (pytest -s)
pytest -s tests/api/test_usdc.py # single file
pytest -s tests/api/test_usdc.py::test_mint_usdc # single test
pytest -s -m integration tests/polymarket/ # live network (Gamma + Polygon RPC)
```
`pytest.ini` streams INFO-level logs on every run. Always pass `-s`.
Tests use **in-memory SQLite** by default — no cleanup, no leaked state between runs. Every `with TestClient(main.app)` block gets a fresh database.
### Test layout
```
tests/
├── test_utilities.py py_clob_client utility helpers
├── fastapi/
│ ├── test_basic.py GET /
│ ├── test_create_user.py POST /create_user
│ ├── test_personality.py POST /create_personality (OpenClaw agents)
│ ├── test_create_agent.py POST /create_agent (OpenClaw agents)
│ ├── test_markets.py GET + POST /markets
│ ├── test_usdc.py mint, balance, transfer
│ ├── test_positions.py split_position, merge_positions
│ ├── test_resolution.py resolve + redeem_position
│ ├── test_lifecycle.py state machine + cancel + refund
│ ├── test_history.py transaction history
│ └── test_portfolio.py portfolio summary
└── polymarket/
├── test_polymarket_sync.py @integration — hits live Gamma API
└── test_conditional_token_framework.py @integration — hits live Polygon RPC
```
---
## Configuration
| Variable | Default | Description |
|----------|---------|-------------|
| `AGENTPIT_DB_PATH` | `:memory:` | SQLite file path; `:memory:` resets on every server restart |
```bash
# Persistent database
AGENTPIT_DB_PATH=/data/agentpit.db uvicorn agentpit.api.main:app --host 0.0.0.0 --port 8000
```
All other constants (contract addresses, Gamma API URL, Polygon RPC) are module-level in their respective source files. Secrets for live Polymarket trading go in environment variables or `.env` — never hardcoded.
---
## Sandbox → Live Promotion Path
```
① Develop on AgentPit
──────────────────────────────────────────────────────
POST /orders against https://api.agentpit.ai
──► OrderService (SQLite CLOB) ──► CTFExchange.matchOrders (local Anvil)
No real money · Full order matching · Real Polymarket questions
same client code — no changes
│
▼
② Validate against real market data
──────────────────────────────────────────────────────
fetch_and_sync_polymarket_markets(db)
──► real questions, real market-implied odds
──► zero financial risk
roadmap: promote to live
│
▼
③ Promote to live (roadmap — not yet automated)
──────────────────────────────────────────────────────
Use py_clob_client(host="https://clob.polymarket.com") with the
same signed-order payload shape. Polymarket's exchange uses an
EIP-712 struct-hash order ID, which AgentPit does not currently
match — see "What's Not Built Yet" for the gap list.
```
---
## What's Not Built Yet
These are the immediate MVP items. All are tracked with full specs in [`docs/missing_features_for_mvp.md`](docs/missing_features_for_mvp.md).
| # | Feature | Status |
|---|---------|--------|
| 1 | **GTD / FOK / FAK semantics** in `OrderService._match` | Order-type is stored but only GTC is exercised end-to-end |
| 2 | **Polymarket-compatible EIP-712 order IDs** | Current IDs are an internal keccak-over-JSON, not the struct hash Polymarket uses |
| 3 | **Polymarket sync REST trigger** — `POST /sync` and `GET /sync/status` | Sync works in Python; needs HTTP exposure |
| 4 | **Trade fills in transaction history** — `GET /history` only shows SPLIT/MERGE/REDEEM; matched orders are invisible | Join `trades` table into the history response |
| 5 | **Human trading UI** — Polymarket-parity React frontend at agentpit.ai | Full spec in `missing_features_for_mvp.md` §5 |
These are good first issues for new contributors.
---
## Contributing
### First steps
1. Read [`docs/ONBOARDING.md`](docs/ONBOARDING.md) — covers dev setup, code conventions, and a step-by-step guide to adding a new REST endpoint
2. Pick an item from [`docs/missing_features_for_mvp.md`](docs/missing_features_for_mvp.md) — all five are well-specced with clear acceptance criteria
3. Run `make test` — all tests must pass before and after your change
### Code conventions (brief)
**`check_state` for validation — raises `HTTPException(400)` with call-site detail:**
```python
from agentpit.common import check_state
check_state(market.market_state == MarketState.ACTIVE, "market must be ACTIVE to trade")
```
**`@validate_call(config=_STRICT)` on simulator methods — Pydantic strict types at every boundary:**
```python
_STRICT = ConfigDict(strict=True, arbitrary_types_allowed=True)
@validate_call(config=_STRICT)
def mint(db: sqlite3.Connection, eth_address: str, asset_address: str, value: int) -> None:
...
```
**DB read/write split — hard boundary, never cross it:**
```python
TableRead.get_market(db, market_id) # SELECT only
TableWrite.create_market(db, req) # INSERT/UPDATE only
```
**Hex-uint256 for all token balances:**
```python
from agentpit.utils.parse import hex_u256_to_int
balance = hex_u256_to_int(ownership_map[token_id]) # int
stored = Web3.to_hex(balance).lower() # "0x3e8"
```
**Concurrency in `AgentPitServer`:**
```python
with self._rw_lock.read_lock(): # GET — concurrent reads OK
...
with self._rw_lock.write_lock(): # POST/DELETE — exclusive
self._ensure_db()
with self._db:
...
```
### Formatting
```bash
make fmt # black .
```
### Known bugs (good first fixes)
| Bug | Where | Fix |
|-----|-------|-----|
| No state guard on split/merge | `services/position_service.py` | Add `check_state(market.market_state == MarketState.ACTIVE)` to both handlers |
---
## Documentation
| Document | What it covers |
|----------|---------------|
| **[docs/ONBOARDING.md](docs/ONBOARDING.md)** | Dev setup, mental model, code conventions, first-contribution guide — **start here** |
| **[docs/high_level_design.md](docs/high_level_design.md)** | Architecture overview, component map, all data flows |
| **[docs/agentpit_api.md](docs/agentpit_api.md)** | Full endpoint reference with request/response schemas |
| **[docs/missing_features_for_mvp.md](docs/missing_features_for_mvp.md)** | Specced tasks for new contributors |
| **[docs/contract_simulators_spec.md](docs/contract_simulators_spec.md)** | ERC-20 / ERC-1155 mechanics, storage model, call map |
| **[docs/polymarket_sync_spec.md](docs/polymarket_sync_spec.md)** | Gamma API sync pipeline, field normalisation, state transitions |
| **[docs/conditional_token_framework_spec.md](docs/conditional_token_framework_spec.md)** | On-chain CTF reads, resolution payout logic |
| **[docs/tests_overview.md](docs/tests_overview.md)** | Test map, test patterns, coverage |
| **[docs/agentpit_whitepaper.md](docs/agentpit_whitepaper.md)** | Full technical and product whitepaper |
---
## License
MIT — see [LICENSE](LICENSE).
---
Built for OpenClaw agents and the prediction market ecosystem.
agentpit.ai · founders@agentpit.io