Builder Integration Guide
Build prediction market interfaces on top of the Oracle platform. Third-party
frontends ("builders") can embed markets, route orders, and earn attribution
fees. Identity is an EVM 0x address and every write is authorized by a
session cookie plus an EIP-712 signature — there is no central signing
server; your frontend (or the user's wallet) signs with an EVM key directly.
Architecture
Your Frontend --> Oracle Gateway --> Engine
| REST API Matching
| signs EIP-712
| (viem / wallet)
|
Oracle Deposit --> DepositForwarder --> PartiVault
| USDG custody Settlement
|
Deposit addresses (CREATE2, per 0x address)
Each user is an EVM 0x address. Your frontend signs the EIP-712 typed structs
for that user — either with the user's connected wallet, or with a key you hold
on their behalf (e.g. a Privy embedded wallet). The gateway verifies the
signature recovers to the user address and that a valid session cookie is
present.
Auth model
Every write endpoint (POST /v1/orders, POST /v1/positions/redeem-sets,
POST /v1/withdraw-signed) requires both:
- An HMAC-signed, HTTP-only session cookie (lasts ~7 days), and
- A fresh EIP-712 signature over that action's typed struct.
Cancels are the exception — POST /v1/orders/cancel and
POST /v1/orders/cancel-all are authorized by the session cookie alone
(there is no CancelAll EIP-712 struct; the engine owner-checks each resting
order). Read endpoints need neither.
All EIP-712 structs are signed under the PartiVault domain:
domain = { name: "PartiVault", version: "1", chainId, verifyingContract }
// chainId: 4663 mainnet / 46630 testnet
// verifyingContract: the deployed PartiVault address for that chain
If chainId / verifyingContract don't match the gateway's configured domain,
signatures verify against the wrong domain and the gateway returns
403 invalid_signature.
Quick Start
1. Hold an EVM key per user
Use the user's connected wallet, or generate/custody an embedded wallet. The
user's trading address is the 0x address derived from that key (lowercase on
the wire). Never reuse a production wallet's key for automated signing.
import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts';
const account = privateKeyToAccount(generatePrivateKey());
const user = account.address.toLowerCase(); // trading identity
2. Register a session
Sign the SessionBootstrap struct and POST /v1/builders/register. The
response sets the session cookie (capture Set-Cookie) and returns a stable
api_key you attach to orders for fee attribution. The call is idempotent.
SessionBootstrap { address user; uint256 timestamp }
curl -i -X POST ${GATEWAY_URL}/v1/builders/register \
-H "Content-Type: application/json" \
-d '{
"user": "0x4b2a9e7c...",
"timestamp": 1700000000,
"signature": "0x<65-byte EIP-712 SessionBootstrap signature>",
"name": "My Trading App"
}'
Response (plus a Set-Cookie header):
{"api_key": "bld_a1b2c3d4e5f6...", "name": "My Trading App"}
Echo the cookie back on every authenticated request. Without it, writes return
401 unauthenticated regardless of how correct the order signature is.
3. Fund the user
Get the user's deposit address and have them send USDG on Robinhood Chain. See Deposits & Withdrawals for the full flow.
curl ${DEPOSIT_URL}/v1/deposit/robinhood-address/<user-lowercase-0x-address>
4. Place orders
Sign the Order struct with the user's EVM key and POST /v1/orders with the
session cookie. Attach builder_api_key so fills attribute to your builder
wallet.
Order {
bytes32 market; // 64-char hex verbatim, else SHA-256 of the utf-8 slug
address user; // lowercase 0x
uint8 outcome; // 0 = yes, 1 = no
uint8 side; // 0 = buy, 1 = sell
uint256 price; // basis points 1..9999
uint256 size; // contracts
uint256 nonce; // monotonic timestamp nonce (checked off-chain)
}
curl -X POST ${GATEWAY_URL}/v1/orders \
-H "Content-Type: application/json" \
-H "Cookie: ${SESSION_COOKIE}" \
-d '{
"user": "0x4b2a9e7c...",
"market_id": "abc123...",
"side": "buy",
"outcome": "yes",
"price": 6500,
"size": 100,
"order_type": "gtc",
"nonce": 1700000000123,
"signature": "0x<65-byte EIP-712 Order signature>",
"builder_api_key": "bld_a1b2c3d4e5f6..."
}'
The response is documented under POST /v1/orders in the
REST API Reference.
5. Read market data
Query the gateway directly — no cookie or signature needed for reads:
# All markets with prices
curl ${GATEWAY_URL}/v1/markets
# All events (multi-outcome groups)
curl ${GATEWAY_URL}/v1/events
# Order book
curl ${GATEWAY_URL}/v1/markets/${MARKET_ID}/book
# Trade history
curl ${GATEWAY_URL}/v1/markets/${MARKET_ID}/trades?limit=50
# Candles
curl ${GATEWAY_URL}/v1/markets/${MARKET_ID}/candles?interval=5m
# User balance / position / trade history (lowercase 0x address)
curl ${GATEWAY_URL}/v1/balance/${ADDRESS}
curl ${GATEWAY_URL}/v1/position/${MARKET_ID}/${ADDRESS}
curl ${GATEWAY_URL}/v1/trades/${ADDRESS}
Cancel Orders
Cancels are authorized by the session cookie alone — no signature, no nonce:
# Cancel a single resting order
curl -X POST ${GATEWAY_URL}/v1/orders/cancel \
-H "Cookie: ${SESSION_COOKIE}" \
-d '{"market_id": "abc...", "order_id": 42, "user": "0x4b2a9e7c..."}'
# Cancel all orders for a user (optional market_id scopes it)
curl -X POST ${GATEWAY_URL}/v1/orders/cancel-all \
-H "Cookie: ${SESSION_COOKIE}" \
-d '{"user": "0x4b2a9e7c...", "market_id": "abc...", "builder_api_key": "bld_a1b2..."}'
Passing your builder_api_key on cancel-all skips the 60s user-halt that is
only meant for the browser-UI "Cancel All" button.
Real-Time Data
Connect to the gateway WebSocket at /v1/ws (same host as the REST API):
const ws = new WebSocket(`${WS_URL}/v1/ws`)
ws.onopen = () => {
// Subscribe to book updates (includes yes/no prices)
ws.send(JSON.stringify({ type: 'subscribe', channel: `book:${marketId}` }))
// Subscribe to trades
ws.send(JSON.stringify({ type: 'subscribe', channel: `trades:${marketId}` }))
}
See the WebSocket Guide for the full channel + event docs.
Creating Markets
Builders can create markets/events via the gateway (requires admin key in production):
# Binary market (Yes/No)
curl -X POST ${GATEWAY_URL}/v1/events \
-H "X-Admin-Key: ${ADMIN_KEY}" \
-d '{
"title": "Will BTC reach $200k?",
"category": "crypto",
"outcomes": ["Yes"],
"resolution_source": "price_oracle",
"expiry": 1798761600,
"creator": "0x4b2a9e7c..."
}'
# Multi-outcome event
curl -X POST ${GATEWAY_URL}/v1/events \
-H "X-Admin-Key: ${ADMIN_KEY}" \
-d '{
"title": "Who will win the Super Bowl?",
"category": "sports",
"outcomes": ["Chiefs", "Eagles", "49ers", "Other"],
"resolution_source": "api_oracle",
"expiry": 1798761600,
"creator": "0x4b2a9e7c..."
}'
Market creators earn 10% of all trading fees collected on their markets.
Builder Fees
Builder fees are opt-in and net-zero: when an order carries a
builder_api_key, the builder's fee_bps is charged to the party whose order
carried the key and credited to the builder's wallet — it is additive (not taken
from the protocol fee or maker rebate) and operator-capped. A builder wallet and
its fee_bps are configured by the operator via POST /admin/builders (see the
REST API Reference); the builder must be enabled.
Rate Limiting
Write requests are rate-limited per wallet (token bucket; IP as fallback).
Exceeding the limit returns HTTP 429 ({"error": "rate_limited"}). Back off
and retry. Admin-authenticated endpoints (via X-Admin-Key) are exempt.
Security Model
- Session cookie authenticates the acting
0xuser; EIP-712 signatures authorize each individual write. - The gateway cannot withdraw funds on its own — a withdraw requires your
EIP-712
Withdrawsignature, verified on-chain byPartiVault. - Private keys never leave your frontend/custody; the gateway only sees
signatures and the recovered
0xaddress. - Users fund their own CREATE2 deposit forwarders on Robinhood Chain (USDG).
Service URLs
| Service | Purpose | Auth |
|---|---|---|
| Gateway | REST API + WebSocket (markets, orders, balances) | Public reads; cookie + EIP-712 writes; admin writes |
| Deposit | Funding (deposit address, withdraw) | Public |
| Swagger | Interactive API docs | {gateway_url}/swagger-ui/ |
See Deposits & Withdrawals for per-environment hosts, chain IDs,
and the PartiVault / USDG addresses.