ETH Price: $2,646.98 (+1.18%)

Transaction Decoder

Block:
10333145 at Jun-25-2020 05:54:56 AM +UTC
Transaction Fee:
0.004005634 ETH $10.60
Gas Used:
87,079 Gas / 46 Gwei

Account State Difference:

  Address   Before After State Difference Code
0x2b591e99...8c40Eeb39
(Spark Pool)
52.765772786485139203 Eth52.769778420485139203 Eth0.004005634
0x68B008f2...5264d1B66
0.056104183217612311 Eth
Nonce: 76
0.242310077155259551 Eth
Nonce: 77
0.18620589393764724
0xc3a68424...5dBEd3Dc0 14.319367148783973893 Eth14.129155620846326653 Eth0.19021152793764724

Execution Trace

0xc3a6842458fa68ce01ae9fe68d206755dbed3dc0.CALL( )
  • HXY.freezingBalanceOf( account=0x68B008f2c06fA83c8CB8Bb170227Ae35264d1B66 ) => ( balance=683675697033 )
  • HXY.latestFreezeTimeOf( account=0x68B008f2c06fA83c8CB8Bb170227Ae35264d1B66 ) => ( 1592511379 )
  • HEX.transfer( recipient=0x68B008f2c06fA83c8CB8Bb170227Ae35264d1B66, amount=5701421629309 ) => ( True )
  • ETH 0.19021152793764724 0x68b008f2c06fa83c8cb8bb170227ae35264d1b66.CALL( )
    File 1 of 2: HEX
    pragma solidity 0.5.13;
    
    /*
     * @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 GSN 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.
     */
    contract Context {
        // Empty internal constructor, to prevent people from mistakenly deploying
        // an instance of this contract, which should be used via inheritance.
        constructor () internal { }
        // solhint-disable-previous-line no-empty-blocks
    
        function _msgSender() internal view returns (address payable) {
            return msg.sender;
        }
    
        function _msgData() internal view returns (bytes memory) {
            this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
            return msg.data;
        }
    }
    
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
     * the optional functions; to access them see {ERC20Detailed}.
     */
    interface IERC20 {
        /**
         * @dev Returns the amount of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
    
        /**
         * @dev Returns the amount of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
    
        /**
         * @dev Moves `amount` tokens from the caller's account to `recipient`.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transfer(address recipient, uint256 amount) 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 `amount` 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 amount) external returns (bool);
    
        /**
         * @dev Moves `amount` tokens from `sender` to `recipient` using the
         * allowance mechanism. `amount` is then deducted from the caller's
         * allowance.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    
        /**
         * @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 Wrappers over Solidity's arithmetic operations with added overflow
     * checks.
     *
     * Arithmetic operations in Solidity wrap on overflow. This can easily result
     * in bugs, because programmers usually assume that an overflow raises an
     * error, which is the standard behavior in high level programming languages.
     * `SafeMath` restores this intuition by reverting the transaction when an
     * operation overflows.
     *
     * Using this library instead of the unchecked operations eliminates an entire
     * class of bugs, so it's recommended to use it always.
     */
    library SafeMath {
        /**
         * @dev Returns the addition of two unsigned integers, reverting on
         * overflow.
         *
         * Counterpart to Solidity's `+` operator.
         *
         * Requirements:
         * - Addition cannot overflow.
         */
        function add(uint256 a, uint256 b) internal pure returns (uint256) {
            uint256 c = a + b;
            require(c >= a, "SafeMath: addition overflow");
    
            return c;
        }
    
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting on
         * overflow (when the result is negative).
         *
         * Counterpart to Solidity's `-` operator.
         *
         * Requirements:
         * - Subtraction cannot overflow.
         */
        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
            return sub(a, b, "SafeMath: subtraction overflow");
        }
    
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
         * overflow (when the result is negative).
         *
         * Counterpart to Solidity's `-` operator.
         *
         * Requirements:
         * - Subtraction cannot overflow.
         *
         * _Available since v2.4.0._
         */
        function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            require(b <= a, errorMessage);
            uint256 c = a - b;
    
            return c;
        }
    
        /**
         * @dev Returns the multiplication of two unsigned integers, reverting on
         * overflow.
         *
         * Counterpart to Solidity's `*` operator.
         *
         * Requirements:
         * - Multiplication cannot overflow.
         */
        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) {
                return 0;
            }
    
            uint256 c = a * b;
            require(c / a == b, "SafeMath: multiplication overflow");
    
            return c;
        }
    
        /**
         * @dev Returns the integer division of two unsigned integers. Reverts on
         * division by zero. The result is rounded towards zero.
         *
         * Counterpart to Solidity's `/` operator. Note: this function uses a
         * `revert` opcode (which leaves remaining gas untouched) while Solidity
         * uses an invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         */
        function div(uint256 a, uint256 b) internal pure returns (uint256) {
            return div(a, b, "SafeMath: division by zero");
        }
    
        /**
         * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
         * division by zero. The result is rounded towards zero.
         *
         * Counterpart to Solidity's `/` operator. Note: this function uses a
         * `revert` opcode (which leaves remaining gas untouched) while Solidity
         * uses an invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         *
         * _Available since v2.4.0._
         */
        function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            // Solidity only automatically asserts when dividing by 0
            require(b > 0, errorMessage);
            uint256 c = a / b;
            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    
            return c;
        }
    
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * Reverts when dividing by zero.
         *
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
         * opcode (which leaves remaining gas untouched) while Solidity uses an
         * invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         */
        function mod(uint256 a, uint256 b) internal pure returns (uint256) {
            return mod(a, b, "SafeMath: modulo by zero");
        }
    
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * Reverts with custom message when dividing by zero.
         *
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
         * opcode (which leaves remaining gas untouched) while Solidity uses an
         * invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         *
         * _Available since v2.4.0._
         */
        function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            require(b != 0, errorMessage);
            return a % b;
        }
    }
    
    /**
     * @dev Implementation of the {IERC20} interface.
     *
     * This implementation is agnostic to the way tokens are created. This means
     * that a supply mechanism has to be added in a derived contract using {_mint}.
     * For a generic mechanism see {ERC20Mintable}.
     *
     * TIP: For a detailed writeup see our guide
     * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
     * to implement supply mechanisms].
     *
     * We have followed general OpenZeppelin guidelines: functions revert instead
     * of returning `false` on failure. This behavior is nonetheless conventional
     * and does not conflict with the expectations of ERC20 applications.
     *
     * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
     * This allows applications to reconstruct the allowance for all accounts just
     * by listening to said events. Other implementations of the EIP may not emit
     * these events, as it isn't required by the specification.
     *
     * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
     * functions have been added to mitigate the well-known issues around setting
     * allowances. See {IERC20-approve}.
     */
    contract ERC20 is Context, IERC20 {
        using SafeMath for uint256;
    
        mapping (address => uint256) private _balances;
    
        mapping (address => mapping (address => uint256)) private _allowances;
    
        uint256 private _totalSupply;
    
        /**
         * @dev See {IERC20-totalSupply}.
         */
        function totalSupply() public view returns (uint256) {
            return _totalSupply;
        }
    
        /**
         * @dev See {IERC20-balanceOf}.
         */
        function balanceOf(address account) public view returns (uint256) {
            return _balances[account];
        }
    
        /**
         * @dev See {IERC20-transfer}.
         *
         * Requirements:
         *
         * - `recipient` cannot be the zero address.
         * - the caller must have a balance of at least `amount`.
         */
        function transfer(address recipient, uint256 amount) public returns (bool) {
            _transfer(_msgSender(), recipient, amount);
            return true;
        }
    
        /**
         * @dev See {IERC20-allowance}.
         */
        function allowance(address owner, address spender) public view returns (uint256) {
            return _allowances[owner][spender];
        }
    
        /**
         * @dev See {IERC20-approve}.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         */
        function approve(address spender, uint256 amount) public returns (bool) {
            _approve(_msgSender(), spender, amount);
            return true;
        }
    
        /**
         * @dev See {IERC20-transferFrom}.
         *
         * Emits an {Approval} event indicating the updated allowance. This is not
         * required by the EIP. See the note at the beginning of {ERC20};
         *
         * Requirements:
         * - `sender` and `recipient` cannot be the zero address.
         * - `sender` must have a balance of at least `amount`.
         * - the caller must have allowance for `sender`'s tokens of at least
         * `amount`.
         */
        function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
            _transfer(sender, recipient, amount);
            _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
            return true;
        }
    
        /**
         * @dev Atomically increases the allowance granted to `spender` by the caller.
         *
         * This is an alternative to {approve} that can be used as a mitigation for
         * problems described in {IERC20-approve}.
         *
         * Emits an {Approval} event indicating the updated allowance.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         */
        function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
            _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
            return true;
        }
    
        /**
         * @dev Atomically decreases the allowance granted to `spender` by the caller.
         *
         * This is an alternative to {approve} that can be used as a mitigation for
         * problems described in {IERC20-approve}.
         *
         * Emits an {Approval} event indicating the updated allowance.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         * - `spender` must have allowance for the caller of at least
         * `subtractedValue`.
         */
        function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
            _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
            return true;
        }
    
        /**
         * @dev Moves tokens `amount` from `sender` to `recipient`.
         *
         * This is internal function is equivalent to {transfer}, and can be used to
         * e.g. implement automatic token fees, slashing mechanisms, etc.
         *
         * Emits a {Transfer} event.
         *
         * Requirements:
         *
         * - `sender` cannot be the zero address.
         * - `recipient` cannot be the zero address.
         * - `sender` must have a balance of at least `amount`.
         */
        function _transfer(address sender, address recipient, uint256 amount) internal {
            require(sender != address(0), "ERC20: transfer from the zero address");
            require(recipient != address(0), "ERC20: transfer to the zero address");
    
            _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
            _balances[recipient] = _balances[recipient].add(amount);
            emit Transfer(sender, recipient, amount);
        }
    
        /** @dev Creates `amount` tokens and assigns them to `account`, increasing
         * the total supply.
         *
         * Emits a {Transfer} event with `from` set to the zero address.
         *
         * Requirements
         *
         * - `to` cannot be the zero address.
         */
        function _mint(address account, uint256 amount) internal {
            require(account != address(0), "ERC20: mint to the zero address");
    
            _totalSupply = _totalSupply.add(amount);
            _balances[account] = _balances[account].add(amount);
            emit Transfer(address(0), account, amount);
        }
    
         /**
         * @dev Destroys `amount` tokens from `account`, reducing the
         * total supply.
         *
         * Emits a {Transfer} event with `to` set to the zero address.
         *
         * Requirements
         *
         * - `account` cannot be the zero address.
         * - `account` must have at least `amount` tokens.
         */
        function _burn(address account, uint256 amount) internal {
            require(account != address(0), "ERC20: burn from the zero address");
    
            _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
            _totalSupply = _totalSupply.sub(amount);
            emit Transfer(account, address(0), amount);
        }
    
        /**
         * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
         *
         * This is internal function is equivalent to `approve`, and can be used to
         * e.g. set automatic allowances for certain subsystems, etc.
         *
         * Emits an {Approval} event.
         *
         * Requirements:
         *
         * - `owner` cannot be the zero address.
         * - `spender` cannot be the zero address.
         */
        function _approve(address owner, address spender, uint256 amount) internal {
            require(owner != address(0), "ERC20: approve from the zero address");
            require(spender != address(0), "ERC20: approve to the zero address");
    
            _allowances[owner][spender] = amount;
            emit Approval(owner, spender, amount);
        }
    
        /**
         * @dev Destroys `amount` tokens from `account`.`amount` is then deducted
         * from the caller's allowance.
         *
         * See {_burn} and {_approve}.
         */
        function _burnFrom(address account, uint256 amount) internal {
            _burn(account, amount);
            _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
        }
    }
    
    contract GlobalsAndUtility is ERC20 {
        /*  XfLobbyEnter      (auto-generated event)
    
            uint40            timestamp       -->  data0 [ 39:  0]
            address  indexed  memberAddr
            uint256  indexed  entryId
            uint96            rawAmount       -->  data0 [135: 40]
            address  indexed  referrerAddr
        */
        event XfLobbyEnter(
            uint256 data0,
            address indexed memberAddr,
            uint256 indexed entryId,
            address indexed referrerAddr
        );
    
        /*  XfLobbyExit       (auto-generated event)
    
            uint40            timestamp       -->  data0 [ 39:  0]
            address  indexed  memberAddr
            uint256  indexed  entryId
            uint72            xfAmount        -->  data0 [111: 40]
            address  indexed  referrerAddr
        */
        event XfLobbyExit(
            uint256 data0,
            address indexed memberAddr,
            uint256 indexed entryId,
            address indexed referrerAddr
        );
    
        /*  DailyDataUpdate   (auto-generated event)
    
            uint40            timestamp       -->  data0 [ 39:  0]
            uint16            beginDay        -->  data0 [ 55: 40]
            uint16            endDay          -->  data0 [ 71: 56]
            bool              isAutoUpdate    -->  data0 [ 79: 72]
            address  indexed  updaterAddr
        */
        event DailyDataUpdate(
            uint256 data0,
            address indexed updaterAddr
        );
    
        /*  Claim             (auto-generated event)
    
            uint40            timestamp       -->  data0 [ 39:  0]
            bytes20  indexed  btcAddr
            uint56            rawSatoshis     -->  data0 [ 95: 40]
            uint56            adjSatoshis     -->  data0 [151: 96]
            address  indexed  claimToAddr
            uint8             claimFlags      -->  data0 [159:152]
            uint72            claimedHearts   -->  data0 [231:160]
            address  indexed  referrerAddr
            address           senderAddr      -->  data1 [159:  0]
        */
        event Claim(
            uint256 data0,
            uint256 data1,
            bytes20 indexed btcAddr,
            address indexed claimToAddr,
            address indexed referrerAddr
        );
    
        /*  ClaimAssist       (auto-generated event)
    
            uint40            timestamp       -->  data0 [ 39:  0]
            bytes20           btcAddr         -->  data0 [199: 40]
            uint56            rawSatoshis     -->  data0 [255:200]
            uint56            adjSatoshis     -->  data1 [ 55:  0]
            address           claimToAddr     -->  data1 [215: 56]
            uint8             claimFlags      -->  data1 [223:216]
            uint72            claimedHearts   -->  data2 [ 71:  0]
            address           referrerAddr    -->  data2 [231: 72]
            address  indexed  senderAddr
        */
        event ClaimAssist(
            uint256 data0,
            uint256 data1,
            uint256 data2,
            address indexed senderAddr
        );
    
        /*  StakeStart        (auto-generated event)
    
            uint40            timestamp       -->  data0 [ 39:  0]
            address  indexed  stakerAddr
            uint40   indexed  stakeId
            uint72            stakedHearts    -->  data0 [111: 40]
            uint72            stakeShares     -->  data0 [183:112]
            uint16            stakedDays      -->  data0 [199:184]
            bool              isAutoStake     -->  data0 [207:200]
        */
        event StakeStart(
            uint256 data0,
            address indexed stakerAddr,
            uint40 indexed stakeId
        );
    
        /*  StakeGoodAccounting(auto-generated event)
    
            uint40            timestamp       -->  data0 [ 39:  0]
            address  indexed  stakerAddr
            uint40   indexed  stakeId
            uint72            stakedHearts    -->  data0 [111: 40]
            uint72            stakeShares     -->  data0 [183:112]
            uint72            payout          -->  data0 [255:184]
            uint72            penalty         -->  data1 [ 71:  0]
            address  indexed  senderAddr
        */
        event StakeGoodAccounting(
            uint256 data0,
            uint256 data1,
            address indexed stakerAddr,
            uint40 indexed stakeId,
            address indexed senderAddr
        );
    
        /*  StakeEnd          (auto-generated event)
    
            uint40            timestamp       -->  data0 [ 39:  0]
            address  indexed  stakerAddr
            uint40   indexed  stakeId
            uint72            stakedHearts    -->  data0 [111: 40]
            uint72            stakeShares     -->  data0 [183:112]
            uint72            payout          -->  data0 [255:184]
            uint72            penalty         -->  data1 [ 71:  0]
            uint16            servedDays      -->  data1 [ 87: 72]
            bool              prevUnlocked    -->  data1 [ 95: 88]
        */
        event StakeEnd(
            uint256 data0,
            uint256 data1,
            address indexed stakerAddr,
            uint40 indexed stakeId
        );
    
        /*  ShareRateChange   (auto-generated event)
    
            uint40            timestamp       -->  data0 [ 39:  0]
            uint40            shareRate       -->  data0 [ 79: 40]
            uint40   indexed  stakeId
        */
        event ShareRateChange(
            uint256 data0,
            uint40 indexed stakeId
        );
    
        /* Origin address */
        address internal constant ORIGIN_ADDR = 0x9A6a414D6F3497c05E3b1De90520765fA1E07c03;
    
        /* Flush address */
        address payable internal constant FLUSH_ADDR = 0xDEC9f2793e3c17cd26eeFb21C4762fA5128E0399;
    
        /* ERC20 constants */
        string public constant name = "HEX";
        string public constant symbol = "HEX";
        uint8 public constant decimals = 8;
    
        /* Hearts per Satoshi = 10,000 * 1e8 / 1e8 = 1e4 */
        uint256 private constant HEARTS_PER_HEX = 10 ** uint256(decimals); // 1e8
        uint256 private constant HEX_PER_BTC = 1e4;
        uint256 private constant SATOSHIS_PER_BTC = 1e8;
        uint256 internal constant HEARTS_PER_SATOSHI = HEARTS_PER_HEX / SATOSHIS_PER_BTC * HEX_PER_BTC;
    
        /* Time of contract launch (2019-12-03T00:00:00Z) */
        uint256 internal constant LAUNCH_TIME = 1575331200;
    
        /* Size of a Hearts or Shares uint */
        uint256 internal constant HEART_UINT_SIZE = 72;
    
        /* Size of a transform lobby entry index uint */
        uint256 internal constant XF_LOBBY_ENTRY_INDEX_SIZE = 40;
        uint256 internal constant XF_LOBBY_ENTRY_INDEX_MASK = (1 << XF_LOBBY_ENTRY_INDEX_SIZE) - 1;
    
        /* Seed for WAAS Lobby */
        uint256 internal constant WAAS_LOBBY_SEED_HEX = 1e9;
        uint256 internal constant WAAS_LOBBY_SEED_HEARTS = WAAS_LOBBY_SEED_HEX * HEARTS_PER_HEX;
    
        /* Start of claim phase */
        uint256 internal constant PRE_CLAIM_DAYS = 1;
        uint256 internal constant CLAIM_PHASE_START_DAY = PRE_CLAIM_DAYS;
    
        /* Length of claim phase */
        uint256 private constant CLAIM_PHASE_WEEKS = 50;
        uint256 internal constant CLAIM_PHASE_DAYS = CLAIM_PHASE_WEEKS * 7;
    
        /* End of claim phase */
        uint256 internal constant CLAIM_PHASE_END_DAY = CLAIM_PHASE_START_DAY + CLAIM_PHASE_DAYS;
    
        /* Number of words to hold 1 bit for each transform lobby day */
        uint256 internal constant XF_LOBBY_DAY_WORDS = (CLAIM_PHASE_END_DAY + 255) >> 8;
    
        /* BigPayDay */
        uint256 internal constant BIG_PAY_DAY = CLAIM_PHASE_END_DAY + 1;
    
        /* Root hash of the UTXO Merkle tree */
        bytes32 internal constant MERKLE_TREE_ROOT = 0x4e831acb4223b66de3b3d2e54a2edeefb0de3d7916e2886a4b134d9764d41bec;
    
        /* Size of a Satoshi claim uint in a Merkle leaf */
        uint256 internal constant MERKLE_LEAF_SATOSHI_SIZE = 45;
    
        /* Zero-fill between BTC address and Satoshis in a Merkle leaf */
        uint256 internal constant MERKLE_LEAF_FILL_SIZE = 256 - 160 - MERKLE_LEAF_SATOSHI_SIZE;
        uint256 internal constant MERKLE_LEAF_FILL_BASE = (1 << MERKLE_LEAF_FILL_SIZE) - 1;
        uint256 internal constant MERKLE_LEAF_FILL_MASK = MERKLE_LEAF_FILL_BASE << MERKLE_LEAF_SATOSHI_SIZE;
    
        /* Size of a Satoshi total uint */
        uint256 internal constant SATOSHI_UINT_SIZE = 51;
        uint256 internal constant SATOSHI_UINT_MASK = (1 << SATOSHI_UINT_SIZE) - 1;
    
        /* Total Satoshis from all BTC addresses in UTXO snapshot */
        uint256 internal constant FULL_SATOSHIS_TOTAL = 1807766732160668;
    
        /* Total Satoshis from supported BTC addresses in UTXO snapshot after applying Silly Whale */
        uint256 internal constant CLAIMABLE_SATOSHIS_TOTAL = 910087996911001;
    
        /* Number of claimable BTC addresses in UTXO snapshot */
        uint256 internal constant CLAIMABLE_BTC_ADDR_COUNT = 27997742;
    
        /* Largest BTC address Satoshis balance in UTXO snapshot (sanity check) */
        uint256 internal constant MAX_BTC_ADDR_BALANCE_SATOSHIS = 25550214098481;
    
        /* Percentage of total claimed Hearts that will be auto-staked from a claim */
        uint256 internal constant AUTO_STAKE_CLAIM_PERCENT = 90;
    
        /* Stake timing parameters */
        uint256 internal constant MIN_STAKE_DAYS = 1;
        uint256 internal constant MIN_AUTO_STAKE_DAYS = 350;
    
        uint256 internal constant MAX_STAKE_DAYS = 5555; // Approx 15 years
    
        uint256 internal constant EARLY_PENALTY_MIN_DAYS = 90;
    
        uint256 private constant LATE_PENALTY_GRACE_WEEKS = 2;
        uint256 internal constant LATE_PENALTY_GRACE_DAYS = LATE_PENALTY_GRACE_WEEKS * 7;
    
        uint256 private constant LATE_PENALTY_SCALE_WEEKS = 100;
        uint256 internal constant LATE_PENALTY_SCALE_DAYS = LATE_PENALTY_SCALE_WEEKS * 7;
    
        /* Stake shares Longer Pays Better bonus constants used by _stakeStartBonusHearts() */
        uint256 private constant LPB_BONUS_PERCENT = 20;
        uint256 private constant LPB_BONUS_MAX_PERCENT = 200;
        uint256 internal constant LPB = 364 * 100 / LPB_BONUS_PERCENT;
        uint256 internal constant LPB_MAX_DAYS = LPB * LPB_BONUS_MAX_PERCENT / 100;
    
        /* Stake shares Bigger Pays Better bonus constants used by _stakeStartBonusHearts() */
        uint256 private constant BPB_BONUS_PERCENT = 10;
        uint256 private constant BPB_MAX_HEX = 150 * 1e6;
        uint256 internal constant BPB_MAX_HEARTS = BPB_MAX_HEX * HEARTS_PER_HEX;
        uint256 internal constant BPB = BPB_MAX_HEARTS * 100 / BPB_BONUS_PERCENT;
    
        /* Share rate is scaled to increase precision */
        uint256 internal constant SHARE_RATE_SCALE = 1e5;
    
        /* Share rate max (after scaling) */
        uint256 internal constant SHARE_RATE_UINT_SIZE = 40;
        uint256 internal constant SHARE_RATE_MAX = (1 << SHARE_RATE_UINT_SIZE) - 1;
    
        /* Constants for preparing the claim message text */
        uint8 internal constant ETH_ADDRESS_BYTE_LEN = 20;
        uint8 internal constant ETH_ADDRESS_HEX_LEN = ETH_ADDRESS_BYTE_LEN * 2;
    
        uint8 internal constant CLAIM_PARAM_HASH_BYTE_LEN = 12;
        uint8 internal constant CLAIM_PARAM_HASH_HEX_LEN = CLAIM_PARAM_HASH_BYTE_LEN * 2;
    
        uint8 internal constant BITCOIN_SIG_PREFIX_LEN = 24;
        bytes24 internal constant BITCOIN_SIG_PREFIX_STR = "Bitcoin Signed Message:\n";
    
        bytes internal constant STD_CLAIM_PREFIX_STR = "Claim_HEX_to_0x";
        bytes internal constant OLD_CLAIM_PREFIX_STR = "Claim_BitcoinHEX_to_0x";
    
        bytes16 internal constant HEX_DIGITS = "0123456789abcdef";
    
        /* Claim flags passed to btcAddressClaim()  */
        uint8 internal constant CLAIM_FLAG_MSG_PREFIX_OLD = 1 << 0;
        uint8 internal constant CLAIM_FLAG_BTC_ADDR_COMPRESSED = 1 << 1;
        uint8 internal constant CLAIM_FLAG_BTC_ADDR_P2WPKH_IN_P2SH = 1 << 2;
        uint8 internal constant CLAIM_FLAG_BTC_ADDR_BECH32 = 1 << 3;
        uint8 internal constant CLAIM_FLAG_ETH_ADDR_LOWERCASE = 1 << 4;
    
        /* Globals expanded for memory (except _latestStakeId) and compact for storage */
        struct GlobalsCache {
            // 1
            uint256 _lockedHeartsTotal;
            uint256 _nextStakeSharesTotal;
            uint256 _shareRate;
            uint256 _stakePenaltyTotal;
            // 2
            uint256 _dailyDataCount;
            uint256 _stakeSharesTotal;
            uint40 _latestStakeId;
            uint256 _unclaimedSatoshisTotal;
            uint256 _claimedSatoshisTotal;
            uint256 _claimedBtcAddrCount;
            //
            uint256 _currentDay;
        }
    
        struct GlobalsStore {
            // 1
            uint72 lockedHeartsTotal;
            uint72 nextStakeSharesTotal;
            uint40 shareRate;
            uint72 stakePenaltyTotal;
            // 2
            uint16 dailyDataCount;
            uint72 stakeSharesTotal;
            uint40 latestStakeId;
            uint128 claimStats;
        }
    
        GlobalsStore public globals;
    
        /* Claimed BTC addresses */
        mapping(bytes20 => bool) public btcAddressClaims;
    
        /* Daily data */
        struct DailyDataStore {
            uint72 dayPayoutTotal;
            uint72 dayStakeSharesTotal;
            uint56 dayUnclaimedSatoshisTotal;
        }
    
        mapping(uint256 => DailyDataStore) public dailyData;
    
        /* Stake expanded for memory (except _stakeId) and compact for storage */
        struct StakeCache {
            uint40 _stakeId;
            uint256 _stakedHearts;
            uint256 _stakeShares;
            uint256 _lockedDay;
            uint256 _stakedDays;
            uint256 _unlockedDay;
            bool _isAutoStake;
        }
    
        struct StakeStore {
            uint40 stakeId;
            uint72 stakedHearts;
            uint72 stakeShares;
            uint16 lockedDay;
            uint16 stakedDays;
            uint16 unlockedDay;
            bool isAutoStake;
        }
    
        mapping(address => StakeStore[]) public stakeLists;
    
        /* Temporary state for calculating daily rounds */
        struct DailyRoundState {
            uint256 _allocSupplyCached;
            uint256 _mintOriginBatch;
            uint256 _payoutTotal;
        }
    
        struct XfLobbyEntryStore {
            uint96 rawAmount;
            address referrerAddr;
        }
    
        struct XfLobbyQueueStore {
            uint40 headIndex;
            uint40 tailIndex;
            mapping(uint256 => XfLobbyEntryStore) entries;
        }
    
        mapping(uint256 => uint256) public xfLobby;
        mapping(uint256 => mapping(address => XfLobbyQueueStore)) public xfLobbyMembers;
    
        /**
         * @dev PUBLIC FACING: Optionally update daily data for a smaller
         * range to reduce gas cost for a subsequent operation
         * @param beforeDay Only update days before this day number (optional; 0 for current day)
         */
        function dailyDataUpdate(uint256 beforeDay)
            external
        {
            GlobalsCache memory g;
            GlobalsCache memory gSnapshot;
            _globalsLoad(g, gSnapshot);
    
            /* Skip pre-claim period */
            require(g._currentDay > CLAIM_PHASE_START_DAY, "HEX: Too early");
    
            if (beforeDay != 0) {
                require(beforeDay <= g._currentDay, "HEX: beforeDay cannot be in the future");
    
                _dailyDataUpdate(g, beforeDay, false);
            } else {
                /* Default to updating before current day */
                _dailyDataUpdate(g, g._currentDay, false);
            }
    
            _globalsSync(g, gSnapshot);
        }
    
        /**
         * @dev PUBLIC FACING: External helper to return multiple values of daily data with
         * a single call. Ugly implementation due to limitations of the standard ABI encoder.
         * @param beginDay First day of data range
         * @param endDay Last day (non-inclusive) of data range
         * @return Fixed array of packed values
         */
        function dailyDataRange(uint256 beginDay, uint256 endDay)
            external
            view
            returns (uint256[] memory list)
        {
            require(beginDay < endDay && endDay <= globals.dailyDataCount, "HEX: range invalid");
    
            list = new uint256[](endDay - beginDay);
    
            uint256 src = beginDay;
            uint256 dst = 0;
            uint256 v;
            do {
                v = uint256(dailyData[src].dayUnclaimedSatoshisTotal) << (HEART_UINT_SIZE * 2);
                v |= uint256(dailyData[src].dayStakeSharesTotal) << HEART_UINT_SIZE;
                v |= uint256(dailyData[src].dayPayoutTotal);
    
                list[dst++] = v;
            } while (++src < endDay);
    
            return list;
        }
    
        /**
         * @dev PUBLIC FACING: External helper to return most global info with a single call.
         * Ugly implementation due to limitations of the standard ABI encoder.
         * @return Fixed array of values
         */
        function globalInfo()
            external
            view
            returns (uint256[13] memory)
        {
            uint256 _claimedBtcAddrCount;
            uint256 _claimedSatoshisTotal;
            uint256 _unclaimedSatoshisTotal;
    
            (_claimedBtcAddrCount, _claimedSatoshisTotal, _unclaimedSatoshisTotal) = _claimStatsDecode(
                globals.claimStats
            );
    
            return [
                // 1
                globals.lockedHeartsTotal,
                globals.nextStakeSharesTotal,
                globals.shareRate,
                globals.stakePenaltyTotal,
                // 2
                globals.dailyDataCount,
                globals.stakeSharesTotal,
                globals.latestStakeId,
                _unclaimedSatoshisTotal,
                _claimedSatoshisTotal,
                _claimedBtcAddrCount,
                //
                block.timestamp,
                totalSupply(),
                xfLobby[_currentDay()]
            ];
        }
    
        /**
         * @dev PUBLIC FACING: ERC20 totalSupply() is the circulating supply and does not include any
         * staked Hearts. allocatedSupply() includes both.
         * @return Allocated Supply in Hearts
         */
        function allocatedSupply()
            external
            view
            returns (uint256)
        {
            return totalSupply() + globals.lockedHeartsTotal;
        }
    
        /**
         * @dev PUBLIC FACING: External helper for the current day number since launch time
         * @return Current day number (zero-based)
         */
        function currentDay()
            external
            view
            returns (uint256)
        {
            return _currentDay();
        }
    
        function _currentDay()
            internal
            view
            returns (uint256)
        {
            return (block.timestamp - LAUNCH_TIME) / 1 days;
        }
    
        function _dailyDataUpdateAuto(GlobalsCache memory g)
            internal
        {
            _dailyDataUpdate(g, g._currentDay, true);
        }
    
        function _globalsLoad(GlobalsCache memory g, GlobalsCache memory gSnapshot)
            internal
            view
        {
            // 1
            g._lockedHeartsTotal = globals.lockedHeartsTotal;
            g._nextStakeSharesTotal = globals.nextStakeSharesTotal;
            g._shareRate = globals.shareRate;
            g._stakePenaltyTotal = globals.stakePenaltyTotal;
            // 2
            g._dailyDataCount = globals.dailyDataCount;
            g._stakeSharesTotal = globals.stakeSharesTotal;
            g._latestStakeId = globals.latestStakeId;
            (g._claimedBtcAddrCount, g._claimedSatoshisTotal, g._unclaimedSatoshisTotal) = _claimStatsDecode(
                globals.claimStats
            );
            //
            g._currentDay = _currentDay();
    
            _globalsCacheSnapshot(g, gSnapshot);
        }
    
        function _globalsCacheSnapshot(GlobalsCache memory g, GlobalsCache memory gSnapshot)
            internal
            pure
        {
            // 1
            gSnapshot._lockedHeartsTotal = g._lockedHeartsTotal;
            gSnapshot._nextStakeSharesTotal = g._nextStakeSharesTotal;
            gSnapshot._shareRate = g._shareRate;
            gSnapshot._stakePenaltyTotal = g._stakePenaltyTotal;
            // 2
            gSnapshot._dailyDataCount = g._dailyDataCount;
            gSnapshot._stakeSharesTotal = g._stakeSharesTotal;
            gSnapshot._latestStakeId = g._latestStakeId;
            gSnapshot._unclaimedSatoshisTotal = g._unclaimedSatoshisTotal;
            gSnapshot._claimedSatoshisTotal = g._claimedSatoshisTotal;
            gSnapshot._claimedBtcAddrCount = g._claimedBtcAddrCount;
        }
    
        function _globalsSync(GlobalsCache memory g, GlobalsCache memory gSnapshot)
            internal
        {
            if (g._lockedHeartsTotal != gSnapshot._lockedHeartsTotal
                || g._nextStakeSharesTotal != gSnapshot._nextStakeSharesTotal
                || g._shareRate != gSnapshot._shareRate
                || g._stakePenaltyTotal != gSnapshot._stakePenaltyTotal) {
                // 1
                globals.lockedHeartsTotal = uint72(g._lockedHeartsTotal);
                globals.nextStakeSharesTotal = uint72(g._nextStakeSharesTotal);
                globals.shareRate = uint40(g._shareRate);
                globals.stakePenaltyTotal = uint72(g._stakePenaltyTotal);
            }
            if (g._dailyDataCount != gSnapshot._dailyDataCount
                || g._stakeSharesTotal != gSnapshot._stakeSharesTotal
                || g._latestStakeId != gSnapshot._latestStakeId
                || g._unclaimedSatoshisTotal != gSnapshot._unclaimedSatoshisTotal
                || g._claimedSatoshisTotal != gSnapshot._claimedSatoshisTotal
                || g._claimedBtcAddrCount != gSnapshot._claimedBtcAddrCount) {
                // 2
                globals.dailyDataCount = uint16(g._dailyDataCount);
                globals.stakeSharesTotal = uint72(g._stakeSharesTotal);
                globals.latestStakeId = g._latestStakeId;
                globals.claimStats = _claimStatsEncode(
                    g._claimedBtcAddrCount,
                    g._claimedSatoshisTotal,
                    g._unclaimedSatoshisTotal
                );
            }
        }
    
        function _stakeLoad(StakeStore storage stRef, uint40 stakeIdParam, StakeCache memory st)
            internal
            view
        {
            /* Ensure caller's stakeIndex is still current */
            require(stakeIdParam == stRef.stakeId, "HEX: stakeIdParam not in stake");
    
            st._stakeId = stRef.stakeId;
            st._stakedHearts = stRef.stakedHearts;
            st._stakeShares = stRef.stakeShares;
            st._lockedDay = stRef.lockedDay;
            st._stakedDays = stRef.stakedDays;
            st._unlockedDay = stRef.unlockedDay;
            st._isAutoStake = stRef.isAutoStake;
        }
    
        function _stakeUpdate(StakeStore storage stRef, StakeCache memory st)
            internal
        {
            stRef.stakeId = st._stakeId;
            stRef.stakedHearts = uint72(st._stakedHearts);
            stRef.stakeShares = uint72(st._stakeShares);
            stRef.lockedDay = uint16(st._lockedDay);
            stRef.stakedDays = uint16(st._stakedDays);
            stRef.unlockedDay = uint16(st._unlockedDay);
            stRef.isAutoStake = st._isAutoStake;
        }
    
        function _stakeAdd(
            StakeStore[] storage stakeListRef,
            uint40 newStakeId,
            uint256 newStakedHearts,
            uint256 newStakeShares,
            uint256 newLockedDay,
            uint256 newStakedDays,
            bool newAutoStake
        )
            internal
        {
            stakeListRef.push(
                StakeStore(
                    newStakeId,
                    uint72(newStakedHearts),
                    uint72(newStakeShares),
                    uint16(newLockedDay),
                    uint16(newStakedDays),
                    uint16(0), // unlockedDay
                    newAutoStake
                )
            );
        }
    
        /**
         * @dev Efficiently delete from an unordered array by moving the last element
         * to the "hole" and reducing the array length. Can change the order of the list
         * and invalidate previously held indexes.
         * @notice stakeListRef length and stakeIndex are already ensured valid in stakeEnd()
         * @param stakeListRef Reference to stakeLists[stakerAddr] array in storage
         * @param stakeIndex Index of the element to delete
         */
        function _stakeRemove(StakeStore[] storage stakeListRef, uint256 stakeIndex)
            internal
        {
            uint256 lastIndex = stakeListRef.length - 1;
    
            /* Skip the copy if element to be removed is already the last element */
            if (stakeIndex != lastIndex) {
                /* Copy last element to the requested element's "hole" */
                stakeListRef[stakeIndex] = stakeListRef[lastIndex];
            }
    
            /*
                Reduce the array length now that the array is contiguous.
                Surprisingly, 'pop()' uses less gas than 'stakeListRef.length = lastIndex'
            */
            stakeListRef.pop();
        }
    
        function _claimStatsEncode(
            uint256 _claimedBtcAddrCount,
            uint256 _claimedSatoshisTotal,
            uint256 _unclaimedSatoshisTotal
        )
            internal
            pure
            returns (uint128)
        {
            uint256 v = _claimedBtcAddrCount << (SATOSHI_UINT_SIZE * 2);
            v |= _claimedSatoshisTotal << SATOSHI_UINT_SIZE;
            v |= _unclaimedSatoshisTotal;
    
            return uint128(v);
        }
    
        function _claimStatsDecode(uint128 v)
            internal
            pure
            returns (uint256 _claimedBtcAddrCount, uint256 _claimedSatoshisTotal, uint256 _unclaimedSatoshisTotal)
        {
            _claimedBtcAddrCount = v >> (SATOSHI_UINT_SIZE * 2);
            _claimedSatoshisTotal = (v >> SATOSHI_UINT_SIZE) & SATOSHI_UINT_MASK;
            _unclaimedSatoshisTotal = v & SATOSHI_UINT_MASK;
    
            return (_claimedBtcAddrCount, _claimedSatoshisTotal, _unclaimedSatoshisTotal);
        }
    
        /**
         * @dev Estimate the stake payout for an incomplete day
         * @param g Cache of stored globals
         * @param stakeSharesParam Param from stake to calculate bonuses for
         * @param day Day to calculate bonuses for
         * @return Payout in Hearts
         */
        function _estimatePayoutRewardsDay(GlobalsCache memory g, uint256 stakeSharesParam, uint256 day)
            internal
            view
            returns (uint256 payout)
        {
            /* Prevent updating state for this estimation */
            GlobalsCache memory gTmp;
            _globalsCacheSnapshot(g, gTmp);
    
            DailyRoundState memory rs;
            rs._allocSupplyCached = totalSupply() + g._lockedHeartsTotal;
    
            _dailyRoundCalc(gTmp, rs, day);
    
            /* Stake is no longer locked so it must be added to total as if it were */
            gTmp._stakeSharesTotal += stakeSharesParam;
    
            payout = rs._payoutTotal * stakeSharesParam / gTmp._stakeSharesTotal;
    
            if (day == BIG_PAY_DAY) {
                uint256 bigPaySlice = gTmp._unclaimedSatoshisTotal * HEARTS_PER_SATOSHI * stakeSharesParam
                    / gTmp._stakeSharesTotal;
                payout += bigPaySlice + _calcAdoptionBonus(gTmp, bigPaySlice);
            }
    
            return payout;
        }
    
        function _calcAdoptionBonus(GlobalsCache memory g, uint256 payout)
            internal
            pure
            returns (uint256)
        {
            /*
                VIRAL REWARDS: Add adoption percentage bonus to payout
    
                viral = payout * (claimedBtcAddrCount / CLAIMABLE_BTC_ADDR_COUNT)
            */
            uint256 viral = payout * g._claimedBtcAddrCount / CLAIMABLE_BTC_ADDR_COUNT;
    
            /*
                CRIT MASS REWARDS: Add adoption percentage bonus to payout
    
                crit  = payout * (claimedSatoshisTotal / CLAIMABLE_SATOSHIS_TOTAL)
            */
            uint256 crit = payout * g._claimedSatoshisTotal / CLAIMABLE_SATOSHIS_TOTAL;
    
            return viral + crit;
        }
    
        function _dailyRoundCalc(GlobalsCache memory g, DailyRoundState memory rs, uint256 day)
            private
            pure
        {
            /*
                Calculate payout round
    
                Inflation of 3.69% inflation per 364 days             (approx 1 year)
                dailyInterestRate   = exp(log(1 + 3.69%)  / 364) - 1
                                    = exp(log(1 + 0.0369) / 364) - 1
                                    = exp(log(1.0369) / 364) - 1
                                    = 0.000099553011616349            (approx)
    
                payout  = allocSupply * dailyInterestRate
                        = allocSupply / (1 / dailyInterestRate)
                        = allocSupply / (1 / 0.000099553011616349)
                        = allocSupply / 10044.899534066692            (approx)
                        = allocSupply * 10000 / 100448995             (* 10000/10000 for int precision)
            */
            rs._payoutTotal = rs._allocSupplyCached * 10000 / 100448995;
    
            if (day < CLAIM_PHASE_END_DAY) {
                uint256 bigPaySlice = g._unclaimedSatoshisTotal * HEARTS_PER_SATOSHI / CLAIM_PHASE_DAYS;
    
                uint256 originBonus = bigPaySlice + _calcAdoptionBonus(g, rs._payoutTotal + bigPaySlice);
                rs._mintOriginBatch += originBonus;
                rs._allocSupplyCached += originBonus;
    
                rs._payoutTotal += _calcAdoptionBonus(g, rs._payoutTotal);
            }
    
            if (g._stakePenaltyTotal != 0) {
                rs._payoutTotal += g._stakePenaltyTotal;
                g._stakePenaltyTotal = 0;
            }
        }
    
        function _dailyRoundCalcAndStore(GlobalsCache memory g, DailyRoundState memory rs, uint256 day)
            private
        {
            _dailyRoundCalc(g, rs, day);
    
            dailyData[day].dayPayoutTotal = uint72(rs._payoutTotal);
            dailyData[day].dayStakeSharesTotal = uint72(g._stakeSharesTotal);
            dailyData[day].dayUnclaimedSatoshisTotal = uint56(g._unclaimedSatoshisTotal);
        }
    
        function _dailyDataUpdate(GlobalsCache memory g, uint256 beforeDay, bool isAutoUpdate)
            private
        {
            if (g._dailyDataCount >= beforeDay) {
                /* Already up-to-date */
                return;
            }
    
            DailyRoundState memory rs;
            rs._allocSupplyCached = totalSupply() + g._lockedHeartsTotal;
    
            uint256 day = g._dailyDataCount;
    
            _dailyRoundCalcAndStore(g, rs, day);
    
            /* Stakes started during this day are added to the total the next day */
            if (g._nextStakeSharesTotal != 0) {
                g._stakeSharesTotal += g._nextStakeSharesTotal;
                g._nextStakeSharesTotal = 0;
            }
    
            while (++day < beforeDay) {
                _dailyRoundCalcAndStore(g, rs, day);
            }
    
            _emitDailyDataUpdate(g._dailyDataCount, day, isAutoUpdate);
            g._dailyDataCount = day;
    
            if (rs._mintOriginBatch != 0) {
                _mint(ORIGIN_ADDR, rs._mintOriginBatch);
            }
        }
    
        function _emitDailyDataUpdate(uint256 beginDay, uint256 endDay, bool isAutoUpdate)
            private
        {
            emit DailyDataUpdate( // (auto-generated event)
                uint256(uint40(block.timestamp))
                    | (uint256(uint16(beginDay)) << 40)
                    | (uint256(uint16(endDay)) << 56)
                    | (isAutoUpdate ? (1 << 72) : 0),
                msg.sender
            );
        }
    }
    
    contract StakeableToken is GlobalsAndUtility {
        /**
         * @dev PUBLIC FACING: Open a stake.
         * @param newStakedHearts Number of Hearts to stake
         * @param newStakedDays Number of days to stake
         */
        function stakeStart(uint256 newStakedHearts, uint256 newStakedDays)
            external
        {
            GlobalsCache memory g;
            GlobalsCache memory gSnapshot;
            _globalsLoad(g, gSnapshot);
    
            /* Enforce the minimum stake time */
            require(newStakedDays >= MIN_STAKE_DAYS, "HEX: newStakedDays lower than minimum");
    
            /* Check if log data needs to be updated */
            _dailyDataUpdateAuto(g);
    
            _stakeStart(g, newStakedHearts, newStakedDays, false);
    
            /* Remove staked Hearts from balance of staker */
            _burn(msg.sender, newStakedHearts);
    
            _globalsSync(g, gSnapshot);
        }
    
        /**
         * @dev PUBLIC FACING: Unlocks a completed stake, distributing the proceeds of any penalty
         * immediately. The staker must still call stakeEnd() to retrieve their stake return (if any).
         * @param stakerAddr Address of staker
         * @param stakeIndex Index of stake within stake list
         * @param stakeIdParam The stake's id
         */
        function stakeGoodAccounting(address stakerAddr, uint256 stakeIndex, uint40 stakeIdParam)
            external
        {
            GlobalsCache memory g;
            GlobalsCache memory gSnapshot;
            _globalsLoad(g, gSnapshot);
    
            /* require() is more informative than the default assert() */
            require(stakeLists[stakerAddr].length != 0, "HEX: Empty stake list");
            require(stakeIndex < stakeLists[stakerAddr].length, "HEX: stakeIndex invalid");
    
            StakeStore storage stRef = stakeLists[stakerAddr][stakeIndex];
    
            /* Get stake copy */
            StakeCache memory st;
            _stakeLoad(stRef, stakeIdParam, st);
    
            /* Stake must have served full term */
            require(g._currentDay >= st._lockedDay + st._stakedDays, "HEX: Stake not fully served");
    
            /* Stake must still be locked */
            require(st._unlockedDay == 0, "HEX: Stake already unlocked");
    
            /* Check if log data needs to be updated */
            _dailyDataUpdateAuto(g);
    
            /* Unlock the completed stake */
            _stakeUnlock(g, st);
    
            /* stakeReturn value is unused here */
            (, uint256 payout, uint256 penalty, uint256 cappedPenalty) = _stakePerformance(
                g,
                st,
                st._stakedDays
            );
    
            _emitStakeGoodAccounting(
                stakerAddr,
                stakeIdParam,
                st._stakedHearts,
                st._stakeShares,
                payout,
                penalty
            );
    
            if (cappedPenalty != 0) {
                _splitPenaltyProceeds(g, cappedPenalty);
            }
    
            /* st._unlockedDay has changed */
            _stakeUpdate(stRef, st);
    
            _globalsSync(g, gSnapshot);
        }
    
        /**
         * @dev PUBLIC FACING: Closes a stake. The order of the stake list can change so
         * a stake id is used to reject stale indexes.
         * @param stakeIndex Index of stake within stake list
         * @param stakeIdParam The stake's id
         */
        function stakeEnd(uint256 stakeIndex, uint40 stakeIdParam)
            external
        {
            GlobalsCache memory g;
            GlobalsCache memory gSnapshot;
            _globalsLoad(g, gSnapshot);
    
            StakeStore[] storage stakeListRef = stakeLists[msg.sender];
    
            /* require() is more informative than the default assert() */
            require(stakeListRef.length != 0, "HEX: Empty stake list");
            require(stakeIndex < stakeListRef.length, "HEX: stakeIndex invalid");
    
            /* Get stake copy */
            StakeCache memory st;
            _stakeLoad(stakeListRef[stakeIndex], stakeIdParam, st);
    
            /* Check if log data needs to be updated */
            _dailyDataUpdateAuto(g);
    
            uint256 servedDays = 0;
    
            bool prevUnlocked = (st._unlockedDay != 0);
            uint256 stakeReturn;
            uint256 payout = 0;
            uint256 penalty = 0;
            uint256 cappedPenalty = 0;
    
            if (g._currentDay >= st._lockedDay) {
                if (prevUnlocked) {
                    /* Previously unlocked in stakeGoodAccounting(), so must have served full term */
                    servedDays = st._stakedDays;
                } else {
                    _stakeUnlock(g, st);
    
                    servedDays = g._currentDay - st._lockedDay;
                    if (servedDays > st._stakedDays) {
                        servedDays = st._stakedDays;
                    } else {
                        /* Deny early-unstake before an auto-stake minimum has been served */
                        if (servedDays < MIN_AUTO_STAKE_DAYS) {
                            require(!st._isAutoStake, "HEX: Auto-stake still locked");
                        }
                    }
                }
    
                (stakeReturn, payout, penalty, cappedPenalty) = _stakePerformance(g, st, servedDays);
            } else {
                /* Deny early-unstake before an auto-stake minimum has been served */
                require(!st._isAutoStake, "HEX: Auto-stake still locked");
    
                /* Stake hasn't been added to the total yet, so no penalties or rewards apply */
                g._nextStakeSharesTotal -= st._stakeShares;
    
                stakeReturn = st._stakedHearts;
            }
    
            _emitStakeEnd(
                stakeIdParam,
                st._stakedHearts,
                st._stakeShares,
                payout,
                penalty,
                servedDays,
                prevUnlocked
            );
    
            if (cappedPenalty != 0 && !prevUnlocked) {
                /* Split penalty proceeds only if not previously unlocked by stakeGoodAccounting() */
                _splitPenaltyProceeds(g, cappedPenalty);
            }
    
            /* Pay the stake return, if any, to the staker */
            if (stakeReturn != 0) {
                _mint(msg.sender, stakeReturn);
    
                /* Update the share rate if necessary */
                _shareRateUpdate(g, st, stakeReturn);
            }
            g._lockedHeartsTotal -= st._stakedHearts;
    
            _stakeRemove(stakeListRef, stakeIndex);
    
            _globalsSync(g, gSnapshot);
        }
    
        /**
         * @dev PUBLIC FACING: Return the current stake count for a staker address
         * @param stakerAddr Address of staker
         */
        function stakeCount(address stakerAddr)
            external
            view
            returns (uint256)
        {
            return stakeLists[stakerAddr].length;
        }
    
        /**
         * @dev Open a stake.
         * @param g Cache of stored globals
         * @param newStakedHearts Number of Hearts to stake
         * @param newStakedDays Number of days to stake
         * @param newAutoStake Stake is automatic directly from a new claim
         */
        function _stakeStart(
            GlobalsCache memory g,
            uint256 newStakedHearts,
            uint256 newStakedDays,
            bool newAutoStake
        )
            internal
        {
            /* Enforce the maximum stake time */
            require(newStakedDays <= MAX_STAKE_DAYS, "HEX: newStakedDays higher than maximum");
    
            uint256 bonusHearts = _stakeStartBonusHearts(newStakedHearts, newStakedDays);
            uint256 newStakeShares = (newStakedHearts + bonusHearts) * SHARE_RATE_SCALE / g._shareRate;
    
            /* Ensure newStakedHearts is enough for at least one stake share */
            require(newStakeShares != 0, "HEX: newStakedHearts must be at least minimum shareRate");
    
            /*
                The stakeStart timestamp will always be part-way through the current
                day, so it needs to be rounded-up to the next day to ensure all
                stakes align with the same fixed calendar days. The current day is
                already rounded-down, so rounded-up is current day + 1.
            */
            uint256 newLockedDay = g._currentDay < CLAIM_PHASE_START_DAY
                ? CLAIM_PHASE_START_DAY + 1
                : g._currentDay + 1;
    
            /* Create Stake */
            uint40 newStakeId = ++g._latestStakeId;
            _stakeAdd(
                stakeLists[msg.sender],
                newStakeId,
                newStakedHearts,
                newStakeShares,
                newLockedDay,
                newStakedDays,
                newAutoStake
            );
    
            _emitStakeStart(newStakeId, newStakedHearts, newStakeShares, newStakedDays, newAutoStake);
    
            /* Stake is added to total in the next round, not the current round */
            g._nextStakeSharesTotal += newStakeShares;
    
            /* Track total staked Hearts for inflation calculations */
            g._lockedHeartsTotal += newStakedHearts;
        }
    
        /**
         * @dev Calculates total stake payout including rewards for a multi-day range
         * @param g Cache of stored globals
         * @param stakeSharesParam Param from stake to calculate bonuses for
         * @param beginDay First day to calculate bonuses for
         * @param endDay Last day (non-inclusive) of range to calculate bonuses for
         * @return Payout in Hearts
         */
        function _calcPayoutRewards(
            GlobalsCache memory g,
            uint256 stakeSharesParam,
            uint256 beginDay,
            uint256 endDay
        )
            private
            view
            returns (uint256 payout)
        {
            for (uint256 day = beginDay; day < endDay; day++) {
                payout += dailyData[day].dayPayoutTotal * stakeSharesParam
                    / dailyData[day].dayStakeSharesTotal;
            }
    
            /* Less expensive to re-read storage than to have the condition inside the loop */
            if (beginDay <= BIG_PAY_DAY && endDay > BIG_PAY_DAY) {
                uint256 bigPaySlice = g._unclaimedSatoshisTotal * HEARTS_PER_SATOSHI * stakeSharesParam
                    / dailyData[BIG_PAY_DAY].dayStakeSharesTotal;
    
                payout += bigPaySlice + _calcAdoptionBonus(g, bigPaySlice);
            }
            return payout;
        }
    
        /**
         * @dev Calculate bonus Hearts for a new stake, if any
         * @param newStakedHearts Number of Hearts to stake
         * @param newStakedDays Number of days to stake
         */
        function _stakeStartBonusHearts(uint256 newStakedHearts, uint256 newStakedDays)
            private
            pure
            returns (uint256 bonusHearts)
        {
            /*
                LONGER PAYS BETTER:
    
                If longer than 1 day stake is committed to, each extra day
                gives bonus shares of approximately 0.0548%, which is approximately 20%
                extra per year of increased stake length committed to, but capped to a
                maximum of 200% extra.
    
                extraDays       =  stakedDays - 1
    
                longerBonus%    = (extraDays / 364) * 20%
                                = (extraDays / 364) / 5
                                =  extraDays / 1820
                                =  extraDays / LPB
    
                extraDays       =  longerBonus% * 1820
                extraDaysMax    =  longerBonusMax% * 1820
                                =  200% * 1820
                                =  3640
                                =  LPB_MAX_DAYS
    
                BIGGER PAYS BETTER:
    
                Bonus percentage scaled 0% to 10% for the first 150M HEX of stake.
    
                biggerBonus%    = (cappedHearts /  BPB_MAX_HEARTS) * 10%
                                = (cappedHearts /  BPB_MAX_HEARTS) / 10
                                =  cappedHearts / (BPB_MAX_HEARTS * 10)
                                =  cappedHearts /  BPB
    
                COMBINED:
    
                combinedBonus%  =            longerBonus%  +  biggerBonus%
    
                                          cappedExtraDays     cappedHearts
                                =         ---------------  +  ------------
                                                LPB               BPB
    
                                    cappedExtraDays * BPB     cappedHearts * LPB
                                =   ---------------------  +  ------------------
                                          LPB * BPB               LPB * BPB
    
                                    cappedExtraDays * BPB  +  cappedHearts * LPB
                                =   --------------------------------------------
                                                      LPB  *  BPB
    
                bonusHearts     = hearts * combinedBonus%
                                = hearts * (cappedExtraDays * BPB  +  cappedHearts * LPB) / (LPB * BPB)
            */
            uint256 cappedExtraDays = 0;
    
            /* Must be more than 1 day for Longer-Pays-Better */
            if (newStakedDays > 1) {
                cappedExtraDays = newStakedDays <= LPB_MAX_DAYS ? newStakedDays - 1 : LPB_MAX_DAYS;
            }
    
            uint256 cappedStakedHearts = newStakedHearts <= BPB_MAX_HEARTS
                ? newStakedHearts
                : BPB_MAX_HEARTS;
    
            bonusHearts = cappedExtraDays * BPB + cappedStakedHearts * LPB;
            bonusHearts = newStakedHearts * bonusHearts / (LPB * BPB);
    
            return bonusHearts;
        }
    
        function _stakeUnlock(GlobalsCache memory g, StakeCache memory st)
            private
            pure
        {
            g._stakeSharesTotal -= st._stakeShares;
            st._unlockedDay = g._currentDay;
        }
    
        function _stakePerformance(GlobalsCache memory g, StakeCache memory st, uint256 servedDays)
            private
            view
            returns (uint256 stakeReturn, uint256 payout, uint256 penalty, uint256 cappedPenalty)
        {
            if (servedDays < st._stakedDays) {
                (payout, penalty) = _calcPayoutAndEarlyPenalty(
                    g,
                    st._lockedDay,
                    st._stakedDays,
                    servedDays,
                    st._stakeShares
                );
                stakeReturn = st._stakedHearts + payout;
            } else {
                // servedDays must == stakedDays here
                payout = _calcPayoutRewards(
                    g,
                    st._stakeShares,
                    st._lockedDay,
                    st._lockedDay + servedDays
                );
                stakeReturn = st._stakedHearts + payout;
    
                penalty = _calcLatePenalty(st._lockedDay, st._stakedDays, st._unlockedDay, stakeReturn);
            }
            if (penalty != 0) {
                if (penalty > stakeReturn) {
                    /* Cannot have a negative stake return */
                    cappedPenalty = stakeReturn;
                    stakeReturn = 0;
                } else {
                    /* Remove penalty from the stake return */
                    cappedPenalty = penalty;
                    stakeReturn -= cappedPenalty;
                }
            }
            return (stakeReturn, payout, penalty, cappedPenalty);
        }
    
        function _calcPayoutAndEarlyPenalty(
            GlobalsCache memory g,
            uint256 lockedDayParam,
            uint256 stakedDaysParam,
            uint256 servedDays,
            uint256 stakeSharesParam
        )
            private
            view
            returns (uint256 payout, uint256 penalty)
        {
            uint256 servedEndDay = lockedDayParam + servedDays;
    
            /* 50% of stakedDays (rounded up) with a minimum applied */
            uint256 penaltyDays = (stakedDaysParam + 1) / 2;
            if (penaltyDays < EARLY_PENALTY_MIN_DAYS) {
                penaltyDays = EARLY_PENALTY_MIN_DAYS;
            }
    
            if (servedDays == 0) {
                /* Fill penalty days with the estimated average payout */
                uint256 expected = _estimatePayoutRewardsDay(g, stakeSharesParam, lockedDayParam);
                penalty = expected * penaltyDays;
                return (payout, penalty); // Actual payout was 0
            }
    
            if (penaltyDays < servedDays) {
                /*
                    Simplified explanation of intervals where end-day is non-inclusive:
    
                    penalty:    [lockedDay  ...  penaltyEndDay)
                    delta:                      [penaltyEndDay  ...  servedEndDay)
                    payout:     [lockedDay  .......................  servedEndDay)
                */
                uint256 penaltyEndDay = lockedDayParam + penaltyDays;
                penalty = _calcPayoutRewards(g, stakeSharesParam, lockedDayParam, penaltyEndDay);
    
                uint256 delta = _calcPayoutRewards(g, stakeSharesParam, penaltyEndDay, servedEndDay);
                payout = penalty + delta;
                return (payout, penalty);
            }
    
            /* penaltyDays >= servedDays  */
            payout = _calcPayoutRewards(g, stakeSharesParam, lockedDayParam, servedEndDay);
    
            if (penaltyDays == servedDays) {
                penalty = payout;
            } else {
                /*
                    (penaltyDays > servedDays) means not enough days served, so fill the
                    penalty days with the average payout from only the days that were served.
                */
                penalty = payout * penaltyDays / servedDays;
            }
            return (payout, penalty);
        }
    
        function _calcLatePenalty(
            uint256 lockedDayParam,
            uint256 stakedDaysParam,
            uint256 unlockedDayParam,
            uint256 rawStakeReturn
        )
            private
            pure
            returns (uint256)
        {
            /* Allow grace time before penalties accrue */
            uint256 maxUnlockedDay = lockedDayParam + stakedDaysParam + LATE_PENALTY_GRACE_DAYS;
            if (unlockedDayParam <= maxUnlockedDay) {
                return 0;
            }
    
            /* Calculate penalty as a percentage of stake return based on time */
            return rawStakeReturn * (unlockedDayParam - maxUnlockedDay) / LATE_PENALTY_SCALE_DAYS;
        }
    
        function _splitPenaltyProceeds(GlobalsCache memory g, uint256 penalty)
            private
        {
            /* Split a penalty 50:50 between Origin and stakePenaltyTotal */
            uint256 splitPenalty = penalty / 2;
    
            if (splitPenalty != 0) {
                _mint(ORIGIN_ADDR, splitPenalty);
            }
    
            /* Use the other half of the penalty to account for an odd-numbered penalty */
            splitPenalty = penalty - splitPenalty;
            g._stakePenaltyTotal += splitPenalty;
        }
    
        function _shareRateUpdate(GlobalsCache memory g, StakeCache memory st, uint256 stakeReturn)
            private
        {
            if (stakeReturn > st._stakedHearts) {
                /*
                    Calculate the new shareRate that would yield the same number of shares if
                    the user re-staked this stakeReturn, factoring in any bonuses they would
                    receive in stakeStart().
                */
                uint256 bonusHearts = _stakeStartBonusHearts(stakeReturn, st._stakedDays);
                uint256 newShareRate = (stakeReturn + bonusHearts) * SHARE_RATE_SCALE / st._stakeShares;
    
                if (newShareRate > SHARE_RATE_MAX) {
                    /*
                        Realistically this can't happen, but there are contrived theoretical
                        scenarios that can lead to extreme values of newShareRate, so it is
                        capped to prevent them anyway.
                    */
                    newShareRate = SHARE_RATE_MAX;
                }
    
                if (newShareRate > g._shareRate) {
                    g._shareRate = newShareRate;
    
                    _emitShareRateChange(newShareRate, st._stakeId);
                }
            }
        }
    
        function _emitStakeStart(
            uint40 stakeId,
            uint256 stakedHearts,
            uint256 stakeShares,
            uint256 stakedDays,
            bool isAutoStake
        )
            private
        {
            emit StakeStart( // (auto-generated event)
                uint256(uint40(block.timestamp))
                    | (uint256(uint72(stakedHearts)) << 40)
                    | (uint256(uint72(stakeShares)) << 112)
                    | (uint256(uint16(stakedDays)) << 184)
                    | (isAutoStake ? (1 << 200) : 0),
                msg.sender,
                stakeId
            );
        }
    
        function _emitStakeGoodAccounting(
            address stakerAddr,
            uint40 stakeId,
            uint256 stakedHearts,
            uint256 stakeShares,
            uint256 payout,
            uint256 penalty
        )
            private
        {
            emit StakeGoodAccounting( // (auto-generated event)
                uint256(uint40(block.timestamp))
                    | (uint256(uint72(stakedHearts)) << 40)
                    | (uint256(uint72(stakeShares)) << 112)
                    | (uint256(uint72(payout)) << 184),
                uint256(uint72(penalty)),
                stakerAddr,
                stakeId,
                msg.sender
            );
        }
    
        function _emitStakeEnd(
            uint40 stakeId,
            uint256 stakedHearts,
            uint256 stakeShares,
            uint256 payout,
            uint256 penalty,
            uint256 servedDays,
            bool prevUnlocked
        )
            private
        {
            emit StakeEnd( // (auto-generated event)
                uint256(uint40(block.timestamp))
                    | (uint256(uint72(stakedHearts)) << 40)
                    | (uint256(uint72(stakeShares)) << 112)
                    | (uint256(uint72(payout)) << 184),
                uint256(uint72(penalty))
                    | (uint256(uint16(servedDays)) << 72)
                    | (prevUnlocked ? (1 << 88) : 0),
                msg.sender,
                stakeId
            );
        }
    
        function _emitShareRateChange(uint256 shareRate, uint40 stakeId)
            private
        {
            emit ShareRateChange( // (auto-generated event)
                uint256(uint40(block.timestamp))
                    | (uint256(uint40(shareRate)) << 40),
                stakeId
            );
        }
    }
    
    /**
     * @dev These functions deal with verification of Merkle trees (hash trees),
     */
    library MerkleProof {
        /**
         * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
         * defined by `root`. For this, a `proof` must be provided, containing
         * sibling hashes on the branch from the leaf to the root of the tree. Each
         * pair of leaves and each pair of pre-images are assumed to be sorted.
         */
        function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
            bytes32 computedHash = leaf;
    
            for (uint256 i = 0; i < proof.length; i++) {
                bytes32 proofElement = proof[i];
    
                if (computedHash < proofElement) {
                    // Hash(current computed hash + current element of the proof)
                    computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
                } else {
                    // Hash(current element of the proof + current computed hash)
                    computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
                }
            }
    
            // Check if the computed hash (root) is equal to the provided root
            return computedHash == root;
        }
    }
    
    contract UTXOClaimValidation is StakeableToken {
        /**
         * @dev PUBLIC FACING: Verify a BTC address and balance are unclaimed and part of the Merkle tree
         * @param btcAddr Bitcoin address (binary; no base58-check encoding)
         * @param rawSatoshis Raw BTC address balance in Satoshis
         * @param proof Merkle tree proof
         * @return True if can be claimed
         */
        function btcAddressIsClaimable(bytes20 btcAddr, uint256 rawSatoshis, bytes32[] calldata proof)
            external
            view
            returns (bool)
        {
            uint256 day = _currentDay();
    
            require(day >= CLAIM_PHASE_START_DAY, "HEX: Claim phase has not yet started");
            require(day < CLAIM_PHASE_END_DAY, "HEX: Claim phase has ended");
    
            /* Don't need to check Merkle proof if UTXO BTC address has already been claimed    */
            if (btcAddressClaims[btcAddr]) {
                return false;
            }
    
            /* Verify the Merkle tree proof */
            return _btcAddressIsValid(btcAddr, rawSatoshis, proof);
        }
    
        /**
         * @dev PUBLIC FACING: Verify a BTC address and balance are part of the Merkle tree
         * @param btcAddr Bitcoin address (binary; no base58-check encoding)
         * @param rawSatoshis Raw BTC address balance in Satoshis
         * @param proof Merkle tree proof
         * @return True if valid
         */
        function btcAddressIsValid(bytes20 btcAddr, uint256 rawSatoshis, bytes32[] calldata proof)
            external
            pure
            returns (bool)
        {
            return _btcAddressIsValid(btcAddr, rawSatoshis, proof);
        }
    
        /**
         * @dev PUBLIC FACING: Verify a Merkle proof using the UTXO Merkle tree
         * @param merkleLeaf Leaf asserted to be present in the Merkle tree
         * @param proof Generated Merkle tree proof
         * @return True if valid
         */
        function merkleProofIsValid(bytes32 merkleLeaf, bytes32[] calldata proof)
            external
            pure
            returns (bool)
        {
            return _merkleProofIsValid(merkleLeaf, proof);
        }
    
        /**
         * @dev PUBLIC FACING: Verify that a Bitcoin signature matches the claim message containing
         * the Ethereum address and claim param hash
         * @param claimToAddr Eth address within the signed claim message
         * @param claimParamHash Param hash within the signed claim message
         * @param pubKeyX First  half of uncompressed ECDSA public key
         * @param pubKeyY Second half of uncompressed ECDSA public key
         * @param claimFlags Claim flags specifying address and message formats
         * @param v v parameter of ECDSA signature
         * @param r r parameter of ECDSA signature
         * @param s s parameter of ECDSA signature
         * @return True if matching
         */
        function claimMessageMatchesSignature(
            address claimToAddr,
            bytes32 claimParamHash,
            bytes32 pubKeyX,
            bytes32 pubKeyY,
            uint8 claimFlags,
            uint8 v,
            bytes32 r,
            bytes32 s
        )
            public
            pure
            returns (bool)
        {
            require(v >= 27 && v <= 30, "HEX: v invalid");
    
            /*
                ecrecover() returns an Eth address rather than a public key, so
                we must do the same to compare.
            */
            address pubKeyEthAddr = pubKeyToEthAddress(pubKeyX, pubKeyY);
    
            /* Create and hash the claim message text */
            bytes32 messageHash = _hash256(
                _claimMessageCreate(claimToAddr, claimParamHash, claimFlags)
            );
    
            /* Verify the public key */
            return ecrecover(messageHash, v, r, s) == pubKeyEthAddr;
        }
    
        /**
         * @dev PUBLIC FACING: Derive an Ethereum address from an ECDSA public key
         * @param pubKeyX First  half of uncompressed ECDSA public key
         * @param pubKeyY Second half of uncompressed ECDSA public key
         * @return Derived Eth address
         */
        function pubKeyToEthAddress(bytes32 pubKeyX, bytes32 pubKeyY)
            public
            pure
            returns (address)
        {
            return address(uint160(uint256(keccak256(abi.encodePacked(pubKeyX, pubKeyY)))));
        }
    
        /**
         * @dev PUBLIC FACING: Derive a Bitcoin address from an ECDSA public key
         * @param pubKeyX First  half of uncompressed ECDSA public key
         * @param pubKeyY Second half of uncompressed ECDSA public key
         * @param claimFlags Claim flags specifying address and message formats
         * @return Derived Bitcoin address (binary; no base58-check encoding)
         */
        function pubKeyToBtcAddress(bytes32 pubKeyX, bytes32 pubKeyY, uint8 claimFlags)
            public
            pure
            returns (bytes20)
        {
            /*
                Helpful references:
                 - https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
                 - https://github.com/cryptocoinjs/ecurve/blob/master/lib/point.js
            */
            uint8 startingByte;
            bytes memory pubKey;
            bool compressed = (claimFlags & CLAIM_FLAG_BTC_ADDR_COMPRESSED) != 0;
            bool nested = (claimFlags & CLAIM_FLAG_BTC_ADDR_P2WPKH_IN_P2SH) != 0;
            bool bech32 = (claimFlags & CLAIM_FLAG_BTC_ADDR_BECH32) != 0;
    
            if (compressed) {
                /* Compressed public key format */
                require(!(nested && bech32), "HEX: claimFlags invalid");
    
                startingByte = (pubKeyY[31] & 0x01) == 0 ? 0x02 : 0x03;
                pubKey = abi.encodePacked(startingByte, pubKeyX);
            } else {
                /* Uncompressed public key format */
                require(!nested && !bech32, "HEX: claimFlags invalid");
    
                startingByte = 0x04;
                pubKey = abi.encodePacked(startingByte, pubKeyX, pubKeyY);
            }
    
            bytes20 pubKeyHash = _hash160(pubKey);
            if (nested) {
                return _hash160(abi.encodePacked(hex"0014", pubKeyHash));
            }
            return pubKeyHash;
        }
    
        /**
         * @dev Verify a BTC address and balance are part of the Merkle tree
         * @param btcAddr Bitcoin address (binary; no base58-check encoding)
         * @param rawSatoshis Raw BTC address balance in Satoshis
         * @param proof Merkle tree proof
         * @return True if valid
         */
        function _btcAddressIsValid(bytes20 btcAddr, uint256 rawSatoshis, bytes32[] memory proof)
            internal
            pure
            returns (bool)
        {
            /*
                Ensure the proof does not attempt to treat a Merkle leaf as if it were an
                internal Merkle tree node. A leaf will always have the zero-fill. An
                internal node will never have the zero-fill, as guaranteed by HEX's Merkle
                tree construction.
    
                The first element, proof[0], will always be a leaf because it is the pair
                of the leaf being validated. The rest of the elements, proof[1..length-1],
                must be internal nodes.
    
                The number of leaves (CLAIMABLE_BTC_ADDR_COUNT) is even, as guaranteed by
                HEX's Merkle tree construction, which eliminates the only edge-case where
                this validation would not apply.
            */
            require((uint256(proof[0]) & MERKLE_LEAF_FILL_MASK) == 0, "HEX: proof invalid");
            for (uint256 i = 1; i < proof.length; i++) {
                require((uint256(proof[i]) & MERKLE_LEAF_FILL_MASK) != 0, "HEX: proof invalid");
            }
    
            /*
                Calculate the 32 byte Merkle leaf associated with this BTC address and balance
                    160 bits: BTC address
                     52 bits: Zero-fill
                     45 bits: Satoshis (limited by MAX_BTC_ADDR_BALANCE_SATOSHIS)
            */
            bytes32 merkleLeaf = bytes32(btcAddr) | bytes32(rawSatoshis);
    
            /* Verify the Merkle tree proof */
            return _merkleProofIsValid(merkleLeaf, proof);
        }
    
        /**
         * @dev Verify a Merkle proof using the UTXO Merkle tree
         * @param merkleLeaf Leaf asserted to be present in the Merkle tree
         * @param proof Generated Merkle tree proof
         * @return True if valid
         */
        function _merkleProofIsValid(bytes32 merkleLeaf, bytes32[] memory proof)
            private
            pure
            returns (bool)
        {
            return MerkleProof.verify(proof, MERKLE_TREE_ROOT, merkleLeaf);
        }
    
        function _claimMessageCreate(address claimToAddr, bytes32 claimParamHash, uint8 claimFlags)
            private
            pure
            returns (bytes memory)
        {
            bytes memory prefixStr = (claimFlags & CLAIM_FLAG_MSG_PREFIX_OLD) != 0
                ? OLD_CLAIM_PREFIX_STR
                : STD_CLAIM_PREFIX_STR;
    
            bool includeAddrChecksum = (claimFlags & CLAIM_FLAG_ETH_ADDR_LOWERCASE) == 0;
    
            bytes memory addrStr = _addressStringCreate(claimToAddr, includeAddrChecksum);
    
            if (claimParamHash == 0) {
                return abi.encodePacked(
                    BITCOIN_SIG_PREFIX_LEN,
                    BITCOIN_SIG_PREFIX_STR,
                    uint8(prefixStr.length) + ETH_ADDRESS_HEX_LEN,
                    prefixStr,
                    addrStr
                );
            }
    
            bytes memory claimParamHashStr = new bytes(CLAIM_PARAM_HASH_HEX_LEN);
    
            _hexStringFromData(claimParamHashStr, claimParamHash, CLAIM_PARAM_HASH_BYTE_LEN);
    
            return abi.encodePacked(
                BITCOIN_SIG_PREFIX_LEN,
                BITCOIN_SIG_PREFIX_STR,
                uint8(prefixStr.length) + ETH_ADDRESS_HEX_LEN + 1 + CLAIM_PARAM_HASH_HEX_LEN,
                prefixStr,
                addrStr,
                "_",
                claimParamHashStr
            );
        }
    
        function _addressStringCreate(address addr, bool includeAddrChecksum)
            private
            pure
            returns (bytes memory addrStr)
        {
            addrStr = new bytes(ETH_ADDRESS_HEX_LEN);
            _hexStringFromData(addrStr, bytes32(bytes20(addr)), ETH_ADDRESS_BYTE_LEN);
    
            if (includeAddrChecksum) {
                bytes32 addrStrHash = keccak256(addrStr);
    
                uint256 offset = 0;
    
                for (uint256 i = 0; i < ETH_ADDRESS_BYTE_LEN; i++) {
                    uint8 b = uint8(addrStrHash[i]);
    
                    _addressStringChecksumChar(addrStr, offset++, b >> 4);
                    _addressStringChecksumChar(addrStr, offset++, b & 0x0f);
                }
            }
    
            return addrStr;
        }
    
        function _addressStringChecksumChar(bytes memory addrStr, uint256 offset, uint8 hashNybble)
            private
            pure
        {
            bytes1 ch = addrStr[offset];
    
            if (ch >= "a" && hashNybble >= 8) {
                addrStr[offset] = ch ^ 0x20;
            }
        }
    
        function _hexStringFromData(bytes memory hexStr, bytes32 data, uint256 dataLen)
            private
            pure
        {
            uint256 offset = 0;
    
            for (uint256 i = 0; i < dataLen; i++) {
                uint8 b = uint8(data[i]);
    
                hexStr[offset++] = HEX_DIGITS[b >> 4];
                hexStr[offset++] = HEX_DIGITS[b & 0x0f];
            }
        }
    
        /**
         * @dev sha256(sha256(data))
         * @param data Data to be hashed
         * @return 32-byte hash
         */
        function _hash256(bytes memory data)
            private
            pure
            returns (bytes32)
        {
            return sha256(abi.encodePacked(sha256(data)));
        }
    
        /**
         * @dev ripemd160(sha256(data))
         * @param data Data to be hashed
         * @return 20-byte hash
         */
        function _hash160(bytes memory data)
            private
            pure
            returns (bytes20)
        {
            return ripemd160(abi.encodePacked(sha256(data)));
        }
    }
    
    contract UTXORedeemableToken is UTXOClaimValidation {
        /**
         * @dev PUBLIC FACING: Claim a BTC address and its Satoshi balance in Hearts
         * crediting the appropriate amount to a specified Eth address. Bitcoin ECDSA
         * signature must be from that BTC address and must match the claim message
         * for the Eth address.
         * @param rawSatoshis Raw BTC address balance in Satoshis
         * @param proof Merkle tree proof
         * @param claimToAddr Destination Eth address to credit Hearts to
         * @param pubKeyX First  half of uncompressed ECDSA public key for the BTC address
         * @param pubKeyY Second half of uncompressed ECDSA public key for the BTC address
         * @param claimFlags Claim flags specifying address and message formats
         * @param v v parameter of ECDSA signature
         * @param r r parameter of ECDSA signature
         * @param s s parameter of ECDSA signature
         * @param autoStakeDays Number of days to auto-stake, subject to minimum auto-stake days
         * @param referrerAddr Eth address of referring user (optional; 0x0 for no referrer)
         * @return Total number of Hearts credited, if successful
         */
        function btcAddressClaim(
            uint256 rawSatoshis,
            bytes32[] calldata proof,
            address claimToAddr,
            bytes32 pubKeyX,
            bytes32 pubKeyY,
            uint8 claimFlags,
            uint8 v,
            bytes32 r,
            bytes32 s,
            uint256 autoStakeDays,
            address referrerAddr
        )
            external
            returns (uint256)
        {
            /* Sanity check */
            require(rawSatoshis <= MAX_BTC_ADDR_BALANCE_SATOSHIS, "HEX: CHK: rawSatoshis");
    
            /* Enforce the minimum stake time for the auto-stake from this claim */
            require(autoStakeDays >= MIN_AUTO_STAKE_DAYS, "HEX: autoStakeDays lower than minimum");
    
            /* Ensure signature matches the claim message containing the Eth address and claimParamHash */
            {
                bytes32 claimParamHash = 0;
    
                if (claimToAddr != msg.sender) {
                    /* Claimer did not send this, so claim params must be signed */
                    claimParamHash = keccak256(
                        abi.encodePacked(MERKLE_TREE_ROOT, autoStakeDays, referrerAddr)
                    );
                }
    
                require(
                    claimMessageMatchesSignature(
                        claimToAddr,
                        claimParamHash,
                        pubKeyX,
                        pubKeyY,
                        claimFlags,
                        v,
                        r,
                        s
                    ),
                    "HEX: Signature mismatch"
                );
            }
    
            /* Derive BTC address from public key */
            bytes20 btcAddr = pubKeyToBtcAddress(pubKeyX, pubKeyY, claimFlags);
    
            /* Ensure BTC address has not yet been claimed */
            require(!btcAddressClaims[btcAddr], "HEX: BTC address balance already claimed");
    
            /* Ensure BTC address is part of the Merkle tree */
            require(
                _btcAddressIsValid(btcAddr, rawSatoshis, proof),
                "HEX: BTC address or balance unknown"
            );
    
            /* Mark BTC address as claimed */
            btcAddressClaims[btcAddr] = true;
    
            return _satoshisClaimSync(
                rawSatoshis,
                claimToAddr,
                btcAddr,
                claimFlags,
                autoStakeDays,
                referrerAddr
            );
        }
    
        function _satoshisClaimSync(
            uint256 rawSatoshis,
            address claimToAddr,
            bytes20 btcAddr,
            uint8 claimFlags,
            uint256 autoStakeDays,
            address referrerAddr
        )
            private
            returns (uint256 totalClaimedHearts)
        {
            GlobalsCache memory g;
            GlobalsCache memory gSnapshot;
            _globalsLoad(g, gSnapshot);
    
            totalClaimedHearts = _satoshisClaim(
                g,
                rawSatoshis,
                claimToAddr,
                btcAddr,
                claimFlags,
                autoStakeDays,
                referrerAddr
            );
    
            _globalsSync(g, gSnapshot);
    
            return totalClaimedHearts;
        }
    
        /**
         * @dev Credit an Eth address with the Hearts value of a raw Satoshis balance
         * @param g Cache of stored globals
         * @param rawSatoshis Raw BTC address balance in Satoshis
         * @param claimToAddr Destination Eth address for the claimed Hearts to be sent
         * @param btcAddr Bitcoin address (binary; no base58-check encoding)
         * @param autoStakeDays Number of days to auto-stake, subject to minimum auto-stake days
         * @param referrerAddr Eth address of referring user (optional; 0x0 for no referrer)
         * @return Total number of Hearts credited, if successful
         */
        function _satoshisClaim(
            GlobalsCache memory g,
            uint256 rawSatoshis,
            address claimToAddr,
            bytes20 btcAddr,
            uint8 claimFlags,
            uint256 autoStakeDays,
            address referrerAddr
        )
            private
            returns (uint256 totalClaimedHearts)
        {
            /* Allowed only during the claim phase */
            require(g._currentDay >= CLAIM_PHASE_START_DAY, "HEX: Claim phase has not yet started");
            require(g._currentDay < CLAIM_PHASE_END_DAY, "HEX: Claim phase has ended");
    
            /* Check if log data needs to be updated */
            _dailyDataUpdateAuto(g);
    
            /* Sanity check */
            require(
                g._claimedBtcAddrCount < CLAIMABLE_BTC_ADDR_COUNT,
                "HEX: CHK: _claimedBtcAddrCount"
            );
    
            (uint256 adjSatoshis, uint256 claimedHearts, uint256 claimBonusHearts) = _calcClaimValues(
                g,
                rawSatoshis
            );
    
            /* Increment claim count to track viral rewards */
            g._claimedBtcAddrCount++;
    
            totalClaimedHearts = _remitBonuses(
                claimToAddr,
                btcAddr,
                claimFlags,
                rawSatoshis,
                adjSatoshis,
                claimedHearts,
                claimBonusHearts,
                referrerAddr
            );
    
            /* Auto-stake a percentage of the successful claim */
            uint256 autoStakeHearts = totalClaimedHearts * AUTO_STAKE_CLAIM_PERCENT / 100;
            _stakeStart(g, autoStakeHearts, autoStakeDays, true);
    
            /* Mint remaining claimed Hearts to claim address */
            _mint(claimToAddr, totalClaimedHearts - autoStakeHearts);
    
            return totalClaimedHearts;
        }
    
        function _remitBonuses(
            address claimToAddr,
            bytes20 btcAddr,
            uint8 claimFlags,
            uint256 rawSatoshis,
            uint256 adjSatoshis,
            uint256 claimedHearts,
            uint256 claimBonusHearts,
            address referrerAddr
        )
            private
            returns (uint256 totalClaimedHearts)
        {
            totalClaimedHearts = claimedHearts + claimBonusHearts;
    
            uint256 originBonusHearts = claimBonusHearts;
    
            if (referrerAddr == address(0)) {
                /* No referrer */
                _emitClaim(
                    claimToAddr,
                    btcAddr,
                    claimFlags,
                    rawSatoshis,
                    adjSatoshis,
                    totalClaimedHearts,
                    referrerAddr
                );
            } else {
                /* Referral bonus of 10% of total claimed Hearts to claimer */
                uint256 referralBonusHearts = totalClaimedHearts / 10;
    
                totalClaimedHearts += referralBonusHearts;
    
                /* Then a cumulative referrer bonus of 20% to referrer */
                uint256 referrerBonusHearts = totalClaimedHearts / 5;
    
                originBonusHearts += referralBonusHearts + referrerBonusHearts;
    
                if (referrerAddr == claimToAddr) {
                    /* Self-referred */
                    totalClaimedHearts += referrerBonusHearts;
                    _emitClaim(
                        claimToAddr,
                        btcAddr,
                        claimFlags,
                        rawSatoshis,
                        adjSatoshis,
                        totalClaimedHearts,
                        referrerAddr
                    );
                } else {
                    /* Referred by different address */
                    _emitClaim(
                        claimToAddr,
                        btcAddr,
                        claimFlags,
                        rawSatoshis,
                        adjSatoshis,
                        totalClaimedHearts,
                        referrerAddr
                    );
                    _mint(referrerAddr, referrerBonusHearts);
                }
            }
    
            _mint(ORIGIN_ADDR, originBonusHearts);
    
            return totalClaimedHearts;
        }
    
        function _emitClaim(
            address claimToAddr,
            bytes20 btcAddr,
            uint8 claimFlags,
            uint256 rawSatoshis,
            uint256 adjSatoshis,
            uint256 claimedHearts,
            address referrerAddr
        )
            private
        {
            emit Claim( // (auto-generated event)
                uint256(uint40(block.timestamp))
                    | (uint256(uint56(rawSatoshis)) << 40)
                    | (uint256(uint56(adjSatoshis)) << 96)
                    | (uint256(claimFlags) << 152)
                    | (uint256(uint72(claimedHearts)) << 160),
                uint256(uint160(msg.sender)),
                btcAddr,
                claimToAddr,
                referrerAddr
            );
    
            if (claimToAddr == msg.sender) {
                return;
            }
    
            emit ClaimAssist( // (auto-generated event)
                uint256(uint40(block.timestamp))
                    | (uint256(uint160(btcAddr)) << 40)
                    | (uint256(uint56(rawSatoshis)) << 200),
                uint256(uint56(adjSatoshis))
                    | (uint256(uint160(claimToAddr)) << 56)
                    | (uint256(claimFlags) << 216),
                uint256(uint72(claimedHearts))
                    | (uint256(uint160(referrerAddr)) << 72),
                msg.sender
            );
        }
    
        function _calcClaimValues(GlobalsCache memory g, uint256 rawSatoshis)
            private
            pure
            returns (uint256 adjSatoshis, uint256 claimedHearts, uint256 claimBonusHearts)
        {
            /* Apply Silly Whale reduction */
            adjSatoshis = _adjustSillyWhale(rawSatoshis);
            require(
                g._claimedSatoshisTotal + adjSatoshis <= CLAIMABLE_SATOSHIS_TOTAL,
                "HEX: CHK: _claimedSatoshisTotal"
            );
            g._claimedSatoshisTotal += adjSatoshis;
    
            uint256 daysRemaining = CLAIM_PHASE_END_DAY - g._currentDay;
    
            /* Apply late-claim reduction */
            adjSatoshis = _adjustLateClaim(adjSatoshis, daysRemaining);
            g._unclaimedSatoshisTotal -= adjSatoshis;
    
            /* Convert to Hearts and calculate speed bonus */
            claimedHearts = adjSatoshis * HEARTS_PER_SATOSHI;
            claimBonusHearts = _calcSpeedBonus(claimedHearts, daysRemaining);
    
            return (adjSatoshis, claimedHearts, claimBonusHearts);
        }
    
        /**
         * @dev Apply Silly Whale adjustment
         * @param rawSatoshis Raw BTC address balance in Satoshis
         * @return Adjusted BTC address balance in Satoshis
         */
        function _adjustSillyWhale(uint256 rawSatoshis)
            private
            pure
            returns (uint256)
        {
            if (rawSatoshis < 1000e8) {
                /* For < 1,000 BTC: no penalty */
                return rawSatoshis;
            }
            if (rawSatoshis >= 10000e8) {
                /* For >= 10,000 BTC: penalty is 75%, leaving 25% */
                return rawSatoshis / 4;
            }
            /*
                For 1,000 <= BTC < 10,000: penalty scales linearly from 50% to 75%
    
                penaltyPercent  = (btc - 1000) / (10000 - 1000) * (75 - 50) + 50
                                = (btc - 1000) / 9000 * 25 + 50
                                = (btc - 1000) / 360 + 50
    
                appliedPercent  = 100 - penaltyPercent
                                = 100 - ((btc - 1000) / 360 + 50)
                                = 100 - (btc - 1000) / 360 - 50
                                = 50 - (btc - 1000) / 360
                                = (18000 - (btc - 1000)) / 360
                                = (18000 - btc + 1000) / 360
                                = (19000 - btc) / 360
    
                adjustedBtc     = btc * appliedPercent / 100
                                = btc * ((19000 - btc) / 360) / 100
                                = btc * (19000 - btc) / 36000
    
                adjustedSat     = 1e8 * adjustedBtc
                                = 1e8 * (btc * (19000 - btc) / 36000)
                                = 1e8 * ((sat / 1e8) * (19000 - (sat / 1e8)) / 36000)
                                = 1e8 * (sat / 1e8) * (19000 - (sat / 1e8)) / 36000
                                = (sat / 1e8) * 1e8 * (19000 - (sat / 1e8)) / 36000
                                = (sat / 1e8) * (19000e8 - sat) / 36000
                                = sat * (19000e8 - sat) / 36000e8
            */
            return rawSatoshis * (19000e8 - rawSatoshis) / 36000e8;
        }
    
        /**
         * @dev Apply late-claim adjustment to scale claim to zero by end of claim phase
         * @param adjSatoshis Adjusted BTC address balance in Satoshis (after Silly Whale)
         * @param daysRemaining Number of reward days remaining in claim phase
         * @return Adjusted BTC address balance in Satoshis (after Silly Whale and Late-Claim)
         */
        function _adjustLateClaim(uint256 adjSatoshis, uint256 daysRemaining)
            private
            pure
            returns (uint256)
        {
            /*
                Only valid from CLAIM_PHASE_DAYS to 1, and only used during that time.
    
                adjustedSat = sat * (daysRemaining / CLAIM_PHASE_DAYS) * 100%
                            = sat *  daysRemaining / CLAIM_PHASE_DAYS
            */
            return adjSatoshis * daysRemaining / CLAIM_PHASE_DAYS;
        }
    
        /**
         * @dev Calculates speed bonus for claiming earlier in the claim phase
         * @param claimedHearts Hearts claimed from adjusted BTC address balance Satoshis
         * @param daysRemaining Number of claim days remaining in claim phase
         * @return Speed bonus in Hearts
         */
        function _calcSpeedBonus(uint256 claimedHearts, uint256 daysRemaining)
            private
            pure
            returns (uint256)
        {
            /*
                Only valid from CLAIM_PHASE_DAYS to 1, and only used during that time.
                Speed bonus is 20% ... 0% inclusive.
    
                bonusHearts = claimedHearts  * ((daysRemaining - 1)  /  (CLAIM_PHASE_DAYS - 1)) * 20%
                            = claimedHearts  * ((daysRemaining - 1)  /  (CLAIM_PHASE_DAYS - 1)) * 20/100
                            = claimedHearts  * ((daysRemaining - 1)  /  (CLAIM_PHASE_DAYS - 1)) / 5
                            = claimedHearts  *  (daysRemaining - 1)  / ((CLAIM_PHASE_DAYS - 1)  * 5)
            */
            return claimedHearts * (daysRemaining - 1) / ((CLAIM_PHASE_DAYS - 1) * 5);
        }
    }
    
    contract TransformableToken is UTXORedeemableToken {
        /**
         * @dev PUBLIC FACING: Enter the tranform lobby for the current round
         * @param referrerAddr Eth address of referring user (optional; 0x0 for no referrer)
         */
        function xfLobbyEnter(address referrerAddr)
            external
            payable
        {
            uint256 enterDay = _currentDay();
            require(enterDay < CLAIM_PHASE_END_DAY, "HEX: Lobbies have ended");
    
            uint256 rawAmount = msg.value;
            require(rawAmount != 0, "HEX: Amount required");
    
            XfLobbyQueueStore storage qRef = xfLobbyMembers[enterDay][msg.sender];
    
            uint256 entryIndex = qRef.tailIndex++;
    
            qRef.entries[entryIndex] = XfLobbyEntryStore(uint96(rawAmount), referrerAddr);
    
            xfLobby[enterDay] += rawAmount;
    
            _emitXfLobbyEnter(enterDay, entryIndex, rawAmount, referrerAddr);
        }
    
        /**
         * @dev PUBLIC FACING: Leave the transform lobby after the round is complete
         * @param enterDay Day number when the member entered
         * @param count Number of queued-enters to exit (optional; 0 for all)
         */
        function xfLobbyExit(uint256 enterDay, uint256 count)
            external
        {
            require(enterDay < _currentDay(), "HEX: Round is not complete");
    
            XfLobbyQueueStore storage qRef = xfLobbyMembers[enterDay][msg.sender];
    
            uint256 headIndex = qRef.headIndex;
            uint256 endIndex;
    
            if (count != 0) {
                require(count <= qRef.tailIndex - headIndex, "HEX: count invalid");
                endIndex = headIndex + count;
            } else {
                endIndex = qRef.tailIndex;
                require(headIndex < endIndex, "HEX: count invalid");
            }
    
            uint256 waasLobby = _waasLobby(enterDay);
            uint256 _xfLobby = xfLobby[enterDay];
            uint256 totalXfAmount = 0;
            uint256 originBonusHearts = 0;
    
            do {
                uint256 rawAmount = qRef.entries[headIndex].rawAmount;
                address referrerAddr = qRef.entries[headIndex].referrerAddr;
    
                delete qRef.entries[headIndex];
    
                uint256 xfAmount = waasLobby * rawAmount / _xfLobby;
    
                if (referrerAddr == address(0)) {
                    /* No referrer */
                    _emitXfLobbyExit(enterDay, headIndex, xfAmount, referrerAddr);
                } else {
                    /* Referral bonus of 10% of xfAmount to member */
                    uint256 referralBonusHearts = xfAmount / 10;
    
                    xfAmount += referralBonusHearts;
    
                    /* Then a cumulative referrer bonus of 20% to referrer */
                    uint256 referrerBonusHearts = xfAmount / 5;
    
                    if (referrerAddr == msg.sender) {
                        /* Self-referred */
                        xfAmount += referrerBonusHearts;
                        _emitXfLobbyExit(enterDay, headIndex, xfAmount, referrerAddr);
                    } else {
                        /* Referred by different address */
                        _emitXfLobbyExit(enterDay, headIndex, xfAmount, referrerAddr);
                        _mint(referrerAddr, referrerBonusHearts);
                    }
                    originBonusHearts += referralBonusHearts + referrerBonusHearts;
                }
    
                totalXfAmount += xfAmount;
            } while (++headIndex < endIndex);
    
            qRef.headIndex = uint40(headIndex);
    
            if (originBonusHearts != 0) {
                _mint(ORIGIN_ADDR, originBonusHearts);
            }
            if (totalXfAmount != 0) {
                _mint(msg.sender, totalXfAmount);
            }
        }
    
        /**
         * @dev PUBLIC FACING: Release any value that has been sent to the contract
         */
        function xfLobbyFlush()
            external
        {
            require(address(this).balance != 0, "HEX: No value");
    
            FLUSH_ADDR.transfer(address(this).balance);
        }
    
        /**
         * @dev PUBLIC FACING: External helper to return multiple values of xfLobby[] with
         * a single call
         * @param beginDay First day of data range
         * @param endDay Last day (non-inclusive) of data range
         * @return Fixed array of values
         */
        function xfLobbyRange(uint256 beginDay, uint256 endDay)
            external
            view
            returns (uint256[] memory list)
        {
            require(
                beginDay < endDay && endDay <= CLAIM_PHASE_END_DAY && endDay <= _currentDay(),
                "HEX: invalid range"
            );
    
            list = new uint256[](endDay - beginDay);
    
            uint256 src = beginDay;
            uint256 dst = 0;
            do {
                list[dst++] = uint256(xfLobby[src++]);
            } while (src < endDay);
    
            return list;
        }
    
        /**
         * @dev PUBLIC FACING: Return a current lobby member queue entry.
         * Only needed due to limitations of the standard ABI encoder.
         * @param memberAddr Eth address of the lobby member
         * @param entryId 49 bit compound value. Top 9 bits: enterDay, Bottom 40 bits: entryIndex
         * @return 1: Raw amount that was entered with; 2: Referring Eth addr (optional; 0x0 for no referrer)
         */
        function xfLobbyEntry(address memberAddr, uint256 entryId)
            external
            view
            returns (uint256 rawAmount, address referrerAddr)
        {
            uint256 enterDay = entryId >> XF_LOBBY_ENTRY_INDEX_SIZE;
            uint256 entryIndex = entryId & XF_LOBBY_ENTRY_INDEX_MASK;
    
            XfLobbyEntryStore storage entry = xfLobbyMembers[enterDay][memberAddr].entries[entryIndex];
    
            require(entry.rawAmount != 0, "HEX: Param invalid");
    
            return (entry.rawAmount, entry.referrerAddr);
        }
    
        /**
         * @dev PUBLIC FACING: Return the lobby days that a user is in with a single call
         * @param memberAddr Eth address of the user
         * @return Bit vector of lobby day numbers
         */
        function xfLobbyPendingDays(address memberAddr)
            external
            view
            returns (uint256[XF_LOBBY_DAY_WORDS] memory words)
        {
            uint256 day = _currentDay() + 1;
    
            if (day > CLAIM_PHASE_END_DAY) {
                day = CLAIM_PHASE_END_DAY;
            }
    
            while (day-- != 0) {
                if (xfLobbyMembers[day][memberAddr].tailIndex > xfLobbyMembers[day][memberAddr].headIndex) {
                    words[day >> 8] |= 1 << (day & 255);
                }
            }
    
            return words;
        }
    
        function _waasLobby(uint256 enterDay)
            private
            returns (uint256 waasLobby)
        {
            if (enterDay >= CLAIM_PHASE_START_DAY) {
                GlobalsCache memory g;
                GlobalsCache memory gSnapshot;
                _globalsLoad(g, gSnapshot);
    
                _dailyDataUpdateAuto(g);
    
                uint256 unclaimed = dailyData[enterDay].dayUnclaimedSatoshisTotal;
                waasLobby = unclaimed * HEARTS_PER_SATOSHI / CLAIM_PHASE_DAYS;
    
                _globalsSync(g, gSnapshot);
            } else {
                waasLobby = WAAS_LOBBY_SEED_HEARTS;
            }
            return waasLobby;
        }
    
        function _emitXfLobbyEnter(
            uint256 enterDay,
            uint256 entryIndex,
            uint256 rawAmount,
            address referrerAddr
        )
            private
        {
            emit XfLobbyEnter( // (auto-generated event)
                uint256(uint40(block.timestamp))
                    | (uint256(uint96(rawAmount)) << 40),
                msg.sender,
                (enterDay << XF_LOBBY_ENTRY_INDEX_SIZE) | entryIndex,
                referrerAddr
            );
        }
    
        function _emitXfLobbyExit(
            uint256 enterDay,
            uint256 entryIndex,
            uint256 xfAmount,
            address referrerAddr
        )
            private
        {
            emit XfLobbyExit( // (auto-generated event)
                uint256(uint40(block.timestamp))
                    | (uint256(uint72(xfAmount)) << 40),
                msg.sender,
                (enterDay << XF_LOBBY_ENTRY_INDEX_SIZE) | entryIndex,
                referrerAddr
            );
        }
    }
    
    contract HEX is TransformableToken {
        constructor()
            public
        {
            /* Initialize global shareRate to 1 */
            globals.shareRate = uint40(1 * SHARE_RATE_SCALE);
    
            /* Initialize dailyDataCount to skip pre-claim period */
            globals.dailyDataCount = uint16(PRE_CLAIM_DAYS);
    
            /* Add all Satoshis from UTXO snapshot to contract */
            globals.claimStats = _claimStatsEncode(
                0, // _claimedBtcAddrCount
                0, // _claimedSatoshisTotal
                FULL_SATOSHIS_TOTAL // _unclaimedSatoshisTotal
            );
        }
    
        function() external payable {}
    }

    File 2 of 2: HXY
    pragma solidity ^0.6.2;
    
    
    // SPDX-License-Identifier: MIT
    /*
     * @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 GSN 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.
     */
    contract Context {
        // Empty internal constructor, to prevent people from mistakenly deploying
        // an instance of this contract, which should be used via inheritance.
        constructor () internal { }
    
        function _msgSender() internal view virtual returns (address payable) {
            return msg.sender;
        }
    
        function _msgData() internal view virtual returns (bytes memory) {
            this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
            return msg.data;
        }
    }
    
    // SPDX-License-Identifier: MIT
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        /**
         * @dev Returns the amount of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
    
        /**
         * @dev Returns the amount of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
    
        /**
         * @dev Moves `amount` tokens from the caller's account to `recipient`.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transfer(address recipient, uint256 amount) 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 `amount` 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 amount) external returns (bool);
    
        /**
         * @dev Moves `amount` tokens from `sender` to `recipient` using the
         * allowance mechanism. `amount` is then deducted from the caller's
         * allowance.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    
        /**
         * @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);
    }
    
    // SPDX-License-Identifier: MIT
    /**
     * @dev Wrappers over Solidity's arithmetic operations with added overflow
     * checks.
     *
     * Arithmetic operations in Solidity wrap on overflow. This can easily result
     * in bugs, because programmers usually assume that an overflow raises an
     * error, which is the standard behavior in high level programming languages.
     * `SafeMath` restores this intuition by reverting the transaction when an
     * operation overflows.
     *
     * Using this library instead of the unchecked operations eliminates an entire
     * class of bugs, so it's recommended to use it always.
     */
    library SafeMath {
        /**
         * @dev Returns the addition of two unsigned integers, reverting on
         * overflow.
         *
         * Counterpart to Solidity's `+` operator.
         *
         * Requirements:
         * - Addition cannot overflow.
         */
        function add(uint256 a, uint256 b) internal pure returns (uint256) {
            uint256 c = a + b;
            require(c >= a, "SafeMath: addition overflow");
    
            return c;
        }
    
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting on
         * overflow (when the result is negative).
         *
         * Counterpart to Solidity's `-` operator.
         *
         * Requirements:
         * - Subtraction cannot overflow.
         */
        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
            return sub(a, b, "SafeMath: subtraction overflow");
        }
    
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
         * overflow (when the result is negative).
         *
         * Counterpart to Solidity's `-` operator.
         *
         * Requirements:
         * - Subtraction cannot overflow.
         */
        function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            require(b <= a, errorMessage);
            uint256 c = a - b;
    
            return c;
        }
    
        /**
         * @dev Returns the multiplication of two unsigned integers, reverting on
         * overflow.
         *
         * Counterpart to Solidity's `*` operator.
         *
         * Requirements:
         * - Multiplication cannot overflow.
         */
        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) {
                return 0;
            }
    
            uint256 c = a * b;
            require(c / a == b, "SafeMath: multiplication overflow");
    
            return c;
        }
    
        /**
         * @dev Returns the integer division of two unsigned integers. Reverts on
         * division by zero. The result is rounded towards zero.
         *
         * Counterpart to Solidity's `/` operator. Note: this function uses a
         * `revert` opcode (which leaves remaining gas untouched) while Solidity
         * uses an invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         */
        function div(uint256 a, uint256 b) internal pure returns (uint256) {
            return div(a, b, "SafeMath: division by zero");
        }
    
        /**
         * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
         * division by zero. The result is rounded towards zero.
         *
         * Counterpart to Solidity's `/` operator. Note: this function uses a
         * `revert` opcode (which leaves remaining gas untouched) while Solidity
         * uses an invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         */
        function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            // Solidity only automatically asserts when dividing by 0
            require(b > 0, errorMessage);
            uint256 c = a / b;
            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    
            return c;
        }
    
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * Reverts when dividing by zero.
         *
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
         * opcode (which leaves remaining gas untouched) while Solidity uses an
         * invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         */
        function mod(uint256 a, uint256 b) internal pure returns (uint256) {
            return mod(a, b, "SafeMath: modulo by zero");
        }
    
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * Reverts with custom message when dividing by zero.
         *
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
         * opcode (which leaves remaining gas untouched) while Solidity uses an
         * invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         * - The divisor cannot be zero.
         */
        function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            require(b != 0, errorMessage);
            return a % b;
        }
    }
    
    // SPDX-License-Identifier: MIT
    /**
     * @dev Collection of functions related to the address type
     */
    library Address {
        /**
         * @dev Returns true if `account` is a contract.
         *
         * [IMPORTANT]
         * ====
         * It is unsafe to assume that an address for which this function returns
         * false is an externally-owned account (EOA) and not a contract.
         *
         * Among others, `isContract` will return false for the following
         * types of addresses:
         *
         *  - an externally-owned account
         *  - a contract in construction
         *  - an address where a contract will be created
         *  - an address where a contract lived, but was destroyed
         * ====
         */
        function isContract(address account) internal view returns (bool) {
            // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
            // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
            // for accounts without code, i.e. `keccak256('')`
            bytes32 codehash;
            bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
            // solhint-disable-next-line no-inline-assembly
            assembly { codehash := extcodehash(account) }
            return (codehash != accountHash && codehash != 0x0);
        }
    
        /**
         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
         * `recipient`, forwarding all available gas and reverting on errors.
         *
         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
         * of certain opcodes, possibly making contracts go over the 2300 gas limit
         * imposed by `transfer`, making them unable to receive funds via
         * `transfer`. {sendValue} removes this limitation.
         *
         * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
         *
         * IMPORTANT: because control is transferred to `recipient`, care must be
         * taken to not create reentrancy vulnerabilities. Consider using
         * {ReentrancyGuard} or the
         * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
         */
        function sendValue(address payable recipient, uint256 amount) internal {
            require(address(this).balance >= amount, "Address: insufficient balance");
    
            // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
            (bool success, ) = recipient.call{ value: amount }("");
            require(success, "Address: unable to send value, recipient may have reverted");
        }
    }
    
    /**
     * @dev Implementation of the {IERC20} interface.
     *
     * This implementation is agnostic to the way tokens are created. This means
     * that a supply mechanism has to be added in a derived contract using {_mint}.
     * For a generic mechanism see {ERC20MinterPauser}.
     *
     * TIP: For a detailed writeup see our guide
     * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
     * to implement supply mechanisms].
     *
     * We have followed general OpenZeppelin guidelines: functions revert instead
     * of returning `false` on failure. This behavior is nonetheless conventional
     * and does not conflict with the expectations of ERC20 applications.
     *
     * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
     * This allows applications to reconstruct the allowance for all accounts just
     * by listening to said events. Other implementations of the EIP may not emit
     * these events, as it isn't required by the specification.
     *
     * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
     * functions have been added to mitigate the well-known issues around setting
     * allowances. See {IERC20-approve}.
     */
    contract ERC20 is Context, IERC20 {
        using SafeMath for uint256;
        using Address for address;
    
        mapping (address => uint256) internal _balances;
    
        mapping (address => mapping (address => uint256)) private _allowances;
    
        uint256 internal _totalSupply;
    
        string private _name;
        string private _symbol;
        uint8 private _decimals;
    
        uint256 private _cap;
    
        /**
         * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
         * a default value of 18.
         *
         * To select a different value for {decimals}, use {_setupDecimals}.
         *
         * All three of these values are immutable: they can only be set once during
         * construction.
         */
        constructor (string memory name, string memory symbol) public {
            _name = name;
            _symbol = symbol;
            _decimals = 18;
        }
    
        /**
         * @dev Returns the name of the token.
         */
        function name() public view returns (string memory) {
            return _name;
        }
    
        /**
         * @dev Returns the symbol of the token, usually a shorter version of the
         * name.
         */
        function symbol() public view returns (string memory) {
            return _symbol;
        }
    
        /**
         * @dev Returns the number of decimals used to get its user representation.
         * For example, if `decimals` equals `2`, a balance of `505` tokens should
         * be displayed to a user as `5,05` (`505 / 10 ** 2`).
         *
         * Tokens usually opt for a value of 18, imitating the relationship between
         * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
         * called.
         *
         * NOTE: This information is only used for _display_ purposes: it in
         * no way affects any of the arithmetic of the contract, including
         * {IERC20-balanceOf} and {IERC20-transfer}.
         */
        function decimals() public view returns (uint8) {
            return _decimals;
        }
    
        /**
         * @dev See {IERC20-totalSupply}.
         */
        function totalSupply() public view override returns (uint256) {
            return _totalSupply;
        }
    
        /**
         * @dev See {IERC20-balanceOf}.
         */
        function balanceOf(address account) public view virtual override returns (uint256) {
            return _balances[account];
        }
    
        /**
         * @dev See {IERC20-transfer}.
         *
         * Requirements:
         *
         * - `recipient` cannot be the zero address.
         * - the caller must have a balance of at least `amount`.
         */
        function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
            require(recipient != address(this), "ERC20: Cannot transfer to self");
            _transfer(_msgSender(), recipient, amount);
            return true;
        }
    
        /**
         * @dev See {IERC20-allowance}.
         */
        function allowance(address owner, address spender) public view virtual override returns (uint256) {
            return _allowances[owner][spender];
        }
    
        /**
         * @dev See {IERC20-approve}.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         */
        function approve(address spender, uint256 amount) public virtual override returns (bool) {
            _approve(_msgSender(), spender, amount);
            return true;
        }
    
        /**
         * @dev See {IERC20-transferFrom}.
         *
         * Emits an {Approval} event indicating the updated allowance. This is not
         * required by the EIP. See the note at the beginning of {ERC20};
         *
         * Requirements:
         * - `sender` and `recipient` cannot be the zero address.
         * - `sender` must have a balance of at least `amount`.
         * - the caller must have allowance for ``sender``'s tokens of at least
         * `amount`.
         */
        function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
            _transfer(sender, recipient, amount);
            _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
            return true;
        }
    
        /**
         * @dev Atomically increases the allowance granted to `spender` by the caller.
         *
         * This is an alternative to {approve} that can be used as a mitigation for
         * problems described in {IERC20-approve}.
         *
         * Emits an {Approval} event indicating the updated allowance.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         */
        function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
            _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
            return true;
        }
    
        /**
         * @dev Atomically decreases the allowance granted to `spender` by the caller.
         *
         * This is an alternative to {approve} that can be used as a mitigation for
         * problems described in {IERC20-approve}.
         *
         * Emits an {Approval} event indicating the updated allowance.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         * - `spender` must have allowance for the caller of at least
         * `subtractedValue`.
         */
        function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
            _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
            return true;
        }
    
        /**
         * @dev Moves tokens `amount` from `sender` to `recipient`.
         *
         * This is internal function is equivalent to {transfer}, and can be used to
         * e.g. implement automatic token fees, slashing mechanisms, etc.S
         *
         * Emits a {Transfer} event.
         *
         * Requirements:
         *
         * - `sender` cannot be the zero address.
         * - `recipient` cannot be the zero address.
         * - `sender` must have a balance of at least `amount`.
         */
        function _transfer(address sender, address recipient, uint256 amount) internal virtual {
            require(sender != address(0), "ERC20: transfer from the zero address");
            require(recipient != address(0), "ERC20: transfer to the zero address");
    
            _beforeTokenTransfer(sender, recipient, amount);
    
            _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
            _balances[recipient] = _balances[recipient].add(amount);
            emit Transfer(sender, recipient, amount);
        }
    
        /** @dev Creates `amount` tokens and assigns them to `account`, increasing
         * the total supply.
         *
         * Emits a {Transfer} event with `from` set to the zero address.
         *
         * Requirements
         *
         * - `to` cannot be the zero address.
         */
        function _mint(address account, uint256 amount) internal virtual {
            require(account != address(0), "ERC20: mint to the zero address");
    
            _beforeTokenTransfer(address(0), account, amount);
    
            _totalSupply = _totalSupply.add(amount);
            _balances[account] = _balances[account].add(amount);
            emit Transfer(address(0), account, amount);
        }
    
        /**
         * @dev Destroys `amount` tokens from `account`, reducing the
         * total supply.
         *
         * Emits a {Transfer} event with `to` set to the zero address.
         *
         * Requirements
         *
         * - `account` cannot be the zero address.
         * - `account` must have at least `amount` tokens.
         */
        function _burn(address account, uint256 amount) internal virtual {
            require(account != address(0), "ERC20: burn from the zero address");
    
            _beforeTokenTransfer(account, address(0), amount);
    
            _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
            _totalSupply = _totalSupply.sub(amount);
            emit Transfer(account, address(0), amount);
        }
    
        /**
         * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
         *
         * This is internal function is equivalent to `approve`, and can be used to
         * e.g. set automatic allowances for certain subsystems, etc.
         *
         * Emits an {Approval} event.
         *
         * Requirements:
         *
         * - `owner` cannot be the zero address.
         * - `spender` cannot be the zero address.
         */
        function _approve(address owner, address spender, uint256 amount) internal virtual {
            require(owner != address(0), "ERC20: approve from the zero address");
            require(spender != address(0), "ERC20: approve to the zero address");
    
            _allowances[owner][spender] = amount;
            emit Approval(owner, spender, amount);
        }
    
        /**
         * @dev Sets {decimals} to a value other than the default one of 18.
         *
         * WARNING: This function should only be called from the constructor. Most
         * applications that interact with token contracts will not expect
         * {decimals} to ever change, and may work incorrectly if it does.
         */
        function _setupDecimals(uint8 decimals_) internal {
            _decimals = decimals_;
        }
    
        /**
         * @dev Hook that is called before any transfer of tokens. This includes
         * minting and burning.
         *
         * Calling conditions:
         *
         * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
         * will be to transferred to `to`.
         * - when `from` is zero, `amount` tokens will be minted for `to`.
         * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
         * - `from` and `to` are never both zero.
         *
         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
         */
        function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
    }
    
    // SPDX-License-Identifier: MIT
    /**
     * @dev Library for managing
     * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
     * types.
     *
     * Sets have the following properties:
     *
     * - Elements are added, removed, and checked for existence in constant time
     * (O(1)).
     * - Elements are enumerated in O(n). No guarantees are made on the ordering.
     *
     * ```
     * contract Example {
     *     // Add the library methods
     *     using EnumerableSet for EnumerableSet.AddressSet;
     *
     *     // Declare a set state variable
     *     EnumerableSet.AddressSet private mySet;
     * }
     * ```
     *
     * As of v3.0.0, only sets of type `address` (`AddressSet`) and `uint256`
     * (`UintSet`) are supported.
     */
    library EnumerableSet {
        // To implement this library for multiple types with as little code
        // repetition as possible, we write it in terms of a generic Set type with
        // bytes32 values.
        // The Set implementation uses private functions, and user-facing
        // implementations (such as AddressSet) are just wrappers around the
        // underlying Set.
        // This means that we can only create new EnumerableSets for types that fit
        // in bytes32.
    
        struct Set {
            // Storage of set values
            bytes32[] _values;
    
            // Position of the value in the `values` array, plus 1 because index 0
            // means a value is not in the set.
            mapping (bytes32 => uint256) _indexes;
        }
    
        /**
         * @dev Add a value to a set. O(1).
         *
         * Returns true if the value was added to the set, that is if it was not
         * already present.
         */
        function _add(Set storage set, bytes32 value) private returns (bool) {
            if (!_contains(set, value)) {
                set._values.push(value);
                // The value is stored at length-1, but we add 1 to all indexes
                // and use 0 as a sentinel value
                set._indexes[value] = set._values.length;
                return true;
            } else {
                return false;
            }
        }
    
        /**
         * @dev Removes a value from a set. O(1).
         *
         * Returns true if the value was removed from the set, that is if it was
         * present.
         */
        function _remove(Set storage set, bytes32 value) private returns (bool) {
            // We read and store the value's index to prevent multiple reads from the same storage slot
            uint256 valueIndex = set._indexes[value];
    
            if (valueIndex != 0) { // Equivalent to contains(set, value)
                // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
                // the array, and then remove the last element (sometimes called as 'swap and pop').
                // This modifies the order of the array, as noted in {at}.
    
                uint256 toDeleteIndex = valueIndex - 1;
                uint256 lastIndex = set._values.length - 1;
    
                // When the value to delete is the last one, the swap operation is unnecessary. However, since this occurs
                // so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement.
    
                bytes32 lastvalue = set._values[lastIndex];
    
                // Move the last value to the index where the value to delete is
                set._values[toDeleteIndex] = lastvalue;
                // Update the index for the moved value
                set._indexes[lastvalue] = toDeleteIndex + 1; // All indexes are 1-based
    
                // Delete the slot where the moved value was stored
                set._values.pop();
    
                // Delete the index for the deleted slot
                delete set._indexes[value];
    
                return true;
            } else {
                return false;
            }
        }
    
        /**
         * @dev Returns true if the value is in the set. O(1).
         */
        function _contains(Set storage set, bytes32 value) private view returns (bool) {
            return set._indexes[value] != 0;
        }
    
        /**
         * @dev Returns the number of values on the set. O(1).
         */
        function _length(Set storage set) private view returns (uint256) {
            return set._values.length;
        }
    
       /**
        * @dev Returns the value stored at position `index` in the set. O(1).
        *
        * Note that there are no guarantees on the ordering of values inside the
        * array, and it may change when more values are added or removed.
        *
        * Requirements:
        *
        * - `index` must be strictly less than {length}.
        */
        function _at(Set storage set, uint256 index) private view returns (bytes32) {
            require(set._values.length > index, "EnumerableSet: index out of bounds");
            return set._values[index];
        }
    
        // AddressSet
    
        struct AddressSet {
            Set _inner;
        }
    
        /**
         * @dev Add a value to a set. O(1).
         *
         * Returns true if the value was added to the set, that is if it was not
         * already present.
         */
        function add(AddressSet storage set, address value) internal returns (bool) {
            return _add(set._inner, bytes32(uint256(value)));
        }
    
        /**
         * @dev Removes a value from a set. O(1).
         *
         * Returns true if the value was removed from the set, that is if it was
         * present.
         */
        function remove(AddressSet storage set, address value) internal returns (bool) {
            return _remove(set._inner, bytes32(uint256(value)));
        }
    
        /**
         * @dev Returns true if the value is in the set. O(1).
         */
        function contains(AddressSet storage set, address value) internal view returns (bool) {
            return _contains(set._inner, bytes32(uint256(value)));
        }
    
        /**
         * @dev Returns the number of values in the set. O(1).
         */
        function length(AddressSet storage set) internal view returns (uint256) {
            return _length(set._inner);
        }
    
       /**
        * @dev Returns the value stored at position `index` in the set. O(1).
        *
        * Note that there are no guarantees on the ordering of values inside the
        * array, and it may change when more values are added or removed.
        *
        * Requirements:
        *
        * - `index` must be strictly less than {length}.
        */
        function at(AddressSet storage set, uint256 index) internal view returns (address) {
            return address(uint256(_at(set._inner, index)));
        }
    
    
        // UintSet
    
        struct UintSet {
            Set _inner;
        }
    
        /**
         * @dev Add a value to a set. O(1).
         *
         * Returns true if the value was added to the set, that is if it was not
         * already present.
         */
        function add(UintSet storage set, uint256 value) internal returns (bool) {
            return _add(set._inner, bytes32(value));
        }
    
        /**
         * @dev Removes a value from a set. O(1).
         *
         * Returns true if the value was removed from the set, that is if it was
         * present.
         */
        function remove(UintSet storage set, uint256 value) internal returns (bool) {
            return _remove(set._inner, bytes32(value));
        }
    
        /**
         * @dev Returns true if the value is in the set. O(1).
         */
        function contains(UintSet storage set, uint256 value) internal view returns (bool) {
            return _contains(set._inner, bytes32(value));
        }
    
        /**
         * @dev Returns the number of values on the set. O(1).
         */
        function length(UintSet storage set) internal view returns (uint256) {
            return _length(set._inner);
        }
    
       /**
        * @dev Returns the value stored at position `index` in the set. O(1).
        *
        * Note that there are no guarantees on the ordering of values inside the
        * array, and it may change when more values are added or removed.
        *
        * Requirements:
        *
        * - `index` must be strictly less than {length}.
        */
        function at(UintSet storage set, uint256 index) internal view returns (uint256) {
            return uint256(_at(set._inner, index));
        }
    }
    
    // SPDX-License-Identifier: MIT
    /**
     * @dev Contract module that allows children to implement role-based access
     * control mechanisms.
     *
     * Roles are referred to by their `bytes32` identifier. These should be exposed
     * in the external API and be unique. The best way to achieve this is by
     * using `public constant` hash digests:
     *
     * ```
     * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
     * ```
     *
     * Roles can be used to represent a set of permissions. To restrict access to a
     * function call, use {hasRole}:
     *
     * ```
     * function foo() public {
     *     require(hasRole(MY_ROLE, msg.sender));
     *     ...
     * }
     * ```
     *
     * Roles can be granted and revoked dynamically via the {grantRole} and
     * {revokeRole} functions. Each role has an associated admin role, and only
     * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
     *
     * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
     * that only accounts with this role will be able to grant or revoke other
     * roles. More complex role relationships can be created by using
     * {_setRoleAdmin}.
     *
     * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
     * grant and revoke this role. Extra precautions should be taken to secure
     * accounts that have been granted it.
     */
    abstract contract AccessControl is Context {
        using EnumerableSet for EnumerableSet.AddressSet;
        using Address for address;
    
        struct RoleData {
            EnumerableSet.AddressSet members;
            bytes32 adminRole;
        }
    
        mapping (bytes32 => RoleData) private _roles;
    
        bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
    
        /**
         * @dev Emitted when `account` is granted `role`.
         *
         * `sender` is the account that originated the contract call, an admin role
         * bearer except when using {_setupRole}.
         */
        event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
    
        /**
         * @dev Emitted when `account` is revoked `role`.
         *
         * `sender` is the account that originated the contract call:
         *   - if using `revokeRole`, it is the admin role bearer
         *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
         */
        event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
    
        /**
         * @dev Returns `true` if `account` has been granted `role`.
         */
        function hasRole(bytes32 role, address account) public view returns (bool) {
            return _roles[role].members.contains(account);
        }
    
        /**
         * @dev Returns the number of accounts that have `role`. Can be used
         * together with {getRoleMember} to enumerate all bearers of a role.
         */
        function getRoleMemberCount(bytes32 role) public view returns (uint256) {
            return _roles[role].members.length();
        }
    
        /**
         * @dev Returns one of the accounts that have `role`. `index` must be a
         * value between 0 and {getRoleMemberCount}, non-inclusive.
         *
         * Role bearers are not sorted in any particular way, and their ordering may
         * change at any point.
         *
         * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
         * you perform all queries on the same block. See the following
         * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
         * for more information.
         */
        function getRoleMember(bytes32 role, uint256 index) public view returns (address) {
            return _roles[role].members.at(index);
        }
    
        /**
         * @dev Returns the admin role that controls `role`. See {grantRole} and
         * {revokeRole}.
         *
         * To change a role's admin, use {_setRoleAdmin}.
         */
        function getRoleAdmin(bytes32 role) public view returns (bytes32) {
            return _roles[role].adminRole;
        }
    
        /**
         * @dev Grants `role` to `account`.
         *
         * If `account` had not been already granted `role`, emits a {RoleGranted}
         * event.
         *
         * Requirements:
         *
         * - the caller must have ``role``'s admin role.
         */
        function grantRole(bytes32 role, address account) public virtual {
            require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to grant");
    
            _grantRole(role, account);
        }
    
        /**
         * @dev Revokes `role` from `account`.
         *
         * If `account` had been granted `role`, emits a {RoleRevoked} event.
         *
         * Requirements:
         *
         * - the caller must have ``role``'s admin role.
         */
        function revokeRole(bytes32 role, address account) public virtual {
            require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to revoke");
    
            _revokeRole(role, account);
        }
    
        /**
         * @dev Revokes `role` from the calling account.
         *
         * Roles are often managed via {grantRole} and {revokeRole}: this function's
         * purpose is to provide a mechanism for accounts to lose their privileges
         * if they are compromised (such as when a trusted device is misplaced).
         *
         * If the calling account had been granted `role`, emits a {RoleRevoked}
         * event.
         *
         * Requirements:
         *
         * - the caller must be `account`.
         */
        function renounceRole(bytes32 role, address account) public virtual {
            require(account == _msgSender(), "AccessControl: can only renounce roles for self");
    
            _revokeRole(role, account);
        }
    
        /**
         * @dev Grants `role` to `account`.
         *
         * If `account` had not been already granted `role`, emits a {RoleGranted}
         * event. Note that unlike {grantRole}, this function doesn't perform any
         * checks on the calling account.
         *
         * [WARNING]
         * ====
         * This function should only be called from the constructor when setting
         * up the initial roles for the system.
         *
         * Using this function in any other way is effectively circumventing the admin
         * system imposed by {AccessControl}.
         * ====
         */
        function _setupRole(bytes32 role, address account) internal virtual {
            _grantRole(role, account);
        }
    
        /**
         * @dev Sets `adminRole` as ``role``'s admin role.
         */
        function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
            _roles[role].adminRole = adminRole;
        }
    
        function _grantRole(bytes32 role, address account) private {
            if (_roles[role].members.add(account)) {
                emit RoleGranted(role, account, _msgSender());
            }
        }
    
        function _revokeRole(bytes32 role, address account) private {
            if (_roles[role].members.remove(account)) {
                emit RoleRevoked(role, account, _msgSender());
            }
        }
    }
    
    // SPDX-License-Identifier: MIT
    /**
     * @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 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].
     */
    contract ReentrancyGuard {
        bool private _notEntered;
    
        constructor () internal {
            // Storing an initial 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 percetange 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.
            _notEntered = true;
        }
    
        /**
         * @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 make it call a
         * `private` function that does the actual work.
         */
        modifier nonReentrant() {
            // On the first call to nonReentrant, _notEntered will be true
            require(_notEntered, "ReentrancyGuard: reentrant call");
    
            // Any calls to nonReentrant after this point will fail
            _notEntered = false;
    
            _;
    
            // By storing the original value once again, a refund is triggered (see
            // https://eips.ethereum.org/EIPS/eip-2200)
            _notEntered = true;
        }
    }
    
    library WhitelistLib {
        struct AllowedAddress {
            bool tradeable;
            uint256 lockPeriod;
            uint256 dailyLimit;
            uint256 dailyLimitToday;
            uint256 addedAt;
            uint256 recordTime;
        }
    }
    
    contract HexWhitelist is AccessControl, ReentrancyGuard {
        bytes32 public constant DEPLOYER_ROLE = keccak256("DEPLOYER_ROLE");
    
        uint256 public constant SECONDS_IN_DAY = 86400;
    
        using WhitelistLib for WhitelistLib.AllowedAddress;
    
        mapping(address => WhitelistLib.AllowedAddress) internal exchanges;
        mapping(address => WhitelistLib.AllowedAddress) internal dapps;
        mapping(address => WhitelistLib.AllowedAddress) internal referrals;
    
        uint256 internal whitelistRecordTime;
    
        modifier onlyAdminOrDeployerRole() {
            bool hasAdminRole = hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
            bool hasDeployerRole = hasRole(DEPLOYER_ROLE, _msgSender());
            require(hasAdminRole || hasDeployerRole, "Must have admin or deployer role");
            _;
        }
    
        constructor (address _adminAddress) public {
            _setupRole(DEPLOYER_ROLE, _msgSender());
            _setupRole(DEFAULT_ADMIN_ROLE, _adminAddress);
    
            whitelistRecordTime = SafeMath.add(block.timestamp, SafeMath.mul(1, SECONDS_IN_DAY));
        }
        function registerExchangeTradeable(address _address, uint256 dailyLimit) public onlyAdminOrDeployerRole {
            _registerExchange(_address, true, 0, dailyLimit);
        }
    
        function registerDappTradeable(address _address, uint256 dailyLimit) public onlyAdminOrDeployerRole {
            _registerDapp(_address, true, 0, dailyLimit);
        }
    
        function registerReferralTradeable(address _address, uint256 dailyLimit) public onlyAdminOrDeployerRole {
            _registerReferral(_address, true, 0, dailyLimit);
        }
    
        function registerExchangeNonTradeable(address _address, uint256 dailyLimit, uint256 lockPeriod) public onlyAdminOrDeployerRole {
            _registerExchange(_address, false, lockPeriod, dailyLimit);
        }
    
        function registerDappNonTradeable(address _address, uint256 dailyLimit, uint256 lockPeriod) public onlyAdminOrDeployerRole {
            _registerDapp(_address, false, lockPeriod, dailyLimit);
        }
    
        function registerReferralNonTradeable(address _address, uint256 dailyLimit, uint256 lockPeriod) public onlyAdminOrDeployerRole {
            _registerReferral(_address, false, lockPeriod, dailyLimit);
        }
    
    
        function unregisterExchange(address _address) public onlyAdminOrDeployerRole {
            delete exchanges[_address];
        }
    
        function unregisterDapp(address _address) public onlyAdminOrDeployerRole {
            delete dapps[_address];
        }
    
        function unregisterReferral(address _address) public onlyAdminOrDeployerRole {
            delete referrals[_address];
        }
    
        function setExchangepDailyLimit(address _address, uint256 _dailyLimit) public onlyAdminOrDeployerRole {
            exchanges[_address].dailyLimit = _dailyLimit;
        }
    
        function setDappDailyLimit(address _address, uint256 _dailyLimit) public onlyAdminOrDeployerRole {
            dapps[_address].dailyLimit = _dailyLimit;
        }
    
        function setReferralDailyLimit(address _address, uint256 _dailyLimit) public onlyAdminOrDeployerRole {
            referrals[_address].dailyLimit = _dailyLimit;
        }
    
        function setExchangeLockPeriod(address _address, uint256 _lockPeriod) public onlyAdminOrDeployerRole {
            require(!getExchangeTradeable(_address), "cannot set lock period to tradeable address");
            exchanges[_address].lockPeriod = _lockPeriod;
        }
    
        function setDappLockPeriod(address _address, uint256 _lockPeriod) public onlyAdminOrDeployerRole {
            require(!getExchangeTradeable(_address), "cannot set lock period to tradeable address");
            dapps[_address].lockPeriod = _lockPeriod;
        }
    
        function setReferralLockPeriod(address _address, uint256 _lockPeriod) public onlyAdminOrDeployerRole {
            require(!getExchangeTradeable(_address), "cannot set lock period to tradeable address");
            dapps[_address].lockPeriod = _lockPeriod;
        }
    
        function addToExchangeDailyLimit(address _address, uint256 amount) public {
            if (exchanges[_address].dailyLimit > 0) {
                if (isNewDayStarted(exchanges[_address].recordTime)) {
                    exchanges[_address].dailyLimitToday = 0;
                    exchanges[_address].recordTime = getNewRecordTime();
                }
    
                uint256 limitToday = dapps[_address].dailyLimitToday;
                require(SafeMath.add(limitToday, amount) < exchanges[_address].dailyLimit, "daily limit exceeded");
    
                exchanges[_address].dailyLimitToday = SafeMath.add(limitToday, amount);
            }
        }
    
        function addToDappDailyLimit(address _address, uint256 amount) public {
            if (dapps[_address].dailyLimit > 0) {
                if (isNewDayStarted(dapps[_address].recordTime)) {
                    dapps[_address].dailyLimitToday = 0;
                    dapps[_address].recordTime = getNewRecordTime();
                }
    
                uint256 limitToday = dapps[_address].dailyLimitToday;
                require(SafeMath.add(limitToday, amount) < dapps[_address].dailyLimit, "daily limit exceeded");
    
                dapps[_address].dailyLimitToday = SafeMath.add(limitToday, amount);
            }
        }
    
        function addToReferralDailyLimit(address _address, uint256 amount) public {
            if (referrals[_address].dailyLimit > 0) {
                if (isNewDayStarted(referrals[_address].recordTime)) {
                    referrals[_address].dailyLimitToday = 0;
                    referrals[_address].recordTime = getNewRecordTime();
                }
    
                uint256 limitToday = referrals[_address].dailyLimitToday;
                require(SafeMath.add(limitToday, amount) < referrals[_address].dailyLimit, "daily limit exceeded");
    
                referrals[_address].dailyLimitToday = SafeMath.add(limitToday, amount);
            }
        }
    
    
        function isRegisteredDapp(address _address) public view returns (bool) {
            return (dapps[_address].addedAt != 0) ? true : false;
        }
    
        function isRegisteredReferral(address _address) public view returns (bool) {
            if (dapps[_address].addedAt != 0) {
                return true;
            } else {
                return false;
            }
        }
    
        function isRegisteredDappOrReferral(address executionAddress) public view returns (bool) {
            if (isRegisteredDapp(executionAddress) || isRegisteredReferral(executionAddress)) {
                return true;
            } else {
                return false;
            }
        }
    
        function isRegisteredExchange(address _address) public view returns (bool) {
            if (exchanges[_address].addedAt != 0) {
                return true;
            } else {
                return false;
            }
        }
    
        function getExchangeTradeable(address _address) public view returns (bool) {
            return exchanges[_address].tradeable;
        }
    
        function getDappTradeable(address _address) public view returns (bool) {
            return dapps[_address].tradeable;
        }
    
        function getReferralTradeable(address _address) public view returns (bool) {
            return referrals[_address].tradeable;
        }
    
        function getDappOrReferralTradeable(address _address) public view returns (bool) {
            if (isRegisteredDapp(_address)) {
                return dapps[_address].tradeable;
            } else {
                return referrals[_address].tradeable;
            }
        }
    
        function getExchangeLockPeriod(address _address) public view returns (uint256) {
            return exchanges[_address].lockPeriod;
        }
    
        function getDappLockPeriod(address _address) public view returns (uint256) {
            return dapps[_address].lockPeriod;
        }
    
        function getReferralLockPeriod(address _address) public view returns (uint256) {
            return referrals[_address].lockPeriod;
        }
    
        function getDappOrReferralLockPeriod(address _address) public view returns (uint256) {
            if (isRegisteredDapp(_address)) {
                return dapps[_address].lockPeriod;
            } else {
                return referrals[_address].lockPeriod;
            }
        }
    
        function getDappDailyLimit(address _address) public view returns (uint256) {
            return dapps[_address].dailyLimit;
        }
    
        function getReferralDailyLimit(address _address) public view returns (uint256) {
            return referrals[_address].dailyLimit;
        }
    
        function getDappOrReferralDailyLimit(address _address) public view returns (uint256) {
            if (isRegisteredDapp(_address)) {
                return dapps[_address].dailyLimit;
            } else {
                return referrals[_address].dailyLimit;
            }
        }
        function getExchangeTodayMinted(address _address) public view returns (uint256) {
            return exchanges[_address].dailyLimitToday;
        }
    
        function getDappTodayMinted(address _address) public view returns (uint256) {
            return dapps[_address].dailyLimitToday;
        }
    
        function getReferralTodayMinted(address _address) public view returns (uint256) {
            return referrals[_address].dailyLimitToday;
        }
    
        function getExchangeRecordTimed(address _address) public view returns (uint256) {
            return exchanges[_address].recordTime;
        }
    
        function getDappRecordTimed(address _address) public view returns (uint256) {
            return dapps[_address].recordTime;
        }
    
        function getReferralRecordTimed(address _address) public view returns (uint256) {
            return referrals[_address].recordTime;
        }
    
        function getNewRecordTime() internal view returns (uint256) {
            return SafeMath.add(block.timestamp, SafeMath.mul(1, SECONDS_IN_DAY));
        }
    
        function isNewDayStarted(uint256 oldRecordTime) internal view returns (bool) {
            return block.timestamp > oldRecordTime ? true : false;
        }
    
        function _registerExchange(address _address, bool tradeable, uint256 lockPeriod, uint256 dailyLimit) internal
        {
            require(!isRegisteredDappOrReferral(_address), "address already registered as dapp or referral");
            require(!isRegisteredExchange(_address), "exchange already registered");
            exchanges[_address] = WhitelistLib.AllowedAddress({
                tradeable: tradeable,
                lockPeriod: lockPeriod,
                dailyLimit: dailyLimit,
                dailyLimitToday: 0,
                addedAt: block.timestamp,
                recordTime: getNewRecordTime()
                });
        }
    
        function _registerDapp(address _address, bool tradeable, uint256 lockPeriod, uint256 dailyLimit) internal
        {
            require(!isRegisteredExchange(_address) && !isRegisteredReferral(_address), "address already registered as exchange or referral");
            require(!isRegisteredDapp(_address), "address already registered");
            dapps[_address] = WhitelistLib.AllowedAddress({
                tradeable: tradeable,
                lockPeriod: lockPeriod,
                dailyLimit: dailyLimit,
                dailyLimitToday: 0,
                addedAt: block.timestamp,
                recordTime: getNewRecordTime()
                });
        }
    
        function _registerReferral(address _address, bool tradeable, uint256 lockPeriod, uint256 dailyLimit) internal
        {
            require(!isRegisteredExchange(_address) && !isRegisteredDapp(_address), "address already registered as exchange or referral");
            require(!isRegisteredReferral(_address), "address already registered");
            referrals[_address] = WhitelistLib.AllowedAddress({
                tradeable: tradeable,
                lockPeriod: lockPeriod,
                dailyLimit: dailyLimit,
                dailyLimitToday: 0,
                addedAt: block.timestamp,
                recordTime: getNewRecordTime()
                });
        }
    }
    
    contract HexMoneyInternal is AccessControl, ReentrancyGuard {
        bytes32 public constant DEPLOYER_ROLE = keccak256("DEPLOYER_ROLE");
    
        // production
        uint256 public constant SECONDS_IN_DAY = 86400;
    
        HexWhitelist internal whitelist;
    
        modifier onlyAdminOrDeployerRole() {
            bool hasAdminRole = hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
            bool hasDeployerRole = hasRole(DEPLOYER_ROLE, _msgSender());
            require(hasAdminRole || hasDeployerRole, "Must have admin or deployer role");
            _;
        }
    
        function getWhitelistAddress() public view returns (address) {
            return address(whitelist);
        }
    
    }
    
    /**
     * @dev Extension of {ERC20} that adds a cap to the supply of tokens.
     */
    abstract contract ERC20FreezableCapped is ERC20, HexMoneyInternal {
        uint256 public constant MINIMAL_FREEZE_PERIOD = 7;    // 7 days
    
        // freezing chains
        mapping (bytes32 => uint256) internal chains;
        // freezing amounts for each chain
        //mapping (bytes32 => uint) internal freezings;
        mapping(bytes32 => Freezing) internal freezings;
        // total freezing balance per address
        mapping (address => uint) internal freezingBalance;
    
        mapping(address => bytes32[]) internal freezingsByUser;
    
        mapping (address => uint256) internal latestFreezingTime;
    
        struct Freezing {
            address user;
            uint256 startDate;
            uint256 freezeDays;
            uint256 freezeAmount;
            bool capitalized;
        }
    
    
    
        event Freezed(address indexed to, uint256 release, uint amount);
        event Released(address indexed owner, uint amount);
    
        uint256 private _cap;
    
        /**
         * @dev Sets the value of the `cap`. This value is immutable, it can only be
         * set once during construction.
         */
        constructor (uint256 cap) public {
            require(cap > 0, "ERC20Capped: cap is 0");
            _cap = cap;
        }
    
        /**
         * @dev Gets the balance of the specified address include freezing tokens.
         * @param account The address to query the the balance of.
         * @return balance An uint256 representing the amount owned by the passed address.
         */
        function balanceOf(address account) public view virtual override returns (uint256) {
            return super.balanceOf(account) + freezingBalance[account];
        }
    
        /**
         * @dev Gets the balance of the specified address without freezing tokens.
         * @param account The address to query the the balance of.
         * @return balance An uint256 representing the amount owned by the passed address.
         */
        function actualBalanceOf(address account) public view returns (uint256 balance) {
            return super.balanceOf(account);
        }
    
        function freezingBalanceOf(address account) public view returns (uint256 balance) {
            return freezingBalance[account];
        }
    
        function latestFreezeTimeOf(address account) public view returns (uint256) {
            return latestFreezingTime[account];
        }
    
        /**
         * @dev Returns the cap on the token's total supply.
         */
        function cap() public view returns (uint256) {
            return _cap;
        }
        
        function getUserFreezings(address _user) public view returns (bytes32[] memory userFreezings) {
            return freezingsByUser[_user];
        }
    
        function getFreezingById(bytes32 freezingId)
            public
            view
            returns (address user, uint256 startDate, uint256 freezeDays, uint256 freezeAmount, bool capitalized)
        {
            Freezing memory userFreeze = freezings[freezingId];
            user = userFreeze.user;
            startDate = userFreeze.startDate;
            freezeDays = userFreeze.freezeDays;
            freezeAmount = userFreeze.freezeAmount;
            capitalized = userFreeze.capitalized;
        }
    
    
        function freeze(address _to, uint256 _start, uint256 _freezeDays, uint256 _amount) internal {
            require(_to != address(0x0), "FreezeContract: address cannot be zero");
            require(_start >= block.timestamp, "FreezeContract: start date cannot be in past");
            require(_freezeDays >= 0, "FreezeContract: amount of freeze days cannot be zero");
            require(_amount <= _balances[_msgSender()], "FreezeContract: freeze amount exceeds unfrozen balance");
    
            Freezing memory userFreeze = Freezing({
                user: _to,
                startDate: _start,
                freezeDays: _freezeDays,
                freezeAmount: _amount,
                capitalized: false
            });
    
            bytes32 freezeId = _toFreezeKey(_to, _start);
    
            _balances[_msgSender()] = _balances[_msgSender()].sub(_amount);
            freezingBalance[_to] = freezingBalance[_to].add(_amount);
    
            freezings[freezeId] = userFreeze;
            freezingsByUser[_to].push(freezeId);
            latestFreezingTime[_to] = _start;
    
            emit Transfer(_msgSender(), _to, _amount);
            emit Freezed(_to, _start, _amount);
        }
    
        function mintAndFreeze(address _to, uint256 _start, uint256 _freezeDays, uint256 _amount) internal {
            require(_to != address(0x0), "FreezeContract: address cannot be zero");
            require(_start >= block.timestamp, "FreezeContract: start date cannot be in past");
            require(_freezeDays >= 0, "FreezeContract: amount of freeze days cannot be zero");
    
            Freezing memory userFreeze = Freezing({
                user: _to,
                startDate: _start,
                freezeDays: _freezeDays,
                freezeAmount: _amount,
                capitalized: false
            });
    
            bytes32 freezeId = _toFreezeKey(_to, _start);
    
            freezingBalance[_to] = freezingBalance[_to].add(_amount);
    
            freezings[freezeId] = userFreeze;
            freezingsByUser[_to].push(freezeId);
            latestFreezingTime[_to] = _start;
    
            _totalSupply = _totalSupply.add(_amount);
    
            emit Transfer(_msgSender(), _to, _amount);
            emit Freezed(_to, _start, _amount);
        }
    
        function _toFreezeKey(address _user, uint256 _startDate) internal pure returns (bytes32) {
            return keccak256(abi.encodePacked(_user, _startDate));
        }
    
        function release(uint256 _startTime) internal {
            bytes32 freezeId = _toFreezeKey(_msgSender(), _startTime);
            Freezing memory userFreeze = freezings[freezeId];
    
            uint256 lockUntil = _daysToTimestampFrom(userFreeze.startDate, userFreeze.freezeDays);
            require(block.timestamp >= lockUntil, "cannot release before lock");
    
            uint256 amount = userFreeze.freezeAmount;
    
            _balances[_msgSender()] = _balances[_msgSender()].add(amount);
            freezingBalance[_msgSender()] = freezingBalance[_msgSender()].sub(amount);
    
            _deleteFreezing(freezeId, freezingsByUser[_msgSender()]);
    
            emit Released(_msgSender(), amount);
        }
    
        function refreeze(uint256 _startTime, uint256 addAmount) internal {
            bytes32 freezeId = _toFreezeKey(_msgSender(), _startTime);
            Freezing storage userFreeze = freezings[freezeId];
    
            uint256 lockUntil;
            if (!userFreeze.capitalized) {
                lockUntil = _daysToTimestampFrom(userFreeze.startDate, userFreeze.freezeDays);
            } else {
                lockUntil = _daysToTimestampFrom(userFreeze.startDate, 1);
            }
    
            require(block.timestamp >= lockUntil, "cannot refreeze before lock");
    
            bytes32 newFreezeId = _toFreezeKey(userFreeze.user, block.timestamp);
            uint256 oldFreezeAmount = userFreeze.freezeAmount;
            uint256 newFreezeAmount = SafeMath.add(userFreeze.freezeAmount, addAmount);
    
            Freezing memory newFreeze = Freezing({
                user: userFreeze.user,
                startDate: block.timestamp,
                freezeDays: userFreeze.freezeDays,
                freezeAmount: newFreezeAmount,
                capitalized: true
            });
    
            freezingBalance[_msgSender()] = freezingBalance[_msgSender()].add(addAmount);
    
            freezings[newFreezeId] = newFreeze;
            freezingsByUser[userFreeze.user].push(newFreezeId);
            latestFreezingTime[userFreeze.user] = block.timestamp;
    
            _deleteFreezing(freezeId, freezingsByUser[_msgSender()]);
            delete freezings[freezeId];
    
            emit Released(_msgSender(), oldFreezeAmount);
            emit Transfer(_msgSender(), _msgSender(), addAmount);
            emit Freezed(_msgSender(), block.timestamp, newFreezeAmount);
        }
    
        function _deleteFreezing(bytes32 freezingId, bytes32[] storage userFreezings) internal {
            uint256 freezingIndex;
            bool freezingFound;
            for (uint256 i; i < userFreezings.length; i++) {
                if (userFreezings[i] == freezingId) {
                    freezingIndex = i;
                    freezingFound = true;
                }
            }
    
            if (freezingFound) {
                userFreezings[freezingIndex] = userFreezings[userFreezings.length - 1];
                delete userFreezings[userFreezings.length - 1];
                userFreezings.pop();
            }
        }
    
        function _daysToTimestampFrom(uint256 from, uint256 lockDays) internal pure returns(uint256) {
            return SafeMath.add(from, SafeMath.mul(lockDays, SECONDS_IN_DAY));
        }
    
        function _daysToTimestamp(uint256 lockDays) internal view returns(uint256) {
            return _daysToTimestampFrom(block.timestamp, lockDays);
        }
    
        function _getBaseLockDays() internal view returns (uint256) {
            return _daysToTimestamp(MINIMAL_FREEZE_PERIOD);
        }
    
        function _getBaseLockDaysFrom(uint256 from) internal pure returns (uint256) {
            return _daysToTimestampFrom(from, MINIMAL_FREEZE_PERIOD);
        }
    
    
        /**
         * @dev See {ERC20-_beforeTokenTransfer}.
         *
         * Requirements:
         *
         * - minted tokens must not cause the total supply to go over the cap.
         */
        function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override {
            super._beforeTokenTransfer(from, to, amount);
    
            if (from == address(0)) { // When minting tokens
                require(totalSupply().add(amount) <= _cap, "ERC20Capped: cap exceeded");
            }
        }
    }
    
    abstract contract HexMoneyTeam is AccessControl {
        bytes32 public constant TEAM_ROLE = keccak256("TEAM_ROLE");
    
        address payable internal teamAddress;
    
        modifier onlyTeamRole() {
            require(hasRole(TEAM_ROLE, _msgSender()), "Must have admin role to setup");
            _;
        }
    
        function getTeamAddress() public view returns (address) {
            return teamAddress;
        }
    }
    
    contract HXY is ERC20FreezableCapped, HexMoneyTeam {
        using WhitelistLib for WhitelistLib.AllowedAddress;
    
        uint256 internal liquidSupply = 694866350105876;
        uint256 internal lockedSupply = SafeMath.mul(6, 10 ** 14);
    
        uint256 internal lockedSupplyFreezingStarted;
    
        address internal lockedSupplyAddress;
        address internal liquidSupplyAddress;
    
        struct LockedSupplyAddresses {
            address firstAddress;
            address secondAddress;
            address thirdAddress;
            address fourthAddress;
            address fifthAddress;
            address sixthAddress;
        }
    
        LockedSupplyAddresses internal lockedSupplyAddresses;
        bool internal lockedSupplyPreminted;
    
        // total amounts variables
        uint256 internal totalMinted;
        uint256 internal totalFrozen;
        uint256 internal totalCirculating;
        uint256 internal totalPayedInterest;
    
        // round logic structures
        uint256 internal hxyMintedMultiplier = 10 ** 6;
        uint256[] internal hxyRoundMintAmount = [3, 6, 9, 12, 15, 18, 21, 24, 27];
        uint256 internal baseHexToHxyRate = 10 ** 3;
        uint256[] internal hxyRoundBaseRate = [2, 3, 4, 5, 6, 7, 8, 9, 10];
    
        uint256 internal maxHxyRounds = 9;
    
        // initial round
        uint256 internal currentHxyRound;
        uint256 internal currentHxyRoundRate = SafeMath.mul(hxyRoundBaseRate[0], baseHexToHxyRate);
    
    
    
        //constructor(address payable _teamAddress,  address _liqSupAddress, address _lockSupAddress, address _migratedSupplyAddress)
        constructor(address _whitelistAddress,  address _liqSupAddress, uint256 _liqSupAmount)
        public
        ERC20FreezableCapped(SafeMath.mul(60,  10 ** 14))        // cap = 60,000,000
        ERC20("HEX Money", "HXY")
        {
            require(address(_whitelistAddress) != address(0x0), "whitelist address should not be empty");
            require(address(_liqSupAddress) != address(0x0), "liquid supply address should not be empty");
            _setupDecimals(8);
    
            _setupRole(DEPLOYER_ROLE, _msgSender());
    
    
            whitelist = HexWhitelist(_whitelistAddress);
            _premintLiquidSupply(_liqSupAddress, _liqSupAmount);
        }
    
        function getRemainingHxyInRound() public view returns (uint256) {
            return _getRemainingHxyInRound(currentHxyRound);
        }
    
        function getTotalHxyInRound() public view returns (uint256) {
            return _getTotalHxyInRound(currentHxyRound);
        }
    
        function getTotalHxyMinted() public view returns (uint256) {
            return totalMinted;
        }
    
        function getCirculatingSupply() public view returns (uint256) {
            return totalCirculating;
        }
    
        function getCurrentHxyRound() public view returns (uint256) {
            return currentHxyRound;
        }
    
        function getCurrentHxyRate() public view returns (uint256) {
            return currentHxyRoundRate;
        }
    
        function getTotalFrozen() public view returns (uint256) {
            return totalFrozen;
        }
    
        function getTotalPayedInterest() public view returns (uint256) {
            return totalPayedInterest;
        }
    
    
        function getCurrentInterestAmount(address _addr, uint256 _freezeStartDate) public view returns (uint256) {
            bytes32 freezeId = _toFreezeKey(_addr, _freezeStartDate);
            Freezing memory userFreeze = freezings[freezeId];
    
            uint256 frozenTokens = userFreeze.freezeAmount;
            if (frozenTokens != 0) {
                uint256 startFreezeDate = userFreeze.startDate;
                uint256 interestDays = SafeMath.div(SafeMath.sub(block.timestamp, startFreezeDate), SECONDS_IN_DAY);
                return SafeMath.mul(SafeMath.div(frozenTokens, 1000), interestDays);
            } else {
                return 0;
            }
        }
    
        function mintFromExchange(address account, uint256 amount) public {
            address executionAddress = _msgSender();
            require(whitelist.isRegisteredExchange(executionAddress), "must be executed from whitelisted dapp");
            whitelist.addToExchangeDailyLimit(executionAddress, amount);
    
            if (whitelist.getExchangeTradeable(executionAddress)) {
                mint(account, amount);
            } else {
                uint256 lockPeriod = whitelist.getExchangeLockPeriod(executionAddress);
                mintAndFreezeTo(account, amount, lockPeriod);
            }
        }
    
        function mintFromDappOrReferral(address account, uint256 amount) public {
            address executionAddress = _msgSender();
            require(whitelist.isRegisteredDappOrReferral(executionAddress), "must be executed from whitelisted address");
            if (whitelist.isRegisteredDapp(executionAddress)) {
                whitelist.addToDappDailyLimit(executionAddress, amount);
            } else {
                whitelist.addToReferralDailyLimit(executionAddress, amount);
            }
    
            if (whitelist.getDappTradeable(executionAddress)) {
                _mintDirectly(account, amount);
            } else {
                uint256 lockPeriod = whitelist.getDappOrReferralLockPeriod(executionAddress);
                _mintAndFreezeDirectly(account, amount, lockPeriod);
            }
        }
    
        function freezeHxy(uint256 lockAmount) public {
            freeze(_msgSender(), block.timestamp, MINIMAL_FREEZE_PERIOD, lockAmount);
            totalFrozen = SafeMath.add(totalFrozen, lockAmount);
            totalCirculating = SafeMath.sub(totalCirculating, lockAmount);
        }
    
        function refreezeHxy(uint256 startDate) public {
            bytes32 freezeId = _toFreezeKey(_msgSender(), startDate);
            Freezing memory userFreezing = freezings[freezeId];
    
            uint256 frozenTokens = userFreezing.freezeAmount;
            uint256 interestDays = SafeMath.div(SafeMath.sub(block.timestamp, userFreezing.startDate), SECONDS_IN_DAY);
            uint256 interestAmount = SafeMath.mul(SafeMath.div(frozenTokens, 1000), interestDays);
    
            refreeze(startDate, interestAmount);
            totalFrozen = SafeMath.add(totalFrozen, interestAmount);
        }
    
        function releaseFrozen(uint256 _startDate) public {
            bytes32 freezeId = _toFreezeKey(_msgSender(), _startDate);
            Freezing memory userFreezing = freezings[freezeId];
    
            uint256 frozenTokens = userFreezing.freezeAmount;
    
            release(_startDate);
    
            if (!_isLockedAddress()) {
                uint256 interestDays = SafeMath.div(SafeMath.sub(block.timestamp, userFreezing.startDate), SECONDS_IN_DAY);
                uint256 interestAmount = SafeMath.mul(SafeMath.div(frozenTokens, 1000), interestDays);
                _mint(_msgSender(), interestAmount);
    
                totalFrozen = SafeMath.sub(totalFrozen, frozenTokens);
                totalCirculating = SafeMath.add(totalCirculating, frozenTokens);
                totalPayedInterest = SafeMath.add(totalPayedInterest, interestAmount);
            }
        }
    
        function mint(address _to, uint256 _amount) internal {
            _preprocessMint(_to, _amount);
        }
    
        function mintAndFreezeTo(address _to, uint _amount, uint256 _lockDays) internal {
            _preprocessMintWithFreeze(_to, _amount, _lockDays);
        }
    
        function _premintLiquidSupply(address _liqSupAddress, uint256 _liqSupAmount) internal {
            require(_liqSupAddress != address(0x0), "liquid supply address cannot be zero");
            require(_liqSupAmount != 0, "liquid supply amount cannot be zero");
            liquidSupplyAddress = _liqSupAddress;
            liquidSupply = _liqSupAmount;
            _mint(_liqSupAddress, _liqSupAmount);
        }
    
        function premintLocked(address[6] memory _lockSupAddresses,  uint256[10] memory _unlockDates) public {
            require(hasRole(DEPLOYER_ROLE, _msgSender()), "Must have deployer role");
            require(!lockedSupplyPreminted, "cannot premint locked twice");
            _premintLockedSupply(_lockSupAddresses, _unlockDates);
        }
    
        function _premintLockedSupply(address[6] memory _lockSupAddresses, uint256[10] memory _unlockDates) internal {
    
            lockedSupplyAddresses.firstAddress = _lockSupAddresses[0];
            lockedSupplyAddresses.secondAddress = _lockSupAddresses[1];
            lockedSupplyAddresses.thirdAddress = _lockSupAddresses[2];
            lockedSupplyAddresses.fourthAddress = _lockSupAddresses[3];
            lockedSupplyAddresses.fifthAddress = _lockSupAddresses[4];
            lockedSupplyAddresses.sixthAddress = _lockSupAddresses[4];
    
            for (uint256 i = 0; i < 10; i++) {
                uint256 startDate = SafeMath.add(block.timestamp, SafeMath.add(i, 5));
    
                uint256 endFreezeDate = _unlockDates[i];
                uint256 lockSeconds = SafeMath.sub(endFreezeDate, startDate);
                uint256 lockDays = SafeMath.div(lockSeconds, SECONDS_IN_DAY);
    
    
                uint256 firstSecondAmount = SafeMath.mul(180000, 10 ** uint256(decimals()));
                uint256 thirdAmount = SafeMath.mul(120000, 10 ** uint256(decimals()));
                uint256 fourthAmount = SafeMath.mul(90000, 10 ** uint256(decimals()));
                uint256 fifthSixthAmount = SafeMath.mul(15000, 10 ** uint256(decimals()));
    
                mintAndFreeze(lockedSupplyAddresses.firstAddress, startDate, lockDays, firstSecondAmount);
                mintAndFreeze(lockedSupplyAddresses.secondAddress, startDate, lockDays, firstSecondAmount);
                mintAndFreeze(lockedSupplyAddresses.thirdAddress, startDate, lockDays, thirdAmount);
                mintAndFreeze(lockedSupplyAddresses.fourthAddress, startDate, lockDays, fourthAmount);
                mintAndFreeze(lockedSupplyAddresses.fifthAddress, startDate, lockDays, fifthSixthAmount);
                mintAndFreeze(lockedSupplyAddresses.sixthAddress, startDate, lockDays, fifthSixthAmount);
            }
    
            lockedSupplyPreminted = true;
        }
    
    
        function _preprocessMint(address _account, uint256 _hexAmount) internal {
            uint256 currentRoundHxyAmount = SafeMath.div(_hexAmount, currentHxyRoundRate);
            if (currentRoundHxyAmount < getRemainingHxyInRound()) {
                uint256 hxyAmount = currentRoundHxyAmount;
                _mint(_account, hxyAmount);
    
                totalMinted = SafeMath.add(totalMinted, hxyAmount);
                totalCirculating = SafeMath.add(totalCirculating, hxyAmount);
            } else if (currentRoundHxyAmount == getRemainingHxyInRound()) {
                uint256 hxyAmount = currentRoundHxyAmount;
                _mint(_account, hxyAmount);
    
                _incrementHxyRateRound();
    
                totalMinted = SafeMath.add(totalMinted, hxyAmount);
                totalCirculating = SafeMath.add(totalCirculating, hxyAmount);
            } else {
                uint256 hxyAmount;
                uint256 hexPaymentAmount;
                while (hexPaymentAmount < _hexAmount) {
                    uint256 hxyRoundTotal = SafeMath.mul(_toDecimals(hxyRoundMintAmount[currentHxyRound]), hxyMintedMultiplier);
    
                    uint256 hxyInCurrentRoundMax = SafeMath.sub(hxyRoundTotal, totalMinted);
                    uint256 hexInCurrentRoundMax = SafeMath.mul(hxyInCurrentRoundMax, currentHxyRoundRate);
    
                    uint256 hexInCurrentRound;
                    uint256 hxyInCurrentRound;
                    if (SafeMath.sub(_hexAmount, hexPaymentAmount) < hexInCurrentRoundMax) {
                        hexInCurrentRound = SafeMath.sub(_hexAmount, hexPaymentAmount);
                        hxyInCurrentRound = SafeMath.div(hexInCurrentRound, currentHxyRoundRate);
                    } else {
                        hexInCurrentRound = hexInCurrentRoundMax;
                        hxyInCurrentRound = hxyInCurrentRoundMax;
    
                        _incrementHxyRateRound();
                    }
    
                    hxyAmount = SafeMath.add(hxyAmount, hxyInCurrentRound);
                    hexPaymentAmount = SafeMath.add(hexPaymentAmount, hexInCurrentRound);
    
                    totalMinted = SafeMath.add(totalMinted, hxyInCurrentRound);
                    totalCirculating = SafeMath.add(totalCirculating, hxyAmount);
                }
                _mint(_account, hxyAmount);
            }
        }
    
        function _preprocessMintWithFreeze(address _account, uint256 _hexAmount, uint256 _freezeDays) internal {
            uint256 currentRoundHxyAmount = SafeMath.div(_hexAmount, currentHxyRoundRate);
            if (currentRoundHxyAmount < getRemainingHxyInRound()) {
                uint256 hxyAmount = currentRoundHxyAmount;
                totalMinted = SafeMath.add(totalMinted, hxyAmount);
                mintAndFreeze(_account, block.timestamp, _freezeDays, hxyAmount);
            } else if (currentRoundHxyAmount == getRemainingHxyInRound()) {
                uint256 hxyAmount = currentRoundHxyAmount;
                mintAndFreeze(_account, block.timestamp, _freezeDays, hxyAmount);
    
                totalMinted = SafeMath.add(totalMinted, hxyAmount);
    
                _incrementHxyRateRound();
            } else {
                uint256 hxyAmount;
                uint256 hexPaymentAmount;
                while (hexPaymentAmount < _hexAmount) {
                    uint256 hxyRoundTotal = SafeMath.mul(_toDecimals(hxyRoundMintAmount[currentHxyRound]), hxyMintedMultiplier);
    
                    uint256 hxyInCurrentRoundMax = SafeMath.sub(hxyRoundTotal, totalMinted);
                    uint256 hexInCurrentRoundMax = SafeMath.mul(hxyInCurrentRoundMax, currentHxyRoundRate);
    
                    uint256 hexInCurrentRound;
                    uint256 hxyInCurrentRound;
                    if (SafeMath.sub(_hexAmount, hexPaymentAmount) < hexInCurrentRoundMax) {
                        hexInCurrentRound = SafeMath.sub(_hexAmount, hexPaymentAmount);
                        hxyInCurrentRound = SafeMath.div(hexInCurrentRound, currentHxyRoundRate);
                    } else {
                        hexInCurrentRound = hexInCurrentRoundMax;
                        hxyInCurrentRound = hxyInCurrentRoundMax;
    
                        _incrementHxyRateRound();
                    }
    
                    hxyAmount = SafeMath.add(hxyAmount, hxyInCurrentRound);
                    hexPaymentAmount = SafeMath.add(hexPaymentAmount, hexInCurrentRound);
    
                    totalMinted = SafeMath.add(totalMinted, hxyInCurrentRound);
                }
                mintAndFreeze(_account, block.timestamp, _freezeDays, hxyAmount);
            }
        }
    
        function _mintDirectly(address _account, uint256 _hxyAmount) internal {
            _mint(_account, _hxyAmount);
        }
    
        function _mintAndFreezeDirectly(address _account, uint256 _hxyAmount, uint256 _freezeDays) internal {
            mintAndFreeze(_account, block.timestamp, _freezeDays, _hxyAmount);
        }
    
        function _isLockedAddress() internal view returns (bool) {
            if (_msgSender() == lockedSupplyAddresses.firstAddress) {
                return true;
            } else if (_msgSender() == lockedSupplyAddresses.secondAddress) {
                return true;
            } else if (_msgSender() == lockedSupplyAddresses.thirdAddress) {
                return true;
            } else if (_msgSender() == lockedSupplyAddresses.fourthAddress) {
                return true;
            } else if (_msgSender() == lockedSupplyAddresses.fifthAddress) {
                return true;
            } else if (_msgSender() == lockedSupplyAddresses.sixthAddress) {
                return true;
            } else {
                return false;
            }
        }
    
        function _getTotalHxyInRound(uint256 _round) public view returns (uint256) {
            return SafeMath.mul(_toDecimals(hxyRoundMintAmount[_round]),hxyMintedMultiplier);
        }
    
        function _getRemainingHxyInRound(uint256 _round) public view returns (uint256) {
            return SafeMath.sub(SafeMath.mul(_toDecimals(hxyRoundMintAmount[_round]), hxyMintedMultiplier), totalMinted);
        }
    
        function _incrementHxyRateRound() internal {
            currentHxyRound = SafeMath.add(currentHxyRound, 1);
            currentHxyRoundRate = SafeMath.mul(hxyRoundBaseRate[currentHxyRound], baseHexToHxyRate);
        }
    
        function _toDecimals(uint256 amount) internal view returns (uint256) {
            return SafeMath.mul(amount, 10 ** uint256(decimals()));
        }
    }