# BetEscrow Smart Contract (Pseudocode)

## Overview

The BetEscrow contract manages the entire lifecycle of peer-to-peer bets on the blockchain. It handles bet creation, escrow locking, settlement, and fund distribution in a trustless manner.

## State Variables

```solidity
// Contract state
mapping(uint256 => Bet) public bets;
mapping(address => uint256[]) public userBetIds;
uint256 public nextBetId;
address public oracleContract;
address public owner;

// Escrow tracking
mapping(uint256 => uint256) public escrowBalance;  // betId => total locked amount
```

## Data Structures

```solidity
enum BetStatus {
    OPEN,              // Created, waiting for opponent
    MATCHED,           // Opponent accepted, funds locked
    PENDING_ORACLE,    // Waiting for oracle settlement
    COMPLETED,         // Settled and paid out
    CANCELLED,         // Cancelled before matching
    DISPUTED           // Settlement disputed
}

struct Bet {
    uint256 betId;
    address creator;
    address opponent;
    uint256 stakeAmount;           // Amount each party stakes (in wei)
    BetStatus status;
    uint256 creatorOdds;           // Multiplier for creator (scaled by 100, e.g., 150 = 1.5x)
    uint256 opponentOdds;          // Multiplier for opponent
    uint256 createdAt;             // Block timestamp
    uint256 eventTimestamp;        // When the real-world event occurs
    bytes32 eventId;               // External event identifier for oracle
    address winner;                // Winner address (null until settled)
    uint256 settledAt;             // Settlement timestamp
}
```

## Events

```solidity
event BetCreated(
    uint256 indexed betId,
    address indexed creator,
    uint256 stakeAmount,
    bytes32 eventId,
    uint256 eventTimestamp
);

event BetMatched(
    uint256 indexed betId,
    address indexed opponent,
    uint256 totalEscrow
);

event BetSettled(
    uint256 indexed betId,
    address indexed winner,
    uint256 payoutAmount
);

event BetCancelled(
    uint256 indexed betId,
    address indexed creator
);

event BetDisputed(
    uint256 indexed betId,
    address indexed disputedBy,
    uint256 timestamp
);

event EscrowLocked(
    uint256 indexed betId,
    address indexed user,
    uint256 amount
);

event EscrowReleased(
    uint256 indexed betId,
    address indexed user,
    uint256 amount
);
```

## Modifiers

```solidity
modifier onlyOracle() {
    require(msg.sender == oracleContract, "Only oracle can call this");
    _;
}

modifier onlyOwner() {
    require(msg.sender == owner, "Only owner can call this");
    _;
}

modifier betExists(uint256 betId) {
    require(betId < nextBetId, "Bet does not exist");
    _;
}

modifier onlyParticipant(uint256 betId) {
    Bet storage bet = bets[betId];
    require(
        msg.sender == bet.creator || msg.sender == bet.opponent,
        "Not a participant"
    );
    _;
}
```

## Core Functions

### 1. Create Bet

```solidity
function createBet(
    uint256 stakeAmount,
    uint256 creatorOdds,
    uint256 opponentOdds,
    uint256 eventTimestamp,
    bytes32 eventId
) external returns (uint256) {
    // Validation
    require(stakeAmount > 0, "Stake must be positive");
    require(stakeAmount <= 10000 ether, "Stake exceeds maximum");
    require(creatorOdds > 0, "Creator odds must be positive");
    require(opponentOdds > 0, "Opponent odds must be positive");
    require(eventTimestamp > block.timestamp, "Event must be in future");

    // Generate bet ID
    uint256 betId = nextBetId++;

    // Create bet (NO funds locked yet)
    bets[betId] = Bet({
        betId: betId,
        creator: msg.sender,
        opponent: address(0),
        stakeAmount: stakeAmount,
        status: BetStatus.OPEN,
        creatorOdds: creatorOdds,
        opponentOdds: opponentOdds,
        createdAt: block.timestamp,
        eventTimestamp: eventTimestamp,
        eventId: eventId,
        winner: address(0),
        settledAt: 0
    });

    // Track user's bets
    userBetIds[msg.sender].push(betId);

    // Emit event
    emit BetCreated(betId, msg.sender, stakeAmount, eventId, eventTimestamp);

    return betId;
}
```

### 2. Accept Bet (Lock Escrow for Both Parties)

