Documentation

otX.markets — System Architecture (v2)

This document provides a high-level yet technically accurate overview of the otX.markets protocol as implemented in the v2 smart-contracts. It explains core on-chain components, roles, phases, trading flows, settlement, default handling, and fees. It is written for technically curious users, integrators, and auditors.

Components

  • AccessControlV3: Central role registry.

    • Roles: ADMIN_ROLE, MARKET_MANAGER_ROLE.

    • Admins grant/revoke roles, create markets, and set module addresses.

    • Market Managers update market configs and transition phases.

  • MarketRegistryV3: Market lifecycle and configuration.

    • Phases: Trading → WindingDown → Settlement → Closure.

    • Stores per-market metadata: name, points identifier, TGE token, conversion rate, settlement window, decimals, phase timestamps.

    • Integrates with OrderBookV3 for a constant-time guard that prevents entering settlement while there are active open offers.

  • OrderBookV3: On-chain order book for Points trading with partial fills and batch fills (market orders).

    • Offer types: SellPoints and BuyPoints.

    • Full collateralization: both makers and takers lock USDC equal to value traded (points × pricePerPoint).

    • States: Open, Partial, Filled, Cancelled, Settled.

    • Tracks fills independently; one offer can service many takers.

    • Batch fill across multiple offers in one market for a “market order” experience.

  • EscrowVaultV3: Secure custody for USDC collateral and TGE tokens.

    • Holds real USDC and deposited TGE tokens; not notional balances.

    • Authorizes specific modules (OrderBook/SettlementEngine) to move funds.

    • Supports EIP-2612 permit and Uniswap Permit2 for gas-efficient collateral locking.

    • Handles fee collection and forwards fees to a treasury address.

  • FeeManagerV3: Centralized fee configuration and accounting.

    • Fee types (basis points, defaults): CancelOffer 0.5%, Settlement 2.5%, BuyToken 2.0%, DefaultOrder 0.5%.

    • Enforces caps and min amounts; exposes pure calculation helpers for previews.

    • Records aggregate fee totals and emits events for analytics.

  • FeeTreasuryV3: Receives USDC fees, controlled withdrawals by Admin/Market Manager.

  • SettlementEngineV3: Settlement, TGE claims, and default handling.

    • Seller-side settlement with batched trade settlement.

    • TGE token deposit per market and per seller.

    • Merkle-based buyer TGE claims during Settlement.

    • Default processing transfers compensation from seller’s collateral to buyer if settlement window ends without seller settlement.

  • OTXPresale (separate subsystem): Presale contract for OTX with Merkle allowlists and immediate treasury forwarding of USDT0. Not part of trading lifecycle, documented here for completeness.

Roles and Trust Model

  • Admins: bootstrap the system, set addresses, manage fees/treasury, and create markets.

  • Market Managers: drive market lifecycle (wind down, start settlement, set merkle roots, process defaults, close market). They cannot seize user funds arbitrarily; they can only execute lifecycle transitions and default resolution constrained by contract rules.

  • Users: place and fill offers (as maker or taker), deposit collateral via EscrowVaultV3, and later either settle trades (sellers) or claim TGE tokens (buyers) with a Merkle proof.

Asset Model

  • Collateral token: USDC (6 decimals). The protocol uses strict, real USDC custody in EscrowVaultV3.

  • TGE token: arbitrary ERC-20 per market, set when starting Settlement. Sellers deposit the required TGE to the vault through SettlementEngineV3.

Market Lifecycle

  1. Trading

    • Markets start in Trading. Users can create SellPoints or BuyPoints offers.

    • Creating or filling offers locks USDC collateral equal to trade value.

  2. WindingDown

    • Market Manager initiates winding down; new offers and fills are blocked on-chain (phase checks).

    • OrderBookV3.batchCleanupOffersForSettlement is used to:

      • Cancel 100% unfilled offers, returning maker collateral.

      • Trim partially filled offers to filled portion only and return unused collateral.

  3. Settlement

    • Preconditions: no active open offers (activeOpenOffersCount[marketId] == 0).

    • Market Manager sets: TGE token, conversionRate (points → TGE), settlementWindow duration, and decimals are read from TGE.

    • Sellers must deposit TGE equal to their filled points × conversion rate via SettlementEngineV3.depositTGE.

    • Sellers settle each trade they participated in using SettlementEngineV3.settleMyTrades (batch of trade ids). On settlement:

      • Buyer’s USDC payment is deducted (Settlement fee applied) and sent to seller.

      • Seller’s own collateral for that fill is unlocked to the seller.

      • Offer’s pointsSettled increments; when all fills are settled, the offer becomes Settled.

    • Buyers claim TGE tokens using a Merkle proof via claimTGEPartial:

      • Market Manager sets a Merkle root per market.

      • On claim, BuyToken fee is taken in TGE, and net TGE is transferred to buyer.

  4. Closure

    • After the settlement window ends, Manager can close the market. Market becomes inactive.

