FairWins P2P Wager System - Flow Diagrams V2 (With Fixes)
Overview
This document contains the updated system flow diagrams incorporating all recommended fixes:
1. ✅ claimWinnings() function
2. ✅ Challenge period for disputes
3. ✅ Claim timeout with treasury fallback
4. ✅ Oracle timeout fallback
5. ✅ Arbitrator fee mechanism
Updated Entity Relationship Diagram
erDiagram
USER ||--o{ WAGER : creates
USER ||--o{ WAGER : accepts
USER ||--o{ WAGER : arbitrates
USER ||--o{ ACCEPTANCE_RECORD : has
WAGER ||--|| ACCEPTANCE_RECORD : tracks
WAGER ||--o| PENDING_RESOLUTION : has
WAGER ||--o| ORACLE_ADAPTER : resolves_via
WAGER }|--|| STAKE_TOKEN : uses
ORACLE_REGISTRY ||--o{ ORACLE_ADAPTER : contains
ORACLE_ADAPTER ||--o{ EXTERNAL_CONDITION : queries
TREASURY ||--o{ WAGER : receives_unclaimed
USER {
address wallet PK
uint256 totalWagered
uint256 totalWon
uint256 totalLost
uint256 winCount
uint256 lossCount
uint256 arbitratorFeesEarned
}
WAGER {
uint256 wagerId PK
address creator FK
address opponent FK
address arbitrator FK
uint256 creatorStake
uint256 opponentStake
address stakeToken FK
string description
bytes32 oracleId FK
bytes32 externalCondition
enum status
enum resolutionType
uint256 acceptanceDeadline
uint256 expectedResolutionTime
uint256 createdAt
uint256 resolvedAt
bool outcome
address winner
bool claimed
uint256 arbitratorFeeBps
}
PENDING_RESOLUTION {
uint256 wagerId FK
bool proposedOutcome
address proposer
uint256 proposedAt
uint256 challengeDeadline
bool challenged
address challenger
uint256 challengeBond
}
ACCEPTANCE_RECORD {
uint256 wagerId FK
address participant FK
uint256 stakedAmount
uint256 acceptedAt
bool hasAccepted
bool isArbitrator
}
ORACLE_ADAPTER {
bytes32 oracleId PK
string name
string oracleType
address adapterAddress
bool isVerified
}
EXTERNAL_CONDITION {
bytes32 conditionId PK
bytes32 oracleId FK
bool resolved
uint256 passNumerator
uint256 failNumerator
uint256 resolvedAt
}
STAKE_TOKEN {
address tokenAddress PK
string symbol
uint8 decimals
bool accepted
}
TREASURY {
address treasuryAddress PK
uint256 totalSwept
}
Updated State Machine (Complete)
stateDiagram-v2
[*] --> Pending : createWager()
state Pending {
[*] --> AwaitingAcceptance
AwaitingAcceptance --> AwaitingAcceptance : acceptWager() [threshold not met]
}
Pending --> Active : acceptWager() [threshold met]
Pending --> Cancelled : cancelWager() [by creator]
Pending --> Refunded : processExpiredDeadline() [threshold not met]
state Active {
[*] --> AwaitingResolution
AwaitingResolution --> AwaitingResolution : [monitoring oracle]
}
Active --> PendingResolution : proposeResolution() [manual types]
Active --> Resolved : resolveFromOracle() [external oracle resolved]
Active --> OracleTimedOut : triggerOracleTimeout() [30+ days past expected]
state PendingResolution {
[*] --> ChallengeWindow
ChallengeWindow --> ChallengeWindow : [24h countdown]
}
PendingResolution --> Challenged : challengeResolution() [within 24h + bond]
PendingResolution --> Resolved : finalizeResolution() [24h passed, no challenge]
state Challenged {
[*] --> AwaitingArbitration
AwaitingArbitration --> AwaitingArbitration : [arbitrator reviewing]
}
Challenged --> Resolved : resolveDispute() [arbitrator decides]
state OracleTimedOut {
[*] --> AwaitingAction
AwaitingAction --> MutualRefundPending : acceptMutualRefund() [one party]
MutualRefundPending --> MutualRefundPending : [waiting for other party]
}
OracleTimedOut --> Refunded : acceptMutualRefund() [both parties agree]
OracleTimedOut --> Resolved : forceManualResolution() [arbitrator]
state Resolved {
[*] --> AwaitingClaim
AwaitingClaim --> AwaitingClaim : [90 day countdown]
}
Resolved --> Claimed : claimWinnings() [winner claims]
Resolved --> Swept : sweepUnclaimedFunds() [90+ days, to treasury]
Claimed --> [*]
Swept --> [*]
Cancelled --> [*]
Refunded --> [*]
note right of PendingResolution
24-hour challenge window
Either party can challenge
with bond deposit
end note
note right of OracleTimedOut
30 days past expected
resolution time
end note
note right of Resolved
90-day claim window
After that, treasury sweeps
end note
User Flows (Updated)
Flow 1: Create Wager (Complete)
flowchart TD
Start([User Opens App]) --> Dashboard[View Dashboard]
Dashboard --> CreateBtn[Click "Create Wager"]
CreateBtn --> DescForm[Enter Wager Description]
DescForm --> ResChoice{Choose Resolution<br/>Method}
ResChoice -->|Find Existing| SearchMarket[Search External Markets]
ResChoice -->|Price Oracle| SelectChainlink[Select Chainlink Feed]
ResChoice -->|Manual| SelectResolver[Choose Who Resolves]
ResChoice -->|Arbitrator| EnterArb[Enter Arbitrator Address]
SearchMarket --> SelectCondition[Select Market/Condition]
SelectCondition --> SetExpectedTime[Set Expected Resolution Time]
SetExpectedTime --> Stakes
SelectChainlink --> ConfigThreshold[Set Price Threshold & Date]
ConfigThreshold --> Stakes
SelectResolver --> Stakes[Set Stake Amount]
EnterArb --> SetArbFee[Set Arbitrator Fee %]
SetArbFee --> Stakes
Stakes --> Token[Select Payment Token]
Token --> Odds{Equal Stakes?}
Odds -->|Yes| Equal[1:1 Odds]
Odds -->|No| Custom[Set Custom Odds]
Equal --> Opponent
Custom --> Opponent
Opponent[Enter Opponent Address] --> Deadline[Set Acceptance Deadline]
Deadline --> Review[Review All Terms]
Review --> Confirm{Confirm?}
Confirm -->|No| Edit[Edit Terms]
Edit --> DescForm
Confirm -->|Yes| ApproveToken{ERC20?}
ApproveToken -->|Yes| Approve[Approve Token Spend]
ApproveToken -->|No| Submit
Approve --> Submit[Submit Transaction]
Submit --> TxStatus{Status?}
TxStatus -->|Failed| Error[Show Error]
Error --> Review
TxStatus -->|Success| Created[Wager Created!]
Created --> SharePrompt{Share?}
SharePrompt -->|Twitter| Twitter[Open Twitter]
SharePrompt -->|Discord| Discord[Copy Embed]
SharePrompt -->|Telegram| Telegram[Open Telegram]
SharePrompt -->|Link| CopyLink[Copy Link]
SharePrompt -->|Skip| Done
Twitter --> Done[Return to Dashboard]
Discord --> Done
Telegram --> Done
CopyLink --> Done
Flow 2: Accept Wager (Complete)
flowchart TD
Start([Receive Invitation]) --> Source{Source?}
Source -->|Social Link| ClickLink[Click Link]
Source -->|App Notification| OpenApp[Open App]
Source -->|QR Code| ScanQR[Scan QR]
ClickLink --> DeepLink[Deep Link Handler]
ScanQR --> DeepLink
OpenApp --> InviteList[View Pending Invitations]
InviteList --> SelectInvite[Select Wager]
DeepLink --> ViewDetails[View Wager Details]
SelectInvite --> ViewDetails
ViewDetails --> CheckDeadline{Deadline<br/>Passed?}
CheckDeadline -->|Yes| Expired[Show Expired]
Expired --> End([End])
CheckDeadline -->|No| CheckMembership{Has<br/>Membership?}
CheckMembership -->|No| Purchase[Purchase Tier]
Purchase --> CheckMembership
CheckMembership -->|Yes| CheckFunds{Sufficient<br/>Funds?}
CheckFunds -->|No| AddFunds[Add Funds]
AddFunds --> CheckFunds
CheckFunds -->|Yes| ReviewTerms[Review Terms]
ReviewTerms --> Decision{Decision?}
Decision -->|Decline| Decline[Decline]
Decline --> End
Decision -->|Counter| Counter[Create Counter-offer]
Counter --> End
Decision -->|Accept| AcceptFlow[Begin Accept Flow]
AcceptFlow --> ApproveToken{ERC20?}
ApproveToken -->|Yes| Approve[Approve Spend]
ApproveToken -->|No| SignAccept
Approve --> SignAccept[Sign Accept Tx]
SignAccept --> TxStatus{Status?}
TxStatus -->|Failed| TxError[Show Error]
TxError --> ReviewTerms
TxStatus -->|Success| CheckThreshold{Threshold<br/>Met?}
CheckThreshold -->|No| ShowPending[Show Pending Status]
ShowPending --> End
CheckThreshold -->|Yes| Activated[Wager Activated!]
Activated --> ViewActive[View in Active Wagers]
ViewActive --> End
Flow 3: Manual Resolution (With Challenge Period)
flowchart TD
Start([Wager Active]) --> CheckType{Resolution<br/>Type?}
CheckType -->|External Oracle| OracleFlow[Oracle Resolution Flow]
CheckType -->|Manual| ManualFlow[Manual Resolution Flow]
subgraph ManualResolution[Manual Resolution Flow]
ManualFlow --> CheckAuth{Authorized<br/>Resolver?}
CheckAuth -->|No| Reject[Revert: NotAuthorized]
CheckAuth -->|Yes| ProposeOutcome[Propose Outcome]
ProposeOutcome --> SetDeadline[Set 24h Challenge Deadline]
SetDeadline --> EmitProposed[Emit ResolutionProposed]
EmitProposed --> WaitPeriod[Wait Challenge Period]
WaitPeriod --> Challenged{Challenged?}
Challenged -->|Yes| RecordChallenge[Record Challenge + Bond]
RecordChallenge --> NotifyArb[Notify Arbitrator]
NotifyArb --> ArbReview[Arbitrator Reviews]
ArbReview --> ArbDecision[Arbitrator Decides]
ArbDecision --> DistributeBonds[Distribute Bonds]
DistributeBonds --> SetResolved
Challenged -->|No, 24h passed| Finalize[Finalize Resolution]
Finalize --> SetResolved[Set Status = Resolved]
end
subgraph OracleResolution[Oracle Resolution Flow]
OracleFlow --> CheckOracleResolved{Oracle<br/>Resolved?}
CheckOracleResolved -->|No| CheckTimeout{30+ Days<br/>Past Expected?}
CheckTimeout -->|No| WaitOracle[Wait for Oracle]
WaitOracle --> CheckOracleResolved
CheckTimeout -->|Yes| TriggerTimeout[Trigger Oracle Timeout]
TriggerTimeout --> TimeoutFlow[Oracle Timeout Flow]
CheckOracleResolved -->|Yes| FetchOutcome[Fetch Outcome]
FetchOutcome --> SetResolved
end
SetResolved --> EnableClaim[Enable Claim]
EnableClaim --> End([Wager Resolved])
Flow 4: Oracle Timeout Handling
flowchart TD
Start([Oracle Timed Out]) --> Status[Status = OracleTimedOut]
Status --> Options{Resolution<br/>Path?}
Options -->|Mutual Refund| RefundPath[Mutual Refund Path]
Options -->|Arbitrator| ArbPath[Arbitrator Path]
subgraph MutualRefund[Mutual Refund Path]
RefundPath --> Party1[First Party Accepts]
Party1 --> RecordAccept1[Record Acceptance]
RecordAccept1 --> WaitParty2[Wait for Other Party]
WaitParty2 --> Party2{Other Party<br/>Accepts?}
Party2 -->|No| Stalemate[Stalemate - Need Arbitrator]
Stalemate --> ArbPath
Party2 -->|Yes| BothAccepted[Both Accepted]
BothAccepted --> RefundBoth[Refund Both Stakes]
RefundBoth --> SetRefunded[Status = Refunded]
end
subgraph ArbitratorResolution[Arbitrator Resolution]
ArbPath --> CheckArb{Has<br/>Arbitrator?}
CheckArb -->|No| NeedArb[Stuck - Contact Support]
CheckArb -->|Yes| ArbForce[Arbitrator Forces Resolution]
ArbForce --> SetOutcome[Set Outcome]
SetOutcome --> SetResolved[Status = Resolved]
end
SetRefunded --> End([Complete])
SetResolved --> End
NeedArb --> End
Flow 5: Claim Winnings (With Timeout)
flowchart TD
Start([Wager Resolved]) --> CheckWinner{Is User<br/>Winner?}
CheckWinner -->|No| NoAction[No Claim Available]
NoAction --> End([End])
CheckWinner -->|Yes| CheckClaimed{Already<br/>Claimed?}
CheckClaimed -->|Yes| AlreadyClaimed[Already Claimed]
AlreadyClaimed --> End
CheckClaimed -->|No| CheckTimeout{Within 90<br/>Days?}
CheckTimeout -->|No| Expired[Claim Period Expired]
Expired --> SweepAvailable[Sweep Available for Treasury]
SweepAvailable --> End
CheckTimeout -->|Yes| ShowClaim[Show Claim Button]
ShowClaim --> ClickClaim[Click Claim]
ClickClaim --> SignTx[Sign Transaction]
SignTx --> TxStatus{Status?}
TxStatus -->|Failed| Error[Show Error]
Error --> ShowClaim
TxStatus -->|Success| CalcPayout[Calculate Payout]
CalcPayout --> CheckArbFee{Arbitrator<br/>Fee?}
CheckArbFee -->|Yes| PayArb[Pay Arbitrator Fee]
PayArb --> PayWinner
CheckArbFee -->|No| PayWinner[Pay Winner]
PayWinner --> MarkClaimed[Mark as Claimed]
MarkClaimed --> EmitEvent[Emit WinningsClaimed]
EmitEvent --> ShowSuccess[Show Success]
ShowSuccess --> End
Flow 6: Treasury Sweep (Unclaimed Funds)
flowchart TD
Start([Check Resolved Wagers]) --> FindUnclaimed[Find Unclaimed > 90 Days]
FindUnclaimed --> HasUnclaimed{Any<br/>Unclaimed?}
HasUnclaimed -->|No| Done[Nothing to Sweep]
Done --> End([End])
HasUnclaimed -->|Yes| ForEach[For Each Unclaimed Wager]
ForEach --> CheckTime{resolvedAt +<br/>90 days < now?}
CheckTime -->|No| Skip[Skip - Not Yet]
Skip --> ForEach
CheckTime -->|Yes| CalcAmount[Calculate Total Stakes]
CalcAmount --> TransferTreasury[Transfer to Treasury]
TransferTreasury --> MarkSwept[Mark as Swept]
MarkSwept --> EmitEvent[Emit UnclaimedFundsSwept]
EmitEvent --> ForEach
ForEach --> Complete[All Processed]
Complete --> End
Sequence Diagrams (Updated)
Sequence 1: Complete Wager Lifecycle (With All Fixes)
sequenceDiagram
autonumber
participant Creator
participant UI as Frontend
participant Factory as P2PWagerFactory
participant Token as StakeToken
participant Oracle as OracleRegistry
participant Opponent
participant Arbitrator
participant Treasury
participant Indexer as Subgraph
Note over Creator, Indexer: PHASE 1: Creation
Creator->>UI: Create new wager
UI->>UI: Validate inputs
Creator->>Factory: createWager(opponent, stake, oracleId, arbFee, ...)
alt ERC20 Stake
Creator->>Token: approve(factory, amount)
Factory->>Token: transferFrom(creator, factory, amount)
else Native Token
Creator->>Factory: Send ETH with transaction
end
Factory->>Factory: Store wager, status=Pending
Factory-->>Creator: Emit WagerCreated
Indexer->>Factory: Index event
Note over Creator, Indexer: PHASE 2: Acceptance
Creator->>UI: Share to Twitter
UI->>UI: Generate share intent
Opponent->>UI: Click shared link
UI->>Factory: getWagerDetails(wagerId)
Factory-->>UI: Return details
Opponent->>Factory: acceptWager(wagerId) + stake
Factory->>Factory: Check threshold
Factory->>Factory: status = Active
Factory-->>Opponent: Emit WagerActivated
Indexer->>Factory: Index event
Note over Creator, Indexer: PHASE 3a: Oracle Resolution Path
rect rgb(200, 230, 200)
Note right of Oracle: Oracle resolves externally
Creator->>Factory: resolveFromOracle(wagerId)
Factory->>Oracle: getOutcome(oracleId, conditionId)
Oracle-->>Factory: (outcome, confidence)
Factory->>Factory: status = Resolved, winner set
Factory-->>Creator: Emit WagerResolved
end
Note over Creator, Indexer: PHASE 3b: Manual Resolution Path
rect rgb(230, 230, 200)
Creator->>Factory: proposeResolution(wagerId, true)
Factory->>Factory: status = PendingResolution
Factory->>Factory: challengeDeadline = now + 24h
Factory-->>Creator: Emit ResolutionProposed
alt Opponent Challenges
Opponent->>Factory: challengeResolution(wagerId) + bond
Factory->>Factory: status = Challenged
Factory-->>Opponent: Emit ResolutionChallenged
Arbitrator->>Factory: resolveDispute(wagerId, outcome)
Factory->>Factory: Distribute bonds
Factory->>Factory: status = Resolved
Factory-->>Arbitrator: Emit DisputeResolved
else No Challenge
Note over Factory: 24 hours pass
Creator->>Factory: finalizeResolution(wagerId)
Factory->>Factory: status = Resolved
Factory-->>Creator: Emit ResolutionFinalized
end
end
Note over Creator, Indexer: PHASE 4: Claim
Creator->>Factory: claimWinnings(wagerId)
Factory->>Factory: Verify winner, not claimed, within 90 days
alt Has Arbitrator Fee
Factory->>Factory: Calculate fee
Factory->>Arbitrator: Transfer arbitrator fee
Factory-->>Factory: Emit ArbitratorPaid
end
Factory->>Creator: Transfer winnings
Factory->>Factory: Mark claimed
Factory-->>Creator: Emit WinningsClaimed
Indexer->>Factory: Index event
Note over Creator, Indexer: PHASE 5: Timeout Paths
rect rgb(255, 230, 230)
Note over Factory: If winner doesn't claim within 90 days
Treasury->>Factory: sweepUnclaimedFunds(wagerId)
Factory->>Factory: Verify 90 days passed
Factory->>Treasury: Transfer unclaimed funds
Factory-->>Treasury: Emit UnclaimedFundsSwept
end
Sequence 2: Oracle Timeout Flow
sequenceDiagram
autonumber
participant Anyone
participant Factory as P2PWagerFactory
participant Creator
participant Opponent
participant Arbitrator
participant Timer as Block.timestamp
Note over Anyone, Timer: Oracle Never Resolves
Anyone->>Factory: Check wager status
Factory-->>Anyone: Active, expectedResolution passed
Anyone->>Factory: triggerOracleTimeout(wagerId)
Factory->>Factory: Verify expectedResolution + 30 days < now
Factory->>Factory: status = OracleTimedOut
Factory-->>Anyone: Emit OracleTimeoutTriggered
alt Mutual Refund Path
Creator->>Factory: acceptMutualRefund(wagerId)
Factory->>Factory: Record creator acceptance
Factory-->>Creator: Emit MutualRefundAccepted(creator)
Opponent->>Factory: acceptMutualRefund(wagerId)
Factory->>Factory: Both parties accepted
Factory->>Factory: status = Refunded
Factory->>Creator: Return creator stake
Factory->>Opponent: Return opponent stake
Factory-->>Opponent: Emit MutualRefundCompleted
else Arbitrator Forces Resolution
Note over Arbitrator: After reasonable waiting period
Arbitrator->>Factory: forceManualResolution(wagerId, outcome)
Factory->>Factory: Verify arbitrator authorized
Factory->>Factory: Set winner based on outcome
Factory->>Factory: status = Resolved
Factory-->>Arbitrator: Emit ForcedResolution
Note over Creator, Opponent: Normal claim flow follows
end
Sequence 3: Challenge and Dispute Flow
sequenceDiagram
autonumber
participant Proposer
participant Factory as P2PWagerFactory
participant Challenger
participant Arbitrator
participant Timer as Block.timestamp
Note over Proposer, Timer: Resolution Proposed
Proposer->>Factory: proposeResolution(wagerId, true)
Factory->>Factory: Verify proposer authorized
Factory->>Factory: status = PendingResolution
Factory->>Factory: challengeDeadline = now + 24h
Factory->>Factory: Store proposed outcome
Factory-->>Proposer: Emit ResolutionProposed
Note over Challenger: Within 24 hours
Challenger->>Factory: challengeResolution(wagerId) + 0.1 ETH bond
Factory->>Factory: Verify within challenge window
Factory->>Factory: Verify challenger is other party
Factory->>Factory: Store challenger bond
Factory->>Factory: status = Challenged
Factory-->>Challenger: Emit ResolutionChallenged
Note over Arbitrator: Reviews evidence
Arbitrator->>Factory: resolveDispute(wagerId, finalOutcome)
Factory->>Factory: Verify arbitrator authorized
alt Proposer was correct
Factory->>Proposer: Return proposer's implicit bond
Factory->>Proposer: Award challenger's bond
Factory-->>Factory: Challenger loses bond
else Challenger was correct
Factory->>Challenger: Return challenger's bond
Factory->>Challenger: Award proposer's implicit bond
Factory-->>Factory: Proposer loses bond
end
Factory->>Factory: Set winner based on finalOutcome
Factory->>Factory: status = Resolved
Factory-->>Arbitrator: Emit DisputeResolved
Error Handling (Complete)
All Error States and Recovery
flowchart TD
subgraph Creation[Creation Errors]
C1[InvalidOpponent] --> C1R[Fix: Valid non-self address]
C2[InvalidDescription] --> C2R[Fix: Add description]
C3[InvalidDeadline] --> C3R[Fix: 1h-30d range]
C4[InvalidStake] --> C4R[Fix: Non-zero stake]
C5[InvalidOdds] --> C5R[Fix: Multiplier >= 200]
C6[InvalidArbitratorFee] --> C6R[Fix: <= 10%]
C7[MembershipRequired] --> C7R[Fix: Purchase tier]
C8[InsufficientFunds] --> C8R[Fix: Add funds]
end
subgraph Acceptance[Acceptance Errors]
A1[InvalidMarketId] --> A1R[Fix: Check wager exists]
A2[DeadlinePassed] --> A2R[Info: Wager expired]
A3[AlreadyAccepted] --> A3R[Info: Already accepted]
A4[NotInvited] --> A4R[Info: Not participant]
A5[InsufficientStake] --> A5R[Fix: Send correct amount]
end
subgraph Resolution[Resolution Errors]
R1[NotActive] --> R1R[Info: Wrong status]
R2[NotAuthorized] --> R2R[Fix: Use authorized account]
R3[OracleNotResolved] --> R3R[Wait: Oracle pending]
R4[ChallengeWindowActive] --> R4R[Wait: 24h not passed]
R5[AlreadyChallenged] --> R5R[Info: Already challenged]
R6[InsufficientBond] --> R6R[Fix: Send 0.1 ETH]
end
subgraph Claim[Claim Errors]
CL1[NotResolved] --> CL1R[Wait: Resolution pending]
CL2[NotWinner] --> CL2R[Info: Not winner]
CL3[AlreadyClaimed] --> CL3R[Info: Already claimed]
CL4[ClaimPeriodExpired] --> CL4R[Info: Funds swept]
end
subgraph Timeout[Timeout Errors]
T1[NotTimedOut] --> T1R[Wait: 30 days not passed]
T2[AlreadyResolved] --> T2R[Info: Already resolved]
T3[RefundNotAccepted] --> T3R[Wait: Other party]
T4[NoArbitrator] --> T4R[Stuck: Contact support]
end
Cross-Reference: Functions to States (Updated)
| Function |
Required State |
New State |
Events |
Bond/Fee |
createWager |
N/A |
Pending |
WagerCreated |
- |
acceptWager |
Pending |
Active |
WagerActivated |
- |
cancelWager |
Pending |
Cancelled |
WagerCancelled |
- |
proposeResolution |
Active |
PendingResolution |
ResolutionProposed |
- |
challengeResolution |
PendingResolution |
Challenged |
ResolutionChallenged |
0.1 ETH bond |
finalizeResolution |
PendingResolution |
Resolved |
ResolutionFinalized |
- |
resolveDispute |
Challenged |
Resolved |
DisputeResolved |
Bond distributed |
resolveFromOracle |
Active |
Resolved |
WagerResolved |
- |
triggerOracleTimeout |
Active |
OracleTimedOut |
OracleTimeoutTriggered |
- |
acceptMutualRefund |
OracleTimedOut |
OracleTimedOut/Refunded |
MutualRefundAccepted/Completed |
- |
forceManualResolution |
OracleTimedOut |
Resolved |
ForcedResolution |
- |
claimWinnings |
Resolved |
Resolved (claimed) |
WinningsClaimed, ArbitratorPaid |
Arb fee deducted |
sweepUnclaimedFunds |
Resolved (90d+) |
Resolved (swept) |
UnclaimedFundsSwept |
- |
Configuration Parameters
| Parameter |
Default |
Range |
Modifiable By |
challengePeriod |
24 hours |
1h - 7 days |
Owner |
challengeBond |
0.1 ETH |
0.01 - 1 ETH |
Owner |
claimTimeout |
90 days |
30 - 365 days |
Owner |
oracleTimeout |
30 days |
7 - 90 days |
Owner |
maxArbitratorFee |
1000 (10%) |
100 - 2000 |
Owner |
treasury |
DAO address |
Any address |
Owner |
Invariants (Must Always Hold)
// 1. Stakes are always accounted for
totalStakes[token] == sum(activeWagers.stakes) + treasury.swept
// 2. Only one outcome possible
wager.resolved => (wager.winner == creator XOR wager.winner == opponent)
// 3. Challenge only in window
wager.challenged => block.timestamp <= wager.challengeDeadline
// 4. Claim only once
wager.claimed => claimWinnings() reverts
// 5. Timeout only after deadline
wager.status == OracleTimedOut =>
block.timestamp > wager.expectedResolutionTime + oracleTimeout
// 6. Sweep only after claim period
swept[wagerId] =>
block.timestamp > wager.resolvedAt + claimTimeout
// 7. Arbitrator fee within bounds
wager.arbitratorFeeBps <= maxArbitratorFee
// 8. Status transitions are valid (see state machine)
validTransition(oldStatus, newStatus)
Summary of Changes from V1
| Issue |
V1 Status |
V2 Status |
| claimWinnings() |
❌ Missing |
✅ Implemented |
| Challenge period |
❌ Missing |
✅ 24h window with bonds |
| Claim timeout |
❌ Missing |
✅ 90 days + treasury sweep |
| Oracle fallback |
❌ Missing |
✅ 30 day timeout + mutual refund |
| Arbitrator fees |
❌ Missing |
✅ Configurable % |
| State machine |
Incomplete |
Complete with all transitions |
| Error handling |
Partial |
Comprehensive |