ETH Price: $2,077.75 (-1.20%)

Transaction Decoder

Block:
14255753 at Feb-22-2022 12:02:02 PM +UTC
Transaction Fee:
0.00261539176783841 ETH $5.43
Gas Used:
61,030 Gas / 42.854199047 Gwei

Emitted Events:

357 TransparentUpgradeableProxy.0xc87dac464bd9964598d47d6e7f7cbe0b76cad55b33430228240e72c18da0edf6( 0xc87dac464bd9964598d47d6e7f7cbe0b76cad55b33430228240e72c18da0edf6, 0x00000000000000000000000070cc0875ef229b781e6b1dc0867925c017179d17, 0x0000000000000000000000000000000000000000000000000000000000000007, 0x00000000000000000000000000000000000000000000000000000000000017c7 )

Account State Difference:

  Address   Before After State Difference Code
(Hiveon Pool)
9,574.191997436642755205 Eth9,574.192057687391270155 Eth0.00006025074851495
0x70Cc0875...017179d17
0.839164734641863744 Eth
Nonce: 361
0.836549342874025334 Eth
Nonce: 362
0.00261539176783841
0xA351B769...ec05C1E75

Execution Trace

TransparentUpgradeableProxy.bddb95c1( )
  • EthernalElvesV4.heal( healer=6087, target=343 )
    File 1 of 2: TransparentUpgradeableProxy
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
    import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
    import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
    // Kept for backwards compatibility with older versions of Hardhat and Truffle plugins.
    contract AdminUpgradeabilityProxy is TransparentUpgradeableProxy {
        constructor(address logic, address admin, bytes memory data) payable TransparentUpgradeableProxy(logic, admin, data) {}
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    import "../Proxy.sol";
    import "./ERC1967Upgrade.sol";
    /**
     * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
     * implementation address that can be changed. This address is stored in storage in the location specified by
     * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
     * implementation behind the proxy.
     */
    contract ERC1967Proxy is Proxy, ERC1967Upgrade {
        /**
         * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
         *
         * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
         * function call, and allows initializating the storage of the proxy like a Solidity constructor.
         */
        constructor(address _logic, bytes memory _data) payable {
            assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
            _upgradeToAndCall(_logic, _data, false);
        }
        /**
         * @dev Returns the current implementation address.
         */
        function _implementation() internal view virtual override returns (address impl) {
            return ERC1967Upgrade._getImplementation();
        }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    import "../ERC1967/ERC1967Proxy.sol";
    /**
     * @dev This contract implements a proxy that is upgradeable by an admin.
     *
     * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
     * clashing], which can potentially be used in an attack, this contract uses the
     * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
     * things that go hand in hand:
     *
     * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
     * that call matches one of the admin functions exposed by the proxy itself.
     * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
     * implementation. If the admin tries to call a function on the implementation it will fail with an error that says
     * "admin cannot fallback to proxy target".
     *
     * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
     * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
     * to sudden errors when trying to call a function from the proxy implementation.
     *
     * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
     * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
     */
    contract TransparentUpgradeableProxy is ERC1967Proxy {
        /**
         * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
         * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
         */
        constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
            assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
            _changeAdmin(admin_);
        }
        /**
         * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
         */
        modifier ifAdmin() {
            if (msg.sender == _getAdmin()) {
                _;
            } else {
                _fallback();
            }
        }
        /**
         * @dev Returns the current admin.
         *
         * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.
         *
         * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
         * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
         * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
         */
        function admin() external ifAdmin returns (address admin_) {
            admin_ = _getAdmin();
        }
        /**
         * @dev Returns the current implementation.
         *
         * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.
         *
         * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
         * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
         * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
         */
        function implementation() external ifAdmin returns (address implementation_) {
            implementation_ = _implementation();
        }
        /**
         * @dev Changes the admin of the proxy.
         *
         * Emits an {AdminChanged} event.
         *
         * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.
         */
        function changeAdmin(address newAdmin) external virtual ifAdmin {
            _changeAdmin(newAdmin);
        }
        /**
         * @dev Upgrade the implementation of the proxy.
         *
         * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.
         */
        function upgradeTo(address newImplementation) external ifAdmin {
            _upgradeToAndCall(newImplementation, bytes(""), false);
        }
        /**
         * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
         * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
         * proxied contract.
         *
         * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.
         */
        function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
            _upgradeToAndCall(newImplementation, data, true);
        }
        /**
         * @dev Returns the current admin.
         */
        function _admin() internal view virtual returns (address) {
            return _getAdmin();
        }
        /**
         * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.
         */
        function _beforeFallback() internal virtual override {
            require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
            super._beforeFallback();
        }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    import "./TransparentUpgradeableProxy.sol";
    import "../../access/Ownable.sol";
    /**
     * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
     * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
     */
    contract ProxyAdmin is Ownable {
        /**
         * @dev Returns the current implementation of `proxy`.
         *
         * Requirements:
         *
         * - This contract must be the admin of `proxy`.
         */
        function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
            // We need to manually run the static call since the getter cannot be flagged as view
            // bytes4(keccak256("implementation()")) == 0x5c60da1b
            (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b");
            require(success);
            return abi.decode(returndata, (address));
        }
        /**
         * @dev Returns the current admin of `proxy`.
         *
         * Requirements:
         *
         * - This contract must be the admin of `proxy`.
         */
        function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
            // We need to manually run the static call since the getter cannot be flagged as view
            // bytes4(keccak256("admin()")) == 0xf851a440
            (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
            require(success);
            return abi.decode(returndata, (address));
        }
        /**
         * @dev Changes the admin of `proxy` to `newAdmin`.
         *
         * Requirements:
         *
         * - This contract must be the current admin of `proxy`.
         */
        function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {
            proxy.changeAdmin(newAdmin);
        }
        /**
         * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.
         *
         * Requirements:
         *
         * - This contract must be the admin of `proxy`.
         */
        function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {
            proxy.upgradeTo(implementation);
        }
        /**
         * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See
         * {TransparentUpgradeableProxy-upgradeToAndCall}.
         *
         * Requirements:
         *
         * - This contract must be the admin of `proxy`.
         */
        function upgradeAndCall(TransparentUpgradeableProxy proxy, address implementation, bytes memory data) public payable virtual onlyOwner {
            proxy.upgradeToAndCall{value: msg.value}(implementation, data);
        }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    /**
     * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
     * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
     * be specified by overriding the virtual {_implementation} function.
     *
     * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
     * different contract through the {_delegate} function.
     *
     * The success and return data of the delegated call will be returned back to the caller of the proxy.
     */
    abstract contract Proxy {
        /**
         * @dev Delegates the current call to `implementation`.
         *
         * This function does not return to its internall call site, it will return directly to the external caller.
         */
        function _delegate(address implementation) internal virtual {
            // solhint-disable-next-line no-inline-assembly
            assembly {
                // Copy msg.data. We take full control of memory in this inline assembly
                // block because it will not return to Solidity code. We overwrite the
                // Solidity scratch pad at memory position 0.
                calldatacopy(0, 0, calldatasize())
                // Call the implementation.
                // out and outsize are 0 because we don't know the size yet.
                let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
                // Copy the returned data.
                returndatacopy(0, 0, returndatasize())
                switch result
                // delegatecall returns 0 on error.
                case 0 { revert(0, returndatasize()) }
                default { return(0, returndatasize()) }
            }
        }
        /**
         * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function
         * and {_fallback} should delegate.
         */
        function _implementation() internal view virtual returns (address);
        /**
         * @dev Delegates the current call to the address returned by `_implementation()`.
         *
         * This function does not return to its internall call site, it will return directly to the external caller.
         */
        function _fallback() internal virtual {
            _beforeFallback();
            _delegate(_implementation());
        }
        /**
         * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
         * function in the contract matches the call data.
         */
        fallback () external payable virtual {
            _fallback();
        }
        /**
         * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
         * is empty.
         */
        receive () external payable virtual {
            _fallback();
        }
        /**
         * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
         * call, or as part of the Solidity `fallback` or `receive` functions.
         *
         * If overriden should call `super._beforeFallback()`.
         */
        function _beforeFallback() internal virtual {
        }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.2;
    import "../beacon/IBeacon.sol";
    import "../../utils/Address.sol";
    import "../../utils/StorageSlot.sol";
    /**
     * @dev This abstract contract provides getters and event emitting update functions for
     * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
     *
     * _Available since v4.1._
     *
     * @custom:oz-upgrades-unsafe-allow delegatecall
     */
    abstract contract ERC1967Upgrade {
        // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
        bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
        /**
         * @dev Storage slot with the address of the current implementation.
         * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
         * validated in the constructor.
         */
        bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
        /**
         * @dev Emitted when the implementation is upgraded.
         */
        event Upgraded(address indexed implementation);
        /**
         * @dev Returns the current implementation address.
         */
        function _getImplementation() internal view returns (address) {
            return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
        }
        /**
         * @dev Stores a new address in the EIP1967 implementation slot.
         */
        function _setImplementation(address newImplementation) private {
            require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
            StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
        }
        /**
         * @dev Perform implementation upgrade
         *
         * Emits an {Upgraded} event.
         */
        function _upgradeTo(address newImplementation) internal {
            _setImplementation(newImplementation);
            emit Upgraded(newImplementation);
        }
        /**
         * @dev Perform implementation upgrade with additional setup call.
         *
         * Emits an {Upgraded} event.
         */
        function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
            _setImplementation(newImplementation);
            emit Upgraded(newImplementation);
            if (data.length > 0 || forceCall) {
                Address.functionDelegateCall(newImplementation, data);
            }
        }
        /**
         * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
         *
         * Emits an {Upgraded} event.
         */
        function _upgradeToAndCallSecure(address newImplementation, bytes memory data, bool forceCall) internal {
            address oldImplementation = _getImplementation();
            // Initial upgrade and setup call
            _setImplementation(newImplementation);
            if (data.length > 0 || forceCall) {
                Address.functionDelegateCall(newImplementation, data);
            }
            // Perform rollback test if not already in progress
            StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT);
            if (!rollbackTesting.value) {
                // Trigger rollback using upgradeTo from the new implementation
                rollbackTesting.value = true;
                Address.functionDelegateCall(
                    newImplementation,
                    abi.encodeWithSignature(
                        "upgradeTo(address)",
                        oldImplementation
                    )
                );
                rollbackTesting.value = false;
                // Check rollback was effective
                require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
                // Finally reset to the new implementation and log the upgrade
                _setImplementation(newImplementation);
                emit Upgraded(newImplementation);
            }
        }
        /**
         * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
         * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
         *
         * Emits a {BeaconUpgraded} event.
         */
        function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
            _setBeacon(newBeacon);
            emit BeaconUpgraded(newBeacon);
            if (data.length > 0 || forceCall) {
                Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
            }
        }
        /**
         * @dev Storage slot with the admin of the contract.
         * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
         * validated in the constructor.
         */
        bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
        /**
         * @dev Emitted when the admin account has changed.
         */
        event AdminChanged(address previousAdmin, address newAdmin);
        /**
         * @dev Returns the current admin.
         */
        function _getAdmin() internal view returns (address) {
            return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
        }
        /**
         * @dev Stores a new address in the EIP1967 admin slot.
         */
        function _setAdmin(address newAdmin) private {
            require(newAdmin != address(0), "ERC1967: new admin is the zero address");
            StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
        }
        /**
         * @dev Changes the admin of the proxy.
         *
         * Emits an {AdminChanged} event.
         */
        function _changeAdmin(address newAdmin) internal {
            emit AdminChanged(_getAdmin(), newAdmin);
            _setAdmin(newAdmin);
        }
        /**
         * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
         * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
         */
        bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
        /**
         * @dev Emitted when the beacon is upgraded.
         */
        event BeaconUpgraded(address indexed beacon);
        /**
         * @dev Returns the current beacon.
         */
        function _getBeacon() internal view returns (address) {
            return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
        }
        /**
         * @dev Stores a new beacon in the EIP1967 beacon slot.
         */
        function _setBeacon(address newBeacon) private {
            require(
                Address.isContract(newBeacon),
                "ERC1967: new beacon is not a contract"
            );
            require(
                Address.isContract(IBeacon(newBeacon).implementation()),
                "ERC1967: beacon implementation is not a contract"
            );
            StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
        }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    /**
     * @dev This is the interface that {BeaconProxy} expects of its beacon.
     */
    interface IBeacon {
        /**
         * @dev Must return an address that can be used as a delegate call target.
         *
         * {BeaconProxy} will check that this address is a contract.
         */
        function implementation() external view returns (address);
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    /**
     * @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) {
            // This method relies on extcodesize, which returns 0 for contracts in
            // construction, since the code is only stored at the end of the
            // constructor execution.
            uint256 size;
            // solhint-disable-next-line no-inline-assembly
            assembly { size := extcodesize(account) }
            return size > 0;
        }
        /**
         * @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 Performs a Solidity function call using a low level `call`. A
         * plain`call` is an unsafe replacement for a function call: use this
         * function instead.
         *
         * If `target` reverts with a revert reason, it is bubbled up by this
         * function (like regular Solidity function calls).
         *
         * Returns the raw returned data. To convert to the expected return value,
         * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
         *
         * Requirements:
         *
         * - `target` must be a contract.
         * - calling `target` with `data` must not revert.
         *
         * _Available since v3.1._
         */
        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
          return functionCall(target, data, "Address: low-level call failed");
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
         * `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0, errorMessage);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but also transferring `value` wei to `target`.
         *
         * Requirements:
         *
         * - the calling contract must have an ETH balance of at least `value`.
         * - the called Solidity function must be `payable`.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
        }
        /**
         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
         * with `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
            require(address(this).balance >= value, "Address: insufficient balance for call");
            require(isContract(target), "Address: call to non-contract");
            // solhint-disable-next-line avoid-low-level-calls
            (bool success, bytes memory returndata) = target.call{ value: value }(data);
            return _verifyCallResult(success, returndata, errorMessage);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
            return functionStaticCall(target, data, "Address: low-level static call failed");
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
            require(isContract(target), "Address: static call to non-contract");
            // solhint-disable-next-line avoid-low-level-calls
            (bool success, bytes memory returndata) = target.staticcall(data);
            return _verifyCallResult(success, returndata, errorMessage);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
            require(isContract(target), "Address: delegate call to non-contract");
            // solhint-disable-next-line avoid-low-level-calls
            (bool success, bytes memory returndata) = target.delegatecall(data);
            return _verifyCallResult(success, returndata, errorMessage);
        }
        function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
            if (success) {
                return returndata;
            } else {
                // Look for revert reason and bubble it up if present
                if (returndata.length > 0) {
                    // The easiest way to bubble the revert reason is using memory via assembly
                    // solhint-disable-next-line no-inline-assembly
                    assembly {
                        let returndata_size := mload(returndata)
                        revert(add(32, returndata), returndata_size)
                    }
                } else {
                    revert(errorMessage);
                }
            }
        }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    /**
     * @dev Library for reading and writing primitive types to specific storage slots.
     *
     * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
     * This library helps with reading and writing to such slots without the need for inline assembly.
     *
     * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
     *
     * Example usage to set ERC1967 implementation slot:
     * ```
     * contract ERC1967 {
     *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
     *
     *     function _getImplementation() internal view returns (address) {
     *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
     *     }
     *
     *     function _setImplementation(address newImplementation) internal {
     *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
     *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
     *     }
     * }
     * ```
     *
     * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._
     */
    library StorageSlot {
        struct AddressSlot {
            address value;
        }
        struct BooleanSlot {
            bool value;
        }
        struct Bytes32Slot {
            bytes32 value;
        }
        struct Uint256Slot {
            uint256 value;
        }
        /**
         * @dev Returns an `AddressSlot` with member `value` located at `slot`.
         */
        function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
            assembly {
                r.slot := slot
            }
        }
        /**
         * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
         */
        function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
            assembly {
                r.slot := slot
            }
        }
        /**
         * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
         */
        function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
            assembly {
                r.slot := slot
            }
        }
        /**
         * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
         */
        function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
            assembly {
                r.slot := slot
            }
        }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    import "../utils/Context.sol";
    /**
     * @dev Contract module which provides a basic access control mechanism, where
     * there is an account (an owner) that can be granted exclusive access to
     * specific functions.
     *
     * By default, the owner account will be the one that deploys the contract. This
     * can later be changed with {transferOwnership}.
     *
     * This module is used through inheritance. It will make available the modifier
     * `onlyOwner`, which can be applied to your functions to restrict their use to
     * the owner.
     */
    abstract contract Ownable is Context {
        address private _owner;
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
        /**
         * @dev Initializes the contract setting the deployer as the initial owner.
         */
        constructor () {
            address msgSender = _msgSender();
            _owner = msgSender;
            emit OwnershipTransferred(address(0), msgSender);
        }
        /**
         * @dev Returns the address of the current owner.
         */
        function owner() public view virtual returns (address) {
            return _owner;
        }
        /**
         * @dev Throws if called by any account other than the owner.
         */
        modifier onlyOwner() {
            require(owner() == _msgSender(), "Ownable: caller is not the owner");
            _;
        }
        /**
         * @dev Leaves the contract without owner. It will not be possible to call
         * `onlyOwner` functions anymore. Can only be called by the current owner.
         *
         * NOTE: Renouncing ownership will leave the contract without an owner,
         * thereby removing any functionality that is only available to the owner.
         */
        function renounceOwnership() public virtual onlyOwner {
            emit OwnershipTransferred(_owner, address(0));
            _owner = address(0);
        }
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Can only be called by the current owner.
         */
        function transferOwnership(address newOwner) public virtual onlyOwner {
            require(newOwner != address(0), "Ownable: new owner is the zero address");
            emit OwnershipTransferred(_owner, newOwner);
            _owner = newOwner;
        }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    /*
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    abstract contract Context {
        function _msgSender() internal view virtual returns (address) {
            return msg.sender;
        }
        function _msgData() internal view virtual returns (bytes calldata) {
            this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
            return msg.data;
        }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    import "../ERC1967/ERC1967Upgrade.sol";
    /**
     * @dev Base contract for building openzeppelin-upgrades compatible implementations for the {ERC1967Proxy}. It includes
     * publicly available upgrade functions that are called by the plugin and by the secure upgrade mechanism to verify
     * continuation of the upgradability.
     *
     * The {_authorizeUpgrade} function MUST be overridden to include access restriction to the upgrade mechanism.
     *
     * _Available since v4.1._
     */
    abstract contract UUPSUpgradeable is ERC1967Upgrade {
        function upgradeTo(address newImplementation) external virtual {
            _authorizeUpgrade(newImplementation);
            _upgradeToAndCallSecure(newImplementation, bytes(""), false);
        }
        function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual {
            _authorizeUpgrade(newImplementation);
            _upgradeToAndCallSecure(newImplementation, data, true);
        }
        function _authorizeUpgrade(address newImplementation) internal virtual;
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.2;
    import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
    abstract contract Proxiable is UUPSUpgradeable {
        function _authorizeUpgrade(address newImplementation) internal override {
            _beforeUpgrade(newImplementation);
        }
        function _beforeUpgrade(address newImplementation) internal virtual;
    }
    contract ChildOfProxiable is Proxiable {
        function _beforeUpgrade(address newImplementation) internal virtual override {}
    }
    

    File 2 of 2: EthernalElvesV4
    // SPDX-License-Identifier: Unlicense
    pragma solidity 0.8.7;
    import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
    import "./ERC721.sol"; 
    import "./DataStructures.sol";
    import "./Interfaces.sol";
    //import "hardhat/console.sol"; 
    // We are the Ethernal. The Ethernal Elves         
    // Written by 0xHusky & Beff Jezos. Everything is on-chain for all time to come.
    // Version 2.0.0
    // Release notes: Export Sentinel / Polygon
    contract EthernalElvesV4 is ERC721 {
        function name() external pure returns (string memory) { return "Ethernal Elves"; }
        function symbol() external pure returns (string memory) { return "ELV"; }
           
        using DataStructures for DataStructures.ActionVariables;
        using DataStructures for DataStructures.Elf;
        using DataStructures for DataStructures.Token; 
        IElfMetaDataHandler elfmetaDataHandler;
        ICampaigns campaigns;
        IERC20Lite public ren;
        
        using ECDSA for bytes32;
        
    //STATE   
        bool public isGameActive;
        bool public isMintOpen;
        bool public isWlOpen;
        bool private initialized;
        address dev1Address;
        address dev2Address;
        address terminus;
        address public validator;
       
        uint256 public INIT_SUPPLY; 
        uint256 public price;
        bytes32 internal ketchup;
        
        uint256[] public _remaining; 
        mapping(uint256 => uint256) public sentinels; //memory slot for Elfs
        mapping(address => uint256) public bankBalances; //memory slot for bank balances
        mapping(address => bool)    public auth;
        mapping(address => uint16)  public whitelist; 
    /////NEW STORAGE FROM THIS LINE V3///////////////////////////////////////////////////////
        bool public isTerminalOpen;
    /////NEW STORAGE FROM THIS LINE V4///////////////////////////////////////////////////////
        mapping(bytes => uint16)  public usedRenSignatures; 
           function initialize(address _dev1Address, address _dev2Address) public {
        
           require(!initialized, "Already initialized");
           admin                = msg.sender;   
           dev1Address          = _dev1Address;
           dev2Address          = _dev2Address;
           maxSupply            = 6666; 
           INIT_SUPPLY          = 3300; 
           initialized          = true;
           price                = .088 ether;  
           _remaining           = [250,660,2500]; //[200, 600, 2500]; //this is the supply of each whitelist role
           validator            = 0x80861814a8775de20F9506CF41932E95f80f7035;
           
        }
        function setAddresses(address _ren, address _inventory, address _campaigns, address _validator)  public {
           onlyOwner();
           ren                  = IERC20Lite(_ren);
           elfmetaDataHandler   = IElfMetaDataHandler(_inventory);
           campaigns            = ICampaigns(_campaigns);
           validator            = _validator;
        }    
        
        function setAuth(address[] calldata adds_, bool status) public {
           onlyOwner();
           
            for (uint256 index = 0; index < adds_.length; index++) {
                auth[adds_[index]] = status;
            }
        }
    //EVENTS
        event Action(address indexed from, uint256 indexed action, uint256 indexed tokenId);         
        event BalanceChanged(address indexed owner, uint256 indexed amount, bool indexed subtract);
        event Campaigns(address indexed owner, uint256 amount, uint256 indexed campaign, uint256 sector, uint256 indexed tokenId);
        event CheckIn(address indexed from, uint256 timestamp, uint256 indexed tokenId, uint256 indexed sentinel);      
        event CheckOut(address indexed to, uint256 timestamp, uint256 indexed tokenId);      
        event RenTransferOut(address indexed from, uint256 timestamp, uint256 indexed renAmount);   
       
            
    //////////////EXPORT TO OTHER CHAINS/////////////////
    function checkIn(uint256[] calldata ids, uint256 renAmount) public returns (bool) {
         
            isPlayer();
            require(isTerminalOpen, "Terminal is closed");         
             uint256 travelers = ids.length;
             if (travelers > 0) {
                        for (uint256 index = 0; index < ids.length; index++) {  
                            _actions(ids[index], 8, msg.sender, 0, 0, false, false, false, 0);
                            emit CheckIn(msg.sender, block.timestamp, ids[index], sentinels[ids[index]]);
                        }
                      
              }
                if (renAmount > 0) {
                            
                            bankBalances[msg.sender] >= renAmount ? _setAccountBalance(msg.sender, renAmount, true) :  ren.burn(msg.sender, renAmount);
                            emit RenTransferOut(msg.sender,block.timestamp,renAmount);
                 }
        
    }
     function checkOut(uint256[] calldata ids, uint256[] calldata sentinel, bytes[] memory signatures) public returns (bool) {
       
        isPlayer();
        require(isTerminalOpen, "Terminal is closed");
         
        
        //Add this to 
        ///All checks to happen in polygon
         uint256 travelers = ids.length;
             if (travelers > 0) {
                    for (uint256 index = 0; index < ids.length; index++) {  
                        require(_isSignedByValidator(encodeSentinelForSignature(ids[index], msg.sender, sentinel[index]),signatures[index]), "incorrect signature");
                        sentinels[ids[index]] = sentinel[index];
                        _actions(ids[index], 0, msg.sender, 0, 0, false, false, false, 0);
                        emit CheckOut(msg.sender, block.timestamp, ids[index]);
                    }
             }
     }
     function checkOutRen(uint256 renAmount, bytes memory renSignatures, uint256 timestamp) public returns (bool) {
       
        isPlayer();
        require(isTerminalOpen, "Terminal is closed"); 
        require(usedRenSignatures[renSignatures] == 0, "Signature already used");   
            if(renAmount > 0){
                 require(_isSignedByValidator(encodeRenForSignature(renAmount, msg.sender, timestamp),renSignatures), "incorrect signature");
                 usedRenSignatures[renSignatures] = 1;
                 ren.mint(msg.sender, renAmount);
            }
        
     }
    //CheckOut Permissions 
    function encodeSentinelForSignature(uint256 id, address owner, uint256 sentinel) public pure returns (bytes32) {
         return keccak256(
                abi.encodePacked("\\x19Ethereum Signed Message:\
    32", 
                    keccak256(
                            abi.encodePacked(id, owner, sentinel))
                            )
                        );
    } 
    function encodeRenForSignature(uint256 renAmount, address owner, uint256 timestamp) public pure returns (bytes32) {
         return keccak256(
                abi.encodePacked("\\x19Ethereum Signed Message:\
    32", 
                    keccak256(
                            abi.encodePacked(renAmount, owner, timestamp))
                            )
                        );
    }  
      
    function _isSignedByValidator(bytes32 _hash, bytes memory _signature) private view returns (bool) {
        
        bytes32 r;
        bytes32 s;
        uint8 v;
               assembly {
                    r := mload(add(_signature, 0x20))
                    s := mload(add(_signature, 0x40))
                    v := byte(0, mload(add(_signature, 0x60)))
                }
            
                address signer = ecrecover(_hash, v, r, s);
                return signer == validator;
      
    }
    /////////////////////////////////////////////////////////////////
        function mint() external payable  returns (uint256 id) {
            isPlayer();
            require(isMintOpen, "Minting is closed");
            uint256 cost;
            (cost,) = getMintPriceLevel();
            
            if (totalSupply <= INIT_SUPPLY) {            
                 require(msg.value >= cost, "NotEnoughEther");
            }else{
                bankBalances[msg.sender] >= cost ? _setAccountBalance(msg.sender, cost, true) :  ren.burn(msg.sender, cost);
            }
            return _mintElf(msg.sender);
        }
    //GAMEPLAY//
        function unStake(uint256[] calldata ids) external  {
              isPlayer();        
              for (uint256 index = 0; index < ids.length; index++) {  
                _actions(ids[index], 0, msg.sender, 0, 0, false, false, false, 0);
              }
        }
        function sendCampaign(uint256[] calldata ids, uint256 campaign_, uint256 sector_, bool rollWeapons_, bool rollItems_, bool useitem_) external {
              isPlayer();          
              for (uint256 index = 0; index < ids.length; index++) {  
                _actions(ids[index], 2, msg.sender, campaign_, sector_, rollWeapons_, rollItems_, useitem_, 1);
              }
        }
        function passive(uint256[] calldata ids) external {
              isPlayer();         
              for (uint256 index = 0; index < ids.length; index++) {  
                _actions(ids[index], 3, msg.sender, 0, 0, false, false, false, 0);
              }
        }
        function returnPassive(uint256[] calldata ids) external  {
              isPlayer();        
              for (uint256 index = 0; index < ids.length; index++) {  
                _actions(ids[index], 4, msg.sender, 0, 0, false, false, false, 0);
              }
        }
        function forging(uint256[] calldata ids) external payable {
              isPlayer();         
            
              for (uint256 index = 0; index < ids.length; index++) {  
                _actions(ids[index], 5, msg.sender, 0, 0, false, false, false, 0);
              }
        }
        function merchant(uint256[] calldata ids) external payable {
              isPlayer();   
              for (uint256 index = 0; index < ids.length; index++) {  
                _actions(ids[index], 6, msg.sender, 0, 0, false, false, false, 0);
              }
        }
        function heal(uint256 healer, uint256 target) external {
            isPlayer();
            _actions(healer, 7, msg.sender, target, 0, false, false, false, 0);
        }    
        function withdrawTokenBalance() external {
          
            require(bankBalances[msg.sender] > 0, "NoBalance");
            ren.mint(msg.sender, bankBalances[msg.sender]); 
            bankBalances[msg.sender] = 0;
        }
        function withdrawSomeTokenBalance(uint256 amount) external {
          
            require(bankBalances[msg.sender] > 0, "NoBalance");
            require(bankBalances[msg.sender] - amount >= 0,"Overdraft Not permitted");
            bankBalances[msg.sender] =  bankBalances[msg.sender] - amount; //update ledger first
            ren.mint(msg.sender, amount); 
          
        }
    //INTERNALS
        
            function _mintElf(address _to) private returns (uint16 id) {
            
                uint256 rand = _rand();
              
                
                {        
                    DataStructures.Elf memory elf;
                    id = uint16(totalSupply + 1);   
                            
                    elf.owner = address(0);
                    elf.timestamp = block.timestamp;
                    
                    elf.action = elf.weaponTier = elf.inventory = 0;
                    
                    elf.primaryWeapon = 69; //69 is the default weapon - fists.
                    (,elf.level) = getMintPriceLevel();
                    elf.sentinelClass = uint16(_randomize(rand, "Class", id)) % 3;
                    elf.race = rand % 100 > 97 ? 3 : uint16(_randomize(rand, "Race", id)) % 3;
                    elf.hair = elf.race == 3 ? 0 : uint16(_randomize(rand, "Hair", id)) % 3;            
                    elf.accessories = elf.sentinelClass == 0 ? (uint16(_randomize(rand, "Accessories", id)) % 2) + 3 : uint16(_randomize(rand, "Accessories", id)) % 2; //2 accessories MAX 7 
                    uint256 _traits = DataStructures.packAttributes(elf.hair, elf.race, elf.accessories);
                    uint256 _class =  DataStructures.packAttributes(elf.sentinelClass, elf.weaponTier, elf.inventory);
                    
                    elf.healthPoints = DataStructures.calcHealthPoints(elf.sentinelClass, elf.level);
                    elf.attackPoints = DataStructures.calcAttackPoints(elf.sentinelClass, elf.weaponTier); 
                sentinels[id] = DataStructures._setElf(elf.owner, elf.timestamp, elf.action, elf.healthPoints, elf.attackPoints, elf.primaryWeapon, elf.level, _traits, _class);
                
                }
                    
                _mint(_to, id);           
            }
            function _actions(
                uint256 id_, 
                uint action, 
                address elfOwner, 
                uint256 campaign_, 
                uint256 sector_, 
                bool rollWeapons, 
                bool rollItems, 
                bool useItem, 
                uint256 gameMode_) 
            
            private {
                DataStructures.Elf memory elf = DataStructures.getElf(sentinels[id_]);
                DataStructures.ActionVariables memory actions;
                require(isGameActive);
                require(ownerOf[id_] == msg.sender || elf.owner == msg.sender, "NotYourElf");
                require(elf.action != 8, "elf in Polygon");
                uint256 rand = _rand();
                    
                    if(action == 0){//Unstake if currently staked
                        require(ownerOf[id_] == address(this));
                        require(elf.timestamp < block.timestamp, "elf busy");
                         
                         if(elf.action == 3){
                         actions.timeDiff = (block.timestamp - elf.timestamp) / 1 days; //amount of time spent in camp CHANGE TO 1 DAYS!
                         elf.level = _exitPassive(actions.timeDiff, elf.level);
                        
                         }
                        _transfer(address(this), elfOwner, id_);      
                        elf.owner = address(0);                            
                    }else if(action == 2){//campaign loop - bloodthirst and rampage mode loop.
                        require(elf.timestamp < block.timestamp, "elf busy");
                        require(elf.action != 3, "exit passive mode first");                 
                
                            if(ownerOf[id_] != address(this)){
                            _transfer(elfOwner, address(this), id_);
                            elf.owner = elfOwner;
                            }
     
                        (elf.level, actions.reward, elf.timestamp, elf.inventory) = campaigns.gameEngine(campaign_, sector_, elf.level, elf.attackPoints, elf.healthPoints, elf.inventory, useItem);
                        
                        uint256 options;
                        if(rollWeapons && rollItems){
                            options = 3;
                            }else if(rollWeapons){
                            options = 1;
                            }else if(rollItems){
                            options = 2;
                            }else{
                            options = 0;
                        }
                      
                        if(options > 0){
                           (elf.weaponTier, elf.primaryWeapon, elf.inventory) 
                                        = DataStructures.roll(id_, elf.level, _rand(), options, elf.weaponTier, elf.primaryWeapon, elf.inventory);                                    
                                        
                        }
                        
                        if(gameMode_ == 1 || gameMode_ == 2) _setAccountBalance(msg.sender, actions.reward, false);
                        if(gameMode_ == 3) elf.level = elf.level + 1;
                        
                        emit Campaigns(msg.sender, actions.reward, campaign_, sector_, id_);
                    
                    }else if(action == 3){//passive campaign
                        require(elf.timestamp < block.timestamp, "elf busy");
                        
                            if(ownerOf[id_] != address(this)){
                                _transfer(elfOwner, address(this), id_);
                                elf.owner = elfOwner;
                             
                            }
                        elf.timestamp = block.timestamp; //set timestamp to current block time
                    }else if(action == 4){///return from passive mode
                        
                        require(elf.action == 3);                    
                        actions.timeDiff = (block.timestamp - elf.timestamp) / 1 days; //amount of time spent in camp CHANGE TO 1 DAYS!
                        elf.level = _exitPassive(actions.timeDiff, elf.level);
                       
                        
                      /*  if(actions.timeDiff >= 7){
                            actions.reward = 140 ether;
                        }
                        if(actions.timeDiff >= 14 && actions.timeDiff < 30){
                            actions.reward = 420 ether;
                        }
                        if(actions.timeDiff >= 30){
                            actions.reward = 1200 ether;
                        }
                        
                        elf.level = elf.level + (actions.timeDiff * 1); //one level per day
                        elf.level = elf.level > 100 ? 100 : elf.level;
     
                        _setAccountBalance(msg.sender, actions.reward, false);
                    */
                    }else if(action == 5){//forge loop for weapons
                       
                        require(msg.value >= .01 ether);  
                        require(elf.action != 3); //Cant roll in passve mode  
                       //                    
                       // (elf.weaponTier, elf.primaryWeapon, elf.inventory) = DataStructures.roll(id_, elf.level, rand, 1, elf.weaponTier, elf.primaryWeapon, elf.inventory);
                       (elf.primaryWeapon, elf.weaponTier) = _rollWeapon(elf.level, id_, rand);
       
                    
                    }else if(action == 6){//item or merchant loop
                       
                        require(msg.value >= .01 ether); 
                        require(elf.action != 3); //Cant roll in passve mode
                        (elf.weaponTier, elf.primaryWeapon, elf.inventory) = DataStructures.roll(id_, elf.level, rand, 2, elf.weaponTier, elf.primaryWeapon, elf.inventory);                      
                    }else if(action == 7){//healing loop
                        require(elf.sentinelClass == 0, "not a healer"); 
                        require(elf.action != 3, "cant heal while passive"); //Cant heal in passve mode
                        require(elf.timestamp < block.timestamp, "elf busy");
                            if(ownerOf[id_] != address(this)){
                            _transfer(elfOwner, address(this), id_);
                            elf.owner = elfOwner;
                            }
                        
                        
                        elf.timestamp = block.timestamp + (12 hours);
                        elf.level = elf.level + 1;
                        
                        {   
                            DataStructures.Elf memory hElf = DataStructures.getElf(sentinels[campaign_]);//using the campaign varialbe for elfId here.
                            require(ownerOf[campaign_] == msg.sender || hElf.owner == msg.sender, "NotYourElf");
                                   
                                    if(block.timestamp < hElf.timestamp){
                                            actions.timeDiff = hElf.timestamp - block.timestamp;
                    
                                            actions.timeDiff = actions.timeDiff > 0 ? 
                                                
                                                hElf.sentinelClass == 0 ? 0 : 
                                                hElf.sentinelClass == 1 ? actions.timeDiff * 1/4 : 
                                                actions.timeDiff * 1/2
                                            
                                            : actions.timeDiff;
                                            
                                            hElf.timestamp = hElf.timestamp - actions.timeDiff;                        
                                            
                                    }
                                
                            actions.traits = DataStructures.packAttributes(hElf.hair, hElf.race, hElf.accessories);
                            actions.class =  DataStructures.packAttributes(hElf.sentinelClass, hElf.weaponTier, hElf.inventory);
                                    
                            sentinels[campaign_] = DataStructures._setElf(hElf.owner, hElf.timestamp, hElf.action, hElf.healthPoints, hElf.attackPoints, hElf.primaryWeapon, hElf.level, actions.traits, actions.class);
                    }
                    }else if (action == 8){//checkIn loop Do not remove
                        
                            if(ownerOf[id_] != address(this)){
                                 _transfer(elfOwner, address(this), id_);
                                 elf.owner = elfOwner;                                
                            }
                        
                     
                    }           
                 
                actions.traits   = DataStructures.packAttributes(elf.hair, elf.race, elf.accessories);
                actions.class    = DataStructures.packAttributes(elf.sentinelClass, elf.weaponTier, elf.inventory);
                elf.healthPoints = DataStructures.calcHealthPoints(elf.sentinelClass, elf.level); 
                elf.attackPoints = DataStructures.calcAttackPoints(elf.sentinelClass, elf.weaponTier);  
                elf.level        = elf.level > 100 ? 100 : elf.level; 
                elf.action       = action;
                sentinels[id_] = DataStructures._setElf(elf.owner, elf.timestamp, elf.action, elf.healthPoints, elf.attackPoints, elf.primaryWeapon, elf.level, actions.traits, actions.class);
                emit Action(msg.sender, action, id_); 
        }
        function _exitPassive(uint256 timeDiff, uint256 _level) private returns (uint256 level) {
                
                uint256 rewards;
                        if(timeDiff >= 7){
                            rewards = 140 ether;
                        }
                        if(timeDiff >= 14 && timeDiff < 30){
                            rewards = 420 ether;
                        }
                        if(timeDiff >= 30){
                            rewards = 1200 ether;
                        }
                        
                        level = _level + (timeDiff * 1); //one level per day
                        
                        if(level >= 100){
                            level = 100;
                        }
                        
                       
                        _setAccountBalance(msg.sender, rewards, false);
        }
        function _rollWeapon(uint256 level, uint256 id, uint256 rand) internal pure returns (uint256 newWeapon, uint256 newWeaponTier) {
        
            uint256 levelTier = level == 100 ? 5 : uint256((level/20) + 1);
                    
                    uint256  chance = _randomize(rand, "Weapon", id) % 100;
          
                    if(chance > 10 && chance < 80){
            
                                 newWeaponTier = levelTier;
            
                            }else if (chance > 80 ){
            
                                 newWeaponTier = levelTier + 1 > 4 ? 4 : levelTier + 1;
            
                            }else{
                                    newWeaponTier = levelTier - 1 < 1 ? 1 : levelTier - 1;          
                            }
                             
                    newWeapon = ((newWeaponTier - 1) * 3) + (rand % 3);  
                
            
        }
        
        function _setAccountBalance(address _owner, uint256 _amount, bool _subtract) private {
                
                _subtract ? bankBalances[_owner] -= _amount : bankBalances[_owner] += _amount;
                emit BalanceChanged(_owner, _amount, _subtract);
        }
        //NOTE BEFF WE NEED TO CHANGE THIS
        function getMintPriceLevel() public view returns (uint256 mintCost, uint256 mintLevel) {
                
                if (totalSupply <= INIT_SUPPLY) return  (price, 1);
                if (totalSupply < 4000) return  (  60 ether, 3);
                if (totalSupply < 4500) return  ( 180 ether, 5);
                if (totalSupply < 5000) return  ( 360 ether, 15);
                if (totalSupply < 5500) return  ( 600 ether, 25);
                if (totalSupply < 6000) return  ( 900 ether, 35);
                if (totalSupply < 6333) return  ( 1800 ether, 45);
                if (totalSupply < 6666) return  ( 2700  ether, 60);
        }
        function _randomize(uint256 ran, string memory dom, uint256 ness) internal pure returns (uint256) {
        return uint256(keccak256(abi.encode(ran,dom,ness)));}
        function _rand() internal view returns (uint256) {
        return uint256(keccak256(abi.encodePacked(msg.sender, block.difficulty, block.timestamp, block.basefee, ketchup)));}
    //PUBLIC VIEWS
        function tokenURI(uint256 _id) external view returns(string memory) {
        return elfmetaDataHandler.getTokenURI(uint16(_id), sentinels[_id]);
        }
        function attributes(uint256 _id) external view returns(uint hair, uint race, uint accessories, uint sentinelClass, uint weaponTier, uint inventory){
        uint256 character = sentinels[_id];
        uint _traits =        uint256(uint8(character>>240));
        uint _class =         uint256(uint8(character>>248));
        hair           = (_traits / 100) % 10;
        race           = (_traits / 10) % 10;
        accessories    = (_traits) % 10;
        sentinelClass  = (_class / 100) % 10;
        weaponTier     = (_class / 10) % 10;
        inventory      = (_class) % 10; 
    }
    function getSentinel(uint256 _id) external view returns(uint256 sentinel){
        return sentinel = sentinels[_id];
    }
    function getToken(uint256 _id) external view returns(DataStructures.Token memory token){
       
        return DataStructures.getToken(sentinels[_id]);
    }
    function elves(uint256 _id) external view returns(address owner, uint timestamp, uint action, uint healthPoints, uint attackPoints, uint primaryWeapon, uint level) {
        uint256 character = sentinels[_id];
        owner =          address(uint160(uint256(character)));
        timestamp =      uint(uint40(character>>160));
        action =         uint(uint8(character>>200));
        healthPoints =   uint(uint8(character>>208));
        attackPoints =   uint(uint8(character>>216));
        primaryWeapon =  uint(uint8(character>>224));
        level =          uint(uint8(character>>232));   
    }
    //Modifiers but as functions. Less Gas
        function isPlayer() internal {    
            uint256 size = 0;
            address acc = msg.sender;
            assembly { size := extcodesize(acc)}
            require((msg.sender == tx.origin && size == 0));
            ketchup = keccak256(abi.encodePacked(acc, block.coinbase));
        }
        function onlyOwner() internal view {    
            require(admin == msg.sender || auth[msg.sender] == true || dev1Address == msg.sender || dev2Address == msg.sender);
        }
    //Bridge and Tunnel Stuff
        function modifyElfDNA(uint256 id, uint256 sentinel) external {
            require (msg.sender == terminus || admin == msg.sender, "not terminus");
            sentinels[id] = sentinel;
        }
       /*   function pull(address owner_, uint256[] calldata ids) external {
            require (msg.sender == terminus, "not terminus"); 
            for (uint256 index = 0; index < ids.length; index++) {
                  _transfer(owner_, msg.sender, ids[index]);
            }
            ITerminus(msg.sender).pullCallback(owner_, ids);
        }
      */
    //ADMIN Only
        function withdrawAll() public {
            onlyOwner();
            uint256 balance = address(this).balance;
            
            uint256 devShare = balance/2;      
            require(balance > 0);
            _withdraw(dev1Address, devShare);
            _withdraw(dev2Address, devShare);
        }
        //Internal withdraw
        function _withdraw(address _address, uint256 _amount) private {
            (bool success, ) = _address.call{value: _amount}("");
            require(success);
        }
        function flipActiveStatus() external {
            onlyOwner();
            isGameActive = !isGameActive;
        }
        function flipMint() external {
            onlyOwner();
            isMintOpen = !isMintOpen;
        }
        /*function flipWhitelist() external {
            onlyOwner();
            isWlOpen = !isWlOpen;
        }*/
         function flipTerminal() external {
            onlyOwner();
            isTerminalOpen = !isTerminalOpen;
        }
        
        
       function setAccountBalance(address _owner, uint256 _amount) public {                
            onlyOwner();
            bankBalances[_owner] += _amount;
        }
     
        function reserve(uint256 _reserveAmount, address _to) public {    
            onlyOwner();        
            for (uint i = 0; i < _reserveAmount; i++) {
                _mintElf(_to);
            }
        }
        function setElfManually(uint id, uint8 _primaryWeapon, uint8 _weaponTier, uint8 _attackPoints, uint8 _healthPoints, uint8 _level, uint8 _inventory, uint8 _race, uint8 _class, uint8 _accessories) external {
            onlyOwner();
            DataStructures.Elf memory elf = DataStructures.getElf(sentinels[id]);
            DataStructures.ActionVariables memory actions;
            elf.owner           = elf.owner;
            elf.timestamp       = elf.timestamp;
            elf.action          = elf.action;
            elf.healthPoints    = _healthPoints;
            elf.attackPoints    = _attackPoints;
            elf.primaryWeapon   = _primaryWeapon;
            elf.level           = _level;
            elf.weaponTier      = _weaponTier;
            elf.inventory       = _inventory;
            elf.race            = _race;
            elf.sentinelClass   = _class;
            elf.accessories     = _accessories;
            actions.traits = DataStructures.packAttributes(elf.hair, elf.race, elf.accessories);
            actions.class =  DataStructures.packAttributes(elf.sentinelClass, elf.weaponTier, elf.inventory);
                           
            sentinels[id] = DataStructures._setElf(elf.owner, elf.timestamp, elf.action, elf.healthPoints, elf.attackPoints, elf.primaryWeapon, elf.level, actions.traits, actions.class);
            
        }
        
       /* function addManyToWhitelist(address[] calldata _addr, uint256 _whitelistRole) public {
            onlyOwner();
            
                for (uint256 index = 0; index < _addr.length; index++) {
                    whitelist[_addr[index]] = uint16(_whitelistRole);
                }
        }    
        */
        /*
    function whitelistMint(uint256 qty, address to, uint256 roleIndex, bytes memory signature) public payable  {
        
        isPlayer();
        require(_isSignedByValidator(encodeForSignature(to, roleIndex),signature), "incorrect signature");  /////Francesco Sullo Thanks for showing me how to do this. Follow @sullof
        require(isWlOpen, "Whitelist is closed");
        require(whitelist[to] != 1,"Wallet used already"); //not on whitelist
        require(_remaining[roleIndex] > 0, "noneLeft");
        require(qty > 0 && qty <= 2, "max 2"); //max 2
        
        //Role:0 SOG 2 free Role:1 OG 1 free 1 paid Role:2 2 WL paid
        //  bytes32 messageHash = encodeForSignature(to, roleIndex);
        //  bool isValid = _isSignedByValidator(messageHash, signature);
        
        //     if(isValid){
        //        console.log("valid");
        //   }
        
        uint256 amount = msg.value;
        
        _remaining[roleIndex] = _remaining[roleIndex] - qty;
        whitelist[to] = 1; //indicate that address is used
            if(roleIndex == 0){
                
               for (uint i = 0; i < qty; i++) {
                    _mintElf(to);
                }
            }else if(roleIndex == 1){
               
                require(amount >= price * qty/2, "NotEnoughEther");
                for (uint i = 0; i < qty; i++) {
                    _mintElf(to);
                }
            }else if(roleIndex == 2){
                 require(amount >= price * qty, "NotEnoughEther");
                 for (uint i = 0; i < qty; i++) {
                    _mintElf(to);
                 }
            }
        }
    */
        
      /*  function setTerminus(address _terminus)  public {
           onlyOwner();
           terminus             = _terminus;
        }
      
        
        function setInitialSupply(uint256 _initialSupply)  public {
           onlyOwner();
           INIT_SUPPLY             = _initialSupply;
        }
    */
    }// SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.0 (utils/cryptography/ECDSA.sol)
    pragma solidity ^0.8.0;
    import "../Strings.sol";
    /**
     * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
     *
     * These functions can be used to verify that a message was signed by the holder
     * of the private keys of a given address.
     */
    library ECDSA {
        enum RecoverError {
            NoError,
            InvalidSignature,
            InvalidSignatureLength,
            InvalidSignatureS,
            InvalidSignatureV
        }
        function _throwError(RecoverError error) private pure {
            if (error == RecoverError.NoError) {
                return; // no error: do nothing
            } else if (error == RecoverError.InvalidSignature) {
                revert("ECDSA: invalid signature");
            } else if (error == RecoverError.InvalidSignatureLength) {
                revert("ECDSA: invalid signature length");
            } else if (error == RecoverError.InvalidSignatureS) {
                revert("ECDSA: invalid signature 's' value");
            } else if (error == RecoverError.InvalidSignatureV) {
                revert("ECDSA: invalid signature 'v' value");
            }
        }
        /**
         * @dev Returns the address that signed a hashed message (`hash`) with
         * `signature` or error string. This address can then be used for verification purposes.
         *
         * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
         * this function rejects them by requiring the `s` value to be in the lower
         * half order, and the `v` value to be either 27 or 28.
         *
         * IMPORTANT: `hash` _must_ be the result of a hash operation for the
         * verification to be secure: it is possible to craft signatures that
         * recover to arbitrary addresses for non-hashed data. A safe way to ensure
         * this is by receiving a hash of the original message (which may otherwise
         * be too long), and then calling {toEthSignedMessageHash} on it.
         *
         * Documentation for signature generation:
         * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
         * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
         *
         * _Available since v4.3._
         */
        function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
            // Check the signature length
            // - case 65: r,s,v signature (standard)
            // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
            if (signature.length == 65) {
                bytes32 r;
                bytes32 s;
                uint8 v;
                // ecrecover takes the signature parameters, and the only way to get them
                // currently is to use assembly.
                assembly {
                    r := mload(add(signature, 0x20))
                    s := mload(add(signature, 0x40))
                    v := byte(0, mload(add(signature, 0x60)))
                }
                return tryRecover(hash, v, r, s);
            } else if (signature.length == 64) {
                bytes32 r;
                bytes32 vs;
                // ecrecover takes the signature parameters, and the only way to get them
                // currently is to use assembly.
                assembly {
                    r := mload(add(signature, 0x20))
                    vs := mload(add(signature, 0x40))
                }
                return tryRecover(hash, r, vs);
            } else {
                return (address(0), RecoverError.InvalidSignatureLength);
            }
        }
        /**
         * @dev Returns the address that signed a hashed message (`hash`) with
         * `signature`. This address can then be used for verification purposes.
         *
         * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
         * this function rejects them by requiring the `s` value to be in the lower
         * half order, and the `v` value to be either 27 or 28.
         *
         * IMPORTANT: `hash` _must_ be the result of a hash operation for the
         * verification to be secure: it is possible to craft signatures that
         * recover to arbitrary addresses for non-hashed data. A safe way to ensure
         * this is by receiving a hash of the original message (which may otherwise
         * be too long), and then calling {toEthSignedMessageHash} on it.
         */
        function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
            (address recovered, RecoverError error) = tryRecover(hash, signature);
            _throwError(error);
            return recovered;
        }
        /**
         * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
         *
         * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
         *
         * _Available since v4.3._
         */
        function tryRecover(
            bytes32 hash,
            bytes32 r,
            bytes32 vs
        ) internal pure returns (address, RecoverError) {
            bytes32 s;
            uint8 v;
            assembly {
                s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
                v := add(shr(255, vs), 27)
            }
            return tryRecover(hash, v, r, s);
        }
        /**
         * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
         *
         * _Available since v4.2._
         */
        function recover(
            bytes32 hash,
            bytes32 r,
            bytes32 vs
        ) internal pure returns (address) {
            (address recovered, RecoverError error) = tryRecover(hash, r, vs);
            _throwError(error);
            return recovered;
        }
        /**
         * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
         * `r` and `s` signature fields separately.
         *
         * _Available since v4.3._
         */
        function tryRecover(
            bytes32 hash,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) internal pure returns (address, RecoverError) {
            // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
            // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
            // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
            // signatures from current libraries generate a unique signature with an s-value in the lower half order.
            //
            // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
            // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
            // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
            // these malleable signatures as well.
            if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                return (address(0), RecoverError.InvalidSignatureS);
            }
            if (v != 27 && v != 28) {
                return (address(0), RecoverError.InvalidSignatureV);
            }
            // If the signature is valid (and not malleable), return the signer address
            address signer = ecrecover(hash, v, r, s);
            if (signer == address(0)) {
                return (address(0), RecoverError.InvalidSignature);
            }
            return (signer, RecoverError.NoError);
        }
        /**
         * @dev Overload of {ECDSA-recover} that receives the `v`,
         * `r` and `s` signature fields separately.
         */
        function recover(
            bytes32 hash,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) internal pure returns (address) {
            (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
            _throwError(error);
            return recovered;
        }
        /**
         * @dev Returns an Ethereum Signed Message, created from a `hash`. This
         * produces hash corresponding to the one signed with the
         * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
         * JSON-RPC method as part of EIP-191.
         *
         * See {recover}.
         */
        function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
            // 32 is the length in bytes of hash,
            // enforced by the type signature above
            return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
    32", hash));
        }
        /**
         * @dev Returns an Ethereum Signed Message, created from `s`. This
         * produces hash corresponding to the one signed with the
         * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
         * JSON-RPC method as part of EIP-191.
         *
         * See {recover}.
         */
        function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
            return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
    ", Strings.toString(s.length), s));
        }
        /**
         * @dev Returns an Ethereum Signed Typed Data, created from a
         * `domainSeparator` and a `structHash`. This produces hash corresponding
         * to the one signed with the
         * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
         * JSON-RPC method as part of EIP-712.
         *
         * See {recover}.
         */
        function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
            return keccak256(abi.encodePacked("\\x19\\x01", domainSeparator, structHash));
        }
    }
    // SPDX-License-Identifier: AGPL-3.0-only
    pragma solidity 0.8.7;
    /// @notice Modern and gas efficient ERC-721 + ERC-20/EIP-2612-like implementation,
    /// including the MetaData, and partially, Enumerable extensions.
    contract ERC721 {
        /*///////////////////////////////////////////////////////////////
                                      EVENTS
        //////////////////////////////////////////////////////////////*/
        event Transfer(
            address indexed from,
            address indexed to,
            uint256 indexed tokenId
        );
        event Approval(
            address indexed owner,
            address indexed spender,
            uint256 indexed tokenId
        );
        event ApprovalForAll(
            address indexed owner,
            address indexed operator,
            bool approved
        );
        /*///////////////////////////////////////////////////////////////
                                 METADATA STORAGE
        //////////////////////////////////////////////////////////////*/
        address implementation_;
        address public admin;
        /*///////////////////////////////////////////////////////////////
                                 ERC-721 STORAGE
        //////////////////////////////////////////////////////////////*/
        uint256 public totalSupply;
        uint256 public maxSupply;
        mapping(address => uint256) public balanceOf;
        mapping(uint256 => address) public ownerOf;
        mapping(uint256 => address) public getApproved;
        mapping(address => mapping(address => bool)) public isApprovedForAll;
        /*///////////////////////////////////////////////////////////////
                                 VIEW FUNCTION
        //////////////////////////////////////////////////////////////*/
        function owner() external view returns (address) {
            return admin;
        }
        /*///////////////////////////////////////////////////////////////
                                  ERC-20-LIKE LOGIC
        //////////////////////////////////////////////////////////////*/
        function transfer(address to, uint256 tokenId) external {
            require(msg.sender == ownerOf[tokenId], "NOT_OWNER");
            _transfer(msg.sender, to, tokenId);
        }
        /*///////////////////////////////////////////////////////////////
                                  ERC-721 LOGIC
        //////////////////////////////////////////////////////////////*/
        function supportsInterface(bytes4 interfaceId)
            external
            pure
            returns (bool supported)
        {
            supported = interfaceId == 0x80ac58cd || interfaceId == 0x5b5e139f;
        }
        function approve(address spender, uint256 tokenId) external {
            address owner_ = ownerOf[tokenId];
            require(
                msg.sender == owner_ || isApprovedForAll[owner_][msg.sender],
                "NOT_APPROVED"
            );
            getApproved[tokenId] = spender;
            emit Approval(owner_, spender, tokenId);
        }
        function setApprovalForAll(address operator, bool approved) external {
            isApprovedForAll[msg.sender][operator] = approved;
            emit ApprovalForAll(msg.sender, operator, approved);
        }
        function transferFrom(address from, address to, uint256 tokenId) public {
            address owner_ = ownerOf[tokenId];
            require(
                msg.sender == owner_ ||
                    msg.sender == getApproved[tokenId] ||
                    isApprovedForAll[owner_][msg.sender],
                "NOT_APPROVED"
            );
            _transfer(from, to, tokenId);
        }
        function safeTransferFrom(address from, address to, uint256 tokenId) external {
            safeTransferFrom(from, to, tokenId, "");
        }
        function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public {
            transferFrom(from, to, tokenId);
            if (to.code.length != 0) {
                // selector = `onERC721Received(address,address,uint,bytes)`
                (, bytes memory returned) = to.staticcall(
                    abi.encodeWithSelector(
                        0x150b7a02,
                        msg.sender,
                        from, 
                        tokenId,
                        data
                    )
                );
                bytes4 selector = abi.decode(returned, (bytes4));
                require(selector == 0x150b7a02, "NOT_ERC721_RECEIVER");
            }
        }
        /*///////////////////////////////////////////////////////////////
                              INTERNAL UTILS
        //////////////////////////////////////////////////////////////*/
        function _transfer(address from, address to, uint256 tokenId) internal {
            
            require(ownerOf[tokenId] == from);
            balanceOf[from]--;
            balanceOf[to]++;
            delete getApproved[tokenId];
            ownerOf[tokenId] = to;
            emit Transfer(from, to, tokenId);
        }
        function _mint(address to, uint256 tokenId) internal {
            require(ownerOf[tokenId] == address(0), "ALREADY_MINTED");
            require(totalSupply++ <= maxSupply, "MAX SUPPLY REACHED");
            // This is safe because the sum of all user
            // balances can't exceed type(uint256).max!
            unchecked {
                balanceOf[to]++;
            }
            ownerOf[tokenId] = to;
            emit Transfer(address(0), to, tokenId);
        }
        function _burn(uint256 tokenId) internal {
            address owner_ = ownerOf[tokenId];
            require(ownerOf[tokenId] != address(0), "NOT_MINTED");
            totalSupply--;
            balanceOf[owner_]--;
            delete ownerOf[tokenId];
            emit Transfer(owner_, address(0), tokenId);
        }
    }
    // SPDX-License-Identifier: Unlicense
    pragma solidity 0.8.7;
    //import "hardhat/console.sol"; ///REMOVE BEFORE DEPLOYMENT
    //v 1.0.3
    library DataStructures {
    /////////////DATA STRUCTURES///////////////////////////////
        struct Elf {
                address owner;  
                uint256 timestamp; 
                uint256 action; 
                uint256 healthPoints;
                uint256 attackPoints; 
                uint256 primaryWeapon; 
                uint256 level;
                uint256 hair;
                uint256 race; 
                uint256 accessories; 
                uint256 sentinelClass; 
                uint256 weaponTier; 
                uint256 inventory; 
        }
        struct Token {
                address owner;  
                uint256 timestamp; 
                uint8 action; 
                uint8 healthPoints;
                uint8 attackPoints; 
                uint8 primaryWeapon; 
                uint8 level;
                uint8 hair;
                uint8 race; 
                uint8 accessories; 
                uint8 sentinelClass; 
                uint8 weaponTier; 
                uint8 inventory; 
        }
        struct ActionVariables {
                uint256 reward;
                uint256 timeDiff;
                uint256 traits; 
                uint256 class;  
        }
        struct Camps {
                uint32 baseRewards; 
                uint32 creatureCount; 
                uint32 creatureHealth; 
                uint32 expPoints; 
                uint32 minLevel;
                uint32 itemDrop;
                uint32 weaponDrop;
                uint32 spare;
        }
        /*Dont Delete, just keep it for reference
        struct Attributes { 
                uint256 hair; //MAX 3 3 hair traits
                uint256 race;  //MAX 6 Body 4 for body
                uint256 accessories; //MAX 7 4 
                uint256 sentinelClass; //MAX 3 3 in class
                uint256 weaponTier; //MAX 6 5 tiers
                uint256 inventory; //MAX 7 6 items
        }
        */
    /////////////////////////////////////////////////////
    function getElf(uint256 character) internal pure returns(Elf memory _elf) {
       
        _elf.owner =          address(uint160(uint256(character)));
        _elf.timestamp =      uint256(uint40(character>>160));
        _elf.action =         uint256(uint8(character>>200));
        _elf.healthPoints =       uint256(uint8(character>>208));
        _elf.attackPoints =   uint256(uint8(character>>216));
        _elf.primaryWeapon =  uint256(uint8(character>>224));
        _elf.level    =       uint256(uint8(character>>232));
        _elf.hair           = (uint256(uint8(character>>240)) / 100) % 10;
        _elf.race           = (uint256(uint8(character>>240)) / 10) % 10;
        _elf.accessories    = (uint256(uint8(character>>240))) % 10;
        _elf.sentinelClass  = (uint256(uint8(character>>248)) / 100) % 10;
        _elf.weaponTier     = (uint256(uint8(character>>248)) / 10) % 10;
        _elf.inventory      = (uint256(uint8(character>>248))) % 10; 
    } 
    function getToken(uint256 character) internal pure returns(Token memory token) {
       
        token.owner          = address(uint160(uint256(character)));
        token.timestamp      = uint256(uint40(character>>160));
        token.action         = (uint8(character>>200));
        token.healthPoints   = (uint8(character>>208));
        token.attackPoints   = (uint8(character>>216));
        token.primaryWeapon  = (uint8(character>>224));
        token.level          = (uint8(character>>232));
        token.hair           = ((uint8(character>>240)) / 100) % 10; //MAX 3
        token.race           = ((uint8(character>>240)) / 10) % 10; //Max6
        token.accessories    = ((uint8(character>>240))) % 10; //Max7
        token.sentinelClass  = ((uint8(character>>248)) / 100) % 10; //MAX 3
        token.weaponTier     = ((uint8(character>>248)) / 10) % 10; //MAX 6
        token.inventory      = ((uint8(character>>248))) % 10; //MAX 7
        token.hair = (token.sentinelClass * 3) + (token.hair + 1);
        token.race = (token.sentinelClass * 4) + (token.race + 1);
        token.primaryWeapon = token.primaryWeapon == 69 ? 69 : (token.sentinelClass * 15) + (token.primaryWeapon + 1);
        token.accessories = (token.sentinelClass * 7) + (token.accessories + 1);
    }
    function _setElf(
                    address owner, uint256 timestamp, uint256 action, uint256 healthPoints, 
                    uint256 attackPoints, uint256 primaryWeapon, 
                    uint256 level, uint256 traits, uint256 class )
        internal pure returns (uint256 sentinel) {
        uint256 character = uint256(uint160(address(owner)));
        
        character |= timestamp<<160;
        character |= action<<200;
        character |= healthPoints<<208;
        character |= attackPoints<<216;
        character |= primaryWeapon<<224;
        character |= level<<232;
        character |= traits<<240;
        character |= class<<248;
        
        return character;
    }
    //////////////////////////////HELPERS/////////////////
    function packAttributes(uint hundreds, uint tens, uint ones) internal pure returns (uint256 packedAttributes) {
        packedAttributes = uint256(hundreds*100 + tens*10 + ones);
        return packedAttributes;
    }
    function calcAttackPoints(uint256 sentinelClass, uint256 weaponTier) internal pure returns (uint256 attackPoints) {
            attackPoints = ((sentinelClass + 1) * 2) + (weaponTier * 2);
            
            return attackPoints;
    }
    function calcHealthPoints(uint256 sentinelClass, uint256 level) internal pure returns (uint256 healthPoints) {
            healthPoints = (level/(3) +2) + (20 - (sentinelClass * 4));
            
            return healthPoints;
    }
    function calcCreatureHealth(uint256 sector, uint256 baseCreatureHealth) internal pure returns (uint256 creatureHealth) {
            creatureHealth = ((sector - 1) * 12) + baseCreatureHealth; 
            
            return creatureHealth;
    }
    function roll(uint256 id_, uint256 level_, uint256 rand, uint256 rollOption_, uint256 weaponTier_, uint256 primaryWeapon_, uint256 inventory_) 
    internal pure 
    returns (uint256 newWeaponTier, uint256 newWeapon, uint256 newInventory) {
       uint256 levelTier = level_ == 100 ? 5 : uint256((level_/20) + 1);
       newWeaponTier = weaponTier_;
       newWeapon     = primaryWeapon_;
       newInventory  = inventory_;
       if(rollOption_ == 1 || rollOption_ == 3){
           //Weapons
          
            uint16  chance = uint16(_randomize(rand, "Weapon", id_)) % 100;
           // console.log("chance: ", chance);
                    if(chance > 10 && chance < 80){
            
                                  newWeaponTier = levelTier;
            
                            }else if (chance > 80 ){
            
                                  newWeaponTier = levelTier + 1 > 5 ? 5 : levelTier + 1;
            
                            }else{
                                    newWeaponTier = levelTier - 1 < 1 ? 1 : levelTier - 1;          
                            }
                                             
            
            newWeapon = newWeaponTier == 0 ? 0 : ((newWeaponTier - 1) * 3) + (rand % 3);  
            
       }
       
       if(rollOption_ == 2 || rollOption_ == 3){//Items Loop
          
           
            uint16 morerand = uint16(_randomize(rand, "Inventory", id_));
            uint16 diceRoll = uint16(_randomize(rand, "Dice", id_));
            
            diceRoll = (diceRoll % 100);
            
            if(diceRoll <= 20){
                newInventory = levelTier > 3 ? morerand % 3 + 3: morerand % 6 + 1;
                //console.log("Token#: ", id_);
                //console.log("newITEM: ", newInventory);
            } 
       }
                          
                  
    }
    function _randomize(uint256 ran, string memory dom, uint256 ness) internal pure returns (uint256) {
        return uint256(keccak256(abi.encode(ran,dom,ness)));}
    }
    // SPDX-License-Identifier: Unlicense
    pragma solidity 0.8.7;
    interface IERC20Lite {
        
        function transfer(address to, uint256 value) external returns (bool);
        function burn(address from, uint256 value) external;
        function mint(address to, uint256 value) external; 
    }
    interface IElfMetaDataHandler {    
    function getTokenURI(uint16 id_, uint256 sentinel) external view returns (string memory);
    }
    interface ICampaigns {
    function gameEngine(uint256 _campId, uint256 _sector, uint256 _level, uint256 _attackPoints, uint256 _healthPoints, uint256 _inventory, bool _useItem) external 
    returns(uint256 level, uint256 rewards, uint256 timestamp, uint256 inventory);
    }
    interface ITunnel {
        function sendMessage(bytes calldata message_) external;
    }
    interface ITerminus {
        function pullCallback(address owner, uint256[] calldata ids) external;
        
    }
    interface IElves {
        function getSentinel(uint256 _id) external view returns(uint256 sentinel);
        function modifyElfDNA(uint256 id, uint256 sentinel) external;
        function pull(address owner_, uint256[] calldata ids) external;
        function transfer(address to, uint256 id) external;
    }
    interface IERC721Lite {
        function transferFrom(address from, address to, uint256 id) external;   
        function transfer(address to, uint256 id) external;
        function ownerOf(uint256 id) external returns (address owner);
        function mint(address to, uint256 tokenid) external;
    }
    interface IEthernalElves {
    function presale(uint256 _reserveAmount, address _whitelister) external payable returns (uint256 id);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.0 (utils/Strings.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev String operations.
     */
    library Strings {
        bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
        /**
         * @dev Converts a `uint256` to its ASCII `string` decimal representation.
         */
        function toString(uint256 value) internal pure returns (string memory) {
            // Inspired by OraclizeAPI's implementation - MIT licence
            // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
            if (value == 0) {
                return "0";
            }
            uint256 temp = value;
            uint256 digits;
            while (temp != 0) {
                digits++;
                temp /= 10;
            }
            bytes memory buffer = new bytes(digits);
            while (value != 0) {
                digits -= 1;
                buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                value /= 10;
            }
            return string(buffer);
        }
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
         */
        function toHexString(uint256 value) internal pure returns (string memory) {
            if (value == 0) {
                return "0x00";
            }
            uint256 temp = value;
            uint256 length = 0;
            while (temp != 0) {
                length++;
                temp >>= 8;
            }
            return toHexString(value, length);
        }
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
         */
        function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
            bytes memory buffer = new bytes(2 * length + 2);
            buffer[0] = "0";
            buffer[1] = "x";
            for (uint256 i = 2 * length + 1; i > 1; --i) {
                buffer[i] = _HEX_SYMBOLS[value & 0xf];
                value >>= 4;
            }
            require(value == 0, "Strings: hex length insufficient");
            return string(buffer);
        }
    }