Skip to content

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:

  1. An HMAC-signed, HTTP-only session cookie (lasts ~7 days), and
  2. 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 0x user; EIP-712 signatures authorize each individual write.
  • The gateway cannot withdraw funds on its own — a withdraw requires your EIP-712 Withdraw signature, verified on-chain by PartiVault.
  • Private keys never leave your frontend/custody; the gateway only sees signatures and the recovered 0x address.
  • 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.