APE Price: $0.17 (-6.71%)

Contract

0x3F6e3D0b513096D3aA5ef88499Dd7AEc13B78cd4

Overview

APE Balance

Apechain LogoApechain LogoApechain Logo0.029999999999999999 APE

APE Value

Less Than $0.01 (@ $0.17/APE)

More Info

Private Name Tags

Multichain Info

N/A
Transaction Hash
Block
From
To
Start Game162947732025-05-29 0:08:25245 days ago1748477305IN
0x3F6e3D0b...c13B78cd4
0 APE0.0042283725.42069
Admin Set Pyth P...162947652025-05-29 0:08:03245 days ago1748477283IN
0x3F6e3D0b...c13B78cd4
0 APE0.0007845325.42069
Join Lobby162946602025-05-29 0:02:27245 days ago1748476947IN
0x3F6e3D0b...c13B78cd4
0.01 APE0.0028655225.42069
Join Lobby162946562025-05-29 0:02:15245 days ago1748476935IN
0x3F6e3D0b...c13B78cd4
0.01 APE0.0028655225.42069
Join Lobby162946492025-05-29 0:02:00245 days ago1748476920IN
0x3F6e3D0b...c13B78cd4
0.01 APE0.0028655225.42069
Join Lobby162946412025-05-29 0:01:45245 days ago1748476905IN
0x3F6e3D0b...c13B78cd4
0.01 APE0.0028655225.42069
Create Lobby162946352025-05-29 0:01:26245 days ago1748476886IN
0x3F6e3D0b...c13B78cd4
0.01 APE0.0074816325.42069
Admin Set Town H...162945952025-05-28 23:59:15245 days ago1748476755IN
0x3F6e3D0b...c13B78cd4
0 APE0.0012237525.42069
Admin Set Backen...162945912025-05-28 23:59:11245 days ago1748476751IN
0x3F6e3D0b...c13B78cd4
0 APE0.0012130725.42069
Admin Set Pyth P...162945862025-05-28 23:58:57245 days ago1748476737IN
0x3F6e3D0b...c13B78cd4
0 APE0.0012192225.42069

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Block From To
162947732025-05-29 0:08:25245 days ago1748477305
0x3F6e3D0b...c13B78cd4
0.02 APE

Cross-Chain Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
DemWitches

Compiler Version
v0.8.20+commit.a1b79de6

Optimization Enabled:
Yes with 1 runs

Other Settings:
paris EvmVersion
File 1 of 16 : DemWitches.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

// =================================================================================================
// Imports
// =================================================================================================

// --- OpenZeppelin Imports ---
// These imports are required for interacting with the OpenZeppelin contracts.
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

// --- Pyth Entropy Imports ---
// These interfaces are required for interacting with the Pyth Network's secure randomness oracle.
import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol";
import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";

// --- Custom Libraries ---
import "../GameDefinitions.sol";
import "../libraries/RoleLogic.sol";
import "../libraries/PayoutLogic.sol";
import "../libraries/VoteLogic.sol";

// =================================================================================================
// Contract Definition: DemWitches (v3.9)
// =================================================================================================

/**
 * @notice This contract implements the 'DemWitches' game on ApeChain Mainnet.
 * @dev Built for Solidity ^0.8.20, integrating with Pyth Entropy for secure random number generation.
 */
