Architecture: web, API & Sepolia
Everything the other technical documents describe — splits, order matching, resolution — ultimately lands on a handful of contracts on the Sepolia testnet. This document maps the path: which layer talks to which, exactly which contracts are deployed where, and why the browser never signs anything.
Four layers, one direction
Verex is a monorepo of four runtime layers. A request only ever flows one way — browser to web server to API to chain — and only one layer holds keys or an RPC connection:
| Layer | Package | Role |
|---|---|---|
| Web | packages/web | Next.js app. Renders markets, books, and the portfolio from the API's REST endpoints. Holds no keys, opens no RPC connection — it cannot touch the chain even by accident. |
| API | packages/api | Fastify server. Owns the Postgres mirror (markets, orders, fills), runs the matching engine and the market-maker, and is the only process that signs and sends transactions. |
| SDK | packages/sdk | Typed viem clients — one per contract (CTClient, ExchangeClient, UsdcClient, UmaAdapterClient). The API never handles raw ABIs; every chain call goes through these. |
| Contracts | packages/contracts | Foundry project holding the Solidity sources and the deployment scripts, plus deployments.json — the manifest of what is live on Sepolia. |
The database is a mirror, not the source of truth. Balances and positions are read from the chain (balanceOf, batched over every outcome token in one balanceOfBatch call); the database holds what the chain cannot: order books, price history, copy, and categories.
The specific contracts
Five contracts make up an environment. Two are third-party primitives used unmodified — the same ones Polymarket runs on mainnet — and three are written for Verex:
| Contract | Origin | What it does |
|---|---|---|
| ConditionalTokens (CTF) | Gnosis, unmodified | The custody core. Registers conditions (prepareCondition), splits $1 of collateral into a full outcome set (splitPosition), merges it back, records the result (reportPayouts), and pays winners (redeemPositions). Positions are ERC-1155 tokens. |
| CTFExchange | Polymarket, unmodified | Atomic settlement. Takes two signed EIP-712 orders that the off-chain book matched and swaps outcome tokens against collateral in one transaction (matchOrders). |
| MockUSDC | Verex | The demo dollar — a mintable ERC-20 with 6 decimals standing in for USDC, so wallets can be funded without a faucet queue. |
| UmaCtfAdapter | Verex (UMA's design) | Bridges UMA's Optimistic Oracle to the CTF: registers the question (initialize), and once the oracle settles, translates the answer into the payout vector (resolve). It — not the operator — is the oracle address of UMA markets. |
| MockOptimisticOracleV2 | Verex | A faithful mock of UMA's propose/dispute/vote lifecycle where the demo wallets sit as the jury — so the full dispute flow is walkable without real UMA tokens. Local and staging only. |
Deployed addresses on Sepolia
Staging and production each run their own full backbone on Sepolia (chain id 11155111) — separate instances so the two environments cannot interfere. Every address is verifiable on Etherscan:
Production (verex.jaylabs.xyz):
| Contract | Address |
|---|---|
| MockUSDC | 0xAc03…b6B6 |
| ConditionalTokens | 0xEB10…Fb04 |
| CTFExchange | 0xcB22…99Cf |
Staging:
| Contract | Address |
|---|---|
| MockUSDC | 0xF0AB…3edD |
| ConditionalTokens | 0xCa4d…aCBB |
| CTFExchange | 0x19f3…DD22 |
| UmaCtfAdapter | 0x1B45…00AC |
| MockOptimisticOracleV2 | 0x9f12…e88C |
Local development deploys a fresh backbone (plus the mock oracle stack) onto an anvil chain on every seed run, so local addresses are new each time by design. The canonical record of the Sepolia addresses is packages/contracts/deployments.json.
Who holds the keys
This is where Verex deliberately differs from a production exchange. There is no wallet-connect: the demo wallets (#1–9) and the operator (#0) are derived from a publicly known development mnemonic, and their keys live server-side, in the API. When you trade as wallet #3, the API signs as wallet #3.
Every write to the chain goes through one queue. The API answers from the database immediately, enqueues a ChainJob, and a single worker executes jobs strictly one at a time — which doubles as nonce management, since every transaction comes from server-held accounts. Jobs are claimed atomically, retried with backoff, and idempotent, so a crash mid-settlement re-runs safely.
Which layer makes which call
The three chain-touching flows, end to end — the mechanisms are covered in their own documents; this is the map of who calls what:
- Create — web posts the form to the API; the API derives the question id and calls
prepareCondition(operator markets) or the adapter'sinitialize(UMA markets) through the SDK. The condition id comes back derived, not assigned — see Custody & on-chain settlement. - Trade — web posts the order; the API matches it in the database book instantly, then a
SETTLE_MATCHjob submits both signed orders to the exchange'smatchOrders. The UI's settling on-chain… chip is that job in flight. - Resolve & redeem — the operator path calls
reportPayoutsdirectly; the UMA path runs propose → dispute → vote on the oracle and the adapter copies the settled answer in. Either way, redemption is the holder's ownredeemPositionscall — see Resolution & the UMA oracle.
Reads follow the same discipline: the portfolio page asks the API, the API asks the chain — one balanceOfBatch across every outcome token, netted against fills that are still settling, so what you see immediately after a trade equals what the chain confirms a block later.