# BetOracle Smart Contract (Pseudocode)

## Overview

The BetOracle contract acts as a bridge between the blockchain and external data sources. It implements a decentralized oracle network where multiple independent nodes fetch data from APIs, reach consensus, and automatically settle bets based on real-world event outcomes.

## State Variables

```solidity
// Contract references
address public betEscrowContract;
address public owner;

// Oracle network
address[] public trustedNodes;
mapping(address => bool) public isNodeTrusted;
uint256 public consensusThreshold;  // e.g., 3 of 5 nodes must agree

// Oracle requests
mapping(uint256 => OracleRequest) public requests;
uint256 public nextRequestId;

// Node submissions
mapping(uint256 => NodeSubmission[]) public submissions;  // requestId => submissions
mapping(uint256 => mapping(address => bool)) public hasSubmitted;  // requestId => node => submitted
```

## Data Structures

```solidity
enum RequestStatus {
    PENDING,      // Request created, waiting for nodes
    FULFILLED,    // Consensus reached, bet settled
    DISPUTED,     // No consensus or disputed result
    TIMED_OUT     // Oracle failed to respond in time
}

struct OracleRequest {
    uint256 requestId;
    uint256 betId;
    bytes32 eventId;           // External event identifier
    string apiEndpoint;        // URL to fetch result from
    uint256 requestedAt;       // Block timestamp
    RequestStatus status;
    address consensusWinner;   // Agreed-upon winner
    uint256 fulfilledAt;       // Settlement timestamp
}

struct NodeSubmission {
    address nodeAddress;
    address proposedWinner;    // Who the node thinks won
    bytes resultData;          // Raw API response data
    bytes signature;           // Node's signature of the result
    uint256 submittedAt;       // Block timestamp
}

struct ApiAdapter {
    string apiEndpoint;
    string resultPath;         // JSON path to extract winner (e.g., "data.winner")
    mapping(string => address) resultMapping;  // Map API result to bet participant
}
```

## Events

```solidity
event OracleRequested(
    uint256 indexed requestId,
    uint256 indexed betId,
    bytes32 eventId,
    string apiEndpoint
);

event NodeSubmissionReceived(
    uint256 indexed requestId,
    address indexed node,
    address proposedWinner
);

event ConsensusReached(
    uint256 indexed requestId,
    address indexed winner,
    uint256 nodeCount
);

event OracleFulfilled(
    uint256 indexed requestId,
    uint256 indexed betId,
    address winner
);

event OracleDisputed(
    uint256 indexed requestId,
    string reason
);

event TrustedNodeAdded(address indexed node);
event TrustedNodeRemoved(address indexed node);
```

## Modifiers

```solidity
modifier onlyBetEscrow() {
    require(msg.sender == betEscrowContract, "Only BetEscrow can call");
    _;
}

modifier onlyTrustedNode() {
    require(isNodeTrusted[msg.sender], "Not a trusted oracle node");
    _;
}

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

modifier requestExists(uint256 requestId) {
    require(requestId < nextRequestId, "Request does not exist");
    _;
}
```

## Core Functions

### 1. Request Settlement (Called by BetEscrow)

```solidity
function requestSettlement(
    uint256 betId,
    bytes32 eventId
) external onlyBetEscrow returns (uint256) {
    // Fetch bet details from BetEscrow contract
    IBetEscrow.Bet memory bet = IBetEscrow(betEscrowContract).getBet(betId);

    // Determine API endpoint based on event type
    string memory apiEndpoint = getApiEndpointForEvent(eventId);

    // Create oracle request
    uint256 requestId = nextRequestId++;

    requests[requestId] = OracleRequest({
        requestId: requestId,
        betId: betId,
        eventId: eventId,
        apiEndpoint: apiEndpoint,
        requestedAt: block.timestamp,
        status: RequestStatus.PENDING,
        consensusWinner: address(0),
        fulfilledAt: 0
    });

    // Emit event for off-chain oracle nodes to listen
    emit OracleRequested(requestId, betId, eventId, apiEndpoint);

    return requestId;
}
```

