{
  "sources": {
    "SquatchiVictoryRewards.sol": {
      "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\ninterface IERC20 {\n    function transfer(address to, uint256 amount) external returns (bool);\n    function balanceOf(address a) external view returns (uint256);\n}\n\n/// @title Squatchi 1-Month Play-to-Earn Victory Pool\n/// @notice Pays 1 $SQUATCHI once per wallet to champions who beat all 10 levels, for 30 days.\ncontract SquatchiVictoryRewards {\n    IERC20 public immutable SQUATCHI;\n    uint256 public constant REWARD_AMOUNT = 1e18;\n    uint256 public constant ALL_LEVELS_MASK = 0x3FF;\n    uint256 public immutable campaignEndTime;\n    bytes32 private immutable depositorHash;\n\n    mapping(address => bool) public hasClaimed;\n    uint256 public totalWinners;\n    bool private locked;\n\n    event VictoryRewarded(address indexed champion, uint256 amount, uint256 timestamp);\n    event RemainingRecovered(address indexed to, uint256 amount);\n\n    constructor(address tokenAddress) {\n        require(tokenAddress != address(0), \"invalid token\");\n        SQUATCHI = IERC20(tokenAddress);\n        campaignEndTime = block.timestamp + 30 days;\n        depositorHash = keccak256(abi.encodePacked(msg.sender));\n    }\n\n    function expectedProof(address champion) public view returns (bytes32) {\n        return keccak256(abi.encodePacked(\"SQUATCHI_CHAMPION_10_LEVELS\", champion, ALL_LEVELS_MASK, address(this)));\n    }\n\n    function claimVictoryReward(uint256 starsMask, bytes32 completionProof) external {\n        require(!locked, \"reentrant\");\n        require(block.timestamp <= campaignEndTime, \"campaign ended\");\n        require(msg.sender == tx.origin, \"EOA only\");\n        require(!hasClaimed[msg.sender], \"already claimed\");\n        require(starsMask == ALL_LEVELS_MASK, \"beat all 10 levels\");\n        require(completionProof == expectedProof(msg.sender), \"bad proof\");\n        locked = true;\n        hasClaimed[msg.sender] = true;\n        totalWinners += 1;\n        require(SQUATCHI.transfer(msg.sender, REWARD_AMOUNT), \"transfer failed\");\n        locked = false;\n        emit VictoryRewarded(msg.sender, REWARD_AMOUNT, block.timestamp);\n    }\n\n    function poolBalance() external view returns (uint256) { return SQUATCHI.balanceOf(address(this)); }\n\n    function withdrawTokens(address to, uint256 amount) external {\n        require(keccak256(abi.encodePacked(msg.sender)) == depositorHash, \"not depositor\");\n        require(SQUATCHI.transfer(to, amount), \"transfer failed\");\n        emit RemainingRecovered(to, amount);\n    }\n\n    function recoverRemainingAfterExpiry(address to) external {\n        require(block.timestamp > campaignEndTime, \"campaign active\");\n        require(keccak256(abi.encodePacked(msg.sender)) == depositorHash, \"not depositor\");\n        uint256 bal = SQUATCHI.balanceOf(address(this));\n        require(SQUATCHI.transfer(to, bal), \"transfer failed\");\n        emit RemainingRecovered(to, bal);\n    }\n}\n"
    }
  },
  "language": "Solidity",
  "settings": {
    "viaIR": true,
    "optimizer": {
      "runs": 200,
      "enabled": true
    },
    "evmVersion": "paris"
  }
}