```solidity
function acceptBet(uint256 betId)
    external
    payable
    betExists(betId)
{
    Bet storage bet = bets[betId];

    // Validation
    require(bet.status == BetStatus.OPEN, "Bet not open");
    require(msg.sender != bet.creator, "Cannot accept own bet");
    require(msg.value == bet.stakeAmount, "Incorrect stake amount");

    // CRITICAL: Lock creator's funds from their account
    // NOTE: In production, creator would approve this contract to spend their funds
    // For this pseudocode, assume creator has pre-approved the transfer
    require(
        transferFrom(bet.creator, address(this), bet.stakeAmount),
        "Creator funds transfer failed"
    );

    // Lock opponent's funds (already sent with msg.value)
    // Both stakes now held in contract

    // Update escrow balance
    escrowBalance[betId] = bet.stakeAmount * 2;

    // Update bet state
    bet.opponent = msg.sender;
    bet.status = BetStatus.MATCHED;

    // Track opponent's bets
    userBetIds[msg.sender].push(betId);

    // Emit events
    emit EscrowLocked(betId, bet.creator, bet.stakeAmount);
    emit EscrowLocked(betId, msg.sender, bet.stakeAmount);
    emit BetMatched(betId, msg.sender, escrowBalance[betId]);
}
```

### 3. Request Oracle Settlement

```solidity
function requestSettlement(uint256 betId)
    external
    betExists(betId)
    onlyParticipant(betId)
{
    Bet storage bet = bets[betId];

    // Validation
    require(
        bet.status == BetStatus.MATCHED,
        "Bet must be matched"
    );
    require(
        block.timestamp >= bet.eventTimestamp,
        "Event has not occurred yet"
    );

    // Update status
    bet.status = BetStatus.PENDING_ORACLE;

    // Call oracle contract to request settlement
    IOracleContract(oracleContract).requestSettlement(
        betId,
        bet.eventId
    );
}
```

### 4. Settle Bet (Called by Oracle)

```solidity
function settleBet(uint256 betId, address winnerId)
    external
    betExists(betId)
    onlyOracle
{
    Bet storage bet = bets[betId];

    // Validation
    require(
        bet.status == BetStatus.MATCHED ||
        bet.status == BetStatus.PENDING_ORACLE,
        "Invalid bet status for settlement"
    );
    require(
        winnerId == bet.creator || winnerId == bet.opponent,
        "Winner must be a participant"
    );

    // Calculate payout
    uint256 totalPayout = escrowBalance[betId];  // Both stakes

    // Transfer funds to winner
    require(
        payable(winnerId).transfer(totalPayout),
        "Payout transfer failed"
    );

    // Update bet state
    bet.winner = winnerId;
    bet.status = BetStatus.COMPLETED;
    bet.settledAt = block.timestamp;

    // Clear escrow
    escrowBalance[betId] = 0;

    // Emit events
    emit EscrowReleased(betId, winnerId, totalPayout);
    emit BetSettled(betId, winnerId, totalPayout);
}
```

### 5. Dispute Settlement

```solidity
function disputeBet(uint256 betId)
    external
    betExists(betId)
    onlyParticipant(betId)
{
    Bet storage bet = bets[betId];

    // Validation
    require(
        bet.status == BetStatus.PENDING_ORACLE ||
        bet.status == BetStatus.COMPLETED,
        "Can only dispute pending or completed bets"
    );

    // If completed, must dispute within 48 hours
    if (bet.status == BetStatus.COMPLETED) {
        require(
            block.timestamp <= bet.settledAt + 48 hours,
            "Dispute window expired"
        );
    }

    // Mark as disputed
    bet.status = BetStatus.DISPUTED;

    emit BetDisputed(betId, msg.sender, block.timestamp);
}
```

### 6. Cancel Bet (Before Matching)

```solidity
function cancelBet(uint256 betId)
    external
    betExists(betId)
{
    Bet storage bet = bets[betId];

    // Validation
    require(msg.sender == bet.creator, "Only creator can cancel");
    require(bet.status == BetStatus.OPEN, "Can only cancel open bets");

    // Mark as cancelled (no funds to refund since none were locked)
    bet.status = BetStatus.CANCELLED;

    emit BetCancelled(betId, msg.sender);
}
```

### 7. Admin Settlement (For Disputed Bets)

```solidity
function adminSettleBet(uint256 betId, address winnerId)
    external
    betExists(betId)
    onlyOwner
{
    Bet storage bet = bets[betId];

    // Validation
    require(bet.status == BetStatus.DISPUTED, "Only for disputed bets");
    require(
        winnerId == bet.creator || winnerId == bet.opponent,
        "Winner must be a participant"
    );

    // Calculate payout
    uint256 totalPayout = escrowBalance[betId];

    // Transfer funds to winner
    require(
        payable(winnerId).transfer(totalPayout),
        "Payout transfer failed"
    );

    // Update bet state
    bet.winner = winnerId;
    bet.status = BetStatus.COMPLETED;
    bet.settledAt = block.timestamp;

    // Clear escrow
    escrowBalance[betId] = 0;

    // Emit events
    emit EscrowReleased(betId, winnerId, totalPayout);
    emit BetSettled(betId, winnerId, totalPayout);
}
```