### 2. Submit Oracle Response (Called by Trusted Nodes)

```solidity
function submitOracleResponse(
    uint256 requestId,
    address proposedWinner,
    bytes calldata resultData,
    bytes calldata signature
)
    external
    onlyTrustedNode
    requestExists(requestId)
{
    OracleRequest storage request = requests[requestId];

    // Validation
    require(request.status == RequestStatus.PENDING, "Request not pending");
    require(!hasSubmitted[requestId][msg.sender], "Already submitted");

    // Verify signature
    require(
        verifyNodeSignature(requestId, proposedWinner, resultData, signature, msg.sender),
        "Invalid signature"
    );

    // Store submission
    submissions[requestId].push(NodeSubmission({
        nodeAddress: msg.sender,
        proposedWinner: proposedWinner,
        resultData: resultData,
        signature: signature,
        submittedAt: block.timestamp
    }));

    hasSubmitted[requestId][msg.sender] = true;

    emit NodeSubmissionReceived(requestId, msg.sender, proposedWinner);

    // Check if consensus threshold reached
    if (submissions[requestId].length >= consensusThreshold) {
        _checkConsensusAndSettle(requestId);
    }
}
```

### 3. Check Consensus and Settle

```solidity
function _checkConsensusAndSettle(uint256 requestId) internal {
    OracleRequest storage request = requests[requestId];
    NodeSubmission[] storage subs = submissions[requestId];

    // Count votes for each proposed winner
    mapping(address => uint256) memory voteCounts;
    address[] memory candidates = new address[](subs.length);
    uint256 candidateCount = 0;

    for (uint256 i = 0; i < subs.length; i++) {
        address candidate = subs[i].proposedWinner;

        // Add to candidates if not already present
        bool exists = false;
        for (uint256 j = 0; j < candidateCount; j++) {
            if (candidates[j] == candidate) {
                exists = true;
                break;
            }
        }

        if (!exists) {
            candidates[candidateCount] = candidate;
            candidateCount++;
        }

        voteCounts[candidate]++;
    }

    // Find winner with most votes
    address consensusWinner = address(0);
    uint256 maxVotes = 0;

    for (uint256 i = 0; i < candidateCount; i++) {
        if (voteCounts[candidates[i]] > maxVotes) {
            maxVotes = voteCounts[candidates[i]];
            consensusWinner = candidates[i];
        }
    }

    // Verify consensus threshold
    if (maxVotes >= consensusThreshold) {
        // Consensus reached - settle the bet
        request.consensusWinner = consensusWinner;
        request.status = RequestStatus.FULFILLED;
        request.fulfilledAt = block.timestamp;

        // Call BetEscrow to settle
        IBetEscrow(betEscrowContract).settleBet(request.betId, consensusWinner);

        emit ConsensusReached(requestId, consensusWinner, maxVotes);
        emit OracleFulfilled(requestId, request.betId, consensusWinner);
    } else {
        // No consensus - mark as disputed
        request.status = RequestStatus.DISPUTED;

        emit OracleDisputed(requestId, "No consensus reached");
    }
}
```

### 4. Verify Node Signature

```solidity
function verifyNodeSignature(
    uint256 requestId,
    address proposedWinner,
    bytes memory resultData,
    bytes memory signature,
    address nodeAddress
) internal pure returns (bool) {
    // Reconstruct the message hash
    bytes32 messageHash = keccak256(
        abi.encodePacked(requestId, proposedWinner, resultData)
    );

    // Add Ethereum signed message prefix
    bytes32 ethSignedMessageHash = keccak256(
        abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash)
    );

    // Recover signer from signature
    address recoveredSigner = recoverSigner(ethSignedMessageHash, signature);

    // Verify signer matches node address
    return recoveredSigner == nodeAddress;
}

function recoverSigner(bytes32 ethSignedMessageHash, bytes memory signature)
    internal
    pure
    returns (address)
{
    (bytes32 r, bytes32 s, uint8 v) = splitSignature(signature);
    return ecrecover(ethSignedMessageHash, v, r, s);
}

function splitSignature(bytes memory sig)
    internal
    pure
    returns (bytes32 r, bytes32 s, uint8 v)
{
    require(sig.length == 65, "Invalid signature length");

    assembly {
        r := mload(add(sig, 32))
        s := mload(add(sig, 64))
        v := byte(0, mload(add(sig, 96)))
    }
}
```