Trading Scenarios

  • SellPoints (maker sells points):

    • Maker creates offer with pointsAmount and pricePerPoint. Collateral locked = points × price.

    • Taker (buyer) fills partially or fully, locking their USDC equal to filled value.

    • In Settlement phase, seller deposits TGE and settles each fill. Buyer receives TGE (after BuyToken fee) via claim.

  • BuyPoints (maker buys points):

    • Maker creates a buy offer, locking USDC upfront.

    • Taker (seller) fills, locking their own collateral for the fill.

    • In Settlement, the maker’s locked USDC pays the seller, and seller’s collateral is unlocked upon settlement.

  • Market Orders (batch fill):

    • A buyer can fill many offers at once within the same market via fillMultipleOffers, locking total USDC for all fills.

Default Handling

If a seller fails to settle before the settlement window ends, SettlementEngineV3.processDefaults can be run for outstanding trades:

  • Computes each seller’s proportional guarantee (their locked collateral attributable to the fill).

  • Applies DefaultOrder fee to the seller’s guarantee, then transfers the net compensation directly from the seller’s collateral to the buyer’s wallet via the vault.

  • Refunds the buyer’s payment for that fill.

  • Marks the trade as settled/defaulted to prevent double handling.

Fees

All fees are parameterized and enforced by FeeManagerV3 in basis points with min caps. The vault is the execution point for fee transfers.

  • CancelOffer (0.5%): Applied on remaining collateral when a maker cancels an active offer.

  • Settlement (2.5%): Applied on the buyer payment routed to the seller at settlement.

  • BuyToken (2.0%): Applied on TGE amount when the buyer claims TGE with a Merkle proof.

  • DefaultOrder (0.5%): Applied on the seller guarantee when a trade is defaulted after the settlement window.

Fees are forwarded to FeeTreasuryV3 if set in the vault; otherwise to the FeeManagerV3.feeCollector.

Security Considerations

  • Reentrancy protection on all fund-moving entry points (vault, order book, settlement engine).

  • Role-gated lifecycle operations (Admin/Market Manager).

  • Phase checks concentrated in MarketRegistryV3 and referenced by other modules.

  • Constant-time guard to ensure no active offers remain before moving to Settlement.

  • Permit and Permit2 support for safe/efficient collateral locking without prior approvals.

  • Merkle proofs validate buyer eligibility and amounts for TGE claims.

Events and Observability

  • Order lifecycle: OfferCreated, FillCreated, OfferCompleted, OfferCancelled, OfferSettled, batch market-order events.

  • Market lifecycle: MarketCreated, MarketWindDownStarted, SettlementPhaseStarted, MarketClosed.

  • Settlement/TGE: TradeSettled, TGEClaimed, MerkleRootSet, SettlementDefaulted.

  • Vault: collateral/TGE deposit/transfer events; payout settlement events.

  • Fees: FeeCollected, FeesWithdrawn, treasury events.

Off-chain Integration Notes

  • Indexer should model offers, fills, and their states; compute remaining points; and track per-market phases.

  • Trade IDs are ABI-encoded (offerId, fillId) and hashed on-chain for uniqueness in settlement.

  • For claims UX, frontends should display fee previews using FeeManagerV3.calculateFee helpers via exposed preview functions in SettlementEngineV3 and OrderBookV3.

Out-of-Scope / Separate

  • OTXPresale is a separate presale mechanism using USDT0 and Merkle allowlists, with immediate forwarding to a treasury. It does not interact with the trading lifecycle above.

Glossary

  • Points: off-chain program units being traded pre-TGE.

  • TGE: Token Generation Event token distributed at Settlement.

  • Guarantee/Collateral: USDC locked to secure performance and payment.

  • Conversion Rate: multiplier from points to TGE token units.

Deployed contracts

  • Network: HyperEVM Testnet (chainId 998)

  • Explorer: https://testnet.purrsec.com/

  • Addresses (from scripts/verify-addresses.ts):

    • AccessControlV3: 0x9845f98084C4329b6b25815E0286EAf13e86F707

    • FeeManagerV3: 0x5f9a1dE9e8D87F5D9779aD5f9CB89188c6ae33fC

    • EscrowVaultV3: 0x5aFc8db148c0Ed8Ba9a3E1A029308549a285df95

    • MarketRegistryV3: 0xAb0246509a217926C6fB05928B4A04F300947993

    • OrderBookV3: 0xC1151978495e8C43B109209b91B15d2eD4666000

    • SettlementEngineV3: 0x0cE6DBDDD9E8C78e8f85ED9a8DD1Cee34EDE42cf

On-chain data structures (key fields)

  • Market (MarketRegistryV3.Market): name, pointsIdentifier, currentPhase, settlementWindow, settlementStartTime, tgeToken, conversionRate, tgeDecimals, isActive, createdAt.

  • Offer (OrderBookV3.Offer): marketId, maker, offerType, pointsAmount, pricePerPoint, collateralLocked, pointsFilled, pointsSettled, status, createdAt, fillCount.

  • Fill (OrderBookV3.Fill): offerId, taker, pointsAmount, collateralAmount, createdAt.

  • MarketOrder (batch fill): taker, marketId, totalPointsBought, totalCollateralSpent, arrays offerIds, amounts.

