Skip to content

Protocol reference

The canonical model for wenrange: what the contracts do, how the off-chain pieces fit, what assumptions the trust model rests on. Read this if you want a complete picture of the system; the audience-specific tracks (traders, integrators, solvers) link back here for the formal version of concepts they reference.

Overview

Users buy binary options on whether a crypto asset's price at a future hour will fall inside a chosen range. Each contract pays $1 per unit if the prediction is correct, $0 otherwise, and costs between $0 and $1 (in USDC) to open — so the price is also the implied probability.

Ranges may be:

  • Closed: [a, b] (e.g. $50,000–$80,000)
  • Open below: [0, b] (e.g. ≤ $40,000)
  • Open above: [a, +∞) (e.g. ≥ $80,000)

Bounds are inclusive and quantized to a per-asset tick (BTC: $100). Maturities are quantized to whole UTC hours.

Prices are quoted off-chain by solvers, who post on-chain collateral to back their payouts; an orchestrator brokers quote requests but never custodies funds. Settlement is sourced from Pyth and is triggered permissionlessly.

Each asset is served by its own smart contract. A solver who supports multiple assets posts collateral separately into each contract. BTC is the only asset served end-to-end today.

Actors

  • User — buys, optionally early-sells, and ultimately settles prediction contracts.
  • Solver — an off-chain agent that quotes prices and signs commitments. Posts on-chain USDC collateral that backs its payouts. Anyone vetted by the admin can become a solver. The platform launches with a single reference solver operated by the team.
  • Orchestrator — centralized backend that brokers quote requests between users and solvers. Holds no funds and cannot sign commitments.
  • Admin (multisig) — registers, pauses, and removes solvers; tunes protocol parameters; withdraws accumulated protocol fees. Cannot move user or solver funds beyond fees that have already accrued.
  • Trustless callers — anyone can invoke permissionless functions on-chain: record a settlement price, settle a matured position, emergency-cancel a stalled one. settle and emergencyCancel callers earn an ETH bounty.

High-level flows

Purchase

  1. User specifies asset, range, maturity hour, and quantity in the UI.
  2. UI requests a quote from the orchestrator, including the user's wallet address.
  3. Orchestrator broadcasts the request to all connected solvers; collects responses in a short listening window.
  4. Each willing solver responds with a signed commitment binding the trade to that specific buyer.
  5. Orchestrator picks the lowest-priced quote and returns it to the UI.
  6. User submits a transaction to the smart contract carrying the commitment.
  7. Contract verifies the signature, checks the buyer matches msg.sender, transfers the premium into escrow, freezes the solver's collateral, and mints the user's position NFT.

Buyback (early close)

  1. User chooses how many units of a position to sell back.
  2. Orchestrator runs a buyback quote request across all solvers, including the issuer. Issuers usually quote tightest because closing the position releases their frozen collateral.
  3. User submits a buyback transaction.
  4. If the issuer wins: units are burned, user is paid, issuer's collateral is released.
  5. If a different solver wins: units transfer from user to the new solver. The new solver pays from their own balance; the original issuer's frozen collateral stays put and still backs the position (the new solver is taking a long against existing collateral).

If no acceptable quote is offered, the user holds the position to maturity.

Settlement

  1. After the maturity hour passes, any trustless caller submits a Pyth price-update VAA for that hour to the settlement oracle via recordPrice(hour, updateData). First valid recording is final.
  2. Once recorded, any trustless caller invokes settle(tokenId, holder) on the market. The market reads the price live from the oracle, burns the position NFT, and pays out — $1 per unit to the holder if in range, otherwise the issuing solver keeps the premium.
  3. settle callers receive a fixed ETH settleReward from the contract's shared bounty pool.

Emergency cancel

If 7 days pass after maturity without a price ever being recorded for that hour, any caller can invoke emergencyCancel on behalf of a holder. The holder is refunded the premium tracked against their units; the issuing solver's collateral is released. This guarantees that even if the oracle, the orchestrator, and the admin all fail, funds eventually return to their rightful owners.

On-chain components

Position NFT (ERC-1155)

Positions are ERC-1155 tokens. Two users buying the same (range, maturity, solver) get the same token ID and their balances aggregate (semi-fungibility).

Token ID packs four fields into the low 16 bytes of a uint256:

Field Size Notes
lowerBound 4 bytes In ticks. 0 doubles as the open-below sentinel.
upperBound 4 bytes In ticks. 0xFFFFFFFF is the open-above sentinel.
maturityHour 4 bytes Unix timestamp / 3600. Good for ~490,000 years.
solverId 4 bytes Index into the solver registry.

Including the solver in the token ID keeps collateral accounting simple: every token ID corresponds to exactly one backing solver.

Transfers and premium propagation

Positions are freely transferable: holders can send them to other addresses or trade them on secondary markets without going through the protocol. But each position carries a premium — the amount the original buyer paid — that has to be refunded to whoever holds the position if the market needs to graceful-shutdown via emergency-cancel. The premium must therefore follow the units when they move.