### 5. Manual Dispute Resolution

```solidity
function resolveDispute(uint256 requestId, address winner)
    external
    onlyOwner
    requestExists(requestId)
{
    OracleRequest storage request = requests[requestId];

    require(
        request.status == RequestStatus.DISPUTED,
        "Only for disputed requests"
    );

    // Admin manually resolves dispute
    request.consensusWinner = winner;
    request.status = RequestStatus.FULFILLED;
    request.fulfilledAt = block.timestamp;

    // Settle the bet via BetEscrow admin function
    IBetEscrow(betEscrowContract).adminSettleBet(request.betId, winner);

    emit OracleFulfilled(requestId, request.betId, winner);
}
```

### 6. Handle Timeout

```solidity
function markAsTimedOut(uint256 requestId)
    external
    requestExists(requestId)
{
    OracleRequest storage request = requests[requestId];

    require(request.status == RequestStatus.PENDING, "Not pending");
    require(
        block.timestamp >= request.requestedAt + 24 hours,
        "Timeout period not reached"
    );

    request.status = RequestStatus.TIMED_OUT;

    emit OracleDisputed(requestId, "Oracle timed out");

    // Note: BetEscrow contract allows manual settlement after timeout
}
```

## Oracle Network Management

### Add Trusted Node

```solidity
function addTrustedNode(address node) external onlyOwner {
    require(!isNodeTrusted[node], "Node already trusted");

    trustedNodes.push(node);
    isNodeTrusted[node] = true;

    emit TrustedNodeAdded(node);
}
```

### Remove Trusted Node

```solidity
function removeTrustedNode(address node) external onlyOwner {
    require(isNodeTrusted[node], "Node not trusted");

    isNodeTrusted[node] = false;

    // Remove from array
    for (uint256 i = 0; i < trustedNodes.length; i++) {
        if (trustedNodes[i] == node) {
            trustedNodes[i] = trustedNodes[trustedNodes.length - 1];
            trustedNodes.pop();
            break;
        }
    }

    emit TrustedNodeRemoved(node);
}
```

### Update Consensus Threshold

```solidity
function setConsensusThreshold(uint256 newThreshold) external onlyOwner {
    require(newThreshold > 0, "Threshold must be positive");
    require(newThreshold <= trustedNodes.length, "Threshold too high");

    consensusThreshold = newThreshold;
}
```

## API Adapter Functions

### Get API Endpoint for Event

```solidity
mapping(bytes32 => ApiAdapter) public apiAdapters;

function getApiEndpointForEvent(bytes32 eventId)
    internal
    view
    returns (string memory)
{
    ApiAdapter storage adapter = apiAdapters[eventId];
    require(bytes(adapter.apiEndpoint).length > 0, "No adapter for event");

    return adapter.apiEndpoint;
}
```

### Register API Adapter

```solidity
function registerApiAdapter(
    bytes32 eventId,
    string memory apiEndpoint,
    string memory resultPath
) external onlyOwner {
    apiAdapters[eventId] = ApiAdapter({
        apiEndpoint: apiEndpoint,
        resultPath: resultPath
    });
}
```

## View Functions

