--- title: V1 to V2 Migration description: What changed between SBET V1 and V2, why, and what happens to V1 positions during the V2 deployment. canonical: https://sbettoken.org/docs/protocol-v2/v1-to-v2-migration version: 2.0.0 updated: 2026-04-05 --- # V1 → V2 Migration SBET V2 is a **ground-up redesign**, not an upgrade. V2 deploys as a fresh set of contracts alongside V1. This document covers: 1. What V1 positions do during the V2 launch (hint: settle under V1 rules). 2. The ship-blocking bugs that triggered the redesign. 3. The 13 locked architectural decisions that shape V2. 4. What V1 concepts map to V2 concepts (and what doesn't). If you're integrating V2 for the first time without V1 baggage, skip to [architecture.md](./architecture.md). If you're migrating an existing V1 integration, this document is where to start. --- ## V1 contracts continue to operate V1 is a diamond-pattern monolith (`SBET.sol` ~920 lines, `SBETTrading`, `SBETClaim`, `SBETNFT`, etc. totaling ~25kLOC). It is **not deprecated** at V2 launch: - **Existing V1 positions settle under V1 rules.** The V1 `finalizeWithGraders`, `claim`, `matchOrders` paths continue to work. - **No forced migration.** Users are not required to exit V1 positions before betting on V2. - **V2 is a fresh-start deployment** — a new set of contracts, new addresses, new match IDs. V2 matches are independent of V1 matches. ### Why fresh-start instead of upgrade 1. **Audit scope**. V2 introduces fundamentally different accounting (parimutuel pools) and a new oracle model (VRF-drawn panel + two-phase finalization). Fitting these into V1's storage layout would bloat the audit surface. 2. **Immutability**. V2 commits `PoolType`, `panelRoot`, `panelQuorumM`, and the trusted forwarder **immutably at construction or registration**. Introducing these into V1 would require storage migration, which increases upgrade risk. 3. **Clean separation**. V1 and V2 use different storage primitives (V1: `int256` signed positions + `mapping` state; V2: explicit `Direction` enum + `MatchInfo` struct). Co-existing deployments make this explicit rather than forcing runtime branching. ### What happens to V1 liquidity over time V1 will be **deprecated for new matches** post-V2 deployment (governance decision; exact timeline pending deployment plan). Existing V1 matches continue to settle normally. No treasury migration is planned — V1 Treasury remains the custodian of V1 stakes until all V1 matches resolve. --- ## Ship-blocking bugs fixed in V2 These bugs were discovered during the V2 redesign cycle. They are marked in V2 source with `[Bug Fix #N]` comments. ### Bug Fix #1 — Split-outcome staking in parimutuel pools **V1 behavior**: a bettor could stake on multiple outcomes of the same match, effectively hedging inside the protocol. This broke parimutuel payoff semantics (the "loser pays winner" invariant). **V2 fix**: `placeBet` reverts with `ExistingPositionDifferentOutcome` if a user tries to add stake on a different outcome than their existing position: ```solidity uint256 existingStake = _stakes[matchId][msg.sender][token]; if (existingStake > 0 && _bettorOutcome[matchId][msg.sender] != outcome) { revert ExistingPositionDifferentOutcome(); } ``` Constraint is **parimutuel-specific**. Signed-position pools explicitly allow dual long+short per user. ### Bug Fix #2 — SBET token reference bootstrapping **V1 behavior**: the SBET token was looked up dynamically through Treasury, creating a circular dependency at init time and a mid-flight address-change risk. **V2 fix**: `SBET_TOKEN` is wired as a **constructor immutable** in both `SBETCoreV2` and `SBETTreasuryV2`. Match-lock bonds use this explicit reference; dispute bonds use the concrete token address passed into `openDispute`. ### Bug Fix #3 — Panel-size source for challenge-window tier **V1 equivalent**: single-grader finalization. The challenge-window concept did not exist in V1 (replaced by a flat `FINALIZATION_DELAY = 1 minutes`). **V2 fix (during redesign)**: an early V2 draft sourced N (panel size) from the `signers` count, which would collapse `N == M` and defeat the dynamic challenge-window tier entirely. Final V2 sources N from `MatchInfo.panelSize` (committed at `registerMatch`): ```solidity // [Bug Fix #3] N sourced from the per-match panel commitment. uint256 n = uint256(m.panelSize); uint256 m_ = uint256(m.quorumM); ``` ### Bug Fix #4 — Per-match cap semantics by pool type **V1 behavior**: no explicit per-match TVL cap. **V2 fix**: `perMatchPayoutCap()` enforced at bet placement, with different semantics per pool type: - **PARIMUTUEL** — cap bounds `totalAccepted` (full pot) - **SIGNED_POSITION** — cap bounds `max(longPool, shortPool)` (exposure, not TVL) ```solidity // Parimutuel uint256 newTotal = m.totalAccepted + amount; if (newTotal > cap) revert PerMatchCapExceeded(amount, cap - m.totalAccepted); // Signed-position uint256 exposure = newLong >= newShort ? newLong : newShort; if (exposure > cap) revert PerMatchCapExceeded(amount, cap); ``` This is necessary because in signed-position pools, long and short offset at settlement — capping the sum would be over-restrictive. --- ## The 13 locked architectural decisions These are the non-negotiable design decisions that shape V2. Every V2 file carries `[Decision #N]` markers pointing back to this list. ### Decision 1: Dynamic tiered challenge window **V1**: flat `FINALIZATION_DELAY = 1 minutes` after grader report. **V2**: challenge window depends on grader consensus quality: | Tier | Duration | Condition | |------|---------:|-----------| | Fast | 90s | unanimous + conf ≥ 95% + source ≥ 2/3 | | QuorumClean | 15m | M-of-N met, zero dissent | | OneDissent | 2h | exactly 1 dissenter | | Contentious | 4h | ≥ 2 dissents OR conf < 60% | **Why**: V1's 1-minute delay was too short for real oversight but too long for obvious outcomes. V2 adapts duration to consensus confidence. ### Decision 2: Two-phase finalization + immutable PoolType **V1**: `finalizeWithGraders` wrote `matchFinalPrice` directly; no separate "proposed" state; no dispute path. **V2**: `Open → Proposed → Finalized|Disputed → Voided|Finalized`. Two immutable pool types (`SIGNED_POSITION`, `PARIMUTUEL`) per match, set once at `registerMatch`. **Why**: The single-phase V1 finalization had no challenge window and no dispute path. V2 separates "grader attested" from "outcome locked." ### Decision 3: $25k SBET grader stake + dual positions **V1**: no grader staking (graders were implicitly trusted oracle signers). **V2**: $25k SBET minimum grader stake, M=9, N=13. Dual long+short positions allowed per user in signed-position pools. **Why**: Collateralized graders. $25k × 9 = $225k = per-match cap (see Decision #4). ### Decision 4: Per-match cap + slash distribution 50/20/20/10 **V1**: no per-match cap, no slashing (no staked graders to slash). **V2**: `ABSOLUTE_CAP = 225_000e18`. Slash split 50% challenger / 20% treasury / 20% stakers / 10% bounty. No burn. **Why**: economic bound on single-match attacker profit; challenger incentive. ### Decision 5: 5-of-9 Safe multisig arbiter **V1**: `ADMIN_ROLE` could finalize matches (centralized emergency path). **V2**: `ARBITER_ROLE` is held by a 5-of-9 Safe multisig. Quorum enforced off-chain inside Safe; Core and DisputeManager trust the role. **Why**: emergency override requires 5 independent signers; Safe handles signature aggregation. ### Decision 6: Three-tier pause + match-lock bond + counter-outcome in openDispute **V1**: single pause flag (`Pausable`). **V2**: match lock (24h, $1k bond) / category pause (72h, 3-of-5) / global pause (14d, 5-of-9, 72h unpause timelock). `openDispute` takes `counterOutcome` as explicit parameter (challenger commits to what they believe is correct). **Why**: granular response to incidents; explicit dispute outcome commitment. ### Decision 7: Bond-escalation dispute + pull-based staker rewards **V1**: no dispute mechanism. **V2**: initial bond 1% TVL clamped [$5k, $50k], 2× per round, max 3 rounds, 24h per round. StakerRewards uses MasterChef-style `accRewardPerShare`. **Why**: self-funding dispute resolution; pull-based rewards scale with staker count. ### Decision 8: Gasless auto-claim via ERC-2771 + immutable forwarder **V1**: no meta-tx claim path. **V2**: Biconomy-compatible forwarder wired as **immutable** via `ERC2771Context`. 2% SBET / 3.5% other. Cap 5%. **Why**: eliminate gas friction for users; immutable forwarder eliminates signer-rotation replay risk. ### Decision 9: VOID refund enum + always principal+fees **V1**: match cancellation used a single "fallback" flow. **V2**: `VoidReason ∈ {NATURAL, PROTOCOL, DISPUTE}`. Void refund always returns principal + fees regardless of reason. **Why**: clear audit trail per cancellation cause; unified refund semantics. ### Decision 10: 6-tier AI grader escalation **V1**: graders submitted pre-signed outcomes off-chain. **V2**: each grade carries `confidenceBps` (0..10_000). 6-tier off-chain escalation ladder: cheap AI → mid AI → expensive AI → multi-source → re-grade → human/dispute. **Why**: confidence feeds back into challenge-window tier; AI cost-controlled. ### Decision 11: VOID refund reason enum Same as Decision 9 but explicitly naming the enum: ```solidity enum VoidReason { NATURAL, PROTOCOL, DISPUTE } ``` ### Decision 12: VRF-selected grader panel **V1**: fixed grader set per match (committed into `matchId` hash). **V2**: VRF-drawn sub-panel of 13 from the active grader pool, quorum 9, committed via Merkle root at `registerMatch`. **Why**: prevent targeted grader-compromise attacks; tolerate 4 panel dropouts. ### Decision 13: Hybrid payout — signed-position (binary) + parimutuel (N-way) **V1**: only signed long/short positions (binary outcomes). **V2**: both `SIGNED_POSITION` (V1-compatible, binary only) and `PARIMUTUEL` (N-outcome, proportional pot). Immutable per match. **Why**: signed-position works well for sportsbook (moneyline, spreads); parimutuel needed for community pools and multi-outcome prediction markets. --- ## Concept mapping: V1 → V2 | V1 concept | V2 equivalent | Notes | |-----------|---------------|-------| | `SBET.sol` (diamond, 920LOC monolith) | `SBETCoreV2` + `SBETTreasuryV2` + `DisputeManager` + `GuardianCouncil` + `SBETStakerRewards` + `GraderRegistryV2` | Split into single-responsibility contracts | | `positions[matchId][user][token]` (int256) | `_positions[matchId][user][direction][token]` (uint256) | Explicit `Direction` enum replaces signed int | | `matchFinalPrice` (uint32) | `MatchInfo.outcome` (uint8, 0..255) + linear mapping to `finalPrice` domain | Same semantics for binary matches; generalized for multi-outcome | | `FINALIZATION_DELAY = 1 minutes` | Dynamic tier (90s / 15m / 2h / 4h) | See [ChallengeWindowLib](./architecture.md#challengewindowlib) | | Grader = trusted signer in `matchId` hash | Panel drawn via VRF, committed via Merkle root | See [VRF panel](./architecture.md#vrf-panel) | | Single-grader `finalizeWithGraders` | Two-phase `proposeOutcome` → `finalize` | See [state machine](./state-machine.md) | | `graderFeePool[matchId][token]` | Not carried forward | V2 fee distribution uses `SBETStakerRewards` | | `matchRecoveryDeadline` / `matchCancelPrice` | `MatchState.Voided` + `claimVoidRefund` | Unified void flow | | `ADMIN_ROLE.finalize()` emergency | `ARBITER_ROLE.overrideOutcome(matchId, newOutcome, evidenceHash)` | 5-of-9 multisig instead of single admin | | `Pausable.pause()` (global only) | `GuardianCouncil` three tiers + `lockMatch` | Per-match / per-category / global | | EIP-712 order matching (`SBETTrading`) | **Not in V2** | V2 is match registry + grader oracle only; order matching lives in `PredictionExchange` (V1-era, retained) | --- ## What's NOT carried forward from V1 The following V1 contracts and subsystems are **not** part of V2's initial deployment. Some continue to live as V1 contracts; others are retired: - **`PredictionMarket` / `PredictionAMM` / `PredictionExchange`** — V1 prediction-market trading stack; retained as-is (orthogonal to V2's match-oracle focus). - **`SBETNFT` / `NFTVault` / `TreasuryNFTManager`** — NFT betting primitives; V1-only for now. - **`IntegratorHub`** — V1 integrator fee splitter; may be rebuilt for V2 post-launch. - **Diamond facet pattern** — V2 uses composed contracts with explicit interfaces, not a diamond. - **`matchId = keccak256(witness, quorum, fee, graders)` baking** — V2 decouples `matchId` from grader identities; grader panel is committed separately via Merkle root. --- ## Migration checklist for V1 integrators If you have an existing V1 integration: 1. **Keep running V1 code for V1 matches.** V2 is an additional surface, not a replacement. 2. **Subscribe to V2 events.** `MatchRegistered`, `BetPlaced`/`SignedPositionOpened`, `OutcomeProposed`, `MatchFinalized`, `MatchVoided` — see [state machine](./state-machine.md#events-emitted-per-transition-reference). 3. **Update your match model.** V2 matches have `poolType` (binary signed-position vs N-outcome parimutuel), `panelRoot`, `panelSize`, `quorumM` — store these alongside match metadata. 4. **Update claim flow.** V1 `claim()` is replaced by `claimParimutuel` / `claimSignedPosition` / `claimVoidRefund` — dispatch on `poolType` + `state`. 5. **Update approval targets.** V1 approved SBET.sol (diamond); V2 approvals go to Treasury (for stakes), DisputeManager (for dispute bonds), GraderRegistryV2 (for grader stakes). 6. **Adopt ERC-2771.** If you run a meta-tx relayer, register with the immutable Biconomy forwarder configured on Treasury. 7. **Watch pauses.** V2 has three pause tiers; display the active tier to users in real time. --- ## Open questions for product/engineering These are genuinely open, documented in V2 source as TODOs: - **Does V1 get EOL'd?** If so, when? What's the communication to V1 holders? - **Does V2 inherit V1's `PredictionMarket` trading ecosystem?** Currently retained; future integration unclear. - **Per-grader key rotation.** Is this wired, or must a compromised grader deregister and re-register with a new key? - **Grader slashing trigger from `arbiterResolve`.** Currently TODO — DisputeManager needs `SLASHER_ROLE` on GraderRegistryV2 and a per-match signer-list view. - **V1 → V2 position port tool.** Is there a user-facing tool to convert V1 long/short positions to V2 equivalents, or do V1 holders simply wait for natural settlement? --- ## Next - [Architecture](./architecture.md) — V2 contract layout - [State machine](./state-machine.md) — how V2 matches lifecycle differs from V1 - [User flows](./user-flows.md) — concrete V2 bet/claim/dispute flows for integrators