Position tokens implement the full ERC-1155 transfer surface (safeTransferFrom, safeBatchTransferFrom, operator approvals). On every transfer the contract's _update override automatically moves the proportional slice of holderPremium from sender to recipient, so the emergency-cancel refund total is conserved no matter how positions change hands off-protocol.

Position premium accounting

To support graceful shutdown via emergency cancel, the contract tracks the premium attached to each holder's units of each token ID. This lets the contract refund the right amount to whoever currently holds the position at cancel time.

State: holderPremium[holder][tokenId] — total premium currently attached to that holder's units of that token ID.

Transitions:

  • Mint (purchase): holderPremium[buyer][tokenId] += pricePerUnit × units.
  • Transfer (non-issuer buyback or off-protocol transfer): premium follows units proportionally.
  • Burn (issuer buyback or settlement): sender's premium decreases proportionally; no destination — the value is realized by the trade outcome.
  • Emergency cancel: holder receives their holderPremium value and the entry is cleared.

By construction, the sum of holderPremium across all holders for a given token ID equals the premium still escrowed for that ID, so emergency-cancel refunds total exactly to the funds available.

Solver registry

mapping(uint32 solverId => SolverInfo), keyed by solverId. Each entry holds:

  • signer — the public key that signs commitments.
  • ownerAddr — the address that can withdraw collateral.
  • enabled — when false, the solver cannot sign new positions; existing positions still resolve normally.
  • removed — when true, the solver is fully retired but can still withdraw any unfrozen balance.
  • Collateral state (see below).

Only the admin multisig can call registerSolver, pauseSolver, unpauseSolver, and removeSolver.

Collateral and accounting

Per-solver state in this contract:

  • balance — total USDC currently deposited.
  • frozen — sum of collateral currently backing open positions issued by this solver.
  • costBasis — cumulative deposits minus cumulative principal withdrawals.

Profit at any moment is balance - costBasis. Frozen funds are part of balance — they are unrealized P&L until positions settle.

Withdrawal rules

A solver can withdraw up to balance - frozen. The amount splits into:

  1. Fee-free portion: up to costBasis - principalWithdrawn. Reduces principal 1:1.
  2. Profit portion above that: subject to a 20% protocol fee (80% to solver, 20% to treasury).

Solvers can withdraw profit even with some collateral frozen — the frozen amount already covers maximum loss exposure on open positions.

Settlement oracle

Settlement is sourced from Pyth Network via the pull-oracle pattern. The settlement oracle is a separate contract behind a generic ISettlementOracle interface; the market reads it live and never caches.

Recording:

  1. After maturity hour, anyone calls recordPrice(hour, updateData) with a Pyth VAA.
  2. The oracle verifies the VAA, checks the VAA's publish time falls inside the recording window, normalizes the price to a canonical decimal scale, and stores it.
  3. First valid recording is final.

Reads: settle and emergencyCancel call into the oracle directly. A successful recordPrice unblocks every position maturing at that hour at once.

Timelocked oracle swap

Replacing the oracle (e.g. point-release of the Pyth implementation, alternative provider) is guarded by a 2-day timelock:

  • proposeOracle(newOracle) — admin queues a new oracle.
  • cancelPendingOracle() — admin aborts the queued swap.
  • applyPendingOracle() — permissionless once the timelock expires.

Users have a 2-day window between proposal and application to settle outstanding positions (or wait for emergency-cancel) against the previously committed oracle if they distrust the swap.

Bounty pool

Settlement callers are compensated from a shared on-chain bounty pool, not from per-position escrow. Two governance parameters drive it:

  • openFee (ETH) — buyer must send this exact amount with every buy(). Accumulates into the pool.
  • settleReward (ETH) — paid (best-effort) to the caller of settle or emergencyCancel. If the pool is below settleReward at call time, the caller receives min(settleReward, pool) — possibly zero — and the call still succeeds. Settlement is never bricked by an underfunded pool.

Setting settleReward ≤ openFee keeps the pool non-decreasing under honest flow. Any sequence of user-initiated flows leaves the pool ≥ its prior balance, so drain attacks are structurally impossible.

The admin can sweep excess ETH with withdrawBountyPool; over-sweeping only reduces the next caller's reward, never bricks settlement. Anyone may top the pool up by sending ETH directly to the contract.

Commitments

Commitments travel from solvers to users as EIP-712-signed messages, then on-chain in a compact packed form (140 bytes for buy, 160 bytes for buyback). The contract decodes the packed bytes back into the canonical struct before verifying the signature.

Validation on every buy / buyback:

  1. msg.sender matches the commitment's buyer (or user for buyback). Prevents anyone but the intended buyer from submitting it.
  2. Signature is valid against the registered public key for the named solverId. The EIP-712 domain separator pins chainId and verifyingContract, so cross-deployment replay is impossible.
  3. Solver is enabled.
  4. Commitment is not expired.
  5. Parameters in the commitment match the transaction.
  6. Solver has sufficient free collateral.
  7. Commitment nonce has not been used. usedNonces[solverId][nonce] is atomically read and marked within validation.

Full EIP-712 type strings and the byte-level packed layout are on the API docs site.