### 8. Manual Settlement (Fallback if Oracle Fails)

```solidity
function manualSettleAfterTimeout(uint256 betId, address winnerId)
    external
    betExists(betId)
    onlyParticipant(betId)
{
    Bet storage bet = bets[betId];

    // Validation
    require(
        bet.status == BetStatus.PENDING_ORACLE,
        "Must be waiting for oracle"
    );
    require(
        block.timestamp >= bet.eventTimestamp + 24 hours,
        "Oracle timeout not reached"
    );
    require(
        winnerId == bet.creator || winnerId == bet.opponent,
        "Winner must be a participant"
    );

    // Settle manually (without oracle)
    // Note: Other participant can dispute within 48 hours

    uint256 totalPayout = escrowBalance[betId];

    require(
        payable(winnerId).transfer(totalPayout),
        "Payout transfer failed"
    );

    bet.winner = winnerId;
    bet.status = BetStatus.COMPLETED;
    bet.settledAt = block.timestamp;

    escrowBalance[betId] = 0;

    emit EscrowReleased(betId, winnerId, totalPayout);
    emit BetSettled(betId, winnerId, totalPayout);
}
```

## View Functions

```solidity
function getBet(uint256 betId)
    external
    view
    betExists(betId)
    returns (Bet memory)
{
    return bets[betId];
}

function getUserBets(address user)
    external
    view
    returns (uint256[] memory)
{
    return userBetIds[user];
}

function getUserEscrow(address user)
    external
    view
    returns (uint256 totalEscrow)
{
    uint256[] memory betIds = userBetIds[user];

    for (uint256 i = 0; i < betIds.length; i++) {
        Bet memory bet = bets[betIds[i]];

        // Only count matched bets where user is a participant
        if (bet.status == BetStatus.MATCHED || bet.status == BetStatus.PENDING_ORACLE) {
            if (bet.creator == user || bet.opponent == user) {
                totalEscrow += bet.stakeAmount;
            }
        }
    }

    return totalEscrow;
}

function getBetsByStatus(BetStatus status)
    external
    view
    returns (uint256[] memory)
{
    // Count matching bets
    uint256 count = 0;
    for (uint256 i = 0; i < nextBetId; i++) {
        if (bets[i].status == status) {
            count++;
        }
    }

    // Populate array
    uint256[] memory matchingBets = new uint256[](count);
    uint256 index = 0;
    for (uint256 i = 0; i < nextBetId; i++) {
        if (bets[i].status == status) {
            matchingBets[index] = i;
            index++;
        }
    }

    return matchingBets;
}
```

## State Machine Diagram

```
       createBet()
           ↓
        [OPEN]
           ↓
    ┌──────┴──────┐
    │             │
cancelBet()   acceptBet()
    │             │
    ↓             ↓
[CANCELLED]   [MATCHED]
                  ↓
          requestSettlement()
                  ↓
          [PENDING_ORACLE]
                  ↓
         ┌────────┴────────┐
         │                 │
    settleBet()      disputeBet()
         │                 │
         ↓                 ↓
    [COMPLETED]       [DISPUTED]
                           │
                   adminSettleBet()
                           │
                           ↓
                      [COMPLETED]
```

## Security Features

1. **Reentrancy Protection**: Use checks-effects-interactions pattern
2. **Atomic Escrow**: Both parties' funds locked in single transaction
3. **Role-Based Access**: Only oracle can settle, only owner can admin-settle
4. **Dispute Window**: 48-hour window to dispute settlement
5. **Timeout Fallback**: Manual settlement if oracle fails after 24 hours
6. **Event Logging**: All state changes emit events for transparency

## Gas Optimization Notes

- Use `storage` pointers instead of loading full structs
- Pack struct fields to minimize storage slots
- Batch operations where possible
- Consider using `uint128` for stake amounts if max is known
- Use events instead of storing historical data on-chain

## Integration with Oracle

The BetEscrow contract delegates settlement authority to the Oracle contract. The oracle:
1. Listens for `requestSettlement()` calls
2. Fetches external API data
3. Determines winner via multi-node consensus
4. Calls `settleBet()` with the winner address

## Deployment Configuration

```solidity
constructor(address _oracleContract) {
    owner = msg.sender;
    oracleContract = _oracleContract;
    nextBetId = 0;
}

function setOracleContract(address _newOracle) external onlyOwner {
    oracleContract = _newOracle;
}
```