Settlement and fee math (from contracts)

  • Collateral to lock: pointsAmount × pricePerPoint (USDC, 6 decimals).

  • TGE required (seller): fill.pointsAmount × market.conversionRate (TGE token units).

  • Settlement fee base: buyer’s payment amount transferred to seller per fill.

  • BuyToken fee base: buyer’s TGE claimAmount during Merkle claim.

  • Default fee base: seller’s proportional guarantee when defaulting a fill.

  • Fee caps: ≤ 700 bps per type; min amounts enforced; optional max.

End‑to‑end walkthrough (numerical example)

Scenario: Maker sells 1,000 points at 0.50 USDC/point. Conversion rate is 3 TGE per point. Buyer fully fills the offer.

  • Offer create (SellPoints):

    • Maker collateral locked: 1,000 × 0.50 = 500 USDC

    • Offer Open

  • Fill by buyer (full):

    • Buyer collateral locked: 1,000 × 0.50 = 500 USDC

    • Offer Filled

  • Settlement phase starts (Manager sets TGE + conversion + window):

    • Seller deposits TGE required: 1,000 × 3 = 3,000 TGE

  • Seller settles trade:

    • Settlement fee on buyer payment (example 2.5%): 500 × 2.5% = 12.5 USDC

    • Net payment to seller from buyer’s collateral: 487.5 USDC

    • Seller’s own collateral unlocked: 500 USDC

    • Vault nets the transfer to seller in one go: 987.5 USDC total to seller’s wallet

    • Offer becomes Settled

  • Buyer claims TGE using Merkle proof:

    • BuyToken fee (example 2.0%): 3,000 × 2% = 60 TGE to treasury/collector

    • Net TGE to buyer: 2,940 TGE

  • If seller never settles before window ends:

    • Default fee on seller’s guarantee (0.5% on 500 USDC): 2.5 USDC to treasury

    • Buyer’s 500 USDC is unlocked/refunded to buyer’s wallet

    • Net compensation from seller’s collateral to buyer: 497.5 USDC

Merkle claims (SettlementEngineV3)

  • Trade encoding for settlement: bytes trade = abi.encode(offerId, fillId); tradeHash = keccak256(trade).

  • Leaf: keccak256(abi.encodePacked(buyer, tradeHash, totalEligibleAmount)).

  • Guards: trade must be marked settled by seller; valid proof; cumulative claimed ≤ total eligible.

  • Transfers: BuyToken fee to treasury/collector; net TGE to buyer, both via EscrowVaultV3.

Module authorization and fee routing

  • Only authorized modules can call escrow transfers/collections.

  • Fees route to EscrowVaultV3.feeTreasury() if set; otherwise to FeeManagerV3.feeCollector().

Invariants and phase guards

  • Offer create/fill requires isInTradingPhase(marketId).

  • Start settlement requires: WindingDown, active, and OrderBookV3.activeOpenOffersCount(marketId) == 0.

  • Close requires: block.timestamp >= settlementStartTime + settlementWindow and phase Settlement.

  • Settlement engine checks market in Settlement and that tgeDecimals > 0; prevents double-settlement via settledTrades.

Events reference (selected)

  • Markets: MarketCreated, MarketWindDownStarted, SettlementPhaseStarted, MarketClosed

  • Order book: OfferCreated, FillCreated, OfferCompleted, OfferCancelled, OfferPartiallyCleanedForSettlement, OfferSettled, MultipleFillsExecuted, MarketOrderCreated

  • Settlement: BatchTradesSettled, TradeSettled, SettlementDefaulted, MerkleRootSet, TGEFundsDeposited, TGEClaimed

  • Escrow: CollateralLocked, CollateralUnlocked, CompensationTransferred, PayoutSettled, TGETokenDeposited, TGETokenTransferred, ModuleAuthorized, FeeTreasurySet

  • Fees: FeeConfigUpdated, FeeCollected, FeesWithdrawn, FeeCollectorUpdated

Architecture diagrams

Appendix: key formulas

  • Maker collateral at create: offer.pointsAmount × offer.pricePerPoint

  • Taker collateral at fill: pointsAmount × offer.pricePerPoint

  • Proportional guarantee used in settlement/defaults:

    • SellPoints: (fill.pointsAmount × offer.collateralLocked) / offer.pointsAmount

    • BuyPoints: fill.collateralAmount

  • Buyer payment to seller for BuyPoints fill: (fill.pointsAmount × offer.collateralLocked) / offer.pointsAmount

  • Settlement fee: (paymentAmount × rateBps) / 10000

  • BuyToken fee: (claimAmount × rateBps) / 10000

  • Default fee: (proportionalGuarantee × rateBps) / 10000