Divine Igbinoba / Case Study / Apeing

Apeing.

Core Infrastructure & Protocol Engineer · Jul 2025 — Dec 2025

Domain
Decentralised Prediction Markets
My Role
Full backend + protocol ownership
Duration
6 months
Chain
EVM (Ethereum)
Rust Solidity Axum Tokio Ethers-rs PostgreSQL Redis Hardhat SQLx tower_governor UMA Oracle V3 EIP-712

Overview

Prediction Project

At Apeing I buit a decentralised prediction market protocol, a platform where users stake capital on the outcome of real-world events.

We needed a system that handles thousands of simultaneous users transacting in real time, a smart contract architecture that executes trades deterministically on-chain, and a settlement mechanism that resolves outcomes in a trustless way without any central authority.

I owned the entire backend and protocol layer from day one, with no existing infrastructure to build on.

System Architecture

System Flow

The prediction market backend runs on two execution paths.

Data path: To handle the thundering herd problem, SingleFlight collapses simultaneous client requests for the same market data into a single upstream fetch, then broadcasts the result to all waiting connections.

On-chain path: Clients submit transactions directly to the blockchain via an external sign flow, while the MetaRelayWorker polls for confirmation and reconciles off-chain state.

1 — State Management & Request Coalescing

App Client A App Client B Axum Handlers SingleFlight Redis Cache Postgre / RPC GET /market/1 GET /market/1 (ms later) Check Cache Miss Lock key "mkt:detail:1" Fetch Market Data User B's request awaits User A's response Return Market Data Broadcast to A & B Set Key "mkt:detail:1" 200 OK 200 OK App Client A App Client B Axum Handlers SingleFlight Redis Cache Postgre / RPC

SingleFlight collapses concurrent identical requests — one upstream fetch, broadcast to all waiting connections

2 — The Decentralised "Pure On-Chain" Execution Flow

Client Market Service (Rust) Onchain Provider Blockchain PostgreSQL MetaRelayWorker Buy Shares (sign_mode=external) Format calldata & build Tx Return Unsigned Tx Payload Return Unsigned Tx Payload Direct wallet submission Notify Server (tx_hash) Record Pending Tx in Jobs Queue loop [Every N seconds] Fetch Active Jobs Poll for Tx Receipts (tx_hash) alt [Transaction Mined successfully] Update Trade Data & Calculate PnL Invalidate Cache (User/Market) WS push — trade confirmed Client Market Service (Rust) Onchain Provider Blockchain PostgreSQL MetaRelayWorker

External sign flow — client signs off-chain, MetaRelayWorker polls for confirmation and reconciles state via cache invalidation

Impact

Shipped

These numbers are estimates of capacity based on QA testing.

20K+
Concurrent WebSocket connections sustained
Axum / Tokio async gateway
>90%
Reduction in primary PostgreSQL read load
Redis JSON caching layer
100%
Off-chain / on-chain state synchronisation
Deterministic EVM event parsing
>50%
Reduction in contract deployment payload
UUPS proxy + viaIR pipeline

Technical Decisions

Solutions

Each decision below started as a constraint and became an architectural choice.

01 Redis SCAN/UNLINK — why not just DEL? +

Cache invalidation · Redis · Async Rust

Naive cache invalidation uses DEL: when data changes, delete the affected keys.

At small scale it works, but under load it blocks Redis. Because Redis is single‑threaded, a large DEL freezes the event loop, stalling all clients. On a prediction market with live price feeds, this causes latency spikes at peak traffic.

UNLINK avoids the block. It marks keys for deletion and offloads memory reclamation to a background thread, returning immediately. Paired with a SCAN‑based discovery pattern, I batch invalidations in 1,000‑key cycles without blocking.

Load testing showed 1,000 keys was the sweet spot: smaller batches wasted round‑trips, larger ones caused background threads to compete with reads.

Cache flushes had to leave read latency unaffected.
02 UUPS Proxy — hitting the 24.576 KB wall +

Smart Contract Architecture · Solidity · EIP-1967

EVM enforces a hard contract size limit of 24.576 KB. Exceed it and deployment fails.

Apeing’s prediction market required complex on‑chain logic: market creation, position tracking, collateral management, oracle disputes, and settlement. A monolithic contract blew past the limit.

There were two options. First: aggressively compress the contract — This trades contract capability for size compliance. Second: decompose via proxy architecture.

I chose UUPS (Universal Upgradeable Proxy Standard) pattern. The proxy holds state and delegates logic to an implementation contract. Position tracking was offloaded to the Gnosis Conditional Token Framework, avoiding redeployment of that logic. Enabling viaIR compilation gave whole‑program optimizations, cutting bytecode size by over 50% and reducing trade execution gas ~30%.

The proxy pattern also delivered upgradeability — critical for fixing bugs or adding features without redeploying state in a live financial protocol.
03 MetaRelayWorker — making transactions gasless +

EIP-712 · Ethers-rs · Meta-Transactions

On‑chain interactions require ETH for gas, adding friction: users must acquire ETH, estimate gas, and broadcast a transaction. In volatile markets, prices often move before execution.

Meta-transactions: solve this. Users sign structured off‑chain intents, and a relayer pays gas and submits the transaction. The user never needs ETH.

Without binding signatures, relayers could alter transactions. EIP-712 enforces typed structured data for off-chain signing that gets verified on-chain, ensuring the contract executes exactly what the user signed.

The MetaRelayWorker is an async Rust worker (built on Ethers-rs) that receives signed EIP-712 intents, validates them, constructs the on-chain transaction, and submits it. It then listens for the specific EVM event emitted by execution and reconciles off‑chain state deterministically.

Synchronization is guaranteed by confirmed events. If the event isn’t emitted, state doesn’t update
04 UMA Optimistic Oracle — removing the trust assumption +

Oracle Design · UMA V3 · Trustless Settlement

Prediction markets need resolution: someone must decide outcomes. The simplest solution is a centralised oracle — a team‑controlled wallet writing results on‑chain. Works, but creates a single point of trust failure

I adopted UMA Optimistic Oracle V3. Anyone can assert an outcome by posting a collateral bond. The assertion enters a 7,200 seconds (2 hours) dispute window. If challenged, UMA’s decentralized resolution process decides. If unchallenged, the assertion is accepted and settlement executes.

The bond mechanism makes lying irrational: false asserters lose their bond to challengers. Trust is replaced by economic self‑interest.

The 2‑hour liveness window balanced UX and security, short enough for timely settlement, long enough for challenges to form.

By delegating resolution to UMA, I removed Apeing from the trust model entirely. Increasing users trust on the system.

Up next

Case Study

Turbine Web3 Capstone — Solana Streaming Payroll Protocol