```solidity
function getRequest(uint256 requestId)
    external
    view
    requestExists(requestId)
    returns (OracleRequest memory)
{
    return requests[requestId];
}

function getSubmissions(uint256 requestId)
    external
    view
    requestExists(requestId)
    returns (NodeSubmission[] memory)
{
    return submissions[requestId];
}

function getTrustedNodes() external view returns (address[] memory) {
    return trustedNodes;
}

function getVoteCounts(uint256 requestId)
    external
    view
    requestExists(requestId)
    returns (address[] memory candidates, uint256[] memory votes)
{
    NodeSubmission[] storage subs = submissions[requestId];

    // Count unique candidates
    address[] memory tempCandidates = new address[](subs.length);
    uint256[] memory tempVotes = new uint256[](subs.length);
    uint256 candidateCount = 0;

    for (uint256 i = 0; i < subs.length; i++) {
        address candidate = subs[i].proposedWinner;

        // Find or create candidate
        bool found = false;
        for (uint256 j = 0; j < candidateCount; j++) {
            if (tempCandidates[j] == candidate) {
                tempVotes[j]++;
                found = true;
                break;
            }
        }

        if (!found) {
            tempCandidates[candidateCount] = candidate;
            tempVotes[candidateCount] = 1;
            candidateCount++;
        }
    }

    // Trim arrays to actual size
    candidates = new address[](candidateCount);
    votes = new uint256[](candidateCount);

    for (uint256 i = 0; i < candidateCount; i++) {
        candidates[i] = tempCandidates[i];
        votes[i] = tempVotes[i];
    }

    return (candidates, votes);
}
```

## Oracle Flow Diagram

```
  BetEscrow.requestSettlement()
             ↓
    OracleRequested event emitted
             ↓
  ┌──────────┴──────────┐
  │                     │
Node 1              Node 2              Node 3 ... Node N
  │                     │                     │
Fetch API          Fetch API           Fetch API
  │                     │                     │
Sign Result        Sign Result         Sign Result
  │                     │                     │
  └──────────┬──────────┘                     │
             ↓                                 │
  submitOracleResponse()  ←────────────────────┘
             ↓
    Check submissions count
             ↓
  ┌──────────┴──────────┐
  │                     │
Count < threshold   Count >= threshold
  │                     │
Wait for more           ↓
submissions      _checkConsensusAndSettle()
                        ↓
                 Count votes per winner
                        ↓
              ┌─────────┴─────────┐
              │                   │
        Consensus           No consensus
              │                   │
              ↓                   ↓
    BetEscrow.settleBet()   DISPUTED status
              ↓
      Funds distributed
```

## Security Considerations

1. **Sybil Resistance**: Only trusted nodes can submit results
2. **Signature Verification**: All node submissions must be cryptographically signed
3. **Consensus Mechanism**: Require majority agreement (e.g., 3 of 5 nodes)
4. **Replay Protection**: Track submissions per request to prevent double-voting
5. **Timeout Handling**: Allow manual settlement if oracle network fails
6. **Admin Override**: Owner can resolve disputes for edge cases

## Example API Adapters

### Sports (ESPN API)

```solidity
Event ID: keccak256("nfl-super-bowl-2024")
API Endpoint: "https://api.espn.com/v1/sports/football/nfl/scoreboard?event=12345"
Result Path: "events[0].competitions[0].winner.team.name"
Result Mapping: {
    "San Francisco 49ers": creatorAddress,
    "Kansas City Chiefs": opponentAddress
}
```

### Entertainment (Awards API)

```solidity
Event ID: keccak256("oscars-2024-best-picture")
API Endpoint: "https://api.oscars.com/winners/2024"
Result Path: "categories.best_picture.winner"
Result Mapping: {
    "Oppenheimer": creatorAddress,
    "Other": opponentAddress
}
```

## Deployment Configuration

```solidity
constructor(address _betEscrowContract, uint256 _consensusThreshold) {
    owner = msg.sender;
    betEscrowContract = _betEscrowContract;
    consensusThreshold = _consensusThreshold;
}

function setBetEscrowContract(address _newContract) external onlyOwner {
    betEscrowContract = _newContract;
}
```

## Gas Optimization Notes

- Minimize storage writes in submission loop
- Use events for off-chain data indexing instead of storing all submissions
- Consider using Chainlink oracle infrastructure for production
- Batch consensus checks instead of checking on every submission