Admin controls

The market exposes a narrow set of admin functions, all gated by onlyAdmin and emitting events for transparency:

Function Effect
setProfitFeeBps, setEmergencyGracePeriod, setOpenFee, setSettleReward Tune the corresponding parameter.
transferAdmin Hand admin rights to a new address.
proposeOracle, cancelPendingOracle Queue / abort an oracle swap (2-day timelock).
withdrawTreasury Withdraw accumulated solver-profit fees.
withdrawBountyPool Sweep excess ETH from the bounty pool.
registerSolver, pauseSolver, unpauseSolver, removeSolver Manage the solver registry.
pause, unpause Halt buy and buyback. settle and emergencyCancel remain open, so a pause cannot strand user funds.

Off-chain components

Orchestrator

The orchestrator brokers quote requests between users and solvers. It is intentionally stateless beyond live solver connections and a streaming spot-price feed:

  • REST endpoints for the UI: spot price, buy-quote and buy-quote-with-commitment, close-quote and close-quote-with-commitment.
  • WebSocket server for solvers: persistent connections, EIP-191 challenge/response handshake, ping/pong liveness, broadcast quote requests, short listening window for responses.
  • Quote selection: lowest-priced buy quote, highest-priced sell quote.
  • Spot price ingestion: streams BTC/USD from Pyth Hermes — the same data source the settlement oracle ultimately settles against.

The orchestrator can favor or censor solvers but cannot:

  • Forge or modify commitments (signed by solver keys).
  • Move user or solver funds (all custody is on-chain).
  • Prevent users from interacting with the smart contract directly.

API service

A separate read-only service over indexed chain state, decoupled from the orchestrator so a read-path outage or a chain reorg doesn't degrade live quoting. Endpoints: /positions, /settlement-prices, /solver-liquidity. Full schemas on the API docs site.

Indexer pipeline

A Goldsky subgraph indexes the OptionsMarket event stream and a Goldsky Mirror pipeline sinks the entities into a managed Postgres instance, which the API service queries.

Position lifecycle is reconstructed from standard ERC-1155 TransferSingle / TransferBatch events rather than the protocol-specific events alone, so off-chain holder-to-holder transfers are tracked correctly. The one exception is PositionBought — indexed for its premium field, which feeds cost-basis reporting.

Pyth relayer

An off-chain Go service that watches the maturity hour boundary, fetches the appropriate Pyth VAA, and submits recordPrice on-chain. Not trusted — anyone can run an equivalent relayer, and the on-chain validation is the sole correctness gate. The team operates one instance as a liveness convenience.

Range specification

  • Tick size is per-asset, configured at deployment. BTC: $100.
  • All bounds (open or closed, lower or upper) are multiples of the tick.
  • Closed ranges require lowerBound < upperBound.

Fees

  • No fee on the premium itself. Buyers pay only the quoted price.
  • Each buy() carries a fixed ETH openFee that funds the trustless-settlement bounty pool — this is settlement infrastructure, not protocol revenue on the trade.
  • 20% profit fee charged to solvers on profit withdrawals.
  • Profit fees accumulate in a treasury controlled by the admin multisig; openFee collections accumulate in the bounty pool.

Trust and decentralization

What the admin / orchestrator can do:

  • Register, pause, and remove solvers (on-chain).
  • Tune fee, grace-period, and bounty parameters (on-chain).
  • Propose a new settlement oracle (on-chain), subject to a 2-day timelock before activation.
  • Upgrade the market implementation (UUPS).
  • Withdraw accumulated treasury fees and excess ETH from the bounty pool.
  • Pause buy and buybacksettle and emergencyCancel remain open under pause.
  • Favor or censor specific solvers in the orchestrator.
  • Bring the orchestrator offline.

What the admin / orchestrator cannot do:

  • Move user funds.
  • Move solver collateral beyond fees that have already accrued.
  • Forge solver commitments.
  • Prevent users from interacting with the smart contract directly.
  • Block settle or emergencyCancel. They remain open under pause; the oracle is the only external dependency.

Graceful shutdown: even if the orchestrator goes permanently offline and the admin disappears, every user holding an unmatured position can wait for maturity and then either settle themselves (if the oracle still works) or wait for the emergency-cancel grace period and reclaim their premium. Every solver can withdraw their unfrozen balance at any time, and frozen balances unlock as positions resolve.

Current scope

  • BTC only end-to-end. Contracts are parameterized by asset and can be redeployed for any asset; the orchestrator and reference solver currently hardcode BTC.
  • Reference solver only at launch; additional solvers onboarded over time via admin multisig.
  • One solver per transaction (no split fills).
  • No partial fills against a single solver's capacity (quotes are take-it-or-leave-it).
  • Settlement oracle: Pyth.
  • Orchestrator centralized.

Future directions

  • Additional asset contracts (ETH, SOL), each a fresh deployment with its own tick and oracle wiring.
  • Split fills across multiple solvers in one purchase.
  • Cross-solver fungibility (dropping solverId from the token ID).
  • Orchestrator decentralization (multiple competing orchestrators or a P2P discovery layer).