Reference
Trading contracts
All Trading contracts compile with Solidity 0.8.35 for Osaka. Revert strings are operational diagnostics, not stable compatibility commitments.
TwoWayConstantProductFactory
constructor(ISecurityPoolFactory, uint256 feeBps)fixes the core factory and fee;feeBps < 10,000.createPair(ISecurityPool)validates the exact canonical pool, deploys with CREATE2, and returns the existing canonical pair on duplicate calls.getPair(ISecurityPool) → pairmaps exact pool identity to a pair.isPair(address) → boolrecognizes router-eligible pairs.predictPair(ISecurityPool) → addresscalculates the CREATE2 address.securityPoolFactory(),feeBps(), andpredeploymentShareSink()expose immutable configuration.
Canonical validation requires the pool's own securityPoolFactory, the factory's (originId, universeId) lookup, and shareToken.canonicalPoolByUniverse(universeId) all to agree.
CREATE2 pair addresses exist counterfactually before code is deployed, when ERC-1155 receiver checks cannot run. Each factory therefore deploys an ownerless PredeploymentShareSink. A pair constructor transfers any canonical Invalid, Yes, or No already present at its future address into that sink before returning. Those quarantined shares are permanently inaccessible; do not send shares to a predicted pair address.
TwoWayConstantProductPair
Immutable accessors expose factory, securityPool, shareToken, universeId, questionId, invalidTokenId, yesTokenId, noTokenId, and feeBps. Constants are MINIMUM_LIQUIDITY (1,000) and MINIMUM_LIQUIDITY_LOCK (address(1)), which holds the locked minimum liquidity. LP ERC-20 accessors are name, symbol, decimals, totalSupply, balanceOf, allowance, approve, transfer, and transferFrom. ERC-2612 accessors are permit(owner, spender, value, deadline, v, r, s), nonces(owner), and DOMAIN_SEPARATOR().
constructor(factory, securityPool, feeBps, predeploymentShareSink)is called only by the factory. It requires nonzero addresses andfeeBps < 10,000, reads the ShareToken, universe, question, and token IDs from the SecurityPool, and quarantines any canonical shares already at its address, emittingPredeploymentSharesQuarantined.getReserves()returns the recorded Yes and No reserves as of the last pair mutation orsync().getEffectiveReserves()returns the pair's current Yes and No balances, including unsynchronized donations, and reverts if either balance is below its recorded reserve or the pair holds Invalid shares. The router'senterPositionreads it for the before-trade Conditional Yes price.tradingStatus()returnsOpen,QuestionEnded,PoolInactive,AwaitingForkContinuation,UniverseForked,QuestionResolved, orUninitialized.initialize(yesAmount, noAmount, minLiquidity, recipient)pulls the first reserves, locksMINIMUM_LIQUIDITY, and mints the remainder once. It requires an uninitialized pair (Already initialized), a nonzero recipient, zero Invalid, Yes, and No balances at the pair (Nonzero initial balance),min(yesAmount, noAmount) > MINIMUM_LIQUIDITY(Initial liquidity too small), and minted liquidity of at leastminLiquidity(Minimum liquidity).addLiquidity(maxYes, maxNo, minLiquidity, recipient)synchronizes donations, selects the largest proportional amounts, pulls them, and mints LP. It reverts withLiquidity rounds to zerowhen either used amount is zero and withMinimum liquiditywhen the minted LP is zero or belowminLiquidity.removeLiquidity(liquidity, minYes, minNo, recipient, deadline)burns caller LP and returns proportional raw shares when called at or beforedeadline. It has no lifecycle-close guard. It reverts withLiquidity slippagebelowminYesorminNoand withLiquidity output is zerowhen either output rounds to zero, so a dust LP amount cannot be removed on its own.swapExactInput(yesForNo, amountIn, minAmountOut, recipient)pulls the gross input and sends the floor-rounded output; it reverts withSwap slippagebelowminAmountOut.swapExactOutput(yesForNo, amountOut, maxAmountIn, recipient)pulls the ceil-rounded required input and sends the exact output; it reverts withSwap slippageabovemaxAmountIn.quoteExactInput(yesForNo, amountIn) → (amountOut, feeAmount)andquoteExactOutput(yesForNo, amountOut) → (amountIn, feeAmount)quote against effective reserves.sync()records current balances as reserves. It reverts when the pair is uninitialized, a balance fell below its reserve, or the pair holds Invalid shares.onERC1155Received,onERC1155BatchReceived, andsupportsInterfaceimplement receiver discovery. Only the canonical ShareToken's exact Yes/No IDs are accepted. Pre-initialization transfers must be initiated by the pair's own pull.
initialize, addLiquidity, and both swaps require an open lifecycle and otherwise revert with Question ended, Universe forked, Fork continuation pending, Pool inactive, or Question resolved; all but initialize also revert with Uninitialized before the first deposit.
tradingStatus() is a concise action blocker, not an authoritative settlement summary. The first matching condition wins in this order: Uninitialized, UniverseForked, AwaitingForkContinuation, PoolInactive, QuestionResolved, QuestionEnded, then Open. An uninitialized forked pair therefore reports Uninitialized, and a finalized question reports QuestionResolved even after its end time. Read SecurityPoolForker.getQuestionOutcome(pool) when the finalized outcome itself is required.
PredeploymentShareSink
Accepts ERC-1155 transfers and has no withdrawal or administrative method. It exists only to preserve clean pair initialization and the zero-Invalid invariant after counterfactual-address poisoning attempts.
TwoWayConstantProductMath
The internal library provides exact-input and exact-output quotes, ceil rounding through the repository's full-precision Math.mulDiv, proportional deposits, initial odds, and conditional price basis points. Production paths never multiply reserve products. Rounding directions are listed in Units and rounding.