contract DemWitches is Ownable, ReentrancyGuard, Pausable, IEntropyConsumer {

    // =================================================================================================
    // Constants
    // =================================================================================================

    /**
     * @notice The reward for a dead Townfolk when their team wins, displayed as a percentage of their tribute (buy-in).
     * @dev Value is in Basis Points (BPS). E.g., 5000 means 50%.
     */
    // uint256 public constant DEAD_TOWNFOLK_REWARD = 5000; // Moved to GameDefinitions.sol

    /**
     * @notice The duration of the assigned grace periods (seconds) after a phase ends before public timeout is allowed.
     * @dev This buffer allows the backend a chance to process before public intervention.
     */
    uint256 public constant GRACE_DURATION = 5 * 60; // 5 minutes


    /**
     * @notice The maximum platform fee displayed in a percentage (BPS) that the owner can set.
     * @dev Caps the platform fee to prevent excessively high charges.
     */
    uint16 public constant MAX_FEE = 1000; // 10%

    /**
     * @notice The maximum number of players allowed in any given game.
     * @dev Absolute upper limit for game room capacity.
     */
    uint8 public constant MAX_PLAYERS = 12;

    /**
     * @notice The minimum number of players allowed in any given game for it to start.
     * @dev Ensures games are viable and engaging.
     */
    uint8 public constant MIN_PLAYERS = 5;

    /**
     * @notice The total number of witches set for all games. (This version is fixed to 1).
     * @dev Defines the core Witch vs. Townfolk ratio.
     */
    // uint8 public constant WITCH_COUNT = 1; // Moved to GameDefinitions.sol

    // =================================================================================================
    // State Variables
    // =================================================================================================

    /** @notice Immutable address of the Pyth Network Entropy oracle contract, set at deployment. */
    IEntropy public immutable PYTH_ENTROPY; // Access via pythEntropyAddress()
    /** @dev Internal counter for assigning unique IDs to newly created games. Use `nextGameID()` to view. */
    uint256 private _nextGameId;
    /** @dev Address of the Pyth Network service provider. Use `pythProviderAddress()` to view. */
    address private _pythProviderAddress;
    /** @dev Wallet address authorized for backend server operations. Use `backendAddress()` to view. */
    address private _backendAddress;
    /** @dev Wallet address designated for a Validator role. Use `getValidatorAddress()` to view. */
    address private _validatorAddress;
    /** 
     * @notice The designated wallet address where platform fees collected from completed games will be directly transferred.
     * @dev Configured by the owner. Use `getTownHallWallet()` to view.
     */
    address public townHallWallet;
    /** @dev Default platform fee (basis points) for games. Use `fee()` to view. */
    uint256 private _feeBps;
    /** @dev Approximation of native $APE funds locked in active or unsettled games. Use `getPlayersFundsLocked()` to view. */
    uint256 private _totalPlayerFundsLocked;
    /** @dev First eligible NFT collection for Witch role boost. Use `getBoostedCollection1()` to view. */
    address private _boostedCollection1;
    /** @dev Second eligible NFT collection for Witch role boost. Use `getBoostedCollection2()` to view. */
    address private _boostedCollection2;
    /** @dev Configured duration (seconds) for Night phases. Use `nightDuration()` to view. */
    uint256 private _nightDuration;
    /** @dev Configured duration (seconds) for Day phases. Use `dayDuration()` to view. */
    uint256 private _dayDuration;
    /** @dev Configured duration (seconds) for Revote phases. Use `revoteDuration()` to view. */
    uint256 private _revoteDuration;
    /** @dev Configurable duration (seconds) an unstarted lobby can exist before public cancellation via timeout. */
    uint256 private _lobbyTimeoutDuration;
    /** @dev Configurable duration (seconds) Dem Witches will wait for Pyth randomness before admin can cancel. */
    uint256 private _randomnessTimeoutDefault;

    // NFT Boost Tier Configuration
    /** @dev Thresholds for NFT counts for tiered boosts. Length must be N. */
    uint8[] private _nftBoostTiersCounts;
    /** @dev Boost BPS values for each tier. Length must be N+1. */
    uint256[] private _nftBoostTiersBps;

    // Game Data Mappings
    mapping(uint256 => GameSettings) internal games;
    mapping(uint256 => mapping(address => Player)) internal gamePlayers;
    mapping(uint256 => address[]) internal playerList;
    mapping(uint256 => address[]) internal playersInRevote;
    mapping(uint64 => uint256) internal sequenceNumberToGameId;
    mapping(uint256 => address[]) internal winners;
    mapping(uint256 => uint256) internal payoutPerWinnerTownfolk;

    // =================================================================================================
    // Structs & Enums (Moved to GameDefinitions.sol)
    // =================================================================================================

    // enum Role, enum GameState, struct Player, struct GameSettings are now imported from GameDefinitions.sol

    /**
     * @notice Provides a publicly viewable snapshot of a player's information, Excluding their secret role to maintain game integrity.
     */
    struct PublicPlayerInfo {
        /** @notice The unique wallet address of the player. */
        address playerAddress;
        /** @notice Tracks whether the player is currently active or has been eliminated. */
        bool isAlive;
        /** @notice Indicates if the player has successfully claimed their winnings or refund. */
        bool claimedPayout;
        /** @notice The tiered NFT boost percentage (basis points) that was cached for this player upon joining. */
        uint256 nftBoostBps;
    }

    // =================================================================================================
    // Events
    // =================================================================================================

    /** 
     * @notice Emitted when a new game lobby is successfully created.
     * @param gameId Unique ID of the created game.
     * @param host Address of the game's host.
     * @param gameBuyIn Fixed native $APE buy-in amount (Wei) for this game.
     * @param maxPlayers Maximum number of players allowed in this game.
     * @param feeBps Platform fee (basis points) applied to this game.
     */
    event GameCreated(uint256 indexed gameId, address indexed host, uint256 gameBuyIn, uint8 maxPlayers, uint256 feeBps);
    
    /** 
     * @notice Emitted when a player successfully joins a game lobby.
     * @param gameId ID of the game joined.
     * @param player Address of the player who joined.
     * @param nftBoostAppliedBps NFT boost (BPS) applied to the player upon joining.
     */
    event PlayerJoined(uint256 indexed gameId, address indexed player, uint256 nftBoostAppliedBps);
    
    /** 
     * @notice Emitted when a game host requests to start the game, initiating Pyth randomness.
     * @param gameId ID of the game being started.
     * @param host Address of the host initiating the start.
     * @param sequenceNumber Pyth Entropy request sequence number for tracking the randomness request.
     */
    event GameStartRequested(uint256 indexed gameId, address indexed host, uint64 sequenceNumber);
    
    /** 
     * @notice Emitted when Pyth randomness is successfully received via callback.
     * @param gameId ID of the game for which randomness was received.
     * @param sequenceNumber Pyth Entropy request sequence number that was fulfilled.
     */
    event RandomnessReceived(uint256 indexed gameId, uint64 indexed sequenceNumber);
    
    /** 
     * @notice Emitted after roles have been assigned using Pyth randomness at the start of a game.
     * @param gameId ID of the game where roles were assigned.
     */
    event RolesAssigned(uint256 indexed gameId);
    
    /** 
     * @notice Emitted when a Night phase begins.
     * @param gameId ID of the game.
     * @param round The current game round number.
     */
    event NightPhaseStarted(uint256 indexed gameId, uint256 round);
    
    /** 
     * @notice Emitted when a player is cursed by the Witch during the Night phase.
     * @param gameId ID of the game.
     * @param round The round number of the curse.
     * @param target Address of the player cursed by the Witch.
     */
    event PlayerCursed(uint256 indexed gameId, uint256 round, address indexed target);
    
    /** 
     * @notice Emitted when the Witch's curse results in the last Townfolk being eliminated due to the final kill mechanic.
     * @param gameId ID of the game.
     * @param round The round number of this event.
     * @param lastTownfolk Address of the last Townfolk who was eliminated.
     */
    event WitchKilledLastTownfolk(uint256 indexed gameId, uint256 round, address indexed lastTownfolk);
    
    /** 
     * @notice Emitted when a Day phase begins.
     * @param gameId ID of the game.
     * @param round The current game round number.
     * @param killedInNight Address of the player killed in the preceding Night phase (0x0 if no one was killed).
     */
    event DayPhaseStarted(uint256 indexed gameId, uint256 round, address killedInNight);
    
    /** 
     * @notice Emitted when a player is eliminated (burned) as a result of Day phase voting.
     * @param gameId ID of the game.
     * @param round The round number of the burning.
     * @param target Address of the player who was burned.
     * @param votes Number of votes the burned player received (for informational context).
     */
    event PlayerBurned(uint256 indexed gameId, uint256 round, address indexed target, uint256 votes);
    
    /** 
     * @notice Emitted if a Day phase vote results in a tie between two or more players.
     * @param gameId ID of the game.
     * @param round The round number where the tie occurred.
     * @param tiedPlayers Array of player addresses who were involved in the tie.
     */
    event VoteTied(uint256 indexed gameId, uint256 round, address[] tiedPlayers);
    
    /** 
     * @notice Emitted when a Revote phase begins due to a tie in the preceding Day phase voting.
     * @param gameId ID of the game.
     * @param round The round number of the revote (same as the Day phase that tied).
     * @param playersInRevote Array of player addresses participating in this revote.
     */
    event RevotePhaseStarted(uint256 indexed gameId, uint256 round, address[] playersInRevote);
    
    /** 
     * @notice Emitted when a player is eliminated (burned) as a result of Revote phase voting.
     * @param gameId ID of the game.
     * @param round The round number of the burning after the revote.
     * @param target Address of the player who was burned after the revote.
     * @param votes Number of votes the burned player received in the revote (for informational context).
     */
    event PlayerBurnedAfterRevote(uint256 indexed gameId, uint256 round, address indexed target, uint256 votes);
    
    /** 
     * @notice Emitted if a Revote phase also results in a tie (meaning no one was burned from the revote).
     * @param gameId ID of the game.
     * @param round The round number where the revote tied.
     */
    event RevoteTied(uint256 indexed gameId, uint256 round);
    
    /** 
     * @notice Emitted when a game concludes and winners are determined.
     * @param gameId ID of the finished game.
     * @param winningTeam The role of the team that won the game (Townfolk or Witch).
     * @param winners Array of wallet addresses of the winning players (alive members of the winning team).
     * @param payoutPerWinner The calculated payout amount per living winner of the winning team (for context; actual claims are separate).
     */
    event GameFinished(uint256 indexed gameId, Role winningTeam, address[] winners, uint256 payoutPerWinner);
    
    /** 
     * @notice Emitted when a player successfully claims their payout or refund.
     * @param gameId ID of the game from which the claim was made.
     * @param player Address of the player who successfully claimed their funds.
     * @param amount Amount of native $APE claimed by the player.
     */
    event PayoutClaimed(uint256 indexed gameId, address indexed player, uint256 amount);
    
    /** 
     * @notice Emitted when a game is cancelled by an admin or due to a timeout.
     * @param gameId ID of the cancelled game.
     * @param cancelledBy Address of the account that triggered the cancellation.
     * @param reason A descriptive reason for the game's cancellation.
     */
    event GameCancelled(uint256 indexed gameId, address indexed cancelledBy, string reason);
    
    /** 
     * @notice Emitted when a player is refunded their buy-in, typically due to game cancellation.
     * @param gameId ID of the game related to the refund.
     * @param player Address of the player who received the refund.
     * @param amount Amount of native $APE refunded to the player.
     */
    event PlayerRefunded(uint256 indexed gameId, address indexed player, uint256 amount);
    
    /** 
     * @notice Emitted when an admin-triggered payout retry for a player succeeds.
     * @param gameId ID of the game.
     * @param player Address of the player whose payout was successfully retried.
     * @param amount Amount of native $APE paid out.
     */
    event PayoutRetrySucceeded(uint256 indexed gameId, address indexed player, uint256 amount);
    
    /** 
     * @notice Emitted when an admin-triggered payout retry for a player fails.
     * @param gameId ID of the game.
     * @param player Address of the player whose payout retry failed.
     */
    event PayoutRetryFailed(uint256 indexed gameId, address indexed player);
    
    /** 
     * @notice Emitted when a game phase is advanced due to a public timeout (e.g., backend inactivity).
     * @param gameId ID of the game.
     * @param timedOutPhase The specific game phase that timed out.
     * @param round The game round number during which the timeout occurred.
     * @param reason The reason for the forced phase advance (usually "Public timeout fallback invoked").
     */
    event PhaseForcedByTimeout(uint256 indexed gameId, GameState timedOutPhase, uint256 round, string reason);
    
    /** 
     * @notice Emitted when a player is eliminated due to inactivity by an authorized operator.
     * @param gameId ID of the game.
     * @param round The game round number when the elimination occurred.
     * @param player Address of the player who was eliminated for inactivity.
     */
    event PlayerInactiveEliminated(uint256 indexed gameId, uint256 round, address indexed player);
    
    /** 
     * @notice Emitted when the default platform fee is updated by the contract owner.
     * @param newFeeBasisPoints The new platform fee percentage in basis points (BPS).
     */
    event PlatformFeeSet(uint256 newFeeBasisPoints);
    
    /** 
     * @notice Emitted when the Pyth Network entropy provider address is updated by the contract owner.
     * @param providerAddress The new Pyth provider address.
     */
    event PythProviderSet(address indexed providerAddress);
    
    /** 
     * @notice Emitted when the trusted backend server address is updated by the contract owner.
     * @param backendAddress The new authorized backend address.
     */
    event BackendAddressSet(address indexed backendAddress);
    
    /** 
     * @notice Emitted when the validator address is updated by the contract owner.
     * @param oldValidator The previous validator address (can be address(0)).
     * @param newValidator The new validator address.
     */
    event ValidatorSet(address indexed oldValidator, address indexed newValidator);
    
    /** 
     * @notice Emitted when an NFT collection address for role boost is set or updated by the owner.
     * @param index Identifier for the collection slot (1 for the first, 2 for the second).
     * @param collection The new contract address of the NFT collection.
     */
    event NftCollectionSet(uint8 indexed index, address indexed collection);
    
    /** 
     * @notice Emitted when game phase durations are updated by the owner.
     * @param lobby New duration for lobby timeout in seconds.
     * @param randomness New duration for randomness timeout in seconds.
     * @param night New duration for Night phases in seconds.
     * @param day New duration for Day phases in seconds.
     * @param revote New duration for Revote phases in seconds.
     */
    event GameDurationsSet(uint256 lobby, uint256 randomness, uint256 night, uint256 day, uint256 revote);

    /** 
     * @notice Emitted when NFT boost tiers are updated by the owner.
     * @param nftCounts Array of NFT count thresholds for tiers.
     * @param boostBps Array of boost BPS values for corresponding tiers.
     */
    event NftBoostTiersSet(uint8[] nftCounts, uint256[] boostBps);
    
    /** 
     * @notice Emitted when the town hall (treasury) wallet address for fee collection is updated by the owner.
     * @param newTownHallWallet The new wallet address for receiving platform fees.
     */
    event TownHallWalletSet(address indexed newTownHallWallet);
    
    /** 
     * @notice Emitted when the contract's pausable functions are paused by the owner.
     * @param account The owner address that executed the pause.
     */
    event GamePaused(address account);
    
    /** 
     * @notice Emitted when the contract's pausable functions are unpaused by the owner.
     * @param account The owner address that executed the unpause.
     */
    event GameUnpaused(address account);
    
    /** 
     * @notice Emitted when the game state is forcibly changed by an admin using `adminSetPhase`.
     * @param gameId ID of the game whose state was changed.
     * @param oldState The game state before the admin action.
     * @param newState The game state after the admin action.
     * @param admin Address of the admin who forced the state change.
     */
    event AdminForcedState(uint256 indexed gameId, GameState oldState, GameState newState, address indexed admin);

    // =================================================================================================
    // Errors
    // =================================================================================================

    /** 
     * @notice IncorrectAmount: Incorrect amount of $APE sent for an operation.
     * @param sent Amount sent.
     * @param required Amount required.
     */
    error InvalidAmount(uint256 sent, uint256 required);
    
    /** 
     * @notice IncorrectBuyInAmount: Player sent an incorrect buy-in amount.
     * @param sent Amount of $APE sent.
     * @param required Required $APE buy-in.
     */
    error IncorrectBuyInAmount(uint256 sent, uint256 required);
    
    /** @notice ZeroAddress: A critical address parameter was the zero address. */
    error ZeroAddress();
    
    /** 
     * @notice MaxPlayersInvalid: `maxPlayers` for a game is outside allowed limits.
     * @param maxPlayers Invalid number specified.
     */
    error MaxPlayersInvalid(uint8 maxPlayers);
    
    /** 
     * @notice FeeTooHigh: Attempt to set platform fee higher than `MAX_FEE`.
     * @param feeBps Fee (BPS) attempted.
     * @param maxBps Maximum allowed fee (BPS).
     */
    error FeeTooHigh(uint256 feeBps, uint256 maxBps);
    
    /** @notice DurationTooShort: Game phase durations set too short. */
    error DurationTooShort();

    /** @notice InvalidBoostTierConfiguration: NFT boost tier configuration is invalid (e.g., array length mismatch, unsorted counts). */
    error InvalidBoostTierConfiguration();
    
    /** @notice PythProviderAddressNotSet: Action requires Pyth provider address, but it's not set. */
    error PythProviderAddressNotSet();
    
    /** @notice BackendAddressNotSet: Action requires backend address, but it's not set. */
    error BackendAddressNotSet();
    
    /** @notice TownHallWalletNotSet: Platform fees cannot be transferred because the town hall wallet is not set. */
    error TownHallWalletNotSet();
    
    /** 
     * @notice GameNotFound: Operation attempted on a non-existent game ID.
     * @param gameId Non-existent game ID.
     */
    error GameNotFound(uint256 gameId);
    
    /** 
     * @notice GameFull: Player attempts to join a game that is already full.
     * @param gameId ID of the full game.
     * @param maxPlayers Max capacity of the game.
     */
    error GameFull(uint256 gameId, uint8 maxPlayers);
    
    /** 
     * @notice NotEnoughPlayers: Attempt to start a game with fewer than `MIN_PLAYERS`.
     * @param gameId ID of the game.
     * @param current Current players in lobby.
     * @param required Minimum players required.
     */
    error NotEnoughPlayers(uint256 gameId, uint8 current, uint8 required);
    
    /** 
     * @notice GameAlreadyStartedOrFinished: Attempt to start a game that already started or finished.
     * @param gameId ID of the game.
     */
    error GameAlreadyStartedOrFinished(uint256 gameId);
    
    /** 
     * @notice InvalidGameState: Game is not in the expected state for an action.
     * @param gameId ID of the game.
     * @param expected Expected game state.
     * @param actual Actual game state.
     */
    error InvalidGameState(uint256 gameId, GameState expected, GameState actual);
    
    /** 
     * @notice WrongGameStateForAction: Action attempted in a game state where it's not applicable.
     * @param gameId ID of the game.
     * @param actual Current (invalid for action) state.
     */
    error WrongGameStateForAction(uint256 gameId, GameState actual);
    
    /** 
     * @notice GameNotFinished: Payout-related action on a game not yet finished.
     * @param gameId ID of the game.
     * @param actual Current state (not Finished).
     */
    error GameNotFinished(uint256 gameId, GameState actual);
    
    /** 
     * @notice GameIsFinished: Action requiring an active game on a game already finished/cancelled.
     * @param gameId ID of the game.
     */
    error GameIsFinished(uint256 gameId);
    
    /** 
     * @notice RandomnessNotReady: Roles/actions attempted before Pyth randomness fulfilled.
     * @param gameId ID of the game.
     * @param actual Current state, implying randomness not ready.
     */
    error RandomnessNotReady(uint256 gameId, GameState actual);
    
    /** 
     * @notice AlreadyJoined: Player tries to join a game they are already part of.
     * @param gameId ID of the game.
     * @param player Player attempting re-join.
     */
    error AlreadyJoined(uint256 gameId, address player);
    
    /** 
     * @notice PlayerNotInGame: Action for a player not registered in the game.
     * @param gameId ID of the game.
     * @param player Player not found.
     */
    error PlayerNotInGame(uint256 gameId, address player);
    
    /** 
     * @notice PlayerNotAlive: Action requiring an alive player on an eliminated player.
     * @param gameId ID of the game.
     * @param player Player who is not alive.
     */
    error PlayerNotAlive(uint256 gameId, address player);
    
    /** 
     * @notice NotGameHost: Host-specific action by a non-host.
     * @param gameId ID of the game.
     * @param sender Unauthorized caller.
     * @param host Actual game host.
     */
    error NotGameHost(uint256 gameId, address sender, address host);
    
    /** 
     * @notice NotAWitch: Witch-specific action by a non-Witch.
     * @param gameId ID of the game.
     * @param sender Non-Witch caller.
     */
    error NotAWitch(uint256 gameId, address sender);
    
    /** 
     * @notice AlreadyClaimed: Player attempts to claim a payout/refund already received.
     * @param gameId ID of the game.
     * @param player Player attempting double-claim.
     */
    error AlreadyClaimed(uint256 gameId, address player);
    
    /** 
     * @notice InvalidVoteTarget: Vote cast for an invalid target.
     * @param gameId ID of the game.
     * @param target Invalid target address.
     */
    error InvalidVoteTarget(uint256 gameId, address target);
    
    /** 
     * @notice InvalidRevoteTarget: Revote cast for a player not in the current revote.
     * @param gameId ID of the game.
     * @param target Invalid revote target.
     */
    error InvalidRevoteTarget(uint256 gameId, address target);
    
    /** 
     * @notice CannotTargetSelf: Player attempts self-targeting where disallowed.
     * @param gameId ID of the game.
     * @param player Player attempting self-target.
     */
    error CannotTargetSelf(uint256 gameId, address player);
    
    /** @notice NativeTransferFailed: Low-level native $APE transfer (send/call) failed. */
    error NativeTransferFailed();
    
    /** 
     * @notice PayoutRetryInvalid: Admin payout retry for ineligible player or zero amount.
     * @param gameId ID of the game.
     * @param player Player for whom retry is invalid.
     */
    error PayoutRetryInvalid(uint256 gameId, address player);
    
    /** 
     * @notice InvalidCallbackOrigin: Pyth `entropyCallback` called by an unauthorized address.
     * @param expected Expected Pyth Entropy contract address.
     * @param actual Actual caller address.
     */
    error InvalidCallbackOrigin(address expected, address actual);
    
    /** @notice InsufficientContractBalance: Contract's native $APE balance insufficient for a transfer. */
    error InsufficientContractBalance();
    
    /** 
     * @notice InsufficientFundsForPythFee: Contract's native $APE balance insufficient for Pyth fee.
     * @param required $APE amount required for Pyth fee.
     * @param balance Current contract $APE balance.
     */
    error InsufficientFundsForPythFee(uint256 required, uint256 balance);
    
    /** 
     * @notice InvalidSequenceNumber: Invalid or unexpected Pyth sequence number encountered.
     * @param sequenceNum The invalid sequence number.
     */
    error InvalidSequenceNumber(uint64 sequenceNum);
    
    /** @notice NotAuthorized: Restricted function called by an unauthorized address. */
    error NotAuthorized();
    
    /** @notice TimeoutNotReached: Timeout-dependent action attempted before timeout period elapsed. */
    error TimeoutNotReached();
    
    /** 
     * @notice InternalAssignmentError: Internal error during role assignment process.
     * @param gameId ID of game where assignment failed.
     */
    error InternalAssignmentError(uint256 gameId);
    
    /** 
     * @notice CancellationFailed: Game cancellation process failed unexpectedly.
     * @param gameId ID of game where cancellation failed.
     */
    error CancellationFailed(uint256 gameId);

    // =================================================================================================
    // Modifiers
    // =================================================================================================

    /**
     * @notice Ensures a function operates on a valid, existing game.
     * @dev Reverts if `games[_gameId].host` is `address(0)`.
     * @param _gameId The ID of the game to check.
     */
    modifier onlyWhenGameExists(uint256 _gameId) {
        if (games[_gameId].host == address(0)) revert GameNotFound(_gameId);
        _;
    }
    /**
     * @notice Restricts function execution to the game's original host.
     * @dev Reverts if `msg.sender` is not `games[_gameId].host`.
     * @param _gameId The ID of the game to check host against.
     */
    modifier onlyGameHost(uint256 _gameId) {
        if (msg.sender != games[_gameId].host) revert NotGameHost(_gameId, msg.sender, games[_gameId].host);
        _;
    }
    /**
     * @notice Ensures the game is in the specified state.
     * @dev Reverts if `games[_gameId].currentState` is not `_expectedState`.
     * @param _gameId The ID of the game to check state of.
     * @param _expectedState The `GameState` the game must be in.
     */
    modifier inState(uint256 _gameId, GameState _expectedState) {
        GameState cs = games[_gameId].currentState;
        if (cs != _expectedState) revert InvalidGameState(_gameId, _expectedState, cs);
        _;
    }
    /**
     * @notice Ensures the game has not already concluded (Finished or Cancelled).
     * @dev Reverts if `games[_gameId].currentState` is `GameState.Finished` or `GameState.Cancelled`.
     * @param _gameId The ID of the game to check status of.
     */
    modifier notFinished(uint256 _gameId) {
        GameState cs = games[_gameId].currentState;
        if (cs == GameState.Finished || cs == GameState.Cancelled) revert GameIsFinished(_gameId);
        _;
    }
    /**
     * @notice Ensures the player exists and is alive in the game.
     * @dev Reverts if player record doesn't exist or `player.isAlive` is false.
     * @param _gameId The ID of the game.
     * @param _playerAddress The address of the player to check.
     */
    modifier playerIsAlive(uint256 _gameId, address _playerAddress) {
        Player storage p = gamePlayers[_gameId][_playerAddress];
        if (p.playerAddress == address(0)) revert PlayerNotInGame(_gameId, _playerAddress);
        if (!p.isAlive) revert PlayerNotAlive(_gameId, _playerAddress);
        _;
    }
    /**
     * @notice Ensures the player is a participant in the game (alive or dead).
     * @dev Reverts if player record doesn't exist.
     * @param _gameId The ID of the game.
     * @param _playerAddress The address of the player to check.
     */
    modifier playerExists(uint256 _gameId, address _playerAddress) {
        if (gamePlayers[_gameId][_playerAddress].playerAddress == address(0)) revert PlayerNotInGame(_gameId, _playerAddress);
        _;
    }
    /**
     * @notice Restricts function execution to the contract Owner, the configured Backend address, or the configured Validator address.
     * @dev Used for operations requiring a high level of trust or operational capability.
     */
    modifier onlyAuthorizedOps() {
        if (msg.sender != owner() && msg.sender != _backendAddress && msg.sender != _validatorAddress) revert NotAuthorized();
        _;
    }
    /**
     * @notice Restricts function execution strictly to either the configured Backend address or the contract Owner.
     * @dev Used for core game processing functions primarily handled by the backend.
     */
    modifier onlyBackendOrOwner() {
        if (msg.sender != _backendAddress && msg.sender != owner()) revert NotAuthorized();
        _;
    }

    // =================================================================================================
    // Receive Function
    // =================================================================================================

    /**
     * @notice Allows the contract to receive direct native $APE transfers.
     * @dev This is primarily for the owner or system to fund the contract with native tokens
     * required to pay fees for Pyth Network randomness requests, or for other direct deposits if necessary.
     */
    receive() external payable {
        // Accepts native $APE transfers. No specific logic needed here beyond acceptance.
    }

    // =================================================================================================
    // Constructor
    // =================================================================================================

    /**
     * @notice Initializes the DemWitches contract upon deployment.
     * @dev Sets immutable and initial configurable parameters. The `_initialContractOwner` will have
     * administrative privileges to further configure the contract (e.g., backend address, NFT collections).
     * @param _pythEntropyAddr The immutable blockchain address of the Pyth Network Entropy oracle contract. This cannot be changed post-deployment.
     * @param _initialContractOwner The wallet address that will be designated as the initial owner of this contract.
     * @param _initialDefaultFeeBps The initial default platform fee percentage (expressed in basis points, e.g., 100 for 1%) to be applied to games.
     */
    constructor(
        address _pythEntropyAddr,
        address _initialContractOwner,
        uint256 _initialDefaultFeeBps
    ) Ownable(_initialContractOwner) {
        if (_pythEntropyAddr == address(0)) revert ZeroAddress();
        if (_initialDefaultFeeBps > MAX_FEE) revert FeeTooHigh(_initialDefaultFeeBps, MAX_FEE);

        PYTH_ENTROPY = IEntropy(_pythEntropyAddr);
        _feeBps = _initialDefaultFeeBps;

        _nightDuration = 3 * 60; // Default 3 minutes
        _dayDuration = 5 * 60;   // Default 5 minutes
        _revoteDuration = 3 * 60; // Default 3 minutes
        _lobbyTimeoutDuration = 1 * 60 * 60; // Default 1 hour
        _randomnessTimeoutDefault = 1 * 60 * 60; // Default 1 hour

        // Initialize default NFT boost tiers
        _nftBoostTiersCounts = [1, 5, 10];
        _nftBoostTiersBps = [300, 1000, 2000, 3500]; // Corresponds to 0, 1, 2-5, 6-10, 11+

        emit PlatformFeeSet(_initialDefaultFeeBps);
        emit GameDurationsSet(_lobbyTimeoutDuration, _randomnessTimeoutDefault, _nightDuration, _dayDuration, _revoteDuration);
        emit NftBoostTiersSet(_nftBoostTiersCounts, _nftBoostTiersBps);
    }

    // =================================================================================================
    // External Write Functions: Game Setup & Participation
    // =================================================================================================

    /**
     * @notice Create a new game lobby with a fixed buy-in.
     * @dev Allowed by any player. 
     * @param _gameBuyIn The exact native $APE amount (in Wei) required for each player to join.
     * @param _maxPlayers The maximum number of players allowed to join.
     */
    function createLobby(uint256 _gameBuyIn, uint8 _maxPlayers)
        external
        payable
        whenNotPaused
        nonReentrant
    {
        if (_gameBuyIn == 0) { revert IncorrectBuyInAmount(0, 1); } 
        if (_maxPlayers < MIN_PLAYERS || _maxPlayers > MAX_PLAYERS) { revert MaxPlayersInvalid(_maxPlayers); }
        if (msg.value != _gameBuyIn) { revert IncorrectBuyInAmount(msg.value, _gameBuyIn); }

        uint256 gameIdToCreate = _nextGameId++;
        uint256 platformFeeForThisGame = _feeBps;

        GameSettings storage newGame = games[gameIdToCreate];
        newGame.host = msg.sender;
        newGame.gameBuyIn = _gameBuyIn;
        newGame.maxPlayers = _maxPlayers;
        newGame.totalPot = msg.value;
        newGame.platformFeeBasisPoints = platformFeeForThisGame;
        newGame.currentState = GameState.Lobby;
        newGame.createdAt = block.timestamp;

        uint256 hostNftBoostPercentage = RoleLogic.calculateTieredNftBoostInLibrary(msg.sender, _boostedCollection1, _boostedCollection2, _nftBoostTiersCounts, _nftBoostTiersBps);
        gamePlayers[gameIdToCreate][msg.sender] = Player({
            playerAddress: msg.sender, role: Role.Unassigned, isAlive: true,
            claimedPayout: false, nftBoostBps: hostNftBoostPercentage
        });
        playerList[gameIdToCreate].push(msg.sender);
        _totalPlayerFundsLocked += _gameBuyIn;

        emit GameCreated(gameIdToCreate, msg.sender, _gameBuyIn, _maxPlayers, platformFeeForThisGame);
        emit PlayerJoined(gameIdToCreate, msg.sender, hostNftBoostPercentage);
    }

    /**
     * @notice Join a lobby by paying the game buy-in.
     * @dev Allowed by any player. 
     * @param _gameId The ID of the game to join.
     */
    function joinLobby(uint256 _gameId)
        external
        payable
        whenNotPaused
        nonReentrant
        onlyWhenGameExists(_gameId)
        inState(_gameId, GameState.Lobby)
    {
        GameSettings storage gameToJoin = games[_gameId];
        if (playerList[_gameId].length >= gameToJoin.maxPlayers) { revert GameFull(_gameId, gameToJoin.maxPlayers); }
        if (gamePlayers[_gameId][msg.sender].playerAddress != address(0)) { revert AlreadyJoined(_gameId, msg.sender); }
        
        uint256 requiredBuyInAmount = gameToJoin.gameBuyIn;
        if (msg.value != requiredBuyInAmount) { revert IncorrectBuyInAmount(msg.value, requiredBuyInAmount); }

        uint256 playerNftBoostPercentage = RoleLogic.calculateTieredNftBoostInLibrary(msg.sender, _boostedCollection1, _boostedCollection2, _nftBoostTiersCounts, _nftBoostTiersBps);
        gamePlayers[_gameId][msg.sender] = Player({
            playerAddress: msg.sender, role: Role.Unassigned, isAlive: true,
            claimedPayout: false, nftBoostBps: playerNftBoostPercentage
        });
        playerList[_gameId].push(msg.sender);
        gameToJoin.totalPot += msg.value;
        _totalPlayerFundsLocked += msg.value;

        emit PlayerJoined(_gameId, msg.sender, playerNftBoostPercentage);
    }

    /**
     * @notice Start the game.
     * @param _gameId The ID of the game to start.
     */
    function startGame(uint256 _gameId)
        external
        whenNotPaused
        nonReentrant
        onlyWhenGameExists(_gameId)
        onlyGameHost(_gameId)
        inState(_gameId, GameState.Lobby)
    {
        GameSettings storage gameToStart = games[_gameId];
        if (gameToStart.pythSequenceNumber != 0) { revert GameAlreadyStartedOrFinished(_gameId); } 
        
        uint currentPlayersCount = playerList[_gameId].length;
        if (currentPlayersCount < MIN_PLAYERS) { revert NotEnoughPlayers(_gameId, uint8(currentPlayersCount), MIN_PLAYERS); }
        
        address pythProvider = _pythProviderAddress;
        if (pythProvider == address(0)) { revert PythProviderAddressNotSet(); }
        
        uint128 pythOracleFee = PYTH_ENTROPY.getFee(pythProvider);
        if (address(this).balance < pythOracleFee) { revert InsufficientFundsForPythFee(pythOracleFee, address(this).balance); }

        bytes32 randomnessRequestSeed = keccak256(abi.encodePacked(_gameId, block.timestamp, currentPlayersCount, msg.sender));
        uint64 entropySequenceNumber = PYTH_ENTROPY.requestWithCallback{value: pythOracleFee}(pythProvider, randomnessRequestSeed);

        gameToStart.pythSequenceNumber = entropySequenceNumber;
        sequenceNumberToGameId[entropySequenceNumber] = _gameId;
        gameToStart.currentState = GameState.WaitingForRandomness;
        gameToStart.phaseStartTime = block.timestamp;

        emit GameStartRequested(_gameId, msg.sender, entropySequenceNumber);
    }

    /**
     * @notice Claim your reward after the game ends.
     * @dev Allowed by any player on the winning team.
     * @param _gameId The ID of the finished game.
     */
    function claimReward(uint256 _gameId)
        external
        whenNotPaused
        nonReentrant
        onlyWhenGameExists(_gameId)
        inState(_gameId, GameState.Finished)
        playerExists(_gameId, msg.sender)
    {
        Player storage claimingPlayer = gamePlayers[_gameId][msg.sender];
        if (claimingPlayer.claimedPayout) { revert AlreadyClaimed(_gameId, msg.sender); }
        
        GameSettings storage finishedGame = games[_gameId];
        (uint256 payoutAmountDue, ) = PayoutLogic.calculatePayoutInLibrary(
            _gameId,
            msg.sender,
            finishedGame,
            claimingPlayer,
            playerList[_gameId],
            winners[_gameId],
            payoutPerWinnerTownfolk[_gameId]
        );

        if (payoutAmountDue == 0) { revert PayoutRetryInvalid(_gameId, msg.sender); } 
        
        claimingPlayer.claimedPayout = true;

        if (payoutAmountDue <= _totalPlayerFundsLocked) {
            _totalPlayerFundsLocked -= payoutAmountDue;
        } else {
            _totalPlayerFundsLocked = 0; 
        }
        safeNativeTransfer(payable(msg.sender), payoutAmountDue);
        emit PayoutClaimed(_gameId, msg.sender, payoutAmountDue);
    }

    /**
     * @notice Allows the game host to cancel the game in any active state (Lobby, WaitingForRandomness, Night, Day, Revote) after Platform Fee
     * @param _gameId The ID of the game to be cancelled by its host.
     */
    function hostCancelGame(uint256 _gameId)
        external
        whenNotPaused
        nonReentrant
        onlyWhenGameExists(_gameId)
        onlyGameHost(_gameId) // Ensures only the host can call
    {
        GameSettings storage gameToCancel = games[_gameId];
        // Prevent cancellation if already finished or cancelled
        if (gameToCancel.currentState == GameState.Finished || gameToCancel.currentState == GameState.Cancelled) {
            revert GameIsFinished(_gameId);
        }

        uint256 initialPot = gameToCancel.totalPot;
        uint256 feeAmount = 0;

        if (gameToCancel.platformFeeBasisPoints > 0 && townHallWallet != address(0)) {
            feeAmount = (initialPot * gameToCancel.platformFeeBasisPoints) / 10000;
            if (feeAmount > 0 && feeAmount <= initialPot) { // Ensure fee is not more than pot
                safeNativeTransfer(payable(townHallWallet), feeAmount);
            } else {
                feeAmount = 0; // Cannot pay fee if it's zero or exceeds pot
            }
        }

        uint256 netPotForRefunds = initialPot - feeAmount;

        // Decrement global locked funds by the initial pot value as it's now being distributed or paid as fee
        if (initialPot <= _totalPlayerFundsLocked) {
            _totalPlayerFundsLocked -= initialPot;
        } else {
            _totalPlayerFundsLocked = 0; // Should not happen if accounting is correct
        }

        address[] storage playersToRefundList = playerList[_gameId];
        uint256 totalBuyInsForGame = gameToCancel.gameBuyIn * playersToRefundList.length;

        if (totalBuyInsForGame > 0) { // Avoid division by zero if no buy-ins (e.g. empty lobby somehow)
            for (uint i = 0; i < playersToRefundList.length; i++) {
                address playerAddressToRefund = playersToRefundList[i];
                Player storage playerRecord = gamePlayers[_gameId][playerAddressToRefund];
                
                // Calculate proportional refund from the net pot
                uint256 actualRefundAmount = (gameToCancel.gameBuyIn * netPotForRefunds) / totalBuyInsForGame;

                if (actualRefundAmount > 0) {
                    safeNativeTransfer(payable(playerAddressToRefund), actualRefundAmount);
                    emit PlayerRefunded(_gameId, playerAddressToRefund, actualRefundAmount);
                }
                playerRecord.claimedPayout = true; // Mark as "claimed" to prevent other claims
            }
        }

        gameToCancel.currentState = GameState.Cancelled;
        gameToCancel.totalPot = 0; 
        // Consider a new event like HostGameCancelled or add a parameter to GameCancelled
        emit GameCancelled(_gameId, msg.sender, "Cancelled by host; fee deducted, refunds processed.");
    }

    // =================================================================================================
    // External Write Functions: Admin, Owner, and Validator Controls
    // =================================================================================================

    /**
     * @notice Set the trusted backend address.
     * @dev Allowed by owner only.
     * @param _newBackendAddress The new wallet address designated as the trusted backend.
     */
    function adminSetBackend(address _newBackendAddress) external onlyOwner {
        if (_newBackendAddress == address(0)) revert ZeroAddress();
        _adminSetAddresses(_newBackendAddress, address(0), address(0), address(0));
    }

    /**
     * @notice Sets the validator's wallet address.
     * @dev Allowed by owner only.
     * @param _newValidatorAddress The new wallet address designated as the validator.
     */
    function adminSetValidatorAddress(address _newValidatorAddress) external onlyOwner {
        if (_newValidatorAddress == address(0)) revert ZeroAddress();
        _adminSetAddresses(address(0), _newValidatorAddress, address(0), address(0));
    }

    /**
     * @notice Sets the designated treasury wallet for platform fees.
     * @dev Allowed by owner only.
     * @param _newTownHallWallet The new wallet address for collecting platform fees.
     */
    function adminSetTownHallWallet(address _newTownHallWallet) external onlyOwner {
        if (_newTownHallWallet == address(0)) revert ZeroAddress();
        _adminSetAddresses(address(0), address(0), address(0), _newTownHallWallet);
    }

    /**
     * @notice Set the first NFT collection to be eligible for a boost.
     * @dev Allowed by owner only. 
     * @param _collectionAddress The ERC721 contract address of the first eligible NFT collection.
     */
    function adminSetBoostCollection1(address _collectionAddress) external onlyOwner {
        _boostedCollection1 = _collectionAddress;
        emit NftCollectionSet(1, _collectionAddress);
    }

    /**
     * @notice Set the second NFT collection that is boost eligible.
     * @dev Allowed by owner only. 
     * @param _collectionAddress The ERC721 contract address of the second eligible NFT collection.
     */
    function adminSetBoostCollection2(address _collectionAddress) external onlyOwner {
        _boostedCollection2 = _collectionAddress;
        emit NftCollectionSet(2, _collectionAddress);
    }

    /**
     * @notice Set the boost percentage per NFT owned. 
     * @dev Allowed by owner only.
     * @param nftCounts_ Array of NFT count thresholds for tiers (e.g., [1, 5, 10] meaning tiers for 1, 2-5, 6-10 NFTs).
     * @param boostBps_ Array of BPS values for tiers (e.g., [300, 1000, 2000, 3500], corresponding to the tiers defined by `nftCounts_` and one additional for counts exceeding the last threshold). Length must be `nftCounts_.length + 1`.
     */
    function adminSetBoost(uint8[] calldata nftCounts_, uint256[] calldata boostBps_) external onlyOwner {
        if (boostBps_.length != nftCounts_.length + 1) { // e.g., counts [1,5,10] (3 elements) needs BPS [for 0, for 1, for 2-5, for 6-10, for 11+] (5 elements) - wait, this is wrong.
                                                       // If counts is [1,5,10], BPS should be [BPS for 1, BPS for 2-5, BPS for 6-10, BPS for >10]. So length is counts.length + 1.
                                                       // And a BPS for 0 NFTs. So, if counts is [C1, C2, C3], BPS is [BPS_0, BPS_C1, BPS_C2_to_C1, BPS_C3_to_C2, BPS_GT_C3]
                                                       // Let's simplify: counts are upper bounds of previous tier. BPS[0] is for count 0. BPS[i] is for counts up to counts[i-1]. BPS[last] is for > counts[last-1]
                                                       // Example: counts [1, 5, 10]. BPS [BPS_0, BPS_1, BPS_2_5, BPS_6_10, BPS_GT_10] - length 5. counts length 3. boostBps_.length == nftCounts_.length + 2? No.
                                                       // Let's stick to: counts are thresholds. BPS[i] is for count <= counts[i]. BPS[last] is for > counts[last-1].
                                                       // If counts = [1, 5, 10], then BPS = [BPS_for_1, BPS_for_2_to_5, BPS_for_6_to_10, BPS_for_GT_10]. Length is counts.length + 1.
            revert InvalidBoostTierConfiguration(); // Corrected length check
        }
        if (nftCounts_.length > 0) { // Only check sorting if there are counts
            for (uint i = 0; i < nftCounts_.length; i++) {
                if (nftCounts_[i] == 0) revert InvalidBoostTierConfiguration(); // Counts must be > 0
                if (i > 0 && nftCounts_[i] <= nftCounts_[i-1]) revert InvalidBoostTierConfiguration(); // Must be sorted and strictly increasing
            }
        }
        for (uint i = 0; i < boostBps_.length; i++) {
            if (boostBps_[i] > BASIS_POINTS * 10) revert InvalidBoostTierConfiguration(); // Arbitrary sanity check: boost shouldn't be > 1000%
        }

        _nftBoostTiersCounts = nftCounts_;
        _nftBoostTiersBps = boostBps_;
        emit NftBoostTiersSet(nftCounts_, boostBps_);
    }

    /**
     * @notice Set Night, Day,Lobby, Randomness, and Revote durations.
     * @dev Allowed by owner only. 
     * @param lobby_ New duration for lobby timeout in seconds.
     * @param randomness_ New duration for Pyth randomness timeout in seconds.
     * @param night_ The new duration in seconds for the Night phase.
     * @param day_ The new duration in seconds for the Day phase.
     * @param revote_ The new duration in seconds for the Revote phase.
     */
    function adminSetDurations(uint256 lobby_, uint256 randomness_, uint256 night_, uint256 day_, uint256 revote_) external onlyOwner {
        if (night_ < 60 || day_ < 60 || revote_ < 30 || lobby_ < 300 || randomness_ < 300) revert DurationTooShort(); // Min 5 mins for timeouts
        _lobbyTimeoutDuration = lobby_;
        _randomnessTimeoutDefault = randomness_;
        _nightDuration = night_;
        _dayDuration = day_;
        _revoteDuration = revote_;
        emit GameDurationsSet(lobby_, randomness_, night_, day_, revote_);
    }

    /**
     * @notice Eliminate a player from an ongoing game.
     * @dev Allowed by Owner, Backend or Validator. Checks win conditions.
     * @param _gameId The ID of the game from which to eliminate the player.
     * @param _playerAddress The address of the player to be eliminated.
     */
    function adminKill(uint256 _gameId, address _playerAddress)
        external
        onlyWhenGameExists(_gameId)
        playerExists(_gameId, _playerAddress) 
        onlyAuthorizedOps
        notFinished(_gameId)
    {
        if (!gamePlayers[_gameId][_playerAddress].isAlive) revert PlayerNotAlive(_gameId, _playerAddress);
        gamePlayers[_gameId][_playerAddress].isAlive = false;
        emit PlayerInactiveEliminated(_gameId, games[_gameId].currentRound, _playerAddress);
        checkWinConditions(_gameId);
    }

    /**
     * @notice Cancel a game stuck waiting for randomness and refund all players.
     * @dev Allowed by owner or backend only. 
     * @param _gameId The ID of the game stuck in the `WaitingForRandomness` state.
     */
    function backendCancelGame(uint256 _gameId)
        external
        onlyOwner
        nonReentrant
        onlyWhenGameExists(_gameId)
        inState(_gameId, GameState.WaitingForRandomness)
    {
        GameSettings storage gameToResolve = games[_gameId];
        if (block.timestamp < gameToResolve.phaseStartTime + _randomnessTimeoutDefault) revert TimeoutNotReached();
        adminCancelAndRefund(_gameId, "Cancelled by admin: Pyth Network randomness request timed out.");
    }

    /**
     * @notice Manually retry to reward a winning player in a finished game.
     * @dev Allowed by Owner, Backend or Validator.
     * @param _gameId The ID of the finished game.
     * @param _playerAddress The address of the player for whom to retry the payout/refund.
     */
    function adminRetryReward(uint256 _gameId, address _playerAddress)
        external
        nonReentrant
        onlyWhenGameExists(_gameId)
        inState(_gameId, GameState.Finished)
        playerExists(_gameId, _playerAddress)
        onlyAuthorizedOps
    {
        Player storage targetPlayer = gamePlayers[_gameId][_playerAddress];
        if (targetPlayer.claimedPayout) { revert PayoutRetryInvalid(_gameId, _playerAddress); } 
        
        GameSettings storage finishedGame = games[_gameId];
        (uint256 payoutAmountDue, ) = PayoutLogic.calculatePayoutInLibrary(
            _gameId,
            _playerAddress,
            finishedGame,
            targetPlayer,
            playerList[_gameId],
            winners[_gameId],
            payoutPerWinnerTownfolk[_gameId]
        );
        if (payoutAmountDue == 0) { revert PayoutRetryInvalid(_gameId, _playerAddress); }

        if (payoutAmountDue <= _totalPlayerFundsLocked) {
            _totalPlayerFundsLocked -= payoutAmountDue;
        } else {
            _totalPlayerFundsLocked = 0; 
        }
        safeNativeTransfer(payable(_playerAddress), payoutAmountDue);
        targetPlayer.claimedPayout = true;
        emit PayoutRetrySucceeded(_gameId, _playerAddress, payoutAmountDue);
    }

    /**
     * @notice Set the Pyth entropy provider address.
     * @dev Allowed by owner only.
     * @param _providerAddress The new address for the Pyth provider.
     */
    function adminSetPythProvider(address _providerAddress) external onlyOwner {
        if (_providerAddress == address(0)) revert ZeroAddress();
        _adminSetAddresses(address(0), address(0), _providerAddress, address(0));
    }

    /**
     * @notice Set the platform fee in "basis points".
     * @dev Allowed by owner only.
     * @param _newFeeBps The new default platform fee percentage in basis points.
     */
    function adminSetFee(uint256 _newFeeBps) external onlyOwner {
        if (_newFeeBps > MAX_FEE) revert FeeTooHigh(_newFeeBps, MAX_FEE);
        _feeBps = _newFeeBps;
        emit PlatformFeeSet(_newFeeBps);
    }

    /**
     * @notice Pause the entire contract.
     * @dev Allowed by owner only.
     */
    function adminPause() external onlyOwner whenNotPaused {
        _pause();
        emit GamePaused(msg.sender);
    }

    /**
     * @notice Unpause the contract.
     * @dev Allowed by owner only.
     */
    function adminUnpause() external onlyOwner whenPaused {
        _unpause();
        emit GameUnpaused(msg.sender);
    }

    /**
     * @notice Cancel any game at any stage.
     * @dev Allowed by owner only.
     * @param _gameId The ID of the game to be cancelled.
     */
    function adminCancelGame(uint256 _gameId)
        external
        nonReentrant
        onlyWhenGameExists(_gameId)
        inState(_gameId, GameState.Lobby)
    {
        bool isHost = (msg.sender == games[_gameId].host);
        bool isOwner = (msg.sender == owner());
        bool isBackend = (msg.sender == _backendAddress);
        bool isValidator = (msg.sender == _validatorAddress);
        if (!(isHost || isOwner || isBackend || isValidator)) revert NotAuthorized();
        adminCancelAndRefund(_gameId, isHost ? "Cancelled by host while in Lobby state." : "Cancelled by admin/backend/validator while in Lobby state.");
    }

    /**
     * @notice Force the game into either Night or Day (or other states).
     * @dev Allowed by owner only. 
     * @param _gameId The ID of the game to modify.
     * @param _newState The target `GameState` to transition the game into.
     */
    function adminSetPhase(uint256 _gameId, GameState _newState) external onlyOwner onlyWhenGameExists(_gameId) {
        GameSettings storage gameToForce = games[_gameId];
        GameState oldState = gameToForce.currentState;
        if (_newState == GameState.Lobby) {
            require(oldState == GameState.Lobby || oldState == GameState.Cancelled, "AdminSetPhase: Cannot forcibly revert an active or finished game back to Lobby state.");
        }
        if (_newState == GameState.WaitingForRandomness) {
            require(oldState == GameState.Lobby, "AdminSetPhase: Can only forcibly transition to WaitingForRandomness state from Lobby state.");
        }
        if (_newState == GameState.Finished && gameToForce.winningTeam == Role.Unassigned) {
            revert("AdminSetPhase: Winning team must be determined before game can be forcibly set to Finished state.");
        }
        gameToForce.currentState = _newState;
        if (_newState == GameState.Night || _newState == GameState.Day || _newState == GameState.Revote) {
            gameToForce.phaseStartTime = block.timestamp;
            if (_newState == GameState.Night && gameToForce.currentRound == 0) {
                gameToForce.currentRound = 1;
            }
        }
        emit AdminForcedState(_gameId, oldState, _newState, msg.sender);
    }

    // /**
    //  * @notice Throw this game in the dumpster and "soft rug" everyone (renounce ownership).
    //  * @dev Allowed by owner only.
    //  */
    // function adminGiveUp() external onlyOwner {
    //     renounceOwnership();
    // }

    // /**
    //  * @notice Give the whole project away to someone else (transfer ownership).
    //  * @dev Allowed by owner only.
    //  * @param _newOwner The address of the new owner who will gain administrative control.
    //  */
    // function adminNewOwner(address _newOwner) external onlyOwner {
    //     transferOwnership(_newOwner);
    // }

    // =================================================================================================
    // External Write Functions: Restricted Phase Processing (Called by Backend/Owner)
    // =================================================================================================

    /**
     * @notice Process witch's vote to determine which player is cursed.
     * @param _gameId The ID of the game.
     * @param _targetAddress The address of the player targeted.
     */
    function voteAndCurse(uint256 _gameId, address _targetAddress)
        external
        whenNotPaused
        nonReentrant
        onlyWhenGameExists(_gameId)
        inState(_gameId, GameState.Night)
        onlyBackendOrOwner
    {
        processNightInternal(_gameId, _targetAddress);
    }

    /**
     * @notice Process daytime votes to determine if a suspect is burned or if a revote is needed.
     * @param _gameId The ID of the game.
     * @param _voters Array of addresses of players who submitted a valid vote/abstain action to the backend.
     * @param _votedFor Parallel array indicating the address each voter in `_voters` voted for. `address(0)` signifies an abstain vote or no valid vote from a player.
     */
    function voteAndBurn(uint256 _gameId, address[] memory _voters, address[] memory _votedFor)
        external
        whenNotPaused
        nonReentrant
        onlyWhenGameExists(_gameId)
        inState(_gameId, GameState.Day)
        onlyBackendOrOwner
    {
        processDayInternal(_gameId, _voters, _votedFor);
    }

    /**
     * @notice Process revote results and burn the losing player.
     * @param _gameId The ID of the game.
     * @param _voters An array of addresses of players who submitted a valid revote action to the backend.
     * @param _votedFor A parallel array indicating the address each voter in `_voters` voted for. The target must be one of the `playersInRevote` list.
     */
    function voteAgain(uint256 _gameId, address[] memory _voters, address[] memory _votedFor)
        external
        whenNotPaused
        nonReentrant
        onlyWhenGameExists(_gameId)
        inState(_gameId, GameState.Revote)
        onlyBackendOrOwner
    {
        processRevoteInternal(_gameId, _voters, _votedFor);
    }

    // =================================================================================================
    // External Write Functions: Public Fallbacks for Liveness
    // =================================================================================================

    /**
     * @notice Force a Night/Day transition, if for some reason it failed
     * @dev Allowed by any player. 
     * @param _gameId The ID of the potentially stalled game.
     */
    function checkTime(uint256 _gameId)
        external
        whenNotPaused
        onlyWhenGameExists(_gameId)
        notFinished(_gameId)
    {
        GameSettings storage gameToAdvance = games[_gameId];
        GameState currentPhase = gameToAdvance.currentState;
        uint256 phaseStartTime = gameToAdvance.phaseStartTime;
        uint256 phaseConfiguredDuration;

        if (currentPhase == GameState.Night) { phaseConfiguredDuration = _nightDuration; }
        else if (currentPhase == GameState.Day) { phaseConfiguredDuration = _dayDuration; }
        else if (currentPhase == GameState.Revote) { phaseConfiguredDuration = _revoteDuration; }
        else { revert WrongGameStateForAction(_gameId, currentPhase); }

        uint256 backendActionDeadline = phaseStartTime + phaseConfiguredDuration + GRACE_DURATION;
        if (block.timestamp < backendActionDeadline) { revert TimeoutNotReached(); }

        emit PhaseForcedByTimeout(_gameId, currentPhase, gameToAdvance.currentRound, "Public timeout fallback invoked by user.");

        if (currentPhase == GameState.Day || currentPhase == GameState.Revote) {
            if (currentPhase == GameState.Revote) { delete playersInRevote[_gameId]; }
            if (checkWinConditions(_gameId)) { return; }
            transitionToNight(_gameId);
        }
        else if (currentPhase == GameState.Night) {
            if (checkWinConditions(_gameId)) { return; }
            gameToAdvance.currentState = GameState.Day;
            gameToAdvance.phaseStartTime = block.timestamp;
            emit DayPhaseStarted(_gameId, gameToAdvance.currentRound, address(0));
        }
    }

    /**
     * @notice Cancels unstarted game if Lobby phase times out. Refunds players.
     * @dev Allowed by any player.
     * @param _gameId The ID of the game stuck in the Lobby state.
     */
    function triggerLobbyTimeout(uint256 _gameId)
        external
        whenNotPaused
        nonReentrant
        onlyWhenGameExists(_gameId)
        inState(_gameId, GameState.Lobby)
    {
        GameSettings storage gameToCancel = games[_gameId];
        if (block.timestamp < gameToCancel.createdAt + _lobbyTimeoutDuration) revert TimeoutNotReached();
        adminCancelAndRefund(_gameId, "Cancelled due to Lobby phase timeout (public call).");
    }

    // =================================================================================================
    // Internal Functions (Helper Logic - Renamed)
    // =================================================================================================

    /**
     * @dev Internal core logic for processing Witch's night action. Helper for voteAndCurse.
     * @param _gameId The ID of the game.
     * @param _targetAddress The address targeted by the Witch.
     */
    function processNightInternal(uint256 _gameId, address _targetAddress) internal {
        GameSettings storage currentGame = games[_gameId];
        uint256 currentRoundNumber = currentGame.currentRound;

        if (_targetAddress != address(0) &&
            gamePlayers[_gameId][_targetAddress].playerAddress != address(0) && 
            gamePlayers[_gameId][_targetAddress].isAlive) { 
            gamePlayers[_gameId][_targetAddress].isAlive = false;
            emit PlayerCursed(_gameId, currentRoundNumber, _targetAddress);
        }

        uint aliveTownfolkCount = 0;
        uint aliveWitchesCount = 0;
        address lastLivingTownfolk = address(0);
        address[] storage currentPlayersInGame = playerList[_gameId];

        for (uint i = 0; i < currentPlayersInGame.length; i++) {
            Player storage p = gamePlayers[_gameId][currentPlayersInGame[i]];
            if (p.isAlive) {
                if (p.role == Role.Witch) { aliveWitchesCount++; }
                else if (p.role == Role.Townfolk) { aliveTownfolkCount++; lastLivingTownfolk = currentPlayersInGame[i]; }
            }
        }
        
        if (_targetAddress != address(0) && 
            gamePlayers[_gameId][_targetAddress].playerAddress != address(0) &&
            !gamePlayers[_gameId][_targetAddress].isAlive && 
            gamePlayers[_gameId][_targetAddress].role == Role.Townfolk && 
            aliveWitchesCount == WITCH_COUNT && 
            aliveTownfolkCount == 1 && 
            lastLivingTownfolk != address(0) && // Ensure a last one exists
            lastLivingTownfolk != _targetAddress) { // Ensure the one killed wasn't the last one already (though redundant due to aliveTownfolkCount == 1 logic)
            
            gamePlayers[_gameId][lastLivingTownfolk].isAlive = false;
            emit WitchKilledLastTownfolk(_gameId, currentRoundNumber, lastLivingTownfolk);
        }


        if (checkWinConditions(_gameId)) { return; }

        currentGame.currentState = GameState.Day;
        currentGame.phaseStartTime = block.timestamp;
        emit DayPhaseStarted(_gameId, currentRoundNumber, _targetAddress); 
    }

    /**
     * @dev Internal core logic for processing Day phase vote results. Helper for voteAndBurn.
     * @param _gameId The ID of the game.
     * @param _voters Array of voters provided by the backend.
     * @param _votedFor Parallel array of targets voted for by the backend.
     */
    function processDayInternal(uint256 _gameId, address[] memory _voters, address[] memory _votedFor) internal {
        if (_voters.length != _votedFor.length) revert("Internal: Vote array length mismatch.");
        GameSettings storage currentGame = games[_gameId];
        uint256 currentRoundNumber = currentGame.currentRound;

        VoteLogic.VoteOutcome memory outcome = VoteLogic.tallyVotesAndDetermineOutcomeInLibrary(
            _voters,
            _votedFor,
            playerList[_gameId],
            gamePlayers[_gameId]
        );
        
        if (outcome.tieCount == 1 && outcome.playerToBurn != address(0) && outcome.maxVoteCount > 0) { 
            gamePlayers[_gameId][outcome.playerToBurn].isAlive = false;
            emit PlayerBurned(_gameId, currentRoundNumber, outcome.playerToBurn, outcome.maxVoteCount);
            if (checkWinConditions(_gameId)) { return; }
            transitionToNight(_gameId);
        } else if (outcome.tieCount > 1 && outcome.maxVoteCount > 0) { 
            // outcome.tiedPlayers is already correctly sized
            emit VoteTied(_gameId, currentRoundNumber, outcome.tiedPlayers);
            delete playersInRevote[_gameId]; 
            address[] storage revoteParticipants = playersInRevote[_gameId];
            
            uint validPlayersForRevoteRound = 0;
            for (uint i = 0; i < outcome.tiedPlayers.length; i++) {
                // Ensure players added to revote are still alive (though tallyVotes should only consider alive)
                if (gamePlayers[_gameId][outcome.tiedPlayers[i]].isAlive) { 
                    revoteParticipants.push(outcome.tiedPlayers[i]);
                    validPlayersForRevoteRound++;
                }
            }

            if (validPlayersForRevoteRound < 2) { 
                emit RevoteTied(_gameId, currentRoundNumber); 
                delete playersInRevote[_gameId]; // Clear again if revote is not possible
                if (checkWinConditions(_gameId)) { return; }
                transitionToNight(_gameId);
            } else {
                currentGame.currentState = GameState.Revote;
                currentGame.phaseStartTime = block.timestamp;
                // Ensure revoteParticipants is correctly passed if it was re-filtered
                address[] memory finalRevoteParticipants = new address[](validPlayersForRevoteRound);
                for(uint i = 0; i < validPlayersForRevoteRound; i++){
                    finalRevoteParticipants[i] = revoteParticipants[i];
                }
                emit RevotePhaseStarted(_gameId, currentRoundNumber, finalRevoteParticipants);
            }
        } else { // No one burned, no tie leading to revote (e.g. all abstain or no valid votes)
            emit VoteTied(_gameId, currentRoundNumber, new address[](0)); 
            if (checkWinConditions(_gameId)) { return; }
            transitionToNight(_gameId);
        }
    }

    /**
     * @dev Internal core logic for processing Revote phase results. Helper for voteAgain.
     * @param _gameId The ID of the game.
     * @param _voters Array of voters from backend.
     * @param _votedFor Parallel array of targets from backend (must be among revote candidates).
     */
    function processRevoteInternal(uint256 _gameId, address[] memory _voters, address[] memory _votedFor) internal {
        if (_voters.length != _votedFor.length) revert("Internal: Input array length mismatch.");
        GameSettings storage currentGame = games[_gameId];
        uint256 currentRoundNumber = currentGame.currentRound;
        // Note: The VoteLogic library expects the full playerList for context, 
        // but the actual voting targets should be implicitly filtered by the backend to only include playersInRevote.
        // The library itself will check if targets are alive and in the playerList.

        // The `playersInRevote[_gameId]` list is primarily for knowing WHO can be voted for.
        // The backend should ensure `_votedFor` targets are from this list.
        // `tallyVotesAndDetermineOutcomeInLibrary` will use `playerList[_gameId]` to map votes.
        
        if (playersInRevote[_gameId].length == 0) { // Should not happen if revote phase started correctly
            delete playersInRevote[_gameId]; // Ensure cleanup
            if (checkWinConditions(_gameId)) { return; }
            transitionToNight(_gameId);
            return;
        }

        VoteLogic.VoteOutcome memory outcome = VoteLogic.tallyVotesAndDetermineOutcomeInLibrary(
            _voters,
            _votedFor,
            playerList[_gameId], // Full player list for indexing votes
            gamePlayers[_gameId]
        );

        // After tallying, we must ensure the playerToBurn (if any) was actually part of the revote candidates.
        // This is an important validation step if the library is generic.
        bool isValidRevoteTargetBurned = false;
        if (outcome.playerToBurn != address(0)) {
            for (uint i = 0; i < playersInRevote[_gameId].length; i++) {
                if (playersInRevote[_gameId][i] == outcome.playerToBurn) {
                    isValidRevoteTargetBurned = true;
                    break;
                }
            }
        }

        if (outcome.tieCount == 1 && outcome.playerToBurn != address(0) && outcome.maxVoteCount > 0 && isValidRevoteTargetBurned) {
            gamePlayers[_gameId][outcome.playerToBurn].isAlive = false;
            emit PlayerBurnedAfterRevote(_gameId, currentRoundNumber, outcome.playerToBurn, outcome.maxVoteCount);
        } else { 
            // This includes cases where:
            // - No one got votes (maxVoteCount == 0)
            // - Tie was > 1 (outcome.tieCount > 1)
            // - The playerToBurn was not a valid revote candidate (isValidRevoteTargetBurned == false)
            emit RevoteTied(_gameId, currentRoundNumber);
        }

        delete playersInRevote[_gameId];
        if (checkWinConditions(_gameId)) { return; }
        transitionToNight(_gameId);
    }

    /**
     * @dev Internal: Cancels game and refunds all players their buy-in. Helper for admin cancellation functions.
     * @param _gameId The ID of the game to cancel.
     * @param _reason A string describing the reason for cancellation, logged in the `GameCancelled` event.
     */
    function adminCancelAndRefund(uint256 _gameId, string memory _reason) internal {
        GameSettings storage gameToCancel = games[_gameId];
        address[] storage playersToRefund = playerList[_gameId];
        uint256 totalPotToRefund = gameToCancel.totalPot;

        if (totalPotToRefund > _totalPlayerFundsLocked) { 
             _totalPlayerFundsLocked = 0; 
        } else {
            _totalPlayerFundsLocked -= totalPotToRefund;
        }
        
        if (totalPotToRefund > 0 && playersToRefund.length > 0) {
            if (address(this).balance < totalPotToRefund) revert InsufficientContractBalance();
            
            uint256 refundAmountPerPlayer = gameToCancel.gameBuyIn;
            if (refundAmountPerPlayer > 0) { 
                for (uint i = 0; i < playersToRefund.length; i++) {
                    address payable playerAddressToRefund = payable(playersToRefund[i]);
                    if(gamePlayers[_gameId][playerAddressToRefund].playerAddress != address(0)) {
                        gamePlayers[_gameId][playerAddressToRefund].claimedPayout = true; 
                    }
                    safeNativeTransfer(playerAddressToRefund, refundAmountPerPlayer);
                    emit PlayerRefunded(_gameId, playerAddressToRefund, refundAmountPerPlayer);
                }
            }
        }

        gameToCancel.currentState = GameState.Cancelled;
        gameToCancel.totalPot = 0; 
        emit GameCancelled(_gameId, msg.sender, _reason);
    }

    /**
     * @dev Internal: Assigns Witch role using weighted randomness based on cached NFT boost. Core logic for role assignment.
     * @param _gameId The ID of the game for which roles are being assigned.
     * @param _randomNumber The secure random number obtained from the Pyth Network oracle.
     */
    // function assignRolesWeighted(uint256 _gameId, uint256 _randomNumber) internal { ... } // Moved to RoleLogic.sol
    // function calculateTieredNftBoost(address _playerAddress) internal view returns (uint256 calculatedBoostBps) { ... } // Moved to RoleLogic.sol

    /**
     * @dev Internal: Checks win conditions finalizes game transfers fees prepares payout data. Core game end logic.
     * @param _gameId The ID of the game to check for win conditions.
     * @return isGameOver True if the game has ended as a result of this check, false otherwise.
     */
    function checkWinConditions(uint256 _gameId) internal returns (bool isGameOver) {
        uint aliveTownfolkCount = 0;
        uint aliveWitchesCount = 0;
        address[] storage currentPlayersInGame = playerList[_gameId];
        uint totalPlayerCountInList = currentPlayersInGame.length;

        for (uint i = 0; i < totalPlayerCountInList; i++) {
            Player storage p = gamePlayers[_gameId][currentPlayersInGame[i]];
            if (p.isAlive) {
                if (p.role == Role.Witch) { aliveWitchesCount++; }
                else if (p.role == Role.Townfolk) { aliveTownfolkCount++; }
            }
        }

        Role gameWinningTeam = Role.Unassigned;
        if (aliveTownfolkCount == 0 && aliveWitchesCount >= WITCH_COUNT) { 
            gameWinningTeam = Role.Witch;
            isGameOver = true;
        } 
        else if (aliveWitchesCount == 0 && aliveTownfolkCount > 0) {
            gameWinningTeam = Role.Townfolk;
            isGameOver = true;
        }

        if (isGameOver) {
            GameSettings storage finishedGame = games[_gameId];
            // uint256 initialTotalPotForPayoutCalcs = finishedGame.totalPot; // This local var is no longer used directly here

            finishedGame.currentState = GameState.Finished;
            finishedGame.winningTeam = gameWinningTeam;

            delete winners[_gameId]; 
            // finishedGame.deadTownfolkRefundPool = 0; // Will be set by PayoutLogic calculations if needed

            address[] storage gameWinnersListStorage = winners[_gameId]; 
            uint actualWinnerCount = 0;

            // Call PayoutLogic to get fee and refund pool calculations
            PayoutLogic.FeeAndRefundCalculationsOutput memory payoutCalcs = PayoutLogic.calculateFeesAndRefundPoolInLibrary(
                finishedGame,
                currentPlayersInGame, // playerList[_gameId]
                gamePlayers[_gameId]
            );

            uint256 platformFeeAmount = payoutCalcs.platformFeeAmount;
            uint256 potRemainingAfterFee = payoutCalcs.potRemainingAfterFee;
            finishedGame.deadTownfolkRefundPool = payoutCalcs.totalAllocatedForDeadTownfolkRefunds; // Set based on library calculation
            
            if (platformFeeAmount > 0) {
                address currentTownHallWallet = townHallWallet;
                if (currentTownHallWallet == address(0)) { revert TownHallWalletNotSet(); }
                if (platformFeeAmount <= _totalPlayerFundsLocked) {
                    _totalPlayerFundsLocked -= platformFeeAmount; 
                } else {
                    _totalPlayerFundsLocked = 0;
                }
                safeNativeTransfer(payable(currentTownHallWallet), platformFeeAmount);
            }
            
            uint256 potAvailableForPlayerDistribution = potRemainingAfterFee;
            // Adjust potAvailableForPlayerDistribution based on the calculated deadTownfolkRefundPool
            if (finishedGame.deadTownfolkRefundPool <= potAvailableForPlayerDistribution) {
                potAvailableForPlayerDistribution -= finishedGame.deadTownfolkRefundPool;
            } else {
                // This case implies refunds exceed pot after fee, should ideally not happen with correct pot management
                potAvailableForPlayerDistribution = 0; 
            }

            for (uint i = 0; i < totalPlayerCountInList; i++) {
                Player storage p = gamePlayers[_gameId][currentPlayersInGame[i]];
                if (p.isAlive && p.role == gameWinningTeam) {
                    gameWinnersListStorage.push(currentPlayersInGame[i]);
                    actualWinnerCount++;
                }
            }

            uint256 payoutAmountPerLivingWinner = 0;
            if (actualWinnerCount > 0) {
                payoutAmountPerLivingWinner = potAvailableForPlayerDistribution / actualWinnerCount;
            }
            
            if (gameWinningTeam == Role.Townfolk) {
                 payoutPerWinnerTownfolk[_gameId] = payoutAmountPerLivingWinner;
            }
            
            // Update game's totalPot to reflect amount after fee and distributed to players (or available for claims)
            // It would be sum of (payoutAmountPerLivingWinner * actualWinnerCount) + finishedGame.deadTownfolkRefundPool
            // This line might not be necessary if totalPot is implicitly handled by _totalPlayerFundsLocked decrements
            // finishedGame.totalPot = (payoutAmountPerLivingWinner * actualWinnerCount) + finishedGame.deadTownfolkRefundPool;


            emit GameFinished(_gameId, gameWinningTeam, gameWinnersListStorage, payoutAmountPerLivingWinner);
        }
        return isGameOver;
    }

    /**
     * @dev Internal: Transitions game to the next Night phase. Helper for phase management.
     * @param _gameId The ID of the game to transition.
     */
    function transitionToNight(uint256 _gameId) internal {
        GameSettings storage gameToTransition = games[_gameId];
        gameToTransition.currentState = GameState.Night;
        gameToTransition.currentRound++;
        gameToTransition.phaseStartTime = block.timestamp;
        emit NightPhaseStarted(_gameId, gameToTransition.currentRound);
    }

    /**
     * @dev Internal: Safely transfers native $APE with balance check and revert on failure.
     * @param _to The payable address of the recipient.
     * @param _amount The amount of native $APE (in Wei) to send.
     */
    function safeNativeTransfer(address payable _to, uint256 _amount) internal {
        if (_amount == 0) { return; } 
        if (address(this).balance < _amount) revert InsufficientContractBalance();
        (bool success, ) = _to.call{value: _amount}("");
        if (!success) revert NativeTransferFailed();
    }

    /**
     * @dev Internal: Calculates payout/refund for a player in a finished game. Core logic for determining player rewards.
     * @param _gameId Game ID.
     * @param _playerAddress Address of the player.
     * @param game A storage reference to the game's settings.
     * @param player A storage reference to the player's data.
     * @return playerPayoutAmount The amount of native $APE (in Wei) due to the player.
     * @return platformFeeAmountForInfo The total platform fee (in Wei) collected from this specific game's prize pool.
     */
    // function calculatePayoutInternal(...) is now moved to PayoutLogic.sol


    // =================================================================================================
    // Pyth Network Required Functions
    // =================================================================================================

    /**
     * @notice Returns the configured Pyth Entropy contract address.
     * @dev This function is a requirement of the `IEntropyConsumer` interface of the Pyth SDK integration.
     * @return The deployed blockchain address of the Pyth Entropy oracle.
     */
    function getEntropy() internal view override returns (address) {
        return address(PYTH_ENTROPY);
    }

    /**
     * @notice This is the callback function invoked by the Pyth Network Entropy oracle to deliver a secure random number.
     * @dev An internal override called automatically by the Pyth Entropy contract. Assigns roles to players and transitions the game to the first Night phase.
     * @param _sequenceNumber The unique ID of the randomness request that this callback corresponds to.
     * @param _randomNumber The cryptographically secure random number (as a `bytes32` hash) delivered by the Pyth Network oracle.
     */
    function entropyCallback(uint64 _sequenceNumber, address /*_unusedProviderParam*/, bytes32 _randomNumber)
        internal
        override
        nonReentrant
        whenNotPaused
    {
        if (msg.sender != address(PYTH_ENTROPY)) { revert InvalidCallbackOrigin(address(PYTH_ENTROPY), msg.sender); }
        
        uint256 gameIdForCallback = sequenceNumberToGameId[_sequenceNumber];
        if (gameIdForCallback >= _nextGameId && _nextGameId > 0) { return; }
        
        GameSettings storage gameForRoles = games[gameIdForCallback];
        if (gameForRoles.host == address(0)) { return; } 

        if (gameForRoles.pythSequenceNumber != _sequenceNumber) { revert InvalidSequenceNumber(_sequenceNumber); }
        if (gameForRoles.currentState != GameState.WaitingForRandomness) { 
            return; 
        }
        if (gameForRoles.isRandomnessFulfilled) { return; }

        gameForRoles.isRandomnessFulfilled = true;
        emit RandomnessReceived(gameIdForCallback, _sequenceNumber);
        
        // Call the library function for assigning roles
        RoleLogic.assignRolesWeightedInLibrary(
            gamePlayers[gameIdForCallback],
            playerList[gameIdForCallback],
            uint256(_randomNumber)
        );
        delete sequenceNumberToGameId[_sequenceNumber]; 

        gameForRoles.currentState = GameState.Night;
        gameForRoles.currentRound = 1; 
        gameForRoles.phaseStartTime = block.timestamp;
        emit RolesAssigned(gameIdForCallback);
        emit NightPhaseStarted(gameIdForCallback, gameForRoles.currentRound);
    }

    // =================================================================================================
    // View Functions (Publicly Accessible Data Retrievers)
    // =================================================================================================

    /**
     * @notice Retrieve current lobby/game settings players and winners.
     * @param _gameId The ID of the game to query.
     * @return settings A `GameSettings` struct containing all configuration and current state data for the requested game.
     * @return currentPlayers An array of addresses of all players who have joined this game.
     * @return gameWinners An array of addresses of players who were declared winners. Empty if game not finished.
     */
    function getGameSettings(uint256 _gameId)
        external view
        onlyWhenGameExists(_gameId)
        returns (GameSettings memory settings, address[] memory currentPlayers, address[] memory gameWinners)
    {
        settings = games[_gameId];
        currentPlayers = playerList[_gameId];
        if (settings.currentState == GameState.Finished) {
            gameWinners = winners[_gameId];
        } else {
            gameWinners = new address[](0); 
        }
    }

    /**
     * @notice Retrieve information about an active player (address, status, reward, claim, NFT boost).
     * @param _gameId The ID of the game in which to retrieve player details.
     * @param _playerAddress The wallet address of the player whose details are requested.
     * @return publicInfo A `PublicPlayerInfo` struct containing the public data for the specified player.
     */
    function getPlayerInfo(uint256 _gameId, address _playerAddress)
        external view
        onlyWhenGameExists(_gameId)
        playerExists(_gameId, _playerAddress)
        returns (PublicPlayerInfo memory publicInfo)
    {
        Player storage p = gamePlayers[_gameId][_playerAddress];
        publicInfo = PublicPlayerInfo({
            playerAddress: p.playerAddress,
            isAlive: p.isAlive,
            claimedPayout: p.claimedPayout,
            nftBoostBps: p.nftBoostBps
        });
    }

    /**
     * @notice Retrieve your role. 0 for unassigned 1 for townfolk 2 for witch.
     * @param _gameId The ID of the game to check the role in.
     * @return playerRole The calling player's role in the specified game.
     */
    function getMyRole(uint256 _gameId)
        external view
        returns (Role playerRole)
    {
        if (games[_gameId].host == address(0)) { return Role.Unassigned; } 
        if (gamePlayers[_gameId][msg.sender].playerAddress != msg.sender) { return Role.Unassigned; } 
        
        GameSettings storage gameToCheck = games[_gameId];
        if (!gameToCheck.isRandomnessFulfilled || 
            gameToCheck.currentState == GameState.Lobby || 
            gameToCheck.currentState == GameState.WaitingForRandomness) {
            return Role.Unassigned; 
        }
        return gamePlayers[_gameId][msg.sender].role;
    }

    /**
     * @notice Retrieve the potential reward for a player if the game were to end right now.
     * @dev Returns potential payout.
     * @param _gameId The ID of the game, which must be in the `Finished` state.
     * @param _playerAddress The address of the player whose potential reward is being checked.
     * @return payoutAmount The amount of native $APE (in Wei) that the player is eligible to claim.
     * @return platformFeeForGame The total platform fee (in Wei) collected from this game's prize pool.
     */
    function getPotentialReward(uint256 _gameId, address _playerAddress)
        external view
        onlyWhenGameExists(_gameId)
        inState(_gameId, GameState.Finished)
        playerExists(_gameId, _playerAddress)
        returns (uint256 payoutAmount, uint256 platformFeeForGame)
    {
        GameSettings storage finishedGame = games[_gameId];
        Player storage targetPlayer = gamePlayers[_gameId][_playerAddress];
        return PayoutLogic.calculatePayoutInLibrary(
            _gameId,
            _playerAddress,
            finishedGame,
            targetPlayer,
            playerList[_gameId],
            winners[_gameId],
            payoutPerWinnerTownfolk[_gameId]
        );
    }

    /**
     * @notice Retrieve the contract addresses of any NFTcollections receiving a boost, increasing hodlers liklihood of being the Witch.
     * @return collection1 The address of the first boosted NFT collection.
     * @return collection2 The address of the second boosted NFT collection.
     */
    function getBoostedCollections() external view returns (address collection1, address collection2) {
        collection1 = _boostedCollection1;
        collection2 = _boostedCollection2;
    }

    /**
     * @notice This query returns big butts.
     * @return A string literal representing the meme.
     */
    function getBigButts() external pure returns (string memory) {
        return unicode"(‿ˠ‿)";
    }

    /**
     * @notice Retrieves the current NFT boost tier configuration.
     * @return counts Array of NFT count thresholds.
     * @return bps Array of boost BPS values for the tiers.
     */
    function nftBoost() external view returns (uint8[] memory counts, uint256[] memory bps) {
        return (_nftBoostTiersCounts, _nftBoostTiersBps);
    }

    // --- Custom Getters for Internalized State Variables ---

    /**
     * @notice The ID of the next game to be created.
     * @return The numerical ID of the next game lobby.
     */
    function nextGameID() external view returns (uint256) { return _nextGameId; }

    /**
     * @notice The currently set platform fee displayed as a percentage (in Basis Points).
     * @return The current default platform fee in basis points.
     */
    function fee() external view returns (uint256) { return _feeBps; }

    /**
     * @notice The current set Pyth Provider address.
     * @return The address of the Pyth provider.
     */
    function pythProviderAddress() external view returns (address) { return _pythProviderAddress; }

    /**
     * @notice This is the current backend address that controls off-chain mechanics.
     * @dev Returns backend address.
     * @return The address of the trusted backend.
     */
    function backendAddress() external view returns (address) { return _backendAddress; }

    /**
     * @notice The address of the configured game validator.
     * @return The address designated as the game validator.
     */
    function getValidatorAddress() external view returns (address) { return _validatorAddress; }

    /**
     * @notice The designated wallet for platform fees.
     * @return The address of the town hall (treasury) wallet.
     */
    function getTownHallWallet() external view returns (address) { return townHallWallet; }

    /**
     * @notice Total player funds currently locked in active game pots or pending claims. 
     * @dev Returns total locked funds.
     * @return The total amount of player funds locked in Wei
     */
    function getPlayersFundsLocked() external view returns (uint256) { return _totalPlayerFundsLocked; }
 
    /**
     * @notice The address of the first NFT collection that receives a boost, Increasing their likelihood of being assigned the Witch role.
     * @return The address of the first boosted NFT collection (0x0 if not set).
     */
    function boostedCollection1() external view returns (address) { return _boostedCollection1; }

    /**
     * @notice The address of the second NFT collection that receives a boost, Increasing their likelihood of being assigned the Witch role.
     * @return The address of the second boosted NFT collection (0x0 if not set).
     */
    function boostedCollection2() external view returns (address) { return _boostedCollection2; }

    /**
     * @notice The set duration of each night phase.
     * @return The duration of each Night phase in seconds.
     */
    function nightDuration() external view returns (uint256) { return _nightDuration; }

    /**
     * @notice The duration of each Day phase displayed in seconds.
     * @return The duration of each Day phase in seconds.
     */
    function dayDuration() external view returns (uint256) { return _dayDuration; }

    /**
     * @notice The current set duration for each revote phase.
     * @return The duration of each Revote phase in seconds.
     */
    function revoteDuration() external view returns (uint256) { return _revoteDuration; }

    /**
     * @notice The Pyth Entropy Address 
     * @return The address of the Pyth Entropy oracle.
     */
    function pythEntropyAddress() external view returns (address) { return address(PYTH_ENTROPY); }

    /**
     * @notice Retrieve all information about any game.
     * @dev Allowed by all players.
     * @param _gameId The unique ID of the game to query.
     * @return settings A `GameSettings` struct containing the complete settings and current state for the requested game.
     */
    function getGameInfo(uint256 _gameId) external view onlyWhenGameExists(_gameId) returns (GameSettings memory settings) { return games[_gameId]; }

    /**
     * @notice Retrieve a player's wallet/address for a certain game based on their ID.
     * @param _gameId The ID of the game.
     * @param _playerIndex The 0-based index of the player in the game's internal player list.
     * @return The wallet address of the player at the specified index.
     */
    function getPlayerAddress(uint256 _gameId, uint256 _playerIndex) external view onlyWhenGameExists(_gameId) returns (address) { 
        if (_playerIndex >= playerList[_gameId].length) revert("Index out of bounds for player list.");
        return playerList[_gameId][_playerIndex]; 
    }

    /**
     * @notice The Tied Wallets (players involved) during the Revote phase.
     * @dev Returns tied players.
     * @param _gameId The ID of the game.
     * @return An array of addresses of players who are currently part of the revote. Empty if no revote active.
     */
    function getTiedWallets(uint256 _gameId) external view onlyWhenGameExists(_gameId) returns (address[] memory) { 
        if(games[_gameId].currentState == GameState.Revote) {
            return playersInRevote[_gameId];
        }
        return new address[](0); 
    }

    /**
     * @notice Retrieves Game ID from Pyth sequence number.
     * @param _sequenceNumber The unique sequence number generated by a Pyth randomness request.
     * @return The game ID corresponding to the given Pyth sequence number, or 0 if no game is associated.
     */
    function getGamefromSequence(uint64 _sequenceNumber) external view returns (uint256) { return sequenceNumberToGameId[_sequenceNumber]; }

    /**
     * @notice Retrieve the wallet of winning addresses from the Game ID.
     * @param _gameId The ID of the finished game.
     * @return An array of addresses of the winning players for the specified game.
     */
    function getWinnerAddress(uint256 _gameId) external view onlyWhenGameExists(_gameId) returns (address[] memory) { 
        if (games[_gameId].currentState != GameState.Finished) revert GameNotFinished(_gameId, games[_gameId].currentState);
        return winners[_gameId]; 
    }

    /**
     * @notice The amount that the townfolk has won as a reward for any queried game.
     * @param _gameId The ID of the finished game where the Townfolk team won.
     * @return The payout amount in Wei for each living Townfolk player on the winning team.
     */
    function townfolkReward(uint256 _gameId) external view onlyWhenGameExists(_gameId) returns (uint256) { 
        GameSettings storage game = games[_gameId]; 
        if (game.currentState != GameState.Finished) revert GameNotFinished(_gameId, game.currentState);
        if (game.winningTeam != Role.Townfolk) revert WrongGameStateForAction(_gameId, game.currentState); 
        return payoutPerWinnerTownfolk[_gameId]; 
    }

    /**
     * @notice Retrieves whether or not the game is in a Paused status.
     * @dev Returns pause status by checking the Pausable state.
     * @return True if the contract is currently paused, false otherwise.
     */
    function getPauseStatus() external view returns (bool) { return paused(); }

    // Add the internal helper
    function _adminSetAddresses(address newBackend, address newValidator, address newPythProvider, address newTownHallWallet) internal {
        if (newBackend != address(0)) {
            _backendAddress = newBackend;
            emit BackendAddressSet(newBackend);
        }
        if (newValidator != address(0)) {
            address oldValidator = _validatorAddress;
            _validatorAddress = newValidator;
            emit ValidatorSet(oldValidator, newValidator);
        }
        if (newPythProvider != address(0)) {
            _pythProviderAddress = newPythProvider;
            emit PythProviderSet(newPythProvider);
        }
        if (newTownHallWallet != address(0)) {
            townHallWallet = newTownHallWallet;
            emit TownHallWalletSet(newTownHallWallet);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC-721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC-721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
     *   {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    bool private _paused;

    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    /**
     * @dev The operation failed because the contract is paused.
     */
    error EnforcedPause();

    /**
     * @dev The operation failed because the contract is not paused.
     */
    error ExpectedPause();

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        if (paused()) {
            revert EnforcedPause();
        }
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        if (!paused()) {
            revert ExpectedPause();
        }
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    uint256 private _status;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        _status = ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == ENTERED;
    }
}

File 9 of 16 : EntropyEvents.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "./EntropyStructs.sol";

interface EntropyEvents {
    event Registered(EntropyStructs.ProviderInfo provider);

    event Requested(EntropyStructs.Request request);
    event RequestedWithCallback(
        address indexed provider,
        address indexed requestor,
        uint64 indexed sequenceNumber,
        bytes32 userRandomNumber,
        EntropyStructs.Request request
    );

    event Revealed(
        EntropyStructs.Request request,
        bytes32 userRevelation,
        bytes32 providerRevelation,
        bytes32 blockHash,
        bytes32 randomNumber
    );
    event RevealedWithCallback(
        EntropyStructs.Request request,
        bytes32 userRandomNumber,
        bytes32 providerRevelation,
        bytes32 randomNumber
    );

    event ProviderFeeUpdated(address provider, uint128 oldFee, uint128 newFee);

    event ProviderUriUpdated(address provider, bytes oldUri, bytes newUri);

    event ProviderFeeManagerUpdated(
        address provider,
        address oldFeeManager,
        address newFeeManager
    );

    event Withdrawal(
        address provider,
        address recipient,
        uint128 withdrawnAmount
    );
}

File 10 of 16 : EntropyStructs.sol
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;

contract EntropyStructs {
    struct ProviderInfo {
        uint128 feeInWei;
        uint128 accruedFeesInWei;
        // The commitment that the provider posted to the blockchain, and the sequence number
        // where they committed to this. This value is not advanced after the provider commits,
        // and instead is stored to help providers track where they are in the hash chain.
        bytes32 originalCommitment;
        uint64 originalCommitmentSequenceNumber;
        // Metadata for the current commitment. Providers may optionally use this field to help
        // manage rotations (i.e., to pick the sequence number from the correct hash chain).
        bytes commitmentMetadata;
        // Optional URI where clients can retrieve revelations for the provider.
        // Client SDKs can use this field to automatically determine how to retrieve random values for each provider.
        // TODO: specify the API that must be implemented at this URI
        bytes uri;
        // The first sequence number that is *not* included in the current commitment (i.e., an exclusive end index).
        // The contract maintains the invariant that sequenceNumber <= endSequenceNumber.
        // If sequenceNumber == endSequenceNumber, the provider must rotate their commitment to add additional random values.
        uint64 endSequenceNumber;
        // The sequence number that will be assigned to the next inbound user request.
        uint64 sequenceNumber;
        // The current commitment represents an index/value in the provider's hash chain.
        // These values are used to verify requests for future sequence numbers. Note that
        // currentCommitmentSequenceNumber < sequenceNumber.
        //
        // The currentCommitment advances forward through the provider's hash chain as values
        // are revealed on-chain.
        bytes32 currentCommitment;
        uint64 currentCommitmentSequenceNumber;
        // An address that is authorized to set / withdraw fees on behalf of this provider.
        address feeManager;
    }

    struct Request {
        // Storage slot 1 //
        address provider;
        uint64 sequenceNumber;
        // The number of hashes required to verify the provider revelation.
        uint32 numHashes;
        // Storage slot 2 //
        // The commitment is keccak256(userCommitment, providerCommitment). Storing the hash instead of both saves 20k gas by
        // eliminating 1 store.
        bytes32 commitment;
        // Storage slot 3 //
        // The number of the block where this request was created.
        // Note that we're using a uint64 such that we have an additional space for an address and other fields in
        // this storage slot. Although block.number returns a uint256, 64 bits should be plenty to index all of the
        // blocks ever generated.
        uint64 blockNumber;
        // The address that requested this random number.
        address requester;
        // If true, incorporate the blockhash of blockNumber into the generated random value.
        bool useBlockhash;
        // If true, the requester will be called back with the generated random value.
        bool isRequestWithCallback;
        // There are 2 remaining bytes of free space in this slot.
    }
}

// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;

import "./EntropyEvents.sol";

interface IEntropy is EntropyEvents {
    // Register msg.sender as a randomness provider. The arguments are the provider's configuration parameters
    // and initial commitment. Re-registering the same provider rotates the provider's commitment (and updates
    // the feeInWei).
    //
    // chainLength is the number of values in the hash chain *including* the commitment, that is, chainLength >= 1.
    function register(
        uint128 feeInWei,
        bytes32 commitment,
        bytes calldata commitmentMetadata,
        uint64 chainLength,
        bytes calldata uri
    ) external;

    // Withdraw a portion of the accumulated fees for the provider msg.sender.
    // Calling this function will transfer `amount` wei to the caller (provided that they have accrued a sufficient
    // balance of fees in the contract).
    function withdraw(uint128 amount) external;

    // Withdraw a portion of the accumulated fees for provider. The msg.sender must be the fee manager for this provider.
    // Calling this function will transfer `amount` wei to the caller (provided that they have accrued a sufficient
    // balance of fees in the contract).
    function withdrawAsFeeManager(address provider, uint128 amount) external;

    // As a user, request a random number from `provider`. Prior to calling this method, the user should
    // generate a random number x and keep it secret. The user should then compute hash(x) and pass that
    // as the userCommitment argument. (You may call the constructUserCommitment method to compute the hash.)
    //
    // This method returns a sequence number. The user should pass this sequence number to
    // their chosen provider (the exact method for doing so will depend on the provider) to retrieve the provider's
    // number. The user should then call fulfillRequest to construct the final random number.
    //
    // This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
    // Note that excess value is *not* refunded to the caller.
    function request(
        address provider,
        bytes32 userCommitment,
        bool useBlockHash
    ) external payable returns (uint64 assignedSequenceNumber);

    // Request a random number. The method expects the provider address and a secret random number
    // in the arguments. It returns a sequence number.
    //
    // The address calling this function should be a contract that inherits from the IEntropyConsumer interface.
    // The `entropyCallback` method on that interface will receive a callback with the generated random number.
    //
    // This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
    // Note that excess value is *not* refunded to the caller.
    function requestWithCallback(
        address provider,
        bytes32 userRandomNumber
    ) external payable returns (uint64 assignedSequenceNumber);

    // Fulfill a request for a random number. This method validates the provided userRandomness and provider's proof
    // against the corresponding commitments in the in-flight request. If both values are validated, this function returns
    // the corresponding random number.
    //
    // Note that this function can only be called once per in-flight request. Calling this function deletes the stored
    // request information (so that the contract doesn't use a linear amount of storage in the number of requests).
    // If you need to use the returned random number more than once, you are responsible for storing it.
    function reveal(
        address provider,
        uint64 sequenceNumber,
        bytes32 userRevelation,
        bytes32 providerRevelation
    ) external returns (bytes32 randomNumber);

    // Fulfill a request for a random number. This method validates the provided userRandomness
    // and provider's revelation against the corresponding commitment in the in-flight request. If both values are validated
    // and the requestor address is a contract address, this function calls the requester's entropyCallback method with the
    // sequence number, provider address and the random number as arguments. Else if the requestor is an EOA, it won't call it.
    //
    // Note that this function can only be called once per in-flight request. Calling this function deletes the stored
    // request information (so that the contract doesn't use a linear amount of storage in the number of requests).
    // If you need to use the returned random number more than once, you are responsible for storing it.
    //
    // Anyone can call this method to fulfill a request, but the callback will only be made to the original requester.
    function revealWithCallback(
        address provider,
        uint64 sequenceNumber,
        bytes32 userRandomNumber,
        bytes32 providerRevelation
    ) external;

    function getProviderInfo(
        address provider
    ) external view returns (EntropyStructs.ProviderInfo memory info);

    function getDefaultProvider() external view returns (address provider);

    function getRequest(
        address provider,
        uint64 sequenceNumber
    ) external view returns (EntropyStructs.Request memory req);

    function getFee(address provider) external view returns (uint128 feeAmount);

    function getAccruedPythFees()
        external
        view
        returns (uint128 accruedPythFeesInWei);

    function setProviderFee(uint128 newFeeInWei) external;

    function setProviderFeeAsFeeManager(
        address provider,
        uint128 newFeeInWei
    ) external;

    function setProviderUri(bytes calldata newUri) external;

    // Set manager as the fee manager for the provider msg.sender.
    // After calling this function, manager will be able to set the provider's fees and withdraw them.
    // Only one address can be the fee manager for a provider at a time -- calling this function again with a new value
    // will override the previous value. Call this function with the all-zero address to disable the fee manager role.
    function setFeeManager(address manager) external;

    function constructUserCommitment(
        bytes32 userRandomness
    ) external pure returns (bytes32 userCommitment);

    function combineRandomValues(
        bytes32 userRandomness,
        bytes32 providerRandomness,
        bytes32 blockHash
    ) external pure returns (bytes32 combinedRandomness);
}

// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;

abstract contract IEntropyConsumer {
    // This method is called by Entropy to provide the random number to the consumer.
    // It asserts that the msg.sender is the Entropy contract. It is not meant to be
    // override by the consumer.
    function _entropyCallback(
        uint64 sequence,
        address provider,
        bytes32 randomNumber
    ) external {
        address entropy = getEntropy();
        require(entropy != address(0), "Entropy address not set");
        require(msg.sender == entropy, "Only Entropy can call this function");

        entropyCallback(sequence, provider, randomNumber);
    }

    // getEntropy returns Entropy contract address. The method is being used to check that the
    // callback is indeed from Entropy contract. The consumer is expected to implement this method.
    // Entropy address can be found here - https://docs.pyth.network/entropy/contract-addresses
    function getEntropy() internal view virtual returns (address);

    // This method is expected to be implemented by the consumer to handle the random number.
    // It will be called by _entropyCallback after _entropyCallback ensures that the call is
    // indeed from Entropy contract.
    function entropyCallback(
        uint64 sequence,
        address provider,
        bytes32 randomNumber
    ) internal virtual;
}

File 13 of 16 : GameDefinitions.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @notice Defines the possible secret roles a player can be assigned within the game.
 */
enum Role { Unassigned, Townfolk, Witch }

/**
 * @notice Represents the different phases or operational states a game can transition through.
 */
enum GameState { Lobby, WaitingForRandomness, Night, Day, Revote, Finished, Cancelled }

/**
 * @notice Stores critical information about each player participating in a specific game.
 */
struct Player {
    address playerAddress;
    Role role;
    bool isAlive;
    bool claimedPayout;
    uint256 nftBoostBps;
}

/**
 * @notice Holds all configuration settings and dynamic state for a single game instance.
 */
struct GameSettings {
    address host;
    uint256 gameBuyIn;
    uint8 maxPlayers;
    uint256 totalPot;
    uint256 platformFeeBasisPoints;
    GameState currentState;
    uint256 createdAt;
    uint256 currentRound;
    uint256 phaseStartTime;
    uint64 pythSequenceNumber;
    bool isRandomnessFulfilled;
    uint256 deadTownfolkRefundPool;
    Role winningTeam;
}

// Denominator for all basis point (BPS) calculations. 100% = 10,000 BPS.
uint256 constant BASIS_POINTS = 10000;

// The total number of witches set for all games (fixed to 1).
uint8 constant WITCH_COUNT = 1;

// The reward for a dead Townfolk when their team wins, as a percentage of their buy-in (BPS).
uint256 constant DEAD_TOWNFOLK_REWARD = 5000;

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "../GameDefinitions.sol";

library PayoutLogic {

    struct FeeAndRefundCalculationsOutput {
        uint256 platformFeeAmount;
        uint256 potRemainingAfterFee;
        uint256 totalAllocatedForDeadTownfolkRefunds;
    }

    /**
     * @dev Internal: Calculates payout/refund for a player in a finished game. Core logic for determining player rewards.
     * @param _playerAddress Address of the player.
     * @param game A storage reference to the game's settings.
     * @param player A storage reference to the player's data.
     * @param playerListForGame Array of player addresses in the game.
     * @param winnersForGame Array of winner addresses for the game.
     * @param payoutPerWinnerTownfolkForGame Payout amount for each winning townfolk.
     * @return playerPayoutAmount The amount of native $APE (in Wei) due to the player.
     * @return platformFeeAmountForInfo The total platform fee (in Wei) collected from this specific game's prize pool.
     */
    function calculatePayoutInLibrary(
        uint256 /*_gameId*/, // Unused in current logic but kept for context if library expands
        address _playerAddress,
        GameSettings storage game, 
        Player storage player,
        address[] storage playerListForGame,
        address[] storage winnersForGame,
        uint256 payoutPerWinnerTownfolkForGame
    )
        internal view
        returns (uint256 playerPayoutAmount, uint256 platformFeeAmountForInfo)
    {
        // Suppress unused _gameId warning if truly not needed by direct logic here
        // bytes32 temp = keccak256(abi.encode(_gameId)); // Example to use _gameId if needed

        uint256 originalGamePotSize = game.gameBuyIn * playerListForGame.length;
        platformFeeAmountForInfo = (originalGamePotSize * game.platformFeeBasisPoints) / BASIS_POINTS;

        playerPayoutAmount = 0; 

        bool isAnActualWinner = false;
        for (uint i = 0; i < winnersForGame.length; i++) {
            if (winnersForGame[i] == _playerAddress) {
                isAnActualWinner = true;
                break;
            }
        }

        bool isEligibleForDeadTownfolkRefund = !player.isAlive && player.role == Role.Townfolk && game.winningTeam == Role.Townfolk;

        if (isAnActualWinner) { 
            if (game.winningTeam == Role.Witch) {
                uint256 potAfterFee = originalGamePotSize - platformFeeAmountForInfo;
                if (winnersForGame.length > 0) { 
                    playerPayoutAmount = potAfterFee / winnersForGame.length;
                }
            } else if (game.winningTeam == Role.Townfolk) {
                playerPayoutAmount = payoutPerWinnerTownfolkForGame;
            }
        } else if (isEligibleForDeadTownfolkRefund) {
            // DEAD_TOWNFOLK_REWARD needs to be accessible.
            // For now, assuming it's a global constant or passed in.
            // If it's from DemWitches, it needs to be passed or GameDefinitions.
            // Let's assume it's available via GameDefinitions or passed if this library is standalone.
            // For this step, we'll hardcode it to keep the library self-contained for now,
            // or ideally, it should be imported from GameDefinitions if it's moved there.
            // uint256 DEAD_TOWNFOLK_REWARD_CONST = 5000; // Placeholder if not in GameDefinitions
            // For now, let's assume DEAD_TOWNFOLK_REWARD is accessible globally or via GameDefinitions
            // If DemWitches.DEAD_TOWNFOLK_REWARD is used, this library would need to know about DemWitches.
            // The best is to pass it or make it a constant in GameDefinitions.
            // For this specific step, I will assume it's accessible.
            // If DemWitches.DEAD_TOWNFOLK_REWARD is a public constant, it can be accessed.
            // Let's assume it's defined in GameDefinitions.sol for now.
            // If not, this will cause a compile error, which we'll fix.
            // DEAD_TOWNFOLK_REWARD is now imported from GameDefinitions.sol
            playerPayoutAmount = (game.gameBuyIn * DEAD_TOWNFOLK_REWARD) / BASIS_POINTS;
        }
        
        return (playerPayoutAmount, platformFeeAmountForInfo);
    }

    /**
     * @dev Calculates platform fees and the dead townfolk refund pool.
     * @param game A storage reference to the game's settings.
     * @param playerListForGame Array of player addresses in the game.
     * @param gamePlayersForGame Mapping of player addresses to Player structs for the game.
     * @return output A FeeAndRefundCalculationsOutput struct containing calculated fee, remaining pot, and refund pool.
     */
    function calculateFeesAndRefundPoolInLibrary(
        GameSettings storage game,
        address[] storage playerListForGame, // Needed for original pot size if not directly available
        mapping(address => Player) storage gamePlayersForGame // Needed for dead townfolk role check
    ) internal view returns (FeeAndRefundCalculationsOutput memory output) {
        uint256 initialTotalPot;
        // If game.totalPot is already the pre-fee amount from DemWitches, use it.
        // Otherwise, if it's the sum of buy-ins, calculate from there.
        // Assuming game.totalPot is the sum of all buyIns for this calculation.
        // If DemWitches passes the original pot (sum of buyIns) as game.totalPot for this call, this is fine.
        // Alternatively, calculate originalGamePotSize = game.gameBuyIn * playerListForGame.length;
        // For now, let's assume game.totalPot IS the initial total pot before any deductions in checkWinConditions.
        initialTotalPot = game.totalPot; 

        output.platformFeeAmount = (initialTotalPot * game.platformFeeBasisPoints) / BASIS_POINTS;
        output.potRemainingAfterFee = initialTotalPot - output.platformFeeAmount;
        output.totalAllocatedForDeadTownfolkRefunds = 0;

        if (game.winningTeam == Role.Townfolk) { // Only calculate refund pool if Townfolk win
            for (uint i = 0; i < playerListForGame.length; i++) {
                Player storage p = gamePlayersForGame[playerListForGame[i]];
                if (!p.isAlive && p.role == Role.Townfolk) {
                    output.totalAllocatedForDeadTownfolkRefunds += (game.gameBuyIn * DEAD_TOWNFOLK_REWARD) / BASIS_POINTS;
                }
            }
        }
        // Note: This function does not modify game.deadTownfolkRefundPool directly.
        // It also does not modify game.totalPot. DemWitches.sol will use these return values.
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "../GameDefinitions.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; // For balanceOf

library RoleLogic {

    /**
     * @dev Calculates tiered NFT boost based on holdings in 1-2 collections using configurable tiers.
     * @param _playerAddress The address of the player whose NFT holdings are being checked.
     * @param _boostedCollection1 Address of the first boosted NFT collection.
     * @param _boostedCollection2 Address of the second boosted NFT collection.
     * @param _nftBoostTiersCounts Array of NFT count thresholds for tiers.
     * @param _nftBoostTiersBps Array of BPS values for tiers.
     * @return calculatedBoostBps The calculated boost percentage in BPS.
     */
    function calculateTieredNftBoostInLibrary(
        address _playerAddress,
        address _boostedCollection1,
        address _boostedCollection2,
        uint8[] storage _nftBoostTiersCounts, // Pass storage reference
        uint256[] storage _nftBoostTiersBps // Pass storage reference
    ) internal view returns (uint256 calculatedBoostBps) {
        uint256 totalEligibleNftCount = 0;

        if (_boostedCollection1 != address(0)) {
            try IERC721(_boostedCollection1).balanceOf(_playerAddress) returns (uint256 balanceInCollection1) {
                totalEligibleNftCount += balanceInCollection1;
            } catch { /* Silently ignore failures */ }
        }
        if (_boostedCollection2 != address(0)) {
            try IERC721(_boostedCollection2).balanceOf(_playerAddress) returns (uint256 balanceInCollection2) {
                totalEligibleNftCount += balanceInCollection2;
            } catch { /* Silently ignore failures */ }
        }

        if (_nftBoostTiersBps.length == 0) return 0;
        if (_nftBoostTiersCounts.length == 0) {
             return totalEligibleNftCount > 0 ? _nftBoostTiersBps[0] : 0;
        }
        if (_nftBoostTiersBps.length != _nftBoostTiersCounts.length + 1) return 0;

        calculatedBoostBps = 0; 

        for (uint i = 0; i < _nftBoostTiersCounts.length; i++) {
            if (totalEligibleNftCount > 0 && totalEligibleNftCount <= _nftBoostTiersCounts[i]) {
                bool appliesToThisTier = (i == 0) || (totalEligibleNftCount > _nftBoostTiersCounts[i-1]);
                if (appliesToThisTier) {
                    calculatedBoostBps = _nftBoostTiersBps[i];
                    return calculatedBoostBps;
                }
            }
        }

        if (_nftBoostTiersCounts.length > 0 && totalEligibleNftCount > _nftBoostTiersCounts[_nftBoostTiersCounts.length - 1]) {
            calculatedBoostBps = _nftBoostTiersBps[_nftBoostTiersCounts.length];
        }
        
        return calculatedBoostBps;
    }

    /**
     * @dev Assigns Witch role using weighted randomness based on cached NFT boost.
     * @param gamePlayersForGame Mapping of game ID to player address to Player struct (effectively gamePlayers[_gameId]).
     * @param currentPlayersInGame Array of player addresses in the current game.
     * @param _randomNumber The secure random number from Pyth.
     * @notice This function modifies the `role` within the Player structs in `gamePlayersForGame`.
     */
    function assignRolesWeightedInLibrary(
        mapping(address => Player) storage gamePlayersForGame, // This is gamePlayers[_gameId]
        address[] storage currentPlayersInGame,
        uint256 _randomNumber
    ) internal {
        uint playerCount = currentPlayersInGame.length;
        if (playerCount < WITCH_COUNT) { 
            // Cannot revert with custom error from library directly if it's not defined here
            // Revert with a generic string or handle error appropriately if this library were more complex
            revert("RoleLogic: Not enough players for witch assignment"); 
        }

        uint[] memory playerWeights = new uint[](playerCount);
        uint totalCombinedWeight = 0;
        uint baseWeightPerPlayer = 100;

        for (uint i = 0; i < playerCount; i++) {
            uint currentCalculatedWeight = baseWeightPerPlayer;
            uint256 playerCachedNftBoostBps = gamePlayersForGame[currentPlayersInGame[i]].nftBoostBps;
            
            if (playerCachedNftBoostBps > 0) {
                currentCalculatedWeight += (baseWeightPerPlayer * playerCachedNftBoostBps) / BASIS_POINTS;
            }
            playerWeights[i] = currentCalculatedWeight;
            totalCombinedWeight += currentCalculatedWeight;
        }

        if (totalCombinedWeight == 0) { 
            revert("RoleLogic: Total weight is zero, cannot assign roles"); 
        }

        uint randomNumberForSelection = uint(keccak256(abi.encodePacked(_randomNumber, "DEMWITCHES_ROLE_ASSIGNMENT_SALT_V3.8"))) % totalCombinedWeight;
        uint cumulativeWeightTracker = 0;
        address assignedWitchAddress = address(0);

        for (uint i = 0; i < playerCount; i++) {
            cumulativeWeightTracker += playerWeights[i];
            if (randomNumberForSelection < cumulativeWeightTracker) {
                assignedWitchAddress = currentPlayersInGame[i];
                break;
            }
        }
        
        if (assignedWitchAddress == address(0)) {
            assignedWitchAddress = currentPlayersInGame[uint(keccak256(abi.encodePacked(_randomNumber, "DEMWITCHES_WITCH_FALLBACK_SALT_V3.8"))) % playerCount];
        }

        for (uint i = 0; i < playerCount; i++) {
            gamePlayersForGame[currentPlayersInGame[i]].role = (currentPlayersInGame[i] == assignedWitchAddress) ? Role.Witch : Role.Townfolk;
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "../GameDefinitions.sol";

library VoteLogic {

    struct VoteOutcome {
        address playerToBurn;
        uint256 maxVoteCount;
        uint tieCount;
        address[] tiedPlayers; // For event emission primarily
        bool actionTaken; // Indicates if a burn or tie leading to revote occurred
    }

    /**
     * @dev Tallies votes and determines the outcome (player to burn, tie, or no majority).
     * @param voters Array of voter addresses.
     * @param votedFor Array of addresses voted for (parallel to voters).
     * @param playerListForGame Array of all player addresses in the game.
     * @param gamePlayersForGame Mapping of player addresses to Player structs for the game.
     * @return outcome A VoteOutcome struct detailing the result of the vote.
     */
    function tallyVotesAndDetermineOutcomeInLibrary(
        address[] memory voters,
        address[] memory votedFor,
        address[] storage playerListForGame,
        mapping(address => Player) storage gamePlayersForGame
    ) internal view returns (VoteOutcome memory outcome) {
        outcome.playerToBurn = address(0);
        outcome.maxVoteCount = 0;
        outcome.tieCount = 0;
        outcome.actionTaken = false; // Default to no decisive action

        uint totalPlayerCountInList = playerListForGame.length;
        if (totalPlayerCountInList == 0) {
            // No players, should not happen in a real game scenario during voting
            return outcome;
        }
        
        uint[] memory voteCounts = new uint[](totalPlayerCountInList);

        // Tally votes
        for (uint j = 0; j < voters.length; j++) {
            address voter = voters[j];
            address targetPlayer = votedFor[j];

            if (gamePlayersForGame[voter].playerAddress != address(0) && // Voter must exist
                gamePlayersForGame[voter].isAlive && // Voter must be alive
                targetPlayer != address(0) && // Not an abstain
                gamePlayersForGame[targetPlayer].playerAddress != address(0) && // Target must exist
                gamePlayersForGame[targetPlayer].isAlive) { // Target must be alive

                for (uint k = 0; k < totalPlayerCountInList; k++) {
                    if (playerListForGame[k] == targetPlayer) {
                        voteCounts[k]++;
                        break; 
                    }
                }
            }
        }
        
        // Temporary array for tied players, max possible size
        address[] memory tempTiedPlayersList = new address[](totalPlayerCountInList); 
        uint actualTiedPlayerCount = 0;

        // Determine player with most votes / ties
        for (uint i = 0; i < totalPlayerCountInList; i++) {
            address candidatePlayer = playerListForGame[i];
            // Only consider alive players who could have received votes
            if (gamePlayersForGame[candidatePlayer].playerAddress == address(0) || !gamePlayersForGame[candidatePlayer].isAlive) {
                continue; 
            }

            uint votesForThisCandidate = voteCounts[i];

            if (votesForThisCandidate > 0) { 
                if (votesForThisCandidate > outcome.maxVoteCount) {
                    outcome.maxVoteCount = votesForThisCandidate;
                    outcome.playerToBurn = candidatePlayer;
                    outcome.tieCount = 1;
                    tempTiedPlayersList[0] = candidatePlayer; // Start new list of tied players
                    actualTiedPlayerCount = 1;
                } else if (votesForThisCandidate == outcome.maxVoteCount) {
                    outcome.tieCount++;
                    if (actualTiedPlayerCount < totalPlayerCountInList) { // Bound check
                        tempTiedPlayersList[actualTiedPlayerCount++] = candidatePlayer;
                    }
                    // If actualTiedPlayerCount >= totalPlayerCountInList, it's an overflow,
                    // but this implies all players tied, which is fine.
                }
            }
        }
        
        // Finalize tiedPlayers array
        outcome.tiedPlayers = new address[](actualTiedPlayerCount);
        for(uint i = 0; i < actualTiedPlayerCount; i++) {
            outcome.tiedPlayers[i] = tempTiedPlayersList[i];
        }

        // Determine if an action (burn or tie leading to revote) was taken
        if (outcome.playerToBurn != address(0) && outcome.maxVoteCount > 0) {
            outcome.actionTaken = true;
        }
        
        return outcome;
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 1
  },
  "viaIR": true,
  "evmVersion": "paris",
  "debug": {
    "revertStrings": "strip"
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "metadata": {
    "useLiteralContent": true
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_pythEntropyAddr","type":"address"},{"internalType":"address","name":"_initialContractOwner","type":"address"},{"internalType":"uint256","name":"_initialDefaultFeeBps","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"player","type":"address"}],"name":"AlreadyClaimed","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"player","type":"address"}],"name":"AlreadyJoined","type":"error"},{"inputs":[],"name":"BackendAddressNotSet","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"}],"name":"CancellationFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"player","type":"address"}],"name":"CannotTargetSelf","type":"error"},{"inputs":[],"name":"DurationTooShort","type":"error"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[{"internalType":"uint256","name":"feeBps","type":"uint256"},{"internalType":"uint256","name":"maxBps","type":"uint256"}],"name":"FeeTooHigh","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"}],"name":"GameAlreadyStartedOrFinished","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"uint8","name":"maxPlayers","type":"uint8"}],"name":"GameFull","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"}],"name":"GameIsFinished","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"enum GameState","name":"actual","type":"uint8"}],"name":"GameNotFinished","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"}],"name":"GameNotFound","type":"error"},{"inputs":[{"internalType":"uint256","name":"sent","type":"uint256"},{"internalType":"uint256","name":"required","type":"uint256"}],"name":"IncorrectBuyInAmount","type":"error"},{"inputs":[],"name":"InsufficientContractBalance","type":"error"},{"inputs":[{"internalType":"uint256","name":"required","type":"uint256"},{"internalType":"uint256","name":"balance","type":"uint256"}],"name":"InsufficientFundsForPythFee","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"}],"name":"InternalAssignmentError","type":"error"},{"inputs":[{"internalType":"uint256","name":"sent","type":"uint256"},{"internalType":"uint256","name":"required","type":"uint256"}],"name":"InvalidAmount","type":"error"},{"inputs":[],"name":"InvalidBoostTierConfiguration","type":"error"},{"inputs":[{"internalType":"address","name":"expected","type":"address"},{"internalType":"address","name":"actual","type":"address"}],"name":"InvalidCallbackOrigin","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"enum GameState","name":"expected","type":"uint8"},{"internalType":"enum GameState","name":"actual","type":"uint8"}],"name":"InvalidGameState","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"target","type":"address"}],"name":"InvalidRevoteTarget","type":"error"},{"inputs":[{"internalType":"uint64","name":"sequenceNum","type":"uint64"}],"name":"InvalidSequenceNumber","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"target","type":"address"}],"name":"InvalidVoteTarget","type":"error"},{"inputs":[{"internalType":"uint8","name":"maxPlayers","type":"uint8"}],"name":"MaxPlayersInvalid","type":"error"},{"inputs":[],"name":"NativeTransferFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"sender","type":"address"}],"name":"NotAWitch","type":"error"},{"inputs":[],"name":"NotAuthorized","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"uint8","name":"current","type":"uint8"},{"internalType":"uint8","name":"required","type":"uint8"}],"name":"NotEnoughPlayers","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"host","type":"address"}],"name":"NotGameHost","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"player","type":"address"}],"name":"PayoutRetryInvalid","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"player","type":"address"}],"name":"PlayerNotAlive","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"address","name":"player","type":"address"}],"name":"PlayerNotInGame","type":"error"},{"inputs":[],"name":"PythProviderAddressNotSet","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"enum GameState","name":"actual","type":"uint8"}],"name":"RandomnessNotReady","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[],"name":"TimeoutNotReached","type":"error"},{"inputs":[],"name":"TownHallWalletNotSet","type":"error"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"},{"internalType":"enum GameState","name":"actual","type":"uint8"}],"name":"WrongGameStateForAction","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"enum GameState","name":"oldState","type":"uint8"},{"indexed":false,"internalType":"enum GameState","name":"newState","type":"uint8"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"AdminForcedState","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"backendAddress","type":"address"}],"name":"BackendAddressSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"address","name":"killedInNight","type":"address"}],"name":"DayPhaseStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"address","name":"cancelledBy","type":"address"},{"indexed":false,"internalType":"string","name":"reason","type":"string"}],"name":"GameCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"address","name":"host","type":"address"},{"indexed":false,"internalType":"uint256","name":"gameBuyIn","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"maxPlayers","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"feeBps","type":"uint256"}],"name":"GameCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"lobby","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"randomness","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"night","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"day","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"revote","type":"uint256"}],"name":"GameDurationsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"enum Role","name":"winningTeam","type":"uint8"},{"indexed":false,"internalType":"address[]","name":"winners","type":"address[]"},{"indexed":false,"internalType":"uint256","name":"payoutPerWinner","type":"uint256"}],"name":"GameFinished","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"GamePaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"address","name":"host","type":"address"},{"indexed":false,"internalType":"uint64","name":"sequenceNumber","type":"uint64"}],"name":"GameStartRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"GameUnpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8[]","name":"nftCounts","type":"uint8[]"},{"indexed":false,"internalType":"uint256[]","name":"boostBps","type":"uint256[]"}],"name":"NftBoostTiersSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint8","name":"index","type":"uint8"},{"indexed":true,"internalType":"address","name":"collection","type":"address"}],"name":"NftCollectionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"}],"name":"NightPhaseStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PayoutClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"address","name":"player","type":"address"}],"name":"PayoutRetryFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PayoutRetrySucceeded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"enum GameState","name":"timedOutPhase","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"string","name":"reason","type":"string"}],"name":"PhaseForcedByTimeout","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newFeeBasisPoints","type":"uint256"}],"name":"PlatformFeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"votes","type":"uint256"}],"name":"PlayerBurned","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"votes","type":"uint256"}],"name":"PlayerBurnedAfterRevote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":true,"internalType":"address","name":"target","type":"address"}],"name":"PlayerCursed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":true,"internalType":"address","name":"player","type":"address"}],"name":"PlayerInactiveEliminated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"uint256","name":"nftBoostAppliedBps","type":"uint256"}],"name":"PlayerJoined","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PlayerRefunded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"providerAddress","type":"address"}],"name":"PythProviderSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":true,"internalType":"uint64","name":"sequenceNumber","type":"uint64"}],"name":"RandomnessReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"playersInRevote","type":"address[]"}],"name":"RevotePhaseStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"}],"name":"RevoteTied","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"}],"name":"RolesAssigned","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newTownHallWallet","type":"address"}],"name":"TownHallWalletSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldValidator","type":"address"},{"indexed":true,"internalType":"address","name":"newValidator","type":"address"}],"name":"ValidatorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"tiedPlayers","type":"address[]"}],"name":"VoteTied","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":true,"internalType":"address","name":"lastTownfolk","type":"address"}],"name":"WitchKilledLastTownfolk","type":"event"},{"inputs":[],"name":"GRACE_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_FEE","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_PLAYERS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_PLAYERS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PYTH_ENTROPY","outputs":[{"internalType":"contract IEntropy","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"sequence","type":"uint64"},{"internalType":"address","name":"provider","type":"address"},{"internalType":"bytes32","name":"randomNumber","type":"bytes32"}],"name":"_entropyCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"adminCancelGame","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"address","name":"_playerAddress","type":"address"}],"name":"adminKill","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"adminPause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"address","name":"_playerAddress","type":"address"}],"name":"adminRetryReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newBackendAddress","type":"address"}],"name":"adminSetBackend","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8[]","name":"nftCounts_","type":"uint8[]"},{"internalType":"uint256[]","name":"boostBps_","type":"uint256[]"}],"name":"adminSetBoost","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collectionAddress","type":"address"}],"name":"adminSetBoostCollection1","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collectionAddress","type":"address"}],"name":"adminSetBoostCollection2","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"lobby_","type":"uint256"},{"internalType":"uint256","name":"randomness_","type":"uint256"},{"internalType":"uint256","name":"night_","type":"uint256"},{"internalType":"uint256","name":"day_","type":"uint256"},{"internalType":"uint256","name":"revote_","type":"uint256"}],"name":"adminSetDurations","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newFeeBps","type":"uint256"}],"name":"adminSetFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"enum GameState","name":"_newState","type":"uint8"}],"name":"adminSetPhase","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_providerAddress","type":"address"}],"name":"adminSetPythProvider","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newTownHallWallet","type":"address"}],"name":"adminSetTownHallWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newValidatorAddress","type":"address"}],"name":"adminSetValidatorAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"adminUnpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"backendAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"backendCancelGame","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"boostedCollection1","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"boostedCollection2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"checkTime","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"claimReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameBuyIn","type":"uint256"},{"internalType":"uint8","name":"_maxPlayers","type":"uint8"}],"name":"createLobby","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"dayDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBigButts","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getBoostedCollections","outputs":[{"internalType":"address","name":"collection1","type":"address"},{"internalType":"address","name":"collection2","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"getGameInfo","outputs":[{"components":[{"internalType":"address","name":"host","type":"address"},{"internalType":"uint256","name":"gameBuyIn","type":"uint256"},{"internalType":"uint8","name":"maxPlayers","type":"uint8"},{"internalType":"uint256","name":"totalPot","type":"uint256"},{"internalType":"uint256","name":"platformFeeBasisPoints","type":"uint256"},{"internalType":"enum GameState","name":"currentState","type":"uint8"},{"internalType":"uint256","name":"createdAt","type":"uint256"},{"internalType":"uint256","name":"currentRound","type":"uint256"},{"internalType":"uint256","name":"phaseStartTime","type":"uint256"},{"internalType":"uint64","name":"pythSequenceNumber","type":"uint64"},{"internalType":"bool","name":"isRandomnessFulfilled","type":"bool"},{"internalType":"uint256","name":"deadTownfolkRefundPool","type":"uint256"},{"internalType":"enum Role","name":"winningTeam","type":"uint8"}],"internalType":"struct GameSettings","name":"settings","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"getGameSettings","outputs":[{"components":[{"internalType":"address","name":"host","type":"address"},{"internalType":"uint256","name":"gameBuyIn","type":"uint256"},{"internalType":"uint8","name":"maxPlayers","type":"uint8"},{"internalType":"uint256","name":"totalPot","type":"uint256"},{"internalType":"uint256","name":"platformFeeBasisPoints","type":"uint256"},{"internalType":"enum GameState","name":"currentState","type":"uint8"},{"internalType":"uint256","name":"createdAt","type":"uint256"},{"internalType":"uint256","name":"currentRound","type":"uint256"},{"internalType":"uint256","name":"phaseStartTime","type":"uint256"},{"internalType":"uint64","name":"pythSequenceNumber","type":"uint64"},{"internalType":"bool","name":"isRandomnessFulfilled","type":"bool"},{"internalType":"uint256","name":"deadTownfolkRefundPool","type":"uint256"},{"internalType":"enum Role","name":"winningTeam","type":"uint8"}],"internalType":"struct GameSettings","name":"settings","type":"tuple"},{"internalType":"address[]","name":"currentPlayers","type":"address[]"},{"internalType":"address[]","name":"gameWinners","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"_sequenceNumber","type":"uint64"}],"name":"getGamefromSequence","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"getMyRole","outputs":[{"internalType":"enum Role","name":"playerRole","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPauseStatus","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"uint256","name":"_playerIndex","type":"uint256"}],"name":"getPlayerAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"address","name":"_playerAddress","type":"address"}],"name":"getPlayerInfo","outputs":[{"components":[{"internalType":"address","name":"playerAddress","type":"address"},{"internalType":"bool","name":"isAlive","type":"bool"},{"internalType":"bool","name":"claimedPayout","type":"bool"},{"internalType":"uint256","name":"nftBoostBps","type":"uint256"}],"internalType":"struct DemWitches.PublicPlayerInfo","name":"publicInfo","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPlayersFundsLocked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"address","name":"_playerAddress","type":"address"}],"name":"getPotentialReward","outputs":[{"internalType":"uint256","name":"payoutAmount","type":"uint256"},{"internalType":"uint256","name":"platformFeeForGame","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"getTiedWallets","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTownHallWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getValidatorAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"getWinnerAddress","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"hostCancelGame","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"joinLobby","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"nextGameID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nftBoost","outputs":[{"internalType":"uint8[]","name":"counts","type":"uint8[]"},{"internalType":"uint256[]","name":"bps","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nightDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pythEntropyAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pythProviderAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"revoteDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"startGame","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"townHallWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"townfolkReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"}],"name":"triggerLobbyTimeout","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"address[]","name":"_voters","type":"address[]"},{"internalType":"address[]","name":"_votedFor","type":"address[]"}],"name":"voteAgain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"address[]","name":"_voters","type":"address[]"},{"internalType":"address[]","name":"_votedFor","type":"address[]"}],"name":"voteAndBurn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gameId","type":"uint256"},{"internalType":"address","name":"_targetAddress","type":"address"}],"name":"voteAndCurse","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

60a034620009ea57601f62006a1638819003918201601f19168301916001600160401b03831184841017620008fd57808492606094604052833981010312620009ea576200004d81620009ef565b60406200005d60208401620009ef565b920151916001600160a01b03908116918215620009d157600080546001600160a01b03198116851782556040519491908416907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a36001805516908115620009c257506103e88211620009a157608052600881905560b4600c81905561012c600d55600e55610e10600f819055601055604051606081016001600160401b03811182821017620008fd576040526001815260056020820152600a604082015260115460036011558060031062000938575b506011600052602060002060009160005b600381106200091357505055604051608081016001600160401b03811182821017620008fd5760405261012c81526103e860208201526107d06040820152610dac60608201526012549060049182601255808310620008bf575b506012600052602060002060005b838110620008a6577fdc4e4be378c228e1b7b13787e1d0620609304c2f71ed3a6ee69e4aae99d58cd9602086604051908152a17f2621620528f26fb75529114de7b19d9f01b11bb4a9bd0ad57997a33cf8141ec060a0600f54601054600c54600d5490600e54926040519485526020850152604084015260608301526080820152a16040516040810160408252601154809152606082019060116000526020600020906000915b81601f84011062000703575491818110620006ee575b818110620006d6575b818110620006be575b818110620006a6575b8181106200068f575b81811062000677575b8181106200065f575b81811062000647575b8181106200062f575b81811062000617575b818110620005ff575b818110620005e7575b818110620005cf575b818110620005b7575b8181106200059f575b81811062000587575b8181106200056f575b81811062000557575b8181106200053f575b81811062000527575b8181106200050f575b818110620004f7575b818110620004df575b818110620004c7575b818110620004af575b81811062000497575b8181106200047f575b81811062000467575b8181106200044f575b81811062000437575b8181106200041f575b1062000410575b5081810360208301526020601254918281520190601260005260206000209060005b818110620003f9577f12955a64e0444022667406e8e5e263bdac7d6c8246e16b5c3f28215f09c51cdb85850386a1604051615ff8908162000a1e8239608051818181610c6e01528181611532015281816130fb01526152d30152f35b82548452602090930192600192830192016200039d565b60f81c8152602001826200037b565b92602060019160ff8560f01c16815201930162000374565b92602060019160ff8560e81c1681520193016200036b565b92602060019160ff8560e01c16815201930162000362565b92602060019160ff8560d81c16815201930162000359565b92602060019160ff8560d01c16815201930162000350565b92602060019160ff8560c81c16815201930162000347565b92602060019160ff8560c01c1681520193016200033e565b92602060019160ff8560b81c16815201930162000335565b92602060019160ff8560b01c1681520193016200032c565b92602060019160ff8560a81c16815201930162000323565b92602060019160ff8560a01c1681520193016200031a565b92602060019160ff8560981c16815201930162000311565b92602060019160ff8560901c16815201930162000308565b92602060019160ff8560881c168152019301620002ff565b92602060019160ff8560801c168152019301620002f6565b92602060019160ff8560781c168152019301620002ed565b92602060019160ff8560701c168152019301620002e4565b92602060019160ff8560681c168152019301620002db565b92602060019160ff8560601c168152019301620002d2565b92602060019160ff8560581c168152019301620002c9565b92602060019160ff8560501c168152019301620002c0565b92602060019160ff8560481c168152019301620002b7565b92602060019160ff8560401c168152019301620002ae565b92602060019160ff8560381c168152019301620002a5565b92602060019160ff8560301c1681520193016200029c565b92602060019160ff8560281c16815201930162000293565b92602060019160ff85831c1681520193016200028a565b92602060019160ff8560181c16815201930162000281565b92602060019160ff8560101c16815201930162000278565b92602060019160ff8560081c1681520193016200026f565b92602060019160ff8516815201930162000266565b926001610400602092865460ff8116825260ff8160081c168583015260ff8160101c16604083015260ff8160181c16606083015260ff81861c16608083015260ff8160281c1660a083015260ff8160301c1660c083015260ff8160381c1660e083015260ff8160401c1661010083015260ff8160481c1661012083015260ff8160501c1661014083015260ff8160581c1661016083015260ff8160601c1661018083015260ff8160681c166101a083015260ff8160701c166101c083015260ff8160781c166101e083015260ff8160801c1661020083015260ff8160881c1661022083015260ff8160901c1661024083015260ff8160981c1661026083015260ff8160a01c1661028083015260ff8160a81c166102a083015260ff8160b01c166102c083015260ff8160b81c166102e083015260ff8160c01c1661030083015260ff8160c81c1661032083015260ff8160d01c1661034083015260ff8160d81c1661036083015260ff8160e01c1661038083015260ff8160e81c166103a083015260ff8160f01c166103c083015260f81c6103e082015201940192019162000250565b600190602061ffff8551169401938184015501620001a9565b6012600052620008f6907fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec344490810190840162000a04565b386200019b565b634e487b7160e01b600052604160045260246000fd5b9092602060019160ff9081875116918560031b92831b921b1916179401910162000141565b60116000527f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68805462ffffff1681556200099a91601f0160051c017f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6962000a04565b3862000130565b6040516373ab893560e11b8152600481018390526103e86024820152604490fd5b63d92e233d60e01b8152600490fd5b604051631e4fbdf760e01b815260006004820152602490fd5b600080fd5b51906001600160a01b0382168203620009ea57565b81811062000a10575050565b6000815560010162000a0456fe608080604052600436101561001d575b50361561001b57600080fd5b005b60e0600035811c91826202640714613b1a575081630947d30214613ac35781630d72d57f14613a9a5781631154d19014613a715781631562c16b14613a5457816315ec12cd146139eb5781631d9023cb146130b457816322e1dcd01461073157816327bebe88146139c257816330041ba81461370b57816332f6dc7c146136b4578163352ce424146134c55781633ccd10e9146133ac5781634411b3eb1461339057816347e1d550146132815781634f2c0aec1461320b578163515371a31461314c57816352a5f1f8146130d75781635c975abb146130b457816361841cd614613020578163673ee37c146130025781636878b93914612ef35781636919f4d11461151c5781636a6354e914612e295781636bf4da7414612ab7578163715018a614612a705781637cea0c75146128bc5781638267660f1461285357816385f07bbe14612837578163867b49f21461275957816388902d691461258b57816389fc892d1461256d5781638da5cb5b1461254457816390de9f121461250a578163917e7964146124dd578163919cc6a01461236b578163922395b81461230057816392f9c0671461227757816398ddf5841461220e5781639f3e7e4214611b8357508063ad2dfc6514611981578063adcf8110146118c4578063ae169a5014611561578063afb218f51461151c578063b8ca266c1461144b578063bc063e1a1461142e578063c073343b146113c5578063c20860af14611339578063c6b00c7b14610f94578063d18658cd14610f76578063d3780db114610f42578063daef359914610eed578063ddca3f4314610ecf578063e474ee6114610ea6578063e5ed1d5914610baa578063e9173b3a14610954578063ee9c1c7e146108d3578063f0c6fc45146107ec578063f2fde38b14610778578063f6f071f11461075a578063f9e7ad8514610731578063fc9fefb014610713578063fcead4e1146105c75763fe32b0ba146102eb573861000f565b346105c25760403660031901126105c257600435610307613b54565b90806000526020906013825260018060a01b03918260406000205416156105a957816000526013815260ff600560406000200154166007811015610486576005810361057b5750816000526014815260406000209383811694856000528252836040600020541615610559575081600094939452601381526040600020601482526040600020846000528252604060002092600052601582526040600020946018835260406000209460198452604060002054926001976103cd89830154915482614010565b9261271090816103e1600486015487614010565b049860009a6000926000988254995b8a8110610521575b50505050549060ff8260a81c16159182610506575b50816104eb575b1561049c57505050600b015460ff169060038210156104865760409786926002810361046d575050610447929350614003565b8161045a575b50505b8351928352820152f35b610465929450614023565b91388061044d565b92509250501461047e575b50610450565b925038610478565b634e487b7160e01b600052602160045260246000fd5b94509450975050506104b3575b5060409350610450565b909250611388938481029481860414901517156104d5576040930491386104a9565b634e487b7160e01b600052601160045260246000fd5b905060ff600b850154166003811015610486578b1490610414565b60ff91925060a01c166003811015610486578b14903861040d565b828261052d8387613f29565b90549060031b1c16146105485761054390613eb9565b6103f0565b5050505090508a90388080806103f8565b6040516379a6671160e01b8152908190610577908560048401613fca565b0390fd5b604051639cd1128160e01b815260048101849052600560248201526064916105a7906044830190613b6a565bfd5b60405163d13b267760e01b815260048101839052602490fd5b600080fd5b346105c25760203660031901126105c2576004356105e3613e8d565b6105eb613fa7565b6000818152601360205260409020546001600160a01b0316156106fb5780600052601360205260ff60056040600020015416600781101561048657600181036106d0575080600052601360205261064d60086040600020015460105490613f7c565b42106106be576040516106b89161066382613d1c565b603e82527f43616e63656c6c65642062792061646d696e3a2050797468204e6574776f726b60208301527f2072616e646f6d6e65737320726571756573742074696d6564206f75742e00006040830152614b72565b60018055005b6040516326c015ab60e21b8152600490fd5b906105a760649260405192639cd1128160e01b84526004840152600160248401526044830190613b6a565b6024906040519063d13b267760e01b82526004820152fd5b346105c25760003660031901126105c2576020600c54604051908152f35b346105c25760003660031901126105c2576007546040516001600160a01b039091168152602090f35b346105c25760003660031901126105c2576020600d54604051908152f35b346105c25760203660031901126105c257610791613b3e565b610799613e8d565b6001600160a01b039081169081156107d3576000548260018060a01b031982161760005516600080516020615f23833981519152600080a3005b604051631e4fbdf760e01b815260006004820152602490fd5b346105c2576107fa36613dfa565b90610803613f89565b61080b613fa7565b6000838152601360205260409020546001600160a01b03908116156108ba5783600052601360205260ff600560406000200154166007811015610486576004810361088f575080600554163314159081610880575b5061086e576106b8926149b5565b60405163ea8e4eb560e01b8152600490fd5b90506000541633141584610860565b604051639cd1128160e01b8152600480820187905260248201526064916105a7906044830190613b6a565b60405163d13b267760e01b815260048101859052602490fd5b346105c25760203660031901126105c2576108ec613b3e565b6108f4613e8d565b6001600160a01b0390811690811561094257600680546001600160a01b031981168417909155167fae4e8ea4dbc2ca24cbdb325a1b328d6a2401757cb7e2be71fbc628f262771cd5600080a3005b60405163d92e233d60e01b8152600490fd5b6020806003193601126105c25760043561096c613f89565b610974613fa7565b60008181526013835260409020546001600160a01b03908116156105a957816000526013835260ff6005604060002001541660078110156104865780610b7e575081600052601383526040600020926015815260406000205460ff60028601541680911015610b605750826000526014815260406000203360005281528160406000205416610b44576001840154803403610b265750610a1e82600a541683600b541690336158e3565b9360405190610a2c82613d01565b3382528282019160008352604081019460018652606082019360008552608083019189835288600052601487526040600020336000528752604060002093511660018060a01b03198454161783555190600382101561048657610ae0600395610ac5600080516020615f0383398151915299610aaa60019688613ed4565b51865460ff60a81b191690151560a81b60ff60a81b16178655565b51845460ff60b01b191690151560b01b60ff60b01b16178455565b519101558460005260158352610afa336040600020613f41565b01610b06348254613f7c565b9055610b1434600954613f7c565b6009556040519384523393a360018055005b6044906040519063fa36c55360e01b82523460048301526024820152fd5b604051631a0af6eb60e21b815280610577338660048401613fca565b836044916040519163d9e1ab7360e01b835260048301526024820152fd5b604051639cd1128160e01b815260048101849052600060248201526064916105a7906044830190613b6a565b346105c2576020806003193601126105c257600435610bc7613f89565b610bcf613fa7565b60008181526013835260409020546001600160a01b03908116156105a95781600052601383528060406000205416803303610e875750816000526013835260ff6005604060002001541660078110156104865780610b7e57508160005260138352604060002092600984019360018060401b0380865416610e6e57846000526015835260406000205460058110610e46578460045416948515610e34577f0000000000000000000000000000000000000000000000000000000000000000169460405191631711922960e31b835281600484015285836024818a5afa928315610dca57600093610df5575b506001600160801b0390921691478311610dd657859291604491604051858101918b835242604083015260608201523360601b608082015260748152610cff81613d01565b5190209260405198899485936319cb825f60e01b8552600485015260248401525af18015610dca57600090610d84575b600080516020615f63833981519152945016948560018060401b031982541617905584600052601782528360406000205560058101600160ff198254161790556008429101556040519384523393a360018055005b50928281813d8311610dc3575b610d9b8183613d52565b810103126105c257519280841684036105c257600080516020615f6383398151915293610d2f565b503d610d91565b6040513d6000823e3d90fd5b6040516322f2dfd560e11b815260048101849052476024820152604490fd5b9092508581813d8311610e2d575b610e0d8183613d52565b810103126105c257516001600160801b03811681036105c2579189610cba565b503d610e03565b604051630b08b4ef60e41b8152600490fd5b8560ff60649260405192631480153360e31b8452600484015216602482015260056044820152fd5b6040516354dd172960e01b815260048101869052602490fd5b6040516317a9a4f960e21b815290819061057790338660048501613fe3565b346105c25760003660031901126105c2576006546040516001600160a01b039091168152602090f35b346105c25760003660031901126105c2576020600854604051908152f35b346105c25760203660031901126105c2576004356000818152601360205260409020546001600160a01b0316156106fb57610f2a610f3e9161584a565b604051918291602083526020830190613c2e565b0390f35b346105c25760003660031901126105c257600a54600b54604051918291610f3e916001600160a01b03918216911683613e73565b346105c25760003660031901126105c2576020600e54604051908152f35b346105c25760403660031901126105c2576001600160401b036004358181116105c257610fc5903690600401613e43565b9091602480358281116105c257610fe0903690600401613e43565b91610fe9613e8d565b60019081860180871161132457840361127d578561128f575b60005b84811061125a575084861161115657600160401b80871161116a576011548760115580881061120b575b5087956011600052602096876000208960051c9060005b8281106111d15750601f198b168b038061117f575b50505050851161116a578411611156575060125483601255808410611138575b5093816012600052846000209060005b85811061112757505050604051948160408701604088525260608601969160005b8181106111035750505050838503838501528185526001600160fb1b0382116105c2577f12955a64e0444022667406e8e5e263bdac7d6c8246e16b5c3f28215f09c51cdb94849260051b80928583013701030190a1005b9091929788359060ff82168092036105c257908152860197860192919082016110ac565b81358382015590860190870161108b565b61115090601260005284866000209182019101614061565b8661107b565b634e487b7160e01b60009081526041600452fd5b50634e487b7160e01b60009081526041600452fd5b9260009360005b898d83831061119f57505050505001558980808061105b565b90919293966111c790846111b28a614053565b919060ff809160031b9316831b921b19161790565b9601929101611186565b87906000805b8d8082106111eb5750508184015501611046565b90969293916111fe90886111b286614053565b92019501908992916111d7565b6112399060116000526020600020601f808b0160051c820192818c168061123f575b500160051c0190614061565b8861102f565b600019908186019182549160200360031b1c1690558d61122d565b620186a0611269828787614043565b351161127d5761127890613eb9565b611005565b604051630140299b60e71b8152600490fd5b60005b86811061129f5750611002565b60ff806112b56112b0848b8d614043565b614053565b161561127d5781151590816112d8575b5061127d576112d390613eb9565b611292565b90506112e86112b0838a8c614043565b90600019830183811161130f576113046112b083928c8e614043565b1691161115896112c5565b84634e487b7160e01b60005260116004526000fd5b50634e487b7160e01b60009081526011600452fd5b346105c25760003660031901126105c257611352613e8d565b61135a613f89565b611362613f89565b600160ff1960025416176002557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a17ff633f37eb96c51f3eee538b18ab2c8be9023a46d535102a54e986e94a52542066020604051338152a1005b346105c25760203660031901126105c2576113de613b3e565b6113e6613e8d565b6001600160a01b0316801561094257600780546001600160a01b031916821790557f1af232b023641b0b27419a06769dde7a101bdee010ec36c40109696e933761a0600080a2005b346105c25760003660031901126105c25760206040516103e88152f35b346105c25760203660031901126105c2576004356000818152601360205260409020546001600160a01b0316156106fb57806000526013602052604060002060ff60058201541690600782101561048657600582036114f857600b015460ff166003811015610486576001036114d4575060005260196020526020604060002054604051908152f35b906105a760449260405192638a8fcb0760e01b845260048401526024830190613b6a565b604051638e84d22560e01b8152600481018490526044906105a76024820185613b6a565b346105c25760003660031901126105c2576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346105c25760203660031901126105c25761157a613f89565b611582613fa7565b6004356000908152601360205260409020546001600160a01b0316156118ab57600435600052601360205260ff600560406000200154166007811015610486576005810361187e57600435600052601460205260406000203360005260205260018060a01b036040600020541615611860576004356000526014602052604060002033600052602052604060002080549060ff8260b01c166118425760043560005260136020526040600020916015602052604060002092601860205260406000206019602052604060002054916116606001820154965487614010565b612710611671600484015483614010565b0491600097600080958054965b878110611806575b505060ff8860a81c161590816117e5575b816117c9575b15611795575050600b015460ff166003811015610486576002810361177a57506116c8929350614003565b81611767575b50505b82156117495760ff60b01b1916600160b01b179055600954811161173f576116fb81600954614003565b6009555b6117098133615230565b60405190815233907fe97cee5a4c0549d3fdc81e322b718ddf0aeb3418ec87dce4f9a7fb28d117c312602060043592a360018055005b60006009556116ff565b60405163f54d5f5360e01b8152806105773360048035908401613fca565b611772929450614023565b9183806116ce565b915050600191501461178d575b506116d1565b925083611787565b945094505050506117a657506116d1565b909250611388908181029181830414901517156104d55761271090049183611787565b905060ff600b840154166003811015610486576001149061169d565b9050600360ff8960a01c16101561048657600160ff8960a01c161490611697565b6118108183613f29565b905460039190911b1c6001600160a01b031633146118365761183190613eb9565b61167e565b50505060018a80611686565b6040516302a4851560e61b8152806105773360048035908401613fca565b6040516379a6671160e01b8152806105773360048035908401613fca565b604051639cd1128160e01b81526004803590820152600560248201526064916105a7906044830190613b6a565b602460405163d13b267760e01b81526004356004820152fd5b346105c2576118d236613dfa565b906118db613f89565b6118e3613fa7565b6000838152601360205260409020546001600160a01b03908116156108ba5783600052601360205260ff6005604060002001541660078110156104865760038103611955575080600554163314159081611946575b5061086e576106b8926146a0565b90506000541633141584611938565b604051639cd1128160e01b815260048101869052600360248201526064916105a7906044830190613b6a565b60403660031901126105c2576004356024359060ff82168092036105c2576119a7613f89565b6119af613fa7565b8015611b6357600582108015611b59575b611b4057803403610b2657600354906119d882613eb9565b60035560085490826000526020906013825260406000209260018060a01b03199333858254161781558260018201556002810160ff199088828254161790553460038301558260048301556005820190815416905560064291015560018060a01b0395611a4f87600a541688600b541690336158e3565b9660405190611a5d82613d01565b3382528582019060008252604083019160018352606084019860008a5260808501928c84528b60005260148a526040600020336000528a526040600020955116908554161784555191600383101561048657600080516020615f0383398151915298610ac5611ad292610aaa60019688613ed4565b519101558560005260158452611aec336040600020613f41565b611af883600954613f7c565b600955604051928352838301526040820152837fb03d4415051b223aac81e9cf0c228cff5c476e00dde5d1970fbfe34e72f82f6260603393a36040519384523393a360018055005b60405163d666109b60e01b815260048101839052602490fd5b50600c82116119c0565b60405163fa36c55360e01b81526000600482015260016024820152604490fd5b346105c25760003660031901126105c25760405180916011548083528360208080950180946011600052826000209460005b81601f8201106120815784611cd497549383831061206c575b838310612054575b83831061203c575b838310612024575b83831061200d575b838310611ff5575b838310611fdd575b838310611fc5575b838310611fad575b838310611f95575b838310611f7d575b838310611f65575b838310611f4d575b838310611f35575b838310611f1d575b838310611f05575b838310611eed575b838310611ed5575b838310611ebd575b838310611ea5575b838310611e8d575b838310611e75575b838310611e5d575b838310611e45575b838310611e2d575b838310611e15575b838310611dfd575b838310611de5575b838310611dcf575b50828210611db9575b828210611da3575b5010611d95575b509050949392940383613d52565b60405190819381601254938481520180936012600052836000209060005b818110611d815750505085611d08910386613d52565b6040519485946040860190604087525180915260608601929060005b818110611d67575050508482038584015251808252908201929160005b828110611d5057505050500390f35b835185528695509381019392810192600101611d41565b825160ff1685528897509385019391850191600101611d24565b825484529285019260019283019201611cf2565b60f81c815201849087611cc6565b6001919460ff8560f01c16815201930184611cbf565b6001919460ff8560e81c16815201930184611cb7565b9460ff85600194971c168152019301848b611cae565b91948160019160ff8760d81c16815201950191611ca6565b91948160019160ff8760d01c16815201950191611c9e565b91948160019160ff8760c81c16815201950191611c96565b91948160019160ff8760c01c16815201950191611c8e565b91948160019160ff8760b81c16815201950191611c86565b91948160019160ff8760b01c16815201950191611c7e565b91948160019160ff8760a81c16815201950191611c76565b91948160019160ff8760a01c16815201950191611c6e565b91948160019160ff8760981c16815201950191611c66565b91948160019160ff8760901c16815201950191611c5e565b91948160019160ff8760881c16815201950191611c56565b91948160019160ff8760801c16815201950191611c4e565b91948160019160ff8760781c16815201950191611c46565b91948160019160ff8760701c16815201950191611c3e565b91948160019160ff8760681c16815201950191611c36565b91948160019160ff8760601c16815201950191611c2e565b91948160019160ff8760581c16815201950191611c26565b91948160019160ff8760501c16815201950191611c1e565b91948160019160ff8760481c16815201950191611c16565b91948160019160ff8760401c16815201950191611c0e565b91948160019160ff8760381c16815201950191611c06565b91948160019160ff8760301c16815201950191611bfe565b91948160019160ff8760281c16815201950191611bf6565b91948160019160ff87831c16815201950191611bee565b91948160019160ff8760181c16815201950191611be6565b91948160019160ff8760101c16815201950191611bde565b91948160019160ff8760081c16815201950191611bd6565b91948160019160ff8716815201950191611bce565b926001919492955061040090875460ff8082168352808260081c1686840152808260101c16604084015280828a82828782828d82826060828260181c168188015282826080951c168488015260a096838360281c168882015260c09a848460301c168c830152848460381c1690820152838360401c16610100820152838360481c16610120820152838360501c16610140820152610160848460581c169101521c166101808d0152828260681c166101a08d0152828260701c166101c08d0152828260781c166101e08d01521c166102008a0152828260881c166102208a0152828260901c166102408a0152828260981c166102608a01521c16610280870152828260a81c166102a0870152828260b01c166102c0870152828260b81c166102e08701521c16610300840152808260c81c16610320840152808260d01c16610340840152808260d81c1661036084015280828a1c16610380840152808260e81c166103a08401528160f01c166103c083015260f81c6103e082015201950191019286928894959295611bb5565b346105c25760203660031901126105c257612227613b3e565b61222f613e8d565b6001600160a01b0316801561094257600480546001600160a01b031916821790557f1988eae50e3c966bb15e25079f549aabc769fff8405390f7914b211a9ca67972600080a2005b346105c25760003660031901126105c257612290613e8d565b612298614097565b6122a0614097565b60ff19600254166002557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a17fb5a73b054ac6db0c0e6429d3bb998734a660c422d158b35e9cd088b30d42f9e06020604051338152a1005b346105c25760003660031901126105c25760408051908101906001600160401b0382118183101761235557610f3e91604052600a81526928e280bfcba0e280bf2960b01b602082015260405191829182613c81565b634e487b7160e01b600052604160045260246000fd5b346105c2576020806003193601126105c257600435612388613fa7565b60008181526013835260409020546001600160a01b0392908316156105a957816000526013815260ff6005604060002001541660078110156104865780610b7e5750816000526013815282604060002054163314928060005416331490806005541633149060065416331490859286156124d5575b5082156124cd575b5081156124c5575b501561086e576106b89215612467577f43616e63656c6c656420627920686f7374207768696c6520696e204c6f6262796040519161244a83613d1c565b60278352820152661039ba30ba329760c91b604082015290614b72565b7f43616e63656c6c65642062792061646d696e2f6261636b656e642f76616c69646040519161249583613d1c565b603a83528201527930ba37b9103bb434b6329034b7102637b1313c9039ba30ba329760311b604082015290614b72565b90508461240d565b915085612405565b9250866123fd565b346105c25760203660031901126105c25760206124fb600435615771565b6125086040518092613b77565bf35b346105c25760203660031901126105c2576001600160401b0361252b613c6b565b1660005260176020526020604060002054604051908152f35b346105c25760003660031901126105c2576000546040516001600160a01b039091168152602090f35b346105c25760003660031901126105c2576020600954604051908152f35b346105c25760403660031901126105c2576004356125a7613b54565b90806000526020916013835260018060a01b0390816040600020541615612740578260005260148452604060002091808216928360005285528060406000205416156127215780600054163314159081612711575b81612702575b5061086e57826000526013845260ff60056040600020015416600781101561048657600581149081156126f7575b506126de578260005260148452604060002082600052845260ff60406000205460a81c16156126c05750817f56361dafe421b3aba201873229b0c108cb63c45289b37d3d30b68d7ffa876dfe61001b9482600052601481526040600020846000528152604060002060ff60a81b1981541690558260005260138152600760406000200154604051908152a3614d32565b604051638c5355ab60e01b8152908190610577908560048401613fca565b60405163712286e160e11b815260048101849052602490fd5b600691501485612630565b90506006541633141585612602565b80915060055416331415906125fc565b506040516379a6671160e01b8152908190610577908560048401613fca565b60405163d13b267760e01b815260048101849052602490fd5b346105c25760a03660031901126105c257604435602435600435608435606435612781613e8d565b603c8510801561282d575b8015612823575b8015612818575b801561280d575b6127fb577f2621620528f26fb75529114de7b19d9f01b11bb4a9bd0ad57997a33cf8141ec09460a09484600f558060105581600c5582600d5583600e556040519485526020850152604084015260608301526080820152a1005b6040516325c3636760e01b8152600490fd5b5061012c84106127a1565b5061012c831061279a565b50601e8210612793565b50603c811061278c565b346105c25760003660031901126105c257602060405160058152f35b346105c25760203660031901126105c25761286c613b3e565b612874613e8d565b6001600160a01b0316801561094257600580546001600160a01b031916821790557f9177ce6b43739f0c598396fcbb3deeeddefeee033f92b4fa36b1c5ec1afdd14e600080a2005b346105c257602090816003193601126105c2576004356128da615710565b5060008181526013845260409020546001600160a01b0392908316156105a95781600052601384526040600020906040519361291585613ce5565b825416845260018201548585015260ff6002830154166040850152600382015460608501526004820154608085015260ff6005830154169160a0850191600784101561048657600b6129c19260ff926129dd968652600682015460c08a0152600782015490890152600881015461010089015282600982015460018060401b0381166101208b015260401c161515610140890152600a8101546101608901520154166101808601613ec8565b82600052601585526129e4604060002060405193848092614cec565b0383613d52565b5191600783101561048657612a3494610f3e93600503612a555760189160005252612a46612a1f612a26604060002060405192838092614cec565b0382613d52565b915b60405195868096613b84565b6101e0806101a0870152850190613c2e565b908382036101c0850152613c2e565b5050612a46604051612a6681613d37565b6000815291612a28565b346105c25760003660031901126105c257612a89613e8d565b600080546001600160a01b0319811682556001600160a01b0316600080516020615f238339815191528280a3005b346105c25760403660031901126105c257612ad0613b54565b612ad8613fa7565b6004356000908152601360205260409020546001600160a01b0316156118ab57600435600052601360205260ff600560406000200154166007811015610486576005810361187e575060043560009081526014602090815260408083206001600160a01b0385811685529252909120541615612e09576000546001600160a01b031633141580612df4575b80612ddf575b61086e576004356000526014602052604060002060018060a01b03821660005260205260406000209081549160ff8360b01c16612ce15760043560005260136020526040600020601560205260406000206018602052604060002090601960205260406000205492612be16001820154925483614010565b90612710612bf3600483015484614010565b0492600098600080968054975b888110612d9f575b505060ff8260a81c16159182612d7d575b5081612d61575b15612d2d575050600b015460ff1660038110156104865760028103612d125750612c4b929350614003565b81612cff575b50505b8215612ce1576009548311612cd757612c6f83600954614003565b6009555b612c86836001600160a01b038416615230565b805460ff60b01b1916600160b01b1790556040519182526001600160a01b031690600435907f6aab79494515e500f72558d888a9426fa23041c0bf39b592fdd8809c98eabef590602090a360018055005b6000600955612c73565b60405163f54d5f5360e01b8152806105778460048035908401613fca565b612d0a929450614023565b918380612c51565b9150506001915014612d25575b50612c54565b925083612d1f565b94509450505050612d3e5750612c54565b909250611388908181029181830414901517156104d55761271090049183612d1f565b905060ff600b8401541660038110156104865760011490612c20565b909150600360ff8260a01c1610156104865760a01c60ff16600114908b612c19565b612da98183613f29565b905460039190911b1c6001600160a01b03908116908d1614612dd357612dce90613eb9565b612c00565b50505060018b80612c08565b506006546001600160a01b0316331415612b69565b506005546001600160a01b0316331415612b63565b6040516379a6671160e01b81529081906105779060048035908401613fca565b346105c25760403660031901126105c257600435612e45613b54565b612e4d613f89565b612e55613fa7565b6000828152601360205260409020546001600160a01b03908116156127405782600052601360205260ff6005604060002001541660078110156104865760028103612ec7575080600554163314159081612eb8575b5061086e576106b891614307565b90506000541633141583612eaa565b604051639cd1128160e01b815260048101859052600260248201526064916105a7906044830190613b6a565b346105c25760203660031901126105c257600435612f0f613f89565b612f17613fa7565b6000818152601360205260409020546001600160a01b0316156106fb5780600052601360205260ff6005604060002001541660078110156104865780612fd75750806000526013602052612f76600660406000200154600f5490613f7c565b42106106be576040516106b891612f8c82613d1c565b603382527f43616e63656c6c65642064756520746f204c6f6262792070686173652074696d60208301527232b7baba1014383ab13634b19031b0b636149760691b6040830152614b72565b906105a760649260405192639cd1128160e01b84526004840152600060248401526044830190613b6a565b346105c25760003660031901126105c2576020600354604051908152f35b346105c25760203660031901126105c25760043561303c613f89565b6000818152601360205260409020546001600160a01b0316156106fb5780600052601360205260ff60056040600020015416600781101561048657600581149081156130a9575b506130915761001b906140b6565b6024906040519063712286e160e11b82526004820152fd5b600691501482613083565b346105c25760003660031901126105c257602060ff600254166040519015158152f35b346105c25760603660031901126105c2576130f0613c6b565b6130f8613b54565b507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031680156105c25733036105c2576106b89061313b613fa7565b613143613f89565b604435906152d0565b346105c2576020806003193601126105c25760043560008181526013835260409020546001600160a01b0316156106fb57806000526013825260ff600560406000200154166007811015610486576005036131d557906129dd91600052601881526131c1604060002060405193848092614cec565b610f3e604051928284938452830190613c2e565b601360449282600052526105a760ff6005604060002001541660405192638e84d22560e01b845260048401526024830190613b6a565b346105c25760203660031901126105c257600435613227613e8d565b6103e8808211613262577fdc4e4be378c228e1b7b13787e1d0620609304c2f71ed3a6ee69e4aae99d58cd960208380600855604051908152a1005b6040516373ab893560e11b815260048101929092526024820152604490fd5b346105c25760203660031901126105c25760043561329d615710565b506000818152601360205260409020546001600160a01b0391908216156106fb5760005260136020526040600020604051916132d883613ce5565b81541682526001810154602083015260ff6002820154166040830152600381015460608301526004810154608083015260ff60058201541692600784101561048657600b6133839260ff926101a09660a0870152600682015460c0870152600782015490860152600881015461010086015282600982015460018060401b03811661012088015260401c161515610140860152600a8101546101608601520154166101808301613ec8565b6125086040518092613b84565b346105c25760003660031901126105c2576020604051600c8152f35b346105c25760403660031901126105c2576004356133c8613b54565b906040516133d581613cca565b600081526000606060209282848201528260408201520152816000526013815260018060a01b03918260406000205416156106fb578060005260148252604060002090838516918260005283528360406000205416156134a65760809450600052601482526040600020906000528152604060002091600183549182169301549160405161346281613cca565b84815260608282019160ff8560a81c161515835260ff604082019560b01c161515855201938452604051948552511515908401525115156040830152516060820152f35b6040516379a6671160e01b815290819061057790879060048401613fca565b346105c25760403660031901126105c2576024803560043560078210156105c2576134ee613e8d565b6000818152601360205260409020546001600160a01b03161561369d57806000526013602052604060002091600583019283549360ff8516948315613678575b60018414613664575b6005841480613637575b6105c25760ff191660ff841617905560028214600081801561362a575b818115613605575b506135b2575b50505061358a906135806040518095613b6a565b6020840190613b6a565b7f4c2908af6339a7cecca8470b17951a83d590ff59671ec6a761871d3c69fb76ce60403393a3005b4260088401556135f05761358a93949550806135e4575b6135d7575b8493928161356c565b60076001910155846135ce565b506007810154156135c9565b85634e487b7160e01b60005260216004526000fd5b9050613615576004841481613566565b86634e487b7160e01b60005260216004526000fd5b505060006003841461355e565b5060ff600b84015416600381101561364f5715613541565b87634e487b7160e01b60005260216004526000fd5b600786101561361557851561353757600080fd5b60078610156136155785158015613693575b61352e57600080fd5b506006861461368a565b90506040519063d13b267760e01b82526004820152fd5b346105c25760203660031901126105c2576136cd613b3e565b6136d5613e8d565b600b80546001600160a01b0319166001600160a01b039290921691821790556002600080516020615fa3833981519152600080a3005b346105c2576020806003193601126105c25760043590613729613f89565b613731613fa7565b60008281526013825260409020546001600160a01b0392908316156106fb57806000526013825282604060002054168033036139a35750806000526013825260406000206005810160ff81541660078110156104865760058114908115613998575b506126de5760038083019283549160006004830154908115158061398b575b613942575b6137c2915084614003565b6009549093908111613937576137da90600954614003565b6009555b856000526015875260406000209260019889809401926138018454875490614010565b9586613881575b858b8b60008c8c600660ff19825416179055556033604051928084528301527f43616e63656c6c656420627920686f73743b206665652064656475637465642c604083015272103932b33ab7323990383937b1b2b9b9b2b21760691b6060830152600080516020615ea383398151915260803393a38055005b6000865b613890575b50613808565b81548110156139325780868d8d876138ab6139029688613f29565b905490891b1c1691816000526014815260406000208360005281526138df8d6138da8c60406000209754614010565b614023565b9081613908575b5050825460ff60b01b1916600160b01b1790925550613eb99050565b86613885565b81613922600080516020615ee38339815191529386615230565b604051908152a38f8d8f806138e6565b61388a565b5060006009556137de565b506139506127109185614010565b0480151580613981575b1561397657806139716137c2928b60075416615230565b6137b7565b506137c260006137b7565b508381111561395a565b50896007541615156137b2565b600691501486613793565b6040516317a9a4f960e21b815291829161057791339060048501613fe3565b346105c25760003660031901126105c2576004546040516001600160a01b039091168152602090f35b346105c25760403660031901126105c257600435600081815260136020526040902054602435916001600160a01b03918216156106fb576000526015602052604060002080548310156105c257602092613a4491613f29565b9190546040519260031b1c168152f35b346105c25760003660031901126105c257602060405161012c8152f35b346105c25760003660031901126105c257600a546040516001600160a01b039091168152602090f35b346105c25760003660031901126105c2576005546040516001600160a01b039091168152602090f35b346105c25760203660031901126105c257613adc613b3e565b613ae4613e8d565b600a80546001600160a01b0319166001600160a01b039290921691821790556001600080516020615fa3833981519152600080a3005b346105c25760003660031901126105c257600b546001600160a01b03168152602090f35b600435906001600160a01b03821682036105c257565b602435906001600160a01b03821682036105c257565b9060078210156104865752565b9060038210156104865752565b90613c2c9160018060a01b0381511682526020810151602083015260ff60408201511660408301526060810151606083015260808101516080830152613bd260a082015160a0840190613b6a565b60c0818101519083015260e080820151908301526101008082015190830152610120808201516001600160401b03169083015261014080820151151590830152610160808201519083015261018090810151910190613b77565b565b90815180825260208080930193019160005b828110613c4e575050505090565b83516001600160a01b031685529381019392810192600101613c40565b600435906001600160401b03821682036105c257565b6020808252825181830181905290939260005b828110613cb657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501613c94565b608081019081106001600160401b0382111761235557604052565b6101a081019081106001600160401b0382111761235557604052565b60a081019081106001600160401b0382111761235557604052565b606081019081106001600160401b0382111761235557604052565b602081019081106001600160401b0382111761235557604052565b601f909101601f19168101906001600160401b0382119082101761235557604052565b6001600160401b0381116123555760051b60200190565b81601f820112156105c257803591613da383613d75565b92613db16040519485613d52565b808452602092838086019260051b8201019283116105c2578301905b828210613ddb575050505090565b81356001600160a01b03811681036105c2578152908301908301613dcd565b60606003198201126105c257600435916001600160401b036024358181116105c25783613e2991600401613d8c565b926044359182116105c257613e4091600401613d8c565b90565b9181601f840112156105c2578235916001600160401b0383116105c2576020808501948460051b0101116105c257565b6001600160a01b0391821681529116602082015260400190565b6000546001600160a01b03163303613ea157565b60405163118cdaa760e01b8152336004820152602490fd5b60001981146104d55760010190565b60038210156104865752565b90600381101561048657815460ff60a01b191660a09190911b60ff60a01b16179055565b601254811015613f1357601260005260206000200190600090565b634e487b7160e01b600052603260045260246000fd5b8054821015613f135760005260206000200190600090565b8054600160401b81101561235557613f5e91600182018155613f29565b819291549060031b9160018060a01b03809116831b921b1916179055565b919082018092116104d557565b60ff60025416613f9557565b60405163d93c066560e01b8152600490fd5b600260015414613fb8576002600155565b604051633ee5aeb560e01b8152600490fd5b9081526001600160a01b03909116602082015260400190565b9081526001600160a01b0391821660208201529116604082015260600190565b919082039182116104d557565b818102929181159184041417156104d557565b811561402d570490565b634e487b7160e01b600052601260045260246000fd5b9190811015613f135760051b0190565b3560ff811681036105c25790565b81811061406c575050565b60008155600101614061565b9080614082575050565b613c2c91600052602060002090810190614061565b60ff60025416156140a457565b604051638dfc202b60e01b8152600490fd5b60008181526013602052604090818120600581019060ff82541691600882018054906007851015938461427d57600286149283156142b657600c546140fa91613f7c565b61012c81018091116142a2574210614291576007019184897fb5cab6c1716e65f60a857bf7351124136e99963a75d21d778e7727144e35231360c086548c5190614144828d613b6a565b602082015260608d820152602860608201527f5075626c69632074696d656f75742066616c6c6261636b20696e766f6b656420608082015267313c903ab9b2b91760c11b60a0820152a261427d57600386148015614270575b1561420057505050506141ec576004146141cd575b50506141bd81614d32565b6141ca57613c2c906151e1565b50565b6141e591838252601660205281208054918155614078565b38806141b2565b634e487b7160e01b82526021600452602482fd5b919390929695945061425c57614219575b505050505050565b61422286614d32565b61421157600080516020615f8383398151915294600360ff19825416179055429055549082519182526020820152a2388080808080614211565b634e487b7160e01b84526021600452602484fd5b508694506004861461419d565b634e487b7160e01b87526021600452602487fd5b87516326c015ab60e21b8152600490fd5b634e487b7160e01b88526011600452602488fd5b879550600387036142cd57600d546140fa91613f7c565b879550600487036142e457600e546140fa91613f7c565b8851638a8fcb0760e01b8152600481018b90526044906105a7602482018a613b6a565b906000908282526020601381526040928381209060078201549260018060a01b039081861692831515808091614640575b80614621575b6145d5575b8189918b959493968297839888855260158752858520858154905b8181106144f957505050836144db575b836144ba575b8361446b575b83614460575b5082614455575b5081614449575b8161443c575b506143eb575b505050506143a89150614d32565b6143e457600080516020615f83833981519152938160056143df9301600360ff198254161790556008429101555192839283613fca565b0390a2565b5050505050565b7fcb5e8d6d1fd0c2bd9eca2e2546d3d92f27a2b10728927512cb00ce7bdc8738ad938582526014845282822096169586825283522060ff60a81b1981541690558751868152a338858180878161439a565b9050848716141538614394565b8786161515915061438e565b600114915038614387565b600114925038614380565b94509150919394958452601485528a8420828552855260ff8b85205460a01c1660038110156144a657918b939160018e98979694149261437a565b634e487b7160e01b85526021600452602485fd5b8885526014875285852083865287528585205460a81c60ff16159350614374565b8885526014875285852083865287528585205488161515935061436e565b919395978a9b889a9b929496985260148b52808a206145188487613f29565b939054600394851b1c168b528b5289205460ff90818160a81c16614554575b50505061454390613eb9565b918f9998979593918f97959361435e565b60a09693961c16818110156145c1576002810361458357505061457961454391613eb9565b935b903880614537565b919491600114614598575b506145439061457b565b94614543919b506145a98b91613eb9565b956145b48d86613f29565b9054911b1c169a9061458e565b634e487b7160e01b8a52602160045260248afd5b89825260148352888220858352835288822060ff60a81b198154169055848a7f8cc6a126ca79d9271b80277e31507795e87602b1d7ce66e4451363f929f4811c858c518b8152a3614343565b5089825260148352888220858352835260ff8983205460a81c1661433e565b506014835288822085835283528389832054161515614338565b9061466482613d75565b6146716040519182613d52565b8281528092614682601f1991613d75565b0190602036910137565b8051821015613f135760209160051b010190565b90929183518151036105c257600082815260209460138652604091828120936146de60078601549360158a52858420601493848c5287862092615b74565b948486015190600191828114806149a2575b80614996575b1561476957505050968082879899887f47e734b70f4e742ea488db9e70df12c13e01b4d6cc4a500191e3b724b6a83bdd979695999899528252848082209160018060a01b0392838b5116825284522060ff60a81b198154169055875116960151908351928352820152a36141bd81614d32565b918094979192118061498a575b15614942576060019287600080516020615e83833981519152878c6147aa885183519384938c85528401528b830190613c2e565b0390a287875260168a526147c5868820805490898155614078565b87875260168a5285872093879384835b6148c6575b5050505060028210600014614825575050508584600080516020615ec383398151915261481c9697986016948651908152a28584525281208054918155614078565b6141bd81614d32565b60058199969495939901600460ff198254161790556008429101556148498161465a565b925b8181106148915750507f9058488d8f4ca0c7ed50681193e9b6dddd19f1cb87c905f3fc1e73fb6cb6af80949596509081806143df93519586958652850152830190613c2e565b8061489f6148c1928b613f29565b905460039190911b1c6001600160a01b03166148bb828761468c565b52613eb9565b61484b565b82515181101561493d578a8a52818d528c898b2060018060a01b0391826148ee85885161468c565b51168d525260ff8a8c205460a81c16614912575b5061490c90613eb9565b836147d5565b81966149316149369261492961490c95885161468c565b51168a613f41565b613eb9565b9590614902565b6147da565b5050505061497e600080516020615e83833981519152938386979894519161496983613d37565b82528080519586958652850152830190613c2e565b0390a26141bd81614d32565b50898101511515614776565b508a88015115156146f6565b5087516001600160a01b031615156146f0565b909182518151036105c2576000908282526020906013825260409160078385200154956016928383528486208054908115614b5d57505090614a099187875260158452858720906014855286882092615b74565b8051949687956001600160a01b03908116614af0575b87989184879261481c9a94600185880151149081614ae3575b81614ad6575b81614ace575b5015614ab057907fbc2df99d70b88b7387193e2ee99061e3b292151b21724b0b34f9a7fd6aa88d09939291858b5260148252838b20818851168c528252838b2060ff60a81b198154169055865116950151825191825286820152a35b8584525281208054918155614078565b5091600080516020615ec383398151915293945051908152a2614aa0565b905038614a44565b8784015115159150614a3e565b8751831615159150614a38565b865b8888528585528688208054821015614b4b57614b0f828492613f29565b919054818751169260031b1c1614614b2f57614b2a90613eb9565b614af2565b5087985061481c979184879260015b9b50925050919750614a1f565b505087989184879261481c9a94614b3e565b955095935050505061481c9394508155614078565b600081815260209060138252604091828220601582528383209160039283830193845460095481818111600014614cdb575050866009555b80151580614cd1575b614bf0575b50505050906005600080516020615ea383398151915294939201600660ff19825416179055555180614beb339582613c81565b0390a3565b4710614cc057600191828501549081614c0a575b50614bb8565b87845b614c18575b50614c04565b8a8254821015614cba579089614c8d92600080516020615ee3833981519152898e80614c44878a613f29565b90548688526014808652838920928e1b9190911c6001600160a01b03908116808a529286528389205492989216614c93575b505050614c838986615230565b51888152a3613eb9565b84614c0d565b8682528452818120878252845220805460ff60b01b1916600160b01b179055808f38614c76565b50614c12565b865163786e0a9960e01b8152600490fd5b5082541515614bb3565b614ce491614003565b600955614baa565b90815480825260208092019260005281600020916000905b828210614d12575050505090565b83546001600160a01b031685529384019360019384019390910190614d04565b6000818152601560205260408120805492918080805b86811061513957506000911590818061512e575b156151035750505050600292600193845b614d79575b5050505090565b836000526013602052604060002091600583019060ff1991600583825416179055600b84019160038410156104865782541660ff8416178255856000526018602052614dcf604060002080549060008155614078565b8560005260186020526040600020936000926014602052604060002060405191606083019083821060018060401b038311176123555760ff91604052600084526000602085015260006040850152614e406003860154612710614e36600489015483614010565b0490818752614003565b602085015260006040850152541660038110156104865760011461504b575b508051600a60406020840151930151930192835580614ff4575b509054818111614fea57614e8c91614003565b945b60005b828110614f385750505060009381614f25575b50506003811015610486577f4fd7802900f80ee62d07cf9efe951278d8c503d4cbb86ac8ea4d2820d9be00a592614efe614eed9360018414614f10575b60405194858095613b77565b606060208501526060840190614cec565b9060408301520390a238808080614d72565b86600052601960205282604060002055614ee1565b614f30929450614023565b913880614ea4565b8760005260146020526040600020614f508284613f29565b60018060a01b0391549060031b1c1660005260205260406000205460ff8160a81c169081614fc2575b50614f8d575b614f8890613eb9565b614e91565b92614fba614f8891614931614fa28786613f29565b905460039190911b1c6001600160a01b031689613f41565b939050614f7f565b9050600386101561048657600360ff8260a01c1610156104865760a01c60ff16851438614f79565b5050600094614e8e565b6007546001600160a01b0316908115615039576009546150299290821161502f5761502182600954614003565b600955615230565b38614e79565b6000600955615230565b604051637728d5eb60e01b8152600490fd5b87549060005b82811061505f575050614e5f565b615069818b613f29565b60018060a01b0391549060031b1c166000528160205260406000205460ff8160a81c161590816150e3575b506150a8575b6150a390613eb9565b615051565b600185015490611388918281029281840414901517156104d5576150d76127106150a393046040870151613f7c565b6040860152905061509a565b9050600360ff8260a01c1610156104865760a01c60ff1660011438615094565b959295159081615125575b5061511a575b84614d6d565b506001935083615114565b9050153861510e565b506001811015614d5c565b85600052601460205260406000206151518287613f29565b9190549160039260018060a01b0391841b1c1660005260205260406000205460ff90818160a81c1661518f575b50505061518a90613eb9565b614d48565b60a01c169081101561048657600281036151bb5750906151b161518a91613eb9565b915b90388061517e565b6001146151cc575b61518a906151b3565b916151d961518a91613eb9565b9290506151c3565b806000526013602052600080516020615f438339815191526020604060002060058101600260ff19825416179055600781019061521e8254613eb9565b809255600842910155604051908152a2565b81156152cc578147106152ba57600080808094819460018060a01b03165af1903d156152b4573d906001600160401b0382116152a0576040519161527e601f8201601f191660200184613d52565b825260203d92013e5b1561528e57565b604051633d2cec6f60e21b8152600490fd5b634e487b7160e01b81526041600452602490fd5b50615287565b60405163786e0a9960e01b8152600490fd5b5050565b907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316338190036156f1575060018060401b038083166000526017602052604060002054926003548085101590816156e7575b506156e157600084815260136020526040902080549093906001600160a01b0316156143e457600984018054848416858216036156c75760ff6005870154166007811015610486576001036156be5760ff8160401c166156be5760ff60401b1916600160401b179055818316857fae7cceb24659f070aa32d8aa9ced46f64e3ce6a1bcbb5ac917ee33d3188abf7d600080a384600052601460205260406000206015602052604060002090815492600184106105c2576153ea8461465a565b906000805b86811061563a575080156105c25790615456869260405160208101908482527f44454d574954434845535f524f4c455f41535349474e4d454e545f53414c545f6040820152630ac665c760e31b60608201526044815261544e81613cca565b519020615b6a565b926000936000805b8582106155d6575b505050506001600160a01b03831615615564575b505060005b8481106154fa57505050505016600052601760205260006040812055600260ff1960058301541617600582015560016007820155600842910155600080516020615f438339815191526020604051837fb607ccb3d7ced88cd923d2c5f091149c8ecc5fb24209afa25a43ddf94253801d600080a260018152a2565b806155086155559286613f29565b90546001600160a01b0385811660039390931b9190911c160361555a5761493160025b6155358388613f29565b60018060a01b0391549060031b1c16600052856020526040600020613ed4565b61547f565b614931600161552b565b6155bd9250906155b791604051602081019182527f44454d574954434845535f57495443485f46414c4c4241434b5f53414c545f566040820152620665c760eb1b60608201526043815261544e81613cca565b83613f29565b905460039190911b1c6001600160a01b0316833861547a565b839495506155e9826155f093949561468c565b5190613f7c565b908882841061560d575061560390613eb9565b889493929161545e565b94955091505061561e915085613f29565b905460039190911b1c6001600160a01b03169138808080615466565b906064906156488388613f29565b60018060a01b0391549060031b1c166000528560205260016040600020015480615692575b50816156879161568d93615681868961468c565b52613f7c565b91613eb9565b6153ef565b9081830291838304036104d5576127108204830183116104d5576127109091049091019061568761566d565b50505050505050565b60405163f7db5e9d60e01b81528585166004820152602490fd5b50505050565b905015153861532b565b60405163b16a0a0b60e01b815290819061057790339060048401613e73565b6040519061571d82613ce5565b816101806000918281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e0820152826101008201528261012082015282610140820152826101608201520152565b600081815260136020908152604080832054909291906001600160a01b03908116156158425760148252838320338452825233908484205416036157f2578382526013815282822060ff6009820154851c1615908115615828575b81156157f9575b506157f25760ff938252601481528282209033835252205460a01c1690565b5091505090565b60ff91506005015416600781101561581457600114386157d3565b634e487b7160e01b83526021600452602483fd5b905060ff600582015416600781101561425c5715906157cc565b505091505090565b600090808252601360205260ff60056040842001541660078110156158145760041461588757506040519061587e82613d37565b80825236813790565b604082613e4092612a1f945260166020522060405192838092614cec565b90601154821015613f13576011600052600582901c7f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c680191601f1690565b6000926001600160a01b0392839190821680615af1575b50169182615a77575b5050506012548015615a70576011918254918215615a39576001830190818411615a245703615a1c5760009260005b83811061598c5750600019830190838211615978575061595360ff916158a5565b90549060031b1c1610615964575090565b61596e9150613ef8565b90549060031b1c90565b634e487b7160e01b60005260045260246000fd5b82151580615a00575b6159a8575b6159a390613eb9565b615932565b801580156159c4575b1561599a579250505061596e9150613ef8565b5060001981018181116159eb576159dc60ff916158a5565b90549060031b1c1683116159b1565b82634e487b7160e01b60005260045260246000fd5b5060ff615a0c826158a5565b90549060031b1c16831115615995565b505050600090565b84634e487b7160e01b60005260045260246000fd5b5060009250159050613e40575060126000527fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec34445490565b5050600090565b602460209260405194859384926370a0823160e01b84521660048301525afa60009181615abb575b50615aac575b8080615903565b615ab591613f7c565b38615aa5565b90916020823d8211615ae9575b81615ad560209383613d52565b81010312615ae65750519038615a9f565b80fd5b3d9150615ac8565b6040516370a0823160e01b81528486166004820152919250602090829060249082905afa859181615b33575b50615b2b575b9083916158fa565b935082615b23565b9091506020813d8211615b62575b81615b4e60209383613d52565b81010312615b5e57519038615b1d565b8580fd5b3d9150615b41565b811561402d570690565b9390604092835194615b8586613d01565b6060808701528594600091828852602083818a015283838a01528360808a01528454988915615e745750615bb88961465a565b96845b8b51811015615cd0576001600160a01b0380615bd7838f61468c565b51169080615be5848c61468c565b51169188528585528688205481811615159081615cc2575b5080615cb9575b80615ca8575b80615c95575b615c25575b5050615c2090613eb9565b615bbb565b878b8a8f5b8310615c39575b505050615c15565b83615c45848793613f29565b90549060031b1c1614615c645750615c5c90613eb9565b8b8a8f615c2a565b615c20949350819250615c7d615c8a92615c839261468c565b51613eb9565b918c61468c565b529038808b8a615c31565b5081885260ff8789205460a81c16615c10565b508188528087892054161515615c0a565b50811515615c04565b60ff915060a81c1638615bfd565b509950969793929450615ce28461465a565b938596865b828110615d6e57505050505050615cfd8361465a565b91606086019283525b838110615d4657505083516001600160a01b03161515929150829050615d39575b50615d2f5790565b6001608082015290565b9050810151151538615d27565b615d69906001600160a01b03615d5c828561468c565b51166148bb82865161468c565b615d06565b615d788187613f29565b90546001600160a01b0360039290921b1c8116808a52858c52868a20549091811615908115615e65575b50615e5b57615db1828461468c565b5180615dc8575b5050615dc390613eb9565b615ce7565b9091998b8d01908151808411600014615e1557505052808b5260019081868d0152875115615e0157878b015297615dc3905b9038615db8565b634e487b7160e01b89526032600452602489fd5b909b92615dc394925014615e2a575b50615dfa565b868d01615e378151613eb9565b9052848b1015615e2457615e54615e4d8c613eb9565b9b8a61468c565b5238615e24565b50615dc390613eb9565b60ff915060a81c161538615da2565b98505050505050509250509056fe61c0b92f778d9580f61c1368f619231971929502139a169537859d147a3d8db17aaa838aec7a66b4c97d35cf9ef5095d74305ac4327c0bd09378674920a44e58ca623bc6d60c4de3c68b9587a6f35819c7232f5f79176c981fd7835daa19f812ff3116559cac6c47442d1f2083dc842de1f35c5f87fd25d7978d509fa59c1b1803dfbe1fcb4e2d61f3b4a0c93d8f814c92250618b3387f396cc9fc9fafe2c2038be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e06b40fcd6b8efc2e628b2efa06aea41f9d48edcf9f787184c307080c83cd22084481df23c457d78a89f0f5fabd340eb449a09d8690c045c3d8d567988ef019540a2361be2ee66c1a30168107b5157b30d03384356ba97fffc7ab6aeabdc6d211824e30ae881d6fa40479d2f3a64728e211e5620dab64f70e0043787baacd89f0ea2646970667358221220f8f18cc8a587b1d483db5026deda406d10c7898296efb1196cb2ec3d65de9c4d64736f6c6343000814003300000000000000000000000036825bf3fbdf5a29e2d5148bfe7dcf7b5639e32000000000000000000000000041c5c4a0e112717101ff1da950f6ca7ae4f9291900000000000000000000000000000000000000000000000000000000000001f4

Deployed Bytecode

0x608080604052600436101561001d575b50361561001b57600080fd5b005b60e0600035811c91826202640714613b1a575081630947d30214613ac35781630d72d57f14613a9a5781631154d19014613a715781631562c16b14613a5457816315ec12cd146139eb5781631d9023cb146130b457816322e1dcd01461073157816327bebe88146139c257816330041ba81461370b57816332f6dc7c146136b4578163352ce424146134c55781633ccd10e9146133ac5781634411b3eb1461339057816347e1d550146132815781634f2c0aec1461320b578163515371a31461314c57816352a5f1f8146130d75781635c975abb146130b457816361841cd614613020578163673ee37c146130025781636878b93914612ef35781636919f4d11461151c5781636a6354e914612e295781636bf4da7414612ab7578163715018a614612a705781637cea0c75146128bc5781638267660f1461285357816385f07bbe14612837578163867b49f21461275957816388902d691461258b57816389fc892d1461256d5781638da5cb5b1461254457816390de9f121461250a578163917e7964146124dd578163919cc6a01461236b578163922395b81461230057816392f9c0671461227757816398ddf5841461220e5781639f3e7e4214611b8357508063ad2dfc6514611981578063adcf8110146118c4578063ae169a5014611561578063afb218f51461151c578063b8ca266c1461144b578063bc063e1a1461142e578063c073343b146113c5578063c20860af14611339578063c6b00c7b14610f94578063d18658cd14610f76578063d3780db114610f42578063daef359914610eed578063ddca3f4314610ecf578063e474ee6114610ea6578063e5ed1d5914610baa578063e9173b3a14610954578063ee9c1c7e146108d3578063f0c6fc45146107ec578063f2fde38b14610778578063f6f071f11461075a578063f9e7ad8514610731578063fc9fefb014610713578063fcead4e1146105c75763fe32b0ba146102eb573861000f565b346105c25760403660031901126105c257600435610307613b54565b90806000526020906013825260018060a01b03918260406000205416156105a957816000526013815260ff600560406000200154166007811015610486576005810361057b5750816000526014815260406000209383811694856000528252836040600020541615610559575081600094939452601381526040600020601482526040600020846000528252604060002092600052601582526040600020946018835260406000209460198452604060002054926001976103cd89830154915482614010565b9261271090816103e1600486015487614010565b049860009a6000926000988254995b8a8110610521575b50505050549060ff8260a81c16159182610506575b50816104eb575b1561049c57505050600b015460ff169060038210156104865760409786926002810361046d575050610447929350614003565b8161045a575b50505b8351928352820152f35b610465929450614023565b91388061044d565b92509250501461047e575b50610450565b925038610478565b634e487b7160e01b600052602160045260246000fd5b94509450975050506104b3575b5060409350610450565b909250611388938481029481860414901517156104d5576040930491386104a9565b634e487b7160e01b600052601160045260246000fd5b905060ff600b850154166003811015610486578b1490610414565b60ff91925060a01c166003811015610486578b14903861040d565b828261052d8387613f29565b90549060031b1c16146105485761054390613eb9565b6103f0565b5050505090508a90388080806103f8565b6040516379a6671160e01b8152908190610577908560048401613fca565b0390fd5b604051639cd1128160e01b815260048101849052600560248201526064916105a7906044830190613b6a565bfd5b60405163d13b267760e01b815260048101839052602490fd5b600080fd5b346105c25760203660031901126105c2576004356105e3613e8d565b6105eb613fa7565b6000818152601360205260409020546001600160a01b0316156106fb5780600052601360205260ff60056040600020015416600781101561048657600181036106d0575080600052601360205261064d60086040600020015460105490613f7c565b42106106be576040516106b89161066382613d1c565b603e82527f43616e63656c6c65642062792061646d696e3a2050797468204e6574776f726b60208301527f2072616e646f6d6e65737320726571756573742074696d6564206f75742e00006040830152614b72565b60018055005b6040516326c015ab60e21b8152600490fd5b906105a760649260405192639cd1128160e01b84526004840152600160248401526044830190613b6a565b6024906040519063d13b267760e01b82526004820152fd5b346105c25760003660031901126105c2576020600c54604051908152f35b346105c25760003660031901126105c2576007546040516001600160a01b039091168152602090f35b346105c25760003660031901126105c2576020600d54604051908152f35b346105c25760203660031901126105c257610791613b3e565b610799613e8d565b6001600160a01b039081169081156107d3576000548260018060a01b031982161760005516600080516020615f23833981519152600080a3005b604051631e4fbdf760e01b815260006004820152602490fd5b346105c2576107fa36613dfa565b90610803613f89565b61080b613fa7565b6000838152601360205260409020546001600160a01b03908116156108ba5783600052601360205260ff600560406000200154166007811015610486576004810361088f575080600554163314159081610880575b5061086e576106b8926149b5565b60405163ea8e4eb560e01b8152600490fd5b90506000541633141584610860565b604051639cd1128160e01b8152600480820187905260248201526064916105a7906044830190613b6a565b60405163d13b267760e01b815260048101859052602490fd5b346105c25760203660031901126105c2576108ec613b3e565b6108f4613e8d565b6001600160a01b0390811690811561094257600680546001600160a01b031981168417909155167fae4e8ea4dbc2ca24cbdb325a1b328d6a2401757cb7e2be71fbc628f262771cd5600080a3005b60405163d92e233d60e01b8152600490fd5b6020806003193601126105c25760043561096c613f89565b610974613fa7565b60008181526013835260409020546001600160a01b03908116156105a957816000526013835260ff6005604060002001541660078110156104865780610b7e575081600052601383526040600020926015815260406000205460ff60028601541680911015610b605750826000526014815260406000203360005281528160406000205416610b44576001840154803403610b265750610a1e82600a541683600b541690336158e3565b9360405190610a2c82613d01565b3382528282019160008352604081019460018652606082019360008552608083019189835288600052601487526040600020336000528752604060002093511660018060a01b03198454161783555190600382101561048657610ae0600395610ac5600080516020615f0383398151915299610aaa60019688613ed4565b51865460ff60a81b191690151560a81b60ff60a81b16178655565b51845460ff60b01b191690151560b01b60ff60b01b16178455565b519101558460005260158352610afa336040600020613f41565b01610b06348254613f7c565b9055610b1434600954613f7c565b6009556040519384523393a360018055005b6044906040519063fa36c55360e01b82523460048301526024820152fd5b604051631a0af6eb60e21b815280610577338660048401613fca565b836044916040519163d9e1ab7360e01b835260048301526024820152fd5b604051639cd1128160e01b815260048101849052600060248201526064916105a7906044830190613b6a565b346105c2576020806003193601126105c257600435610bc7613f89565b610bcf613fa7565b60008181526013835260409020546001600160a01b03908116156105a95781600052601383528060406000205416803303610e875750816000526013835260ff6005604060002001541660078110156104865780610b7e57508160005260138352604060002092600984019360018060401b0380865416610e6e57846000526015835260406000205460058110610e46578460045416948515610e34577f00000000000000000000000036825bf3fbdf5a29e2d5148bfe7dcf7b5639e320169460405191631711922960e31b835281600484015285836024818a5afa928315610dca57600093610df5575b506001600160801b0390921691478311610dd657859291604491604051858101918b835242604083015260608201523360601b608082015260748152610cff81613d01565b5190209260405198899485936319cb825f60e01b8552600485015260248401525af18015610dca57600090610d84575b600080516020615f63833981519152945016948560018060401b031982541617905584600052601782528360406000205560058101600160ff198254161790556008429101556040519384523393a360018055005b50928281813d8311610dc3575b610d9b8183613d52565b810103126105c257519280841684036105c257600080516020615f6383398151915293610d2f565b503d610d91565b6040513d6000823e3d90fd5b6040516322f2dfd560e11b815260048101849052476024820152604490fd5b9092508581813d8311610e2d575b610e0d8183613d52565b810103126105c257516001600160801b03811681036105c2579189610cba565b503d610e03565b604051630b08b4ef60e41b8152600490fd5b8560ff60649260405192631480153360e31b8452600484015216602482015260056044820152fd5b6040516354dd172960e01b815260048101869052602490fd5b6040516317a9a4f960e21b815290819061057790338660048501613fe3565b346105c25760003660031901126105c2576006546040516001600160a01b039091168152602090f35b346105c25760003660031901126105c2576020600854604051908152f35b346105c25760203660031901126105c2576004356000818152601360205260409020546001600160a01b0316156106fb57610f2a610f3e9161584a565b604051918291602083526020830190613c2e565b0390f35b346105c25760003660031901126105c257600a54600b54604051918291610f3e916001600160a01b03918216911683613e73565b346105c25760003660031901126105c2576020600e54604051908152f35b346105c25760403660031901126105c2576001600160401b036004358181116105c257610fc5903690600401613e43565b9091602480358281116105c257610fe0903690600401613e43565b91610fe9613e8d565b60019081860180871161132457840361127d578561128f575b60005b84811061125a575084861161115657600160401b80871161116a576011548760115580881061120b575b5087956011600052602096876000208960051c9060005b8281106111d15750601f198b168b038061117f575b50505050851161116a578411611156575060125483601255808410611138575b5093816012600052846000209060005b85811061112757505050604051948160408701604088525260608601969160005b8181106111035750505050838503838501528185526001600160fb1b0382116105c2577f12955a64e0444022667406e8e5e263bdac7d6c8246e16b5c3f28215f09c51cdb94849260051b80928583013701030190a1005b9091929788359060ff82168092036105c257908152860197860192919082016110ac565b81358382015590860190870161108b565b61115090601260005284866000209182019101614061565b8661107b565b634e487b7160e01b60009081526041600452fd5b50634e487b7160e01b60009081526041600452fd5b9260009360005b898d83831061119f57505050505001558980808061105b565b90919293966111c790846111b28a614053565b919060ff809160031b9316831b921b19161790565b9601929101611186565b87906000805b8d8082106111eb5750508184015501611046565b90969293916111fe90886111b286614053565b92019501908992916111d7565b6112399060116000526020600020601f808b0160051c820192818c168061123f575b500160051c0190614061565b8861102f565b600019908186019182549160200360031b1c1690558d61122d565b620186a0611269828787614043565b351161127d5761127890613eb9565b611005565b604051630140299b60e71b8152600490fd5b60005b86811061129f5750611002565b60ff806112b56112b0848b8d614043565b614053565b161561127d5781151590816112d8575b5061127d576112d390613eb9565b611292565b90506112e86112b0838a8c614043565b90600019830183811161130f576113046112b083928c8e614043565b1691161115896112c5565b84634e487b7160e01b60005260116004526000fd5b50634e487b7160e01b60009081526011600452fd5b346105c25760003660031901126105c257611352613e8d565b61135a613f89565b611362613f89565b600160ff1960025416176002557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a17ff633f37eb96c51f3eee538b18ab2c8be9023a46d535102a54e986e94a52542066020604051338152a1005b346105c25760203660031901126105c2576113de613b3e565b6113e6613e8d565b6001600160a01b0316801561094257600780546001600160a01b031916821790557f1af232b023641b0b27419a06769dde7a101bdee010ec36c40109696e933761a0600080a2005b346105c25760003660031901126105c25760206040516103e88152f35b346105c25760203660031901126105c2576004356000818152601360205260409020546001600160a01b0316156106fb57806000526013602052604060002060ff60058201541690600782101561048657600582036114f857600b015460ff166003811015610486576001036114d4575060005260196020526020604060002054604051908152f35b906105a760449260405192638a8fcb0760e01b845260048401526024830190613b6a565b604051638e84d22560e01b8152600481018490526044906105a76024820185613b6a565b346105c25760003660031901126105c2576040517f00000000000000000000000036825bf3fbdf5a29e2d5148bfe7dcf7b5639e3206001600160a01b03168152602090f35b346105c25760203660031901126105c25761157a613f89565b611582613fa7565b6004356000908152601360205260409020546001600160a01b0316156118ab57600435600052601360205260ff600560406000200154166007811015610486576005810361187e57600435600052601460205260406000203360005260205260018060a01b036040600020541615611860576004356000526014602052604060002033600052602052604060002080549060ff8260b01c166118425760043560005260136020526040600020916015602052604060002092601860205260406000206019602052604060002054916116606001820154965487614010565b612710611671600484015483614010565b0491600097600080958054965b878110611806575b505060ff8860a81c161590816117e5575b816117c9575b15611795575050600b015460ff166003811015610486576002810361177a57506116c8929350614003565b81611767575b50505b82156117495760ff60b01b1916600160b01b179055600954811161173f576116fb81600954614003565b6009555b6117098133615230565b60405190815233907fe97cee5a4c0549d3fdc81e322b718ddf0aeb3418ec87dce4f9a7fb28d117c312602060043592a360018055005b60006009556116ff565b60405163f54d5f5360e01b8152806105773360048035908401613fca565b611772929450614023565b9183806116ce565b915050600191501461178d575b506116d1565b925083611787565b945094505050506117a657506116d1565b909250611388908181029181830414901517156104d55761271090049183611787565b905060ff600b840154166003811015610486576001149061169d565b9050600360ff8960a01c16101561048657600160ff8960a01c161490611697565b6118108183613f29565b905460039190911b1c6001600160a01b031633146118365761183190613eb9565b61167e565b50505060018a80611686565b6040516302a4851560e61b8152806105773360048035908401613fca565b6040516379a6671160e01b8152806105773360048035908401613fca565b604051639cd1128160e01b81526004803590820152600560248201526064916105a7906044830190613b6a565b602460405163d13b267760e01b81526004356004820152fd5b346105c2576118d236613dfa565b906118db613f89565b6118e3613fa7565b6000838152601360205260409020546001600160a01b03908116156108ba5783600052601360205260ff6005604060002001541660078110156104865760038103611955575080600554163314159081611946575b5061086e576106b8926146a0565b90506000541633141584611938565b604051639cd1128160e01b815260048101869052600360248201526064916105a7906044830190613b6a565b60403660031901126105c2576004356024359060ff82168092036105c2576119a7613f89565b6119af613fa7565b8015611b6357600582108015611b59575b611b4057803403610b2657600354906119d882613eb9565b60035560085490826000526020906013825260406000209260018060a01b03199333858254161781558260018201556002810160ff199088828254161790553460038301558260048301556005820190815416905560064291015560018060a01b0395611a4f87600a541688600b541690336158e3565b9660405190611a5d82613d01565b3382528582019060008252604083019160018352606084019860008a5260808501928c84528b60005260148a526040600020336000528a526040600020955116908554161784555191600383101561048657600080516020615f0383398151915298610ac5611ad292610aaa60019688613ed4565b519101558560005260158452611aec336040600020613f41565b611af883600954613f7c565b600955604051928352838301526040820152837fb03d4415051b223aac81e9cf0c228cff5c476e00dde5d1970fbfe34e72f82f6260603393a36040519384523393a360018055005b60405163d666109b60e01b815260048101839052602490fd5b50600c82116119c0565b60405163fa36c55360e01b81526000600482015260016024820152604490fd5b346105c25760003660031901126105c25760405180916011548083528360208080950180946011600052826000209460005b81601f8201106120815784611cd497549383831061206c575b838310612054575b83831061203c575b838310612024575b83831061200d575b838310611ff5575b838310611fdd575b838310611fc5575b838310611fad575b838310611f95575b838310611f7d575b838310611f65575b838310611f4d575b838310611f35575b838310611f1d575b838310611f05575b838310611eed575b838310611ed5575b838310611ebd575b838310611ea5575b838310611e8d575b838310611e75575b838310611e5d575b838310611e45575b838310611e2d575b838310611e15575b838310611dfd575b838310611de5575b838310611dcf575b50828210611db9575b828210611da3575b5010611d95575b509050949392940383613d52565b60405190819381601254938481520180936012600052836000209060005b818110611d815750505085611d08910386613d52565b6040519485946040860190604087525180915260608601929060005b818110611d67575050508482038584015251808252908201929160005b828110611d5057505050500390f35b835185528695509381019392810192600101611d41565b825160ff1685528897509385019391850191600101611d24565b825484529285019260019283019201611cf2565b60f81c815201849087611cc6565b6001919460ff8560f01c16815201930184611cbf565b6001919460ff8560e81c16815201930184611cb7565b9460ff85600194971c168152019301848b611cae565b91948160019160ff8760d81c16815201950191611ca6565b91948160019160ff8760d01c16815201950191611c9e565b91948160019160ff8760c81c16815201950191611c96565b91948160019160ff8760c01c16815201950191611c8e565b91948160019160ff8760b81c16815201950191611c86565b91948160019160ff8760b01c16815201950191611c7e565b91948160019160ff8760a81c16815201950191611c76565b91948160019160ff8760a01c16815201950191611c6e565b91948160019160ff8760981c16815201950191611c66565b91948160019160ff8760901c16815201950191611c5e565b91948160019160ff8760881c16815201950191611c56565b91948160019160ff8760801c16815201950191611c4e565b91948160019160ff8760781c16815201950191611c46565b91948160019160ff8760701c16815201950191611c3e565b91948160019160ff8760681c16815201950191611c36565b91948160019160ff8760601c16815201950191611c2e565b91948160019160ff8760581c16815201950191611c26565b91948160019160ff8760501c16815201950191611c1e565b91948160019160ff8760481c16815201950191611c16565b91948160019160ff8760401c16815201950191611c0e565b91948160019160ff8760381c16815201950191611c06565b91948160019160ff8760301c16815201950191611bfe565b91948160019160ff8760281c16815201950191611bf6565b91948160019160ff87831c16815201950191611bee565b91948160019160ff8760181c16815201950191611be6565b91948160019160ff8760101c16815201950191611bde565b91948160019160ff8760081c16815201950191611bd6565b91948160019160ff8716815201950191611bce565b926001919492955061040090875460ff8082168352808260081c1686840152808260101c16604084015280828a82828782828d82826060828260181c168188015282826080951c168488015260a096838360281c168882015260c09a848460301c168c830152848460381c1690820152838360401c16610100820152838360481c16610120820152838360501c16610140820152610160848460581c169101521c166101808d0152828260681c166101a08d0152828260701c166101c08d0152828260781c166101e08d01521c166102008a0152828260881c166102208a0152828260901c166102408a0152828260981c166102608a01521c16610280870152828260a81c166102a0870152828260b01c166102c0870152828260b81c166102e08701521c16610300840152808260c81c16610320840152808260d01c16610340840152808260d81c1661036084015280828a1c16610380840152808260e81c166103a08401528160f01c166103c083015260f81c6103e082015201950191019286928894959295611bb5565b346105c25760203660031901126105c257612227613b3e565b61222f613e8d565b6001600160a01b0316801561094257600480546001600160a01b031916821790557f1988eae50e3c966bb15e25079f549aabc769fff8405390f7914b211a9ca67972600080a2005b346105c25760003660031901126105c257612290613e8d565b612298614097565b6122a0614097565b60ff19600254166002557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a17fb5a73b054ac6db0c0e6429d3bb998734a660c422d158b35e9cd088b30d42f9e06020604051338152a1005b346105c25760003660031901126105c25760408051908101906001600160401b0382118183101761235557610f3e91604052600a81526928e280bfcba0e280bf2960b01b602082015260405191829182613c81565b634e487b7160e01b600052604160045260246000fd5b346105c2576020806003193601126105c257600435612388613fa7565b60008181526013835260409020546001600160a01b0392908316156105a957816000526013815260ff6005604060002001541660078110156104865780610b7e5750816000526013815282604060002054163314928060005416331490806005541633149060065416331490859286156124d5575b5082156124cd575b5081156124c5575b501561086e576106b89215612467577f43616e63656c6c656420627920686f7374207768696c6520696e204c6f6262796040519161244a83613d1c565b60278352820152661039ba30ba329760c91b604082015290614b72565b7f43616e63656c6c65642062792061646d696e2f6261636b656e642f76616c69646040519161249583613d1c565b603a83528201527930ba37b9103bb434b6329034b7102637b1313c9039ba30ba329760311b604082015290614b72565b90508461240d565b915085612405565b9250866123fd565b346105c25760203660031901126105c25760206124fb600435615771565b6125086040518092613b77565bf35b346105c25760203660031901126105c2576001600160401b0361252b613c6b565b1660005260176020526020604060002054604051908152f35b346105c25760003660031901126105c2576000546040516001600160a01b039091168152602090f35b346105c25760003660031901126105c2576020600954604051908152f35b346105c25760403660031901126105c2576004356125a7613b54565b90806000526020916013835260018060a01b0390816040600020541615612740578260005260148452604060002091808216928360005285528060406000205416156127215780600054163314159081612711575b81612702575b5061086e57826000526013845260ff60056040600020015416600781101561048657600581149081156126f7575b506126de578260005260148452604060002082600052845260ff60406000205460a81c16156126c05750817f56361dafe421b3aba201873229b0c108cb63c45289b37d3d30b68d7ffa876dfe61001b9482600052601481526040600020846000528152604060002060ff60a81b1981541690558260005260138152600760406000200154604051908152a3614d32565b604051638c5355ab60e01b8152908190610577908560048401613fca565b60405163712286e160e11b815260048101849052602490fd5b600691501485612630565b90506006541633141585612602565b80915060055416331415906125fc565b506040516379a6671160e01b8152908190610577908560048401613fca565b60405163d13b267760e01b815260048101849052602490fd5b346105c25760a03660031901126105c257604435602435600435608435606435612781613e8d565b603c8510801561282d575b8015612823575b8015612818575b801561280d575b6127fb577f2621620528f26fb75529114de7b19d9f01b11bb4a9bd0ad57997a33cf8141ec09460a09484600f558060105581600c5582600d5583600e556040519485526020850152604084015260608301526080820152a1005b6040516325c3636760e01b8152600490fd5b5061012c84106127a1565b5061012c831061279a565b50601e8210612793565b50603c811061278c565b346105c25760003660031901126105c257602060405160058152f35b346105c25760203660031901126105c25761286c613b3e565b612874613e8d565b6001600160a01b0316801561094257600580546001600160a01b031916821790557f9177ce6b43739f0c598396fcbb3deeeddefeee033f92b4fa36b1c5ec1afdd14e600080a2005b346105c257602090816003193601126105c2576004356128da615710565b5060008181526013845260409020546001600160a01b0392908316156105a95781600052601384526040600020906040519361291585613ce5565b825416845260018201548585015260ff6002830154166040850152600382015460608501526004820154608085015260ff6005830154169160a0850191600784101561048657600b6129c19260ff926129dd968652600682015460c08a0152600782015490890152600881015461010089015282600982015460018060401b0381166101208b015260401c161515610140890152600a8101546101608901520154166101808601613ec8565b82600052601585526129e4604060002060405193848092614cec565b0383613d52565b5191600783101561048657612a3494610f3e93600503612a555760189160005252612a46612a1f612a26604060002060405192838092614cec565b0382613d52565b915b60405195868096613b84565b6101e0806101a0870152850190613c2e565b908382036101c0850152613c2e565b5050612a46604051612a6681613d37565b6000815291612a28565b346105c25760003660031901126105c257612a89613e8d565b600080546001600160a01b0319811682556001600160a01b0316600080516020615f238339815191528280a3005b346105c25760403660031901126105c257612ad0613b54565b612ad8613fa7565b6004356000908152601360205260409020546001600160a01b0316156118ab57600435600052601360205260ff600560406000200154166007811015610486576005810361187e575060043560009081526014602090815260408083206001600160a01b0385811685529252909120541615612e09576000546001600160a01b031633141580612df4575b80612ddf575b61086e576004356000526014602052604060002060018060a01b03821660005260205260406000209081549160ff8360b01c16612ce15760043560005260136020526040600020601560205260406000206018602052604060002090601960205260406000205492612be16001820154925483614010565b90612710612bf3600483015484614010565b0492600098600080968054975b888110612d9f575b505060ff8260a81c16159182612d7d575b5081612d61575b15612d2d575050600b015460ff1660038110156104865760028103612d125750612c4b929350614003565b81612cff575b50505b8215612ce1576009548311612cd757612c6f83600954614003565b6009555b612c86836001600160a01b038416615230565b805460ff60b01b1916600160b01b1790556040519182526001600160a01b031690600435907f6aab79494515e500f72558d888a9426fa23041c0bf39b592fdd8809c98eabef590602090a360018055005b6000600955612c73565b60405163f54d5f5360e01b8152806105778460048035908401613fca565b612d0a929450614023565b918380612c51565b9150506001915014612d25575b50612c54565b925083612d1f565b94509450505050612d3e5750612c54565b909250611388908181029181830414901517156104d55761271090049183612d1f565b905060ff600b8401541660038110156104865760011490612c20565b909150600360ff8260a01c1610156104865760a01c60ff16600114908b612c19565b612da98183613f29565b905460039190911b1c6001600160a01b03908116908d1614612dd357612dce90613eb9565b612c00565b50505060018b80612c08565b506006546001600160a01b0316331415612b69565b506005546001600160a01b0316331415612b63565b6040516379a6671160e01b81529081906105779060048035908401613fca565b346105c25760403660031901126105c257600435612e45613b54565b612e4d613f89565b612e55613fa7565b6000828152601360205260409020546001600160a01b03908116156127405782600052601360205260ff6005604060002001541660078110156104865760028103612ec7575080600554163314159081612eb8575b5061086e576106b891614307565b90506000541633141583612eaa565b604051639cd1128160e01b815260048101859052600260248201526064916105a7906044830190613b6a565b346105c25760203660031901126105c257600435612f0f613f89565b612f17613fa7565b6000818152601360205260409020546001600160a01b0316156106fb5780600052601360205260ff6005604060002001541660078110156104865780612fd75750806000526013602052612f76600660406000200154600f5490613f7c565b42106106be576040516106b891612f8c82613d1c565b603382527f43616e63656c6c65642064756520746f204c6f6262792070686173652074696d60208301527232b7baba1014383ab13634b19031b0b636149760691b6040830152614b72565b906105a760649260405192639cd1128160e01b84526004840152600060248401526044830190613b6a565b346105c25760003660031901126105c2576020600354604051908152f35b346105c25760203660031901126105c25760043561303c613f89565b6000818152601360205260409020546001600160a01b0316156106fb5780600052601360205260ff60056040600020015416600781101561048657600581149081156130a9575b506130915761001b906140b6565b6024906040519063712286e160e11b82526004820152fd5b600691501482613083565b346105c25760003660031901126105c257602060ff600254166040519015158152f35b346105c25760603660031901126105c2576130f0613c6b565b6130f8613b54565b507f00000000000000000000000036825bf3fbdf5a29e2d5148bfe7dcf7b5639e3206001600160a01b031680156105c25733036105c2576106b89061313b613fa7565b613143613f89565b604435906152d0565b346105c2576020806003193601126105c25760043560008181526013835260409020546001600160a01b0316156106fb57806000526013825260ff600560406000200154166007811015610486576005036131d557906129dd91600052601881526131c1604060002060405193848092614cec565b610f3e604051928284938452830190613c2e565b601360449282600052526105a760ff6005604060002001541660405192638e84d22560e01b845260048401526024830190613b6a565b346105c25760203660031901126105c257600435613227613e8d565b6103e8808211613262577fdc4e4be378c228e1b7b13787e1d0620609304c2f71ed3a6ee69e4aae99d58cd960208380600855604051908152a1005b6040516373ab893560e11b815260048101929092526024820152604490fd5b346105c25760203660031901126105c25760043561329d615710565b506000818152601360205260409020546001600160a01b0391908216156106fb5760005260136020526040600020604051916132d883613ce5565b81541682526001810154602083015260ff6002820154166040830152600381015460608301526004810154608083015260ff60058201541692600784101561048657600b6133839260ff926101a09660a0870152600682015460c0870152600782015490860152600881015461010086015282600982015460018060401b03811661012088015260401c161515610140860152600a8101546101608601520154166101808301613ec8565b6125086040518092613b84565b346105c25760003660031901126105c2576020604051600c8152f35b346105c25760403660031901126105c2576004356133c8613b54565b906040516133d581613cca565b600081526000606060209282848201528260408201520152816000526013815260018060a01b03918260406000205416156106fb578060005260148252604060002090838516918260005283528360406000205416156134a65760809450600052601482526040600020906000528152604060002091600183549182169301549160405161346281613cca565b84815260608282019160ff8560a81c161515835260ff604082019560b01c161515855201938452604051948552511515908401525115156040830152516060820152f35b6040516379a6671160e01b815290819061057790879060048401613fca565b346105c25760403660031901126105c2576024803560043560078210156105c2576134ee613e8d565b6000818152601360205260409020546001600160a01b03161561369d57806000526013602052604060002091600583019283549360ff8516948315613678575b60018414613664575b6005841480613637575b6105c25760ff191660ff841617905560028214600081801561362a575b818115613605575b506135b2575b50505061358a906135806040518095613b6a565b6020840190613b6a565b7f4c2908af6339a7cecca8470b17951a83d590ff59671ec6a761871d3c69fb76ce60403393a3005b4260088401556135f05761358a93949550806135e4575b6135d7575b8493928161356c565b60076001910155846135ce565b506007810154156135c9565b85634e487b7160e01b60005260216004526000fd5b9050613615576004841481613566565b86634e487b7160e01b60005260216004526000fd5b505060006003841461355e565b5060ff600b84015416600381101561364f5715613541565b87634e487b7160e01b60005260216004526000fd5b600786101561361557851561353757600080fd5b60078610156136155785158015613693575b61352e57600080fd5b506006861461368a565b90506040519063d13b267760e01b82526004820152fd5b346105c25760203660031901126105c2576136cd613b3e565b6136d5613e8d565b600b80546001600160a01b0319166001600160a01b039290921691821790556002600080516020615fa3833981519152600080a3005b346105c2576020806003193601126105c25760043590613729613f89565b613731613fa7565b60008281526013825260409020546001600160a01b0392908316156106fb57806000526013825282604060002054168033036139a35750806000526013825260406000206005810160ff81541660078110156104865760058114908115613998575b506126de5760038083019283549160006004830154908115158061398b575b613942575b6137c2915084614003565b6009549093908111613937576137da90600954614003565b6009555b856000526015875260406000209260019889809401926138018454875490614010565b9586613881575b858b8b60008c8c600660ff19825416179055556033604051928084528301527f43616e63656c6c656420627920686f73743b206665652064656475637465642c604083015272103932b33ab7323990383937b1b2b9b9b2b21760691b6060830152600080516020615ea383398151915260803393a38055005b6000865b613890575b50613808565b81548110156139325780868d8d876138ab6139029688613f29565b905490891b1c1691816000526014815260406000208360005281526138df8d6138da8c60406000209754614010565b614023565b9081613908575b5050825460ff60b01b1916600160b01b1790925550613eb99050565b86613885565b81613922600080516020615ee38339815191529386615230565b604051908152a38f8d8f806138e6565b61388a565b5060006009556137de565b506139506127109185614010565b0480151580613981575b1561397657806139716137c2928b60075416615230565b6137b7565b506137c260006137b7565b508381111561395a565b50896007541615156137b2565b600691501486613793565b6040516317a9a4f960e21b815291829161057791339060048501613fe3565b346105c25760003660031901126105c2576004546040516001600160a01b039091168152602090f35b346105c25760403660031901126105c257600435600081815260136020526040902054602435916001600160a01b03918216156106fb576000526015602052604060002080548310156105c257602092613a4491613f29565b9190546040519260031b1c168152f35b346105c25760003660031901126105c257602060405161012c8152f35b346105c25760003660031901126105c257600a546040516001600160a01b039091168152602090f35b346105c25760003660031901126105c2576005546040516001600160a01b039091168152602090f35b346105c25760203660031901126105c257613adc613b3e565b613ae4613e8d565b600a80546001600160a01b0319166001600160a01b039290921691821790556001600080516020615fa3833981519152600080a3005b346105c25760003660031901126105c257600b546001600160a01b03168152602090f35b600435906001600160a01b03821682036105c257565b602435906001600160a01b03821682036105c257565b9060078210156104865752565b9060038210156104865752565b90613c2c9160018060a01b0381511682526020810151602083015260ff60408201511660408301526060810151606083015260808101516080830152613bd260a082015160a0840190613b6a565b60c0818101519083015260e080820151908301526101008082015190830152610120808201516001600160401b03169083015261014080820151151590830152610160808201519083015261018090810151910190613b77565b565b90815180825260208080930193019160005b828110613c4e575050505090565b83516001600160a01b031685529381019392810192600101613c40565b600435906001600160401b03821682036105c257565b6020808252825181830181905290939260005b828110613cb657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501613c94565b608081019081106001600160401b0382111761235557604052565b6101a081019081106001600160401b0382111761235557604052565b60a081019081106001600160401b0382111761235557604052565b606081019081106001600160401b0382111761235557604052565b602081019081106001600160401b0382111761235557604052565b601f909101601f19168101906001600160401b0382119082101761235557604052565b6001600160401b0381116123555760051b60200190565b81601f820112156105c257803591613da383613d75565b92613db16040519485613d52565b808452602092838086019260051b8201019283116105c2578301905b828210613ddb575050505090565b81356001600160a01b03811681036105c2578152908301908301613dcd565b60606003198201126105c257600435916001600160401b036024358181116105c25783613e2991600401613d8c565b926044359182116105c257613e4091600401613d8c565b90565b9181601f840112156105c2578235916001600160401b0383116105c2576020808501948460051b0101116105c257565b6001600160a01b0391821681529116602082015260400190565b6000546001600160a01b03163303613ea157565b60405163118cdaa760e01b8152336004820152602490fd5b60001981146104d55760010190565b60038210156104865752565b90600381101561048657815460ff60a01b191660a09190911b60ff60a01b16179055565b601254811015613f1357601260005260206000200190600090565b634e487b7160e01b600052603260045260246000fd5b8054821015613f135760005260206000200190600090565b8054600160401b81101561235557613f5e91600182018155613f29565b819291549060031b9160018060a01b03809116831b921b1916179055565b919082018092116104d557565b60ff60025416613f9557565b60405163d93c066560e01b8152600490fd5b600260015414613fb8576002600155565b604051633ee5aeb560e01b8152600490fd5b9081526001600160a01b03909116602082015260400190565b9081526001600160a01b0391821660208201529116604082015260600190565b919082039182116104d557565b818102929181159184041417156104d557565b811561402d570490565b634e487b7160e01b600052601260045260246000fd5b9190811015613f135760051b0190565b3560ff811681036105c25790565b81811061406c575050565b60008155600101614061565b9080614082575050565b613c2c91600052602060002090810190614061565b60ff60025416156140a457565b604051638dfc202b60e01b8152600490fd5b60008181526013602052604090818120600581019060ff82541691600882018054906007851015938461427d57600286149283156142b657600c546140fa91613f7c565b61012c81018091116142a2574210614291576007019184897fb5cab6c1716e65f60a857bf7351124136e99963a75d21d778e7727144e35231360c086548c5190614144828d613b6a565b602082015260608d820152602860608201527f5075626c69632074696d656f75742066616c6c6261636b20696e766f6b656420608082015267313c903ab9b2b91760c11b60a0820152a261427d57600386148015614270575b1561420057505050506141ec576004146141cd575b50506141bd81614d32565b6141ca57613c2c906151e1565b50565b6141e591838252601660205281208054918155614078565b38806141b2565b634e487b7160e01b82526021600452602482fd5b919390929695945061425c57614219575b505050505050565b61422286614d32565b61421157600080516020615f8383398151915294600360ff19825416179055429055549082519182526020820152a2388080808080614211565b634e487b7160e01b84526021600452602484fd5b508694506004861461419d565b634e487b7160e01b87526021600452602487fd5b87516326c015ab60e21b8152600490fd5b634e487b7160e01b88526011600452602488fd5b879550600387036142cd57600d546140fa91613f7c565b879550600487036142e457600e546140fa91613f7c565b8851638a8fcb0760e01b8152600481018b90526044906105a7602482018a613b6a565b906000908282526020601381526040928381209060078201549260018060a01b039081861692831515808091614640575b80614621575b6145d5575b8189918b959493968297839888855260158752858520858154905b8181106144f957505050836144db575b836144ba575b8361446b575b83614460575b5082614455575b5081614449575b8161443c575b506143eb575b505050506143a89150614d32565b6143e457600080516020615f83833981519152938160056143df9301600360ff198254161790556008429101555192839283613fca565b0390a2565b5050505050565b7fcb5e8d6d1fd0c2bd9eca2e2546d3d92f27a2b10728927512cb00ce7bdc8738ad938582526014845282822096169586825283522060ff60a81b1981541690558751868152a338858180878161439a565b9050848716141538614394565b8786161515915061438e565b600114915038614387565b600114925038614380565b94509150919394958452601485528a8420828552855260ff8b85205460a01c1660038110156144a657918b939160018e98979694149261437a565b634e487b7160e01b85526021600452602485fd5b8885526014875285852083865287528585205460a81c60ff16159350614374565b8885526014875285852083865287528585205488161515935061436e565b919395978a9b889a9b929496985260148b52808a206145188487613f29565b939054600394851b1c168b528b5289205460ff90818160a81c16614554575b50505061454390613eb9565b918f9998979593918f97959361435e565b60a09693961c16818110156145c1576002810361458357505061457961454391613eb9565b935b903880614537565b919491600114614598575b506145439061457b565b94614543919b506145a98b91613eb9565b956145b48d86613f29565b9054911b1c169a9061458e565b634e487b7160e01b8a52602160045260248afd5b89825260148352888220858352835288822060ff60a81b198154169055848a7f8cc6a126ca79d9271b80277e31507795e87602b1d7ce66e4451363f929f4811c858c518b8152a3614343565b5089825260148352888220858352835260ff8983205460a81c1661433e565b506014835288822085835283528389832054161515614338565b9061466482613d75565b6146716040519182613d52565b8281528092614682601f1991613d75565b0190602036910137565b8051821015613f135760209160051b010190565b90929183518151036105c257600082815260209460138652604091828120936146de60078601549360158a52858420601493848c5287862092615b74565b948486015190600191828114806149a2575b80614996575b1561476957505050968082879899887f47e734b70f4e742ea488db9e70df12c13e01b4d6cc4a500191e3b724b6a83bdd979695999899528252848082209160018060a01b0392838b5116825284522060ff60a81b198154169055875116960151908351928352820152a36141bd81614d32565b918094979192118061498a575b15614942576060019287600080516020615e83833981519152878c6147aa885183519384938c85528401528b830190613c2e565b0390a287875260168a526147c5868820805490898155614078565b87875260168a5285872093879384835b6148c6575b5050505060028210600014614825575050508584600080516020615ec383398151915261481c9697986016948651908152a28584525281208054918155614078565b6141bd81614d32565b60058199969495939901600460ff198254161790556008429101556148498161465a565b925b8181106148915750507f9058488d8f4ca0c7ed50681193e9b6dddd19f1cb87c905f3fc1e73fb6cb6af80949596509081806143df93519586958652850152830190613c2e565b8061489f6148c1928b613f29565b905460039190911b1c6001600160a01b03166148bb828761468c565b52613eb9565b61484b565b82515181101561493d578a8a52818d528c898b2060018060a01b0391826148ee85885161468c565b51168d525260ff8a8c205460a81c16614912575b5061490c90613eb9565b836147d5565b81966149316149369261492961490c95885161468c565b51168a613f41565b613eb9565b9590614902565b6147da565b5050505061497e600080516020615e83833981519152938386979894519161496983613d37565b82528080519586958652850152830190613c2e565b0390a26141bd81614d32565b50898101511515614776565b508a88015115156146f6565b5087516001600160a01b031615156146f0565b909182518151036105c2576000908282526020906013825260409160078385200154956016928383528486208054908115614b5d57505090614a099187875260158452858720906014855286882092615b74565b8051949687956001600160a01b03908116614af0575b87989184879261481c9a94600185880151149081614ae3575b81614ad6575b81614ace575b5015614ab057907fbc2df99d70b88b7387193e2ee99061e3b292151b21724b0b34f9a7fd6aa88d09939291858b5260148252838b20818851168c528252838b2060ff60a81b198154169055865116950151825191825286820152a35b8584525281208054918155614078565b5091600080516020615ec383398151915293945051908152a2614aa0565b905038614a44565b8784015115159150614a3e565b8751831615159150614a38565b865b8888528585528688208054821015614b4b57614b0f828492613f29565b919054818751169260031b1c1614614b2f57614b2a90613eb9565b614af2565b5087985061481c979184879260015b9b50925050919750614a1f565b505087989184879261481c9a94614b3e565b955095935050505061481c9394508155614078565b600081815260209060138252604091828220601582528383209160039283830193845460095481818111600014614cdb575050866009555b80151580614cd1575b614bf0575b50505050906005600080516020615ea383398151915294939201600660ff19825416179055555180614beb339582613c81565b0390a3565b4710614cc057600191828501549081614c0a575b50614bb8565b87845b614c18575b50614c04565b8a8254821015614cba579089614c8d92600080516020615ee3833981519152898e80614c44878a613f29565b90548688526014808652838920928e1b9190911c6001600160a01b03908116808a529286528389205492989216614c93575b505050614c838986615230565b51888152a3613eb9565b84614c0d565b8682528452818120878252845220805460ff60b01b1916600160b01b179055808f38614c76565b50614c12565b865163786e0a9960e01b8152600490fd5b5082541515614bb3565b614ce491614003565b600955614baa565b90815480825260208092019260005281600020916000905b828210614d12575050505090565b83546001600160a01b031685529384019360019384019390910190614d04565b6000818152601560205260408120805492918080805b86811061513957506000911590818061512e575b156151035750505050600292600193845b614d79575b5050505090565b836000526013602052604060002091600583019060ff1991600583825416179055600b84019160038410156104865782541660ff8416178255856000526018602052614dcf604060002080549060008155614078565b8560005260186020526040600020936000926014602052604060002060405191606083019083821060018060401b038311176123555760ff91604052600084526000602085015260006040850152614e406003860154612710614e36600489015483614010565b0490818752614003565b602085015260006040850152541660038110156104865760011461504b575b508051600a60406020840151930151930192835580614ff4575b509054818111614fea57614e8c91614003565b945b60005b828110614f385750505060009381614f25575b50506003811015610486577f4fd7802900f80ee62d07cf9efe951278d8c503d4cbb86ac8ea4d2820d9be00a592614efe614eed9360018414614f10575b60405194858095613b77565b606060208501526060840190614cec565b9060408301520390a238808080614d72565b86600052601960205282604060002055614ee1565b614f30929450614023565b913880614ea4565b8760005260146020526040600020614f508284613f29565b60018060a01b0391549060031b1c1660005260205260406000205460ff8160a81c169081614fc2575b50614f8d575b614f8890613eb9565b614e91565b92614fba614f8891614931614fa28786613f29565b905460039190911b1c6001600160a01b031689613f41565b939050614f7f565b9050600386101561048657600360ff8260a01c1610156104865760a01c60ff16851438614f79565b5050600094614e8e565b6007546001600160a01b0316908115615039576009546150299290821161502f5761502182600954614003565b600955615230565b38614e79565b6000600955615230565b604051637728d5eb60e01b8152600490fd5b87549060005b82811061505f575050614e5f565b615069818b613f29565b60018060a01b0391549060031b1c166000528160205260406000205460ff8160a81c161590816150e3575b506150a8575b6150a390613eb9565b615051565b600185015490611388918281029281840414901517156104d5576150d76127106150a393046040870151613f7c565b6040860152905061509a565b9050600360ff8260a01c1610156104865760a01c60ff1660011438615094565b959295159081615125575b5061511a575b84614d6d565b506001935083615114565b9050153861510e565b506001811015614d5c565b85600052601460205260406000206151518287613f29565b9190549160039260018060a01b0391841b1c1660005260205260406000205460ff90818160a81c1661518f575b50505061518a90613eb9565b614d48565b60a01c169081101561048657600281036151bb5750906151b161518a91613eb9565b915b90388061517e565b6001146151cc575b61518a906151b3565b916151d961518a91613eb9565b9290506151c3565b806000526013602052600080516020615f438339815191526020604060002060058101600260ff19825416179055600781019061521e8254613eb9565b809255600842910155604051908152a2565b81156152cc578147106152ba57600080808094819460018060a01b03165af1903d156152b4573d906001600160401b0382116152a0576040519161527e601f8201601f191660200184613d52565b825260203d92013e5b1561528e57565b604051633d2cec6f60e21b8152600490fd5b634e487b7160e01b81526041600452602490fd5b50615287565b60405163786e0a9960e01b8152600490fd5b5050565b907f00000000000000000000000036825bf3fbdf5a29e2d5148bfe7dcf7b5639e3206001600160a01b0316338190036156f1575060018060401b038083166000526017602052604060002054926003548085101590816156e7575b506156e157600084815260136020526040902080549093906001600160a01b0316156143e457600984018054848416858216036156c75760ff6005870154166007811015610486576001036156be5760ff8160401c166156be5760ff60401b1916600160401b179055818316857fae7cceb24659f070aa32d8aa9ced46f64e3ce6a1bcbb5ac917ee33d3188abf7d600080a384600052601460205260406000206015602052604060002090815492600184106105c2576153ea8461465a565b906000805b86811061563a575080156105c25790615456869260405160208101908482527f44454d574954434845535f524f4c455f41535349474e4d454e545f53414c545f6040820152630ac665c760e31b60608201526044815261544e81613cca565b519020615b6a565b926000936000805b8582106155d6575b505050506001600160a01b03831615615564575b505060005b8481106154fa57505050505016600052601760205260006040812055600260ff1960058301541617600582015560016007820155600842910155600080516020615f438339815191526020604051837fb607ccb3d7ced88cd923d2c5f091149c8ecc5fb24209afa25a43ddf94253801d600080a260018152a2565b806155086155559286613f29565b90546001600160a01b0385811660039390931b9190911c160361555a5761493160025b6155358388613f29565b60018060a01b0391549060031b1c16600052856020526040600020613ed4565b61547f565b614931600161552b565b6155bd9250906155b791604051602081019182527f44454d574954434845535f57495443485f46414c4c4241434b5f53414c545f566040820152620665c760eb1b60608201526043815261544e81613cca565b83613f29565b905460039190911b1c6001600160a01b0316833861547a565b839495506155e9826155f093949561468c565b5190613f7c565b908882841061560d575061560390613eb9565b889493929161545e565b94955091505061561e915085613f29565b905460039190911b1c6001600160a01b03169138808080615466565b906064906156488388613f29565b60018060a01b0391549060031b1c166000528560205260016040600020015480615692575b50816156879161568d93615681868961468c565b52613f7c565b91613eb9565b6153ef565b9081830291838304036104d5576127108204830183116104d5576127109091049091019061568761566d565b50505050505050565b60405163f7db5e9d60e01b81528585166004820152602490fd5b50505050565b905015153861532b565b60405163b16a0a0b60e01b815290819061057790339060048401613e73565b6040519061571d82613ce5565b816101806000918281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e0820152826101008201528261012082015282610140820152826101608201520152565b600081815260136020908152604080832054909291906001600160a01b03908116156158425760148252838320338452825233908484205416036157f2578382526013815282822060ff6009820154851c1615908115615828575b81156157f9575b506157f25760ff938252601481528282209033835252205460a01c1690565b5091505090565b60ff91506005015416600781101561581457600114386157d3565b634e487b7160e01b83526021600452602483fd5b905060ff600582015416600781101561425c5715906157cc565b505091505090565b600090808252601360205260ff60056040842001541660078110156158145760041461588757506040519061587e82613d37565b80825236813790565b604082613e4092612a1f945260166020522060405192838092614cec565b90601154821015613f13576011600052600582901c7f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c680191601f1690565b6000926001600160a01b0392839190821680615af1575b50169182615a77575b5050506012548015615a70576011918254918215615a39576001830190818411615a245703615a1c5760009260005b83811061598c5750600019830190838211615978575061595360ff916158a5565b90549060031b1c1610615964575090565b61596e9150613ef8565b90549060031b1c90565b634e487b7160e01b60005260045260246000fd5b82151580615a00575b6159a8575b6159a390613eb9565b615932565b801580156159c4575b1561599a579250505061596e9150613ef8565b5060001981018181116159eb576159dc60ff916158a5565b90549060031b1c1683116159b1565b82634e487b7160e01b60005260045260246000fd5b5060ff615a0c826158a5565b90549060031b1c16831115615995565b505050600090565b84634e487b7160e01b60005260045260246000fd5b5060009250159050613e40575060126000527fbb8a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec34445490565b5050600090565b602460209260405194859384926370a0823160e01b84521660048301525afa60009181615abb575b50615aac575b8080615903565b615ab591613f7c565b38615aa5565b90916020823d8211615ae9575b81615ad560209383613d52565b81010312615ae65750519038615a9f565b80fd5b3d9150615ac8565b6040516370a0823160e01b81528486166004820152919250602090829060249082905afa859181615b33575b50615b2b575b9083916158fa565b935082615b23565b9091506020813d8211615b62575b81615b4e60209383613d52565b81010312615b5e57519038615b1d565b8580fd5b3d9150615b41565b811561402d570690565b9390604092835194615b8586613d01565b6060808701528594600091828852602083818a015283838a01528360808a01528454988915615e745750615bb88961465a565b96845b8b51811015615cd0576001600160a01b0380615bd7838f61468c565b51169080615be5848c61468c565b51169188528585528688205481811615159081615cc2575b5080615cb9575b80615ca8575b80615c95575b615c25575b5050615c2090613eb9565b615bbb565b878b8a8f5b8310615c39575b505050615c15565b83615c45848793613f29565b90549060031b1c1614615c645750615c5c90613eb9565b8b8a8f615c2a565b615c20949350819250615c7d615c8a92615c839261468c565b51613eb9565b918c61468c565b529038808b8a615c31565b5081885260ff8789205460a81c16615c10565b508188528087892054161515615c0a565b50811515615c04565b60ff915060a81c1638615bfd565b509950969793929450615ce28461465a565b938596865b828110615d6e57505050505050615cfd8361465a565b91606086019283525b838110615d4657505083516001600160a01b03161515929150829050615d39575b50615d2f5790565b6001608082015290565b9050810151151538615d27565b615d69906001600160a01b03615d5c828561468c565b51166148bb82865161468c565b615d06565b615d788187613f29565b90546001600160a01b0360039290921b1c8116808a52858c52868a20549091811615908115615e65575b50615e5b57615db1828461468c565b5180615dc8575b5050615dc390613eb9565b615ce7565b9091998b8d01908151808411600014615e1557505052808b5260019081868d0152875115615e0157878b015297615dc3905b9038615db8565b634e487b7160e01b89526032600452602489fd5b909b92615dc394925014615e2a575b50615dfa565b868d01615e378151613eb9565b9052848b1015615e2457615e54615e4d8c613eb9565b9b8a61468c565b5238615e24565b50615dc390613eb9565b60ff915060a81c161538615da2565b98505050505050509250509056fe61c0b92f778d9580f61c1368f619231971929502139a169537859d147a3d8db17aaa838aec7a66b4c97d35cf9ef5095d74305ac4327c0bd09378674920a44e58ca623bc6d60c4de3c68b9587a6f35819c7232f5f79176c981fd7835daa19f812ff3116559cac6c47442d1f2083dc842de1f35c5f87fd25d7978d509fa59c1b1803dfbe1fcb4e2d61f3b4a0c93d8f814c92250618b3387f396cc9fc9fafe2c2038be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e06b40fcd6b8efc2e628b2efa06aea41f9d48edcf9f787184c307080c83cd22084481df23c457d78a89f0f5fabd340eb449a09d8690c045c3d8d567988ef019540a2361be2ee66c1a30168107b5157b30d03384356ba97fffc7ab6aeabdc6d211824e30ae881d6fa40479d2f3a64728e211e5620dab64f70e0043787baacd89f0ea2646970667358221220f8f18cc8a587b1d483db5026deda406d10c7898296efb1196cb2ec3d65de9c4d64736f6c63430008140033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

00000000000000000000000036825bf3fbdf5a29e2d5148bfe7dcf7b5639e32000000000000000000000000041c5c4a0e112717101ff1da950f6ca7ae4f9291900000000000000000000000000000000000000000000000000000000000001f4

-----Decoded View---------------
Arg [0] : _pythEntropyAddr (address): 0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320
Arg [1] : _initialContractOwner (address): 0x41C5c4a0E112717101FF1dA950f6Ca7Ae4F92919
Arg [2] : _initialDefaultFeeBps (uint256): 500

-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 00000000000000000000000036825bf3fbdf5a29e2d5148bfe7dcf7b5639e320
Arg [1] : 00000000000000000000000041c5c4a0e112717101ff1da950f6ca7ae4f92919
Arg [2] : 00000000000000000000000000000000000000000000000000000000000001f4


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
0x3F6e3D0b513096D3aA5ef88499Dd7AEc13B78cd4
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.