Liquidity Provision
Oracle uses a pure Central Limit Order Book (CLOB) — no AMM, no shared liquidity pools, no LP vaults with share tokens. Liquidity comes from resting limit orders, and the protocol incentivizes LPs in two ways:
- Maker rebates — paid per fill, funded from the taker fee. Instant.
- Liquidity rewards — a daily USDG budget per market, awarded to wallets that held tight two-sided quotes. Extended quadratic scoring: multi-level depth, gold-band bonus, super-linear uptime weighting, anti-spoofing clamp, 40% per-wallet cap.
Both programs run independently. Any resting order is automatically eligible for both — no opt-in, no vault deposits.
1. Maker Rebates
Every fill on Oracle pays a rebate to the resting (maker) side. Rebates settle into the maker's usdc_balance at match time — spendable immediately, no claim step.
Fee split per fill
| Party | Flow |
|---|---|
| Taker | Pays taker_fee_bps on notional (default 2%) |
| Maker | Receives maker_rebate_bps of notional (default 0.5%) |
| Treasury | Receives the remainder |
Calculation
taker_fee = floor(fill_size × fill_price × taker_fee_bps / 10_000)
maker_rebate = floor(fill_size × fill_price × maker_rebate_bps / 10_000)
fill_price is in micro-USDG per outcome token (6 decimals). fill_size is outcome tokens.
Example — fill 1,000 YES @ $0.65, default bps:
- Taker pays:
1,000 × 650,000 × 200 / 10,000=13,000,000µUSDG = $13.00 - Maker earns:
1,000 × 650,000 × 50 / 10,000=3,250,000µUSDG = $3.25
Who earns it
Only orders that rest on the book and get matched by an incoming taker earn the rebate. A GTC limit order that sits and gets filled: rebate. A market order (IOC) that crosses the spread: no rebate — it's the taker. Partial fills pay pro-rata.
Changing fees
Fees are read live from the fee config on the PartiVault contract (Robinhood Chain). Changes require an admin tx and are announced ≥24h in advance.
2. Liquidity Rewards
A daily per-market USDG budget is distributed to wallets that hold resting orders inside a configured spread band. The Oracle scoring formula extends a quadratic distance base with five mechanics that reward real depth, uptime, and two-sided balance — and punish spoofing and whale capture.
Per-sample scoring
Every 30 seconds the engine snapshots each market's order book. For each wallet with resting orders, compute:
(a) Per-order distance (quadratic base). For every resting order inside max_spread_bps:
order_score = size × ((max_spread − distance_from_mid) / max_spread)² × in_game_multiplier
A quote at mid earns size × multiplier. Halfway to the band → 25% (0.5²). At the boundary → 0.
(b) Multi-level depth. Sum across every resting order on the side, with a per-level decay by rank-from-mid. Rewards real depth, not just a single tight quote:
side_score = Σ order_score(o) × level_decay(rank(o))
level_decay(rank) = 1 / (1 + 0.5 × rank) // 1.00 / 0.67 / 0.50 / 0.40 / …
(c) Gold-band bonus. Orders within the tightest 25% of max_spread get a 1.5× multiplier on top of the quadratic.
(d) Combined sides (single-sided penalty).
combined = max( min(bid_score, ask_score), max(bid_score, ask_score) / c )
c = 2 // stricter than the c=3 used elsewhere; single-sided = 50% credit
Symmetry bonus: if |bid − ask| / max ≤ 0.20, multiply combined by 1.10.
Daily aggregate
uptime = active_samples / 2880 // active = non-zero combined score
daily_score = (Σ sample_scores) × uptime^0.8
Super-linear uptime penalty (uptime 95% → ×0.96, 80% → ×0.83, 50% → ×0.57).
Anti-spoofing clamp: a wallet's cancel_ratio over a rolling 5-min window = cancels / (cancels + fills). If > 0.50 with any cancels, every sample in that window is scored ×0.5.
Daily distribution
At 00:00 UTC, pro-rata by daily_score, then cap at 40% per wallet:
raw_payout(w) = daily_score(w) / Σ daily_scores × daily_budget_usdc
max_share = daily_budget_usdc × 0.40
final_payout(w) = min(raw_payout(w), max_share)
Any undistributed remainder from cap enforcement rolls into the next day's budget.
Credits accumulate in a wallet-level claimable_micro_usdc — a single balance across every market.
Per-market configuration
Each market has its own max_spread_bps, min_size, daily_budget_usdc, in_game_multiplier. See GET /v1/rewards/config. Markets without a config earn nothing.
3. Claim Flow
Reward credits are distinct from wallet balance. Claiming moves USDG from the fee treasury to the user via the PartiVault contract on Robinhood Chain.
On-chain plumbing
- Vault call:
claimRewardsonPartiVault(Robinhood Chain) - Authority:
operatoronly (relayed — user doesn't pay gas) - Source: the vault's
feeTreasuryUSDG balance - Destination: the user's
0xEVM address
API
POST /admin/rewards/claim
X-Admin-Key: <admin key>
{
"wallet": "<0x EVM address>",
"amount_micro_usdc": 5000000 // optional; defaults to full claimable
}
Response (200):
{
"claimed_micro_usdc": 5000000,
"remaining": 400000,
"signature": "0x5e8c..."
}
The signature is the Robinhood Chain tx hash. remaining reflects claimable_micro_usdc after the decrement; the decrement is atomic and clamps at zero (never negative, even on over-claim).
Failure mode: tx succeeds, Redis decrement fails
If the on-chain tx lands but the Redis decrement errors, the response is still 200 with remaining: null and a warning field flagging manual reconciliation. The funds are delivered on-chain; only the accounting counter is out of sync. Operator must reconcile manually.
Public self-service endpoint
Not yet shipped. Today all claims are operator-relayed and admin-key-gated. Market makers running continuous strategies should arrange scheduled sweeps with the Parti team.
4. Running a Market Maker
The reference market-making bot is oracle-lp (Rust). It:
- Subscribes to the order-book WebSocket
- Maintains symmetric two-sided quotes around mid, scaled by
spread_bpsandsize - Cancels and re-posts when mid moves >
requote_bps - Tracks inventory per market, skews the constrained side on drift
Typical loop (pseudocode)
while True:
book = GET /v1/markets/{id}/book
mid = book["yes_price"] # basis points (1..9999)
# Cancel existing resting orders
POST /v1/orders/cancel-all { market_id, user, signature, nonce }
# Re-post inside the rewards band
for offset_bps in [spread_bps, spread_bps*2, spread_bps*3]:
POST /v1/orders {
side: "buy", outcome: "yes",
price: max(1, mid - offset_bps),
size: N, order_type: "gtc",
signature, nonce
}
POST /v1/orders {
side: "sell", outcome: "yes",
price: min(9999, mid + offset_bps),
size: N, order_type: "gtc",
signature, nonce
}
sleep(5)
Order type is gtc (not post_only) because cancel-all runs before each rebalance, so there are no stale LP orders to cross with.
Picking spread_bps
Quote inside the market's max_spread_bps — quotes outside earn rebates on fills but score 0 for rewards. Rule of thumb: spread_bps ≈ max_spread_bps / 4 for the tightest level, widening geometrically.
Key endpoints for market makers
| Endpoint | Use |
|---|---|
GET /v1/markets/{id}/book |
Current order book state |
POST /v1/orders |
Place orders (gtc or post_only) |
POST /v1/orders/cancel-all |
Bulk cancel before requoting |
WS book:{market_id} |
Real-time book updates |
GET /v1/rewards/config |
Per-market max_spread_bps, min_size, daily_budget |
GET /v1/rewards/leaderboard?market_id= |
Current-day scoreboard |
GET /v1/rewards/wallet/{wallet} |
Cumulative claimable balance |
5. Funding
Fund your trading balance by sending USDG on Robinhood Chain to your per-account CREATE2 deposit address (minimum 1 USDG). The watcher sweeps it into the vault and credits your off-chain engine balance, usually within a minute — see Deposits & Withdrawals for the full flow, addresses, and the withdraw path (EIP-712, sequential vault nonce).
6. Why CLOB, not AMM
- Correct price discovery — prediction markets have binary terminal values, so LP-against-AMM strategies bleed to informed traders. A CLOB lets makers step back when they're out-gunned.
- Risk isolation — one market's volatility doesn't affect another's liquidity.
- LP control — makers choose exactly which markets to provide on, at what spread, with what size.
- Capital efficiency — no idle depth in deep out-of-the-money bands; capital quotes where the flow is.