ETH Price: $3,876.94 (-1.38%)
Gas: 5.72 Gwei

Contract

0xBfA75F3DCBEdFf790D60E2A34bF1698c6c269E16
 

Overview

ETH Balance

0.2519999999999994 ETH

Eth Value

$976.99 (@ $3,876.94/ETH)

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Set Max Supply11...151813472022-07-20 19:28:00878 days ago1658345280IN
0xBfA75F3D...c6c269E16
0 ETH0.0018486938.54490642
Batch Mint ERC11...149081032022-06-05 8:32:23923 days ago1654417943IN
0xBfA75F3D...c6c269E16
0 ETH0.0027145624.0994351
Batch Mint ERC11...147085272022-05-04 3:15:26956 days ago1651634126IN
0xBfA75F3D...c6c269E16
0.126 ETH0.0112189164.81851597
Batch Mint ERC11...147078352022-05-04 0:30:13956 days ago1651624213IN
0xBfA75F3D...c6c269E16
0 ETH0.012178750.62393559
Batch Mint ERC11...147078262022-05-04 0:28:20956 days ago1651624100IN
0xBfA75F3D...c6c269E16
0.126 ETH0.0077269144.79992524
Batch Mint ERC11...147073582022-05-03 22:42:53956 days ago1651617773IN
0xBfA75F3D...c6c269E16
0.126 ETH0.0076004954.74050322
Batch Mint ERC11...147072872022-05-03 22:25:56956 days ago1651616756IN
0xBfA75F3D...c6c269E16
0.126 ETH0.0085917149.64986681
Batch Mint ERC11...147069652022-05-03 21:07:38956 days ago1651612058IN
0xBfA75F3D...c6c269E16
0.126 ETH0.011826968.35493954
Batch Mint ERC11...147069032022-05-03 20:53:55956 days ago1651611235IN
0xBfA75F3D...c6c269E16
0.126 ETH0.0096542155.78986798
Batch Mint ERC11...147067572022-05-03 20:19:59956 days ago1651609199IN
0xBfA75F3D...c6c269E16
0.7 ETH0.0126307972.97874904
Batch Mint ERC11...147065782022-05-03 19:40:30956 days ago1651606830IN
0xBfA75F3D...c6c269E16
0.7 ETH0.0096392755.69419121
Batch Mint ERC11...147065322022-05-03 19:30:31956 days ago1651606231IN
0xBfA75F3D...c6c269E16
0.7 ETH0.0088493554.08713944
Batch Mint ERC11...147061372022-05-03 18:06:16956 days ago1651601176IN
0xBfA75F3D...c6c269E16
0.126 ETH0.0162370199.25793817
Batch Mint ERC11...147059132022-05-03 17:13:38956 days ago1651598018IN
0xBfA75F3D...c6c269E16
0.126 ETH0.0084495960.8453746
Batch Mint ERC11...147050352022-05-03 13:59:43956 days ago1651586383IN
0xBfA75F3D...c6c269E16
0.826 ETH0.0144116452.45291708
Batch Mint ERC11...147046792022-05-03 12:33:37956 days ago1651581217IN
0xBfA75F3D...c6c269E16
0 ETH0.0076559341.82681488
Batch Mint ERC11...147046722022-05-03 12:32:25956 days ago1651581145IN
0xBfA75F3D...c6c269E16
0.826 ETH0.0132926548.17225312
Batch Mint ERC11...147043112022-05-03 11:11:58956 days ago1651576318IN
0xBfA75F3D...c6c269E16
0.126 ETH0.0109874879.43982438
Batch Mint ERC11...147042802022-05-03 11:06:07956 days ago1651575967IN
0xBfA75F3D...c6c269E16
0.126 ETH0.0109874879.43982438
Batch Mint ERC11...147042422022-05-03 11:00:10956 days ago1651575610IN
0xBfA75F3D...c6c269E16
0.126 ETH0.0077110144.70155149
Batch Mint ERC11...147039842022-05-03 9:58:27956 days ago1651571907IN
0xBfA75F3D...c6c269E16
0.126 ETH0.0081263446.95407174
Batch Mint ERC11...147038262022-05-03 9:21:12956 days ago1651569672IN
0xBfA75F3D...c6c269E16
0.126 ETH0.0096089958.74040403
Batch Mint ERC11...147037802022-05-03 9:10:00956 days ago1651569000IN
0xBfA75F3D...c6c269E16
0.126 ETH0.0137043279.43982438
Batch Mint ERC11...147031602022-05-03 6:54:13957 days ago1651560853IN
0xBfA75F3D...c6c269E16
0.126 ETH0.0081684347.51716876
Batch Mint ERC11...147015962022-05-03 0:53:28957 days ago1651539208IN
0xBfA75F3D...c6c269E16
0.126 ETH0.0067069441
View all transactions

Latest 25 internal transactions (View All)

Advanced mode:
Parent Transaction Hash Block
From
To
147085272022-05-04 3:15:26956 days ago1651634126
0xBfA75F3D...c6c269E16
0.08442 ETH
147085272022-05-04 3:15:26956 days ago1651634126
0xBfA75F3D...c6c269E16
0.04158 ETH
147078262022-05-04 0:28:20956 days ago1651624100
0xBfA75F3D...c6c269E16
0.08442 ETH
147078262022-05-04 0:28:20956 days ago1651624100
0xBfA75F3D...c6c269E16
0.04158 ETH
147073582022-05-03 22:42:53956 days ago1651617773
0xBfA75F3D...c6c269E16
0.08442 ETH
147073582022-05-03 22:42:53956 days ago1651617773
0xBfA75F3D...c6c269E16
0.04158 ETH
147072872022-05-03 22:25:56956 days ago1651616756
0xBfA75F3D...c6c269E16
0.08442 ETH
147072872022-05-03 22:25:56956 days ago1651616756
0xBfA75F3D...c6c269E16
0.04158 ETH
147069652022-05-03 21:07:38956 days ago1651612058
0xBfA75F3D...c6c269E16
0.08442 ETH
147069652022-05-03 21:07:38956 days ago1651612058
0xBfA75F3D...c6c269E16
0.04158 ETH
147069032022-05-03 20:53:55956 days ago1651611235
0xBfA75F3D...c6c269E16
0.08442 ETH
147069032022-05-03 20:53:55956 days ago1651611235
0xBfA75F3D...c6c269E16
0.04158 ETH
147067572022-05-03 20:19:59956 days ago1651609199
0xBfA75F3D...c6c269E16
0.469 ETH
147067572022-05-03 20:19:59956 days ago1651609199
0xBfA75F3D...c6c269E16
0.231 ETH
147065782022-05-03 19:40:30956 days ago1651606830
0xBfA75F3D...c6c269E16
0.469 ETH
147065782022-05-03 19:40:30956 days ago1651606830
0xBfA75F3D...c6c269E16
0.231 ETH
147065322022-05-03 19:30:31956 days ago1651606231
0xBfA75F3D...c6c269E16
0.469 ETH
147065322022-05-03 19:30:31956 days ago1651606231
0xBfA75F3D...c6c269E16
0.231 ETH
147061372022-05-03 18:06:16956 days ago1651601176
0xBfA75F3D...c6c269E16
0.08442 ETH
147061372022-05-03 18:06:16956 days ago1651601176
0xBfA75F3D...c6c269E16
0.04158 ETH
147059132022-05-03 17:13:38956 days ago1651598018
0xBfA75F3D...c6c269E16
0.08442 ETH
147059132022-05-03 17:13:38956 days ago1651598018
0xBfA75F3D...c6c269E16
0.04158 ETH
147050352022-05-03 13:59:43956 days ago1651586383
0xBfA75F3D...c6c269E16
0.08442 ETH
147050352022-05-03 13:59:43956 days ago1651586383
0xBfA75F3D...c6c269E16
0.04158 ETH
147050352022-05-03 13:59:43956 days ago1651586383
0xBfA75F3D...c6c269E16
0.469 ETH
View All Internal Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
OdysseyRouter

Compiler Version
v0.8.12+commit.f00d7308

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion

Contract Source Code (Solidity)

/**
 *Submitted for verification at Etherscan.io on 2022-04-29
*/

// SPDX-License-Identifier: Unlicense
pragma solidity >=0.8.12;

/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
    uint256 private locked = 1;

    modifier nonReentrant() {
        require(locked == 1, "REENTRANCY");

        locked = 2;

        _;

        locked = 1;
    }
} /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.

/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*///////////////////////////////////////////////////////////////
                                  EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 amount
    );

    /*///////////////////////////////////////////////////////////////
                             METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*///////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*///////////////////////////////////////////////////////////////
                             EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    bytes32 public constant PERMIT_TYPEHASH =
        keccak256(
            "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
        );

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

    /*///////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*///////////////////////////////////////////////////////////////
                              ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount)
        public
        virtual
        returns (bool)
    {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount)
        public
        virtual
        returns (bool)
    {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max)
            allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*///////////////////////////////////////////////////////////////
                              EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            bytes32 digest = keccak256(
                abi.encodePacked(
                    "\x19\x01",
                    DOMAIN_SEPARATOR(),
                    keccak256(
                        abi.encode(
                            PERMIT_TYPEHASH,
                            owner,
                            spender,
                            value,
                            nonces[owner]++,
                            deadline
                        )
                    )
                )
            );

            address recoveredAddress = ecrecover(digest, v, r, s);

            require(
                recoveredAddress != address(0) && recoveredAddress == owner,
                "INVALID_SIGNER"
            );

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return
            block.chainid == INITIAL_CHAIN_ID
                ? INITIAL_DOMAIN_SEPARATOR
                : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256(
                        "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
                    ),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /*///////////////////////////////////////////////////////////////
                       INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
} // OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)

/**
 * @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);
    }
} /// @title Interface for verifying contract-based account signatures

/// @notice Interface that verifies provided signature for the data
/// @dev Interface defined by EIP-1271
interface IERC1271 {
    /// @notice Returns whether the provided signature is valid for the provided data
    /// @dev MUST return the bytes4 magic value 0x1626ba7e when function passes.
    /// MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5).
    /// MUST allow external calls.
    /// @param hash Hash of the data to be signed
    /// @param signature Signature byte array associated with _data
    /// @return magicValue The bytes4 magic value 0x1626ba7e
    function isValidSignature(bytes32 hash, bytes memory signature)
        external
        view
        returns (bytes4 magicValue);
}

// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)

/**
 * @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
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 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"
        );

        (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");

        (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");

        (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");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal 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

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

library Signature {
    function recover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address) {
        // 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.
        require(
            uint256(s) <=
                0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,
            "ODYSSEY: INVALID_SIGNATURE_S_VALUE"
        );
        require(v == 27 || v == 28, "ODYSSEY: INVALID_SIGNATURE_V_VALUE");

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        require(signer != address(0), "ODYSSEY: INVALID_SIGNATURE");

        return signer;
    }

    function verify(
        bytes32 hash,
        address signer,
        uint8 v,
        bytes32 r,
        bytes32 s,
        bytes32 domainSeparator
    ) internal view {
        bytes32 digest = keccak256(
            abi.encodePacked("\x19\x01", domainSeparator, hash)
        );
        if (Address.isContract(signer)) {
            require(
                IERC1271(signer).isValidSignature(
                    digest,
                    abi.encodePacked(r, s, v)
                ) == 0x1626ba7e,
                "ODYSSEY: UNAUTHORIZED"
            );
        } else {
            require(
                recover(digest, v, r, s) == signer,
                "ODYSSEY: UNAUTHORIZED"
            );
        }
    }
} // OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/MerkleProof.sol)

/**
 * @dev These functions deal with verification of Merkle Trees proofs.
 *
 * The proofs can be generated using the JavaScript library
 * https://github.com/miguelmota/merkletreejs[merkletreejs].
 * Note: the hashing algorithm should be keccak256 and pair sorting should be enabled.
 *
 * See `test/utils/cryptography/MerkleProof.test.js` for some examples.
 *
 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, or use a hash function other than keccak256 for hashing leaves.
 * This is because the concatenation of a sorted pair of internal nodes in
 * the merkle tree could be reinterpreted as a leaf value.
 */
library MerkleProof {
    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     */
    function verify(
        bytes32[] memory proof,
        bytes32 root,
        bytes32 leaf
    ) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leafs & pre-images are assumed to be sorted.
     *
     * _Available since v4.4._
     */
    function processProof(bytes32[] memory proof, bytes32 leaf)
        internal
        pure
        returns (bytes32)
    {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            bytes32 proofElement = proof[i];
            if (computedHash <= proofElement) {
                // Hash(current computed hash + current element of the proof)
                computedHash = _efficientHash(computedHash, proofElement);
            } else {
                // Hash(current element of the proof + current computed hash)
                computedHash = _efficientHash(proofElement, computedHash);
            }
        }
        return computedHash;
    }

    function _efficientHash(bytes32 a, bytes32 b)
        private
        pure
        returns (bytes32 value)
    {
        assembly {
            mstore(0x00, a)
            mstore(0x20, b)
            value := keccak256(0x00, 0x40)
        }
    }
}

library MerkleWhiteList {
    function verify(
        address sender,
        bytes32[] calldata merkleProof,
        bytes32 merkleRoot
    ) internal pure {
        // Verify whitelist
        require(address(0) != sender);
        bytes32 leaf = keccak256(abi.encodePacked(sender));
        require(
            MerkleProof.verify(merkleProof, merkleRoot, leaf),
            "Not whitelisted"
        );
    }
} /// @notice Modern, minimalist, and gas efficient ERC-721 implementation.

/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
/// @dev Note that balanceOf does not revert if passed the zero address, in defiance of the ERC.
abstract contract ERC721 {
    /*///////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(
        address indexed from,
        address indexed to,
        uint256 indexed id
    );

    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 indexed id
    );

    event ApprovalForAll(
        address indexed owner,
        address indexed operator,
        bool approved
    );

    /*///////////////////////////////////////////////////////////////
                          METADATA STORAGE/LOGIC
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    function tokenURI(uint256 id) public view virtual returns (string memory);

    /*///////////////////////////////////////////////////////////////
                            ERC721 STORAGE                        
    //////////////////////////////////////////////////////////////*/

    mapping(address => uint256) public balanceOf;

    mapping(uint256 => address) public ownerOf;

    mapping(uint256 => address) public getApproved;

    mapping(address => mapping(address => bool)) public isApprovedForAll;

    /*///////////////////////////////////////////////////////////////
                              CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(string memory _name, string memory _symbol) {
        name = _name;
        symbol = _symbol;
    }

    /*///////////////////////////////////////////////////////////////
                              ERC721 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 id) public virtual {
        address owner = ownerOf[id];

        require(
            msg.sender == owner || isApprovedForAll[owner][msg.sender],
            "NOT_AUTHORIZED"
        );

        getApproved[id] = spender;

        emit Approval(owner, spender, id);
    }

    function setApprovalForAll(address operator, bool approved) public virtual {
        isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function transferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual {
        require(from == ownerOf[id], "WRONG_FROM");

        require(to != address(0), "INVALID_RECIPIENT");

        require(
            msg.sender == from ||
                msg.sender == getApproved[id] ||
                isApprovedForAll[from][msg.sender],
            "NOT_AUTHORIZED"
        );

        // Underflow of the sender's balance is impossible because we check for
        // ownership above and the recipient's balance can't realistically overflow.
        unchecked {
            balanceOf[from]--;

            balanceOf[to]++;
        }

        ownerOf[id] = to;

        delete getApproved[id];

        emit Transfer(from, to, id);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(
                    msg.sender,
                    from,
                    id,
                    ""
                ) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        bytes memory data
    ) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(
                    msg.sender,
                    from,
                    id,
                    data
                ) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    /*///////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId)
        public
        pure
        virtual
        returns (bool)
    {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
            interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
    }

    /*///////////////////////////////////////////////////////////////
                       INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 id) internal virtual {
        require(to != address(0), "INVALID_RECIPIENT");

        require(ownerOf[id] == address(0), "ALREADY_MINTED");

        // Counter overflow is incredibly unrealistic.
        unchecked {
            balanceOf[to]++;
        }

        ownerOf[id] = to;

        emit Transfer(address(0), to, id);
    }

    function _burn(uint256 id) internal virtual {
        address owner = ownerOf[id];

        require(ownerOf[id] != address(0), "NOT_MINTED");

        // Ownership check above ensures no underflow.
        unchecked {
            balanceOf[owner]--;
        }

        delete ownerOf[id];

        delete getApproved[id];

        emit Transfer(owner, address(0), id);
    }

    /*///////////////////////////////////////////////////////////////
                       INTERNAL SAFE MINT LOGIC
    //////////////////////////////////////////////////////////////*/

    function _safeMint(address to, uint256 id) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(
                    msg.sender,
                    address(0),
                    id,
                    ""
                ) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _safeMint(
        address to,
        uint256 id,
        bytes memory data
    ) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(
                    msg.sender,
                    address(0),
                    id,
                    data
                ) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }
}

/// @notice A generic interface for a contract which properly accepts ERC721 tokens.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
interface ERC721TokenReceiver {
    function onERC721Received(
        address operator,
        address from,
        uint256 id,
        bytes calldata data
    ) external returns (bytes4);
}

library UInt2Str {
    function uint2str(uint256 _i)
        internal
        pure
        returns (string memory _uintAsString)
    {
        if (_i == 0) {
            return "0";
        }
        uint256 j = _i;
        uint256 len;
        while (j != 0) {
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint256 k = len;
        while (_i != 0) {
            k = k - 1;
            uint8 temp = (48 + uint8(_i - (_i / 10) * 10));
            bytes1 b1 = bytes1(temp);
            bstr[k] = b1;
            _i /= 10;
        }
        return string(bstr);
    }
}

contract OdysseyERC721 is ERC721("", "") {
    using UInt2Str for uint256;

    /*///////////////////////////////////////////////////////////////
                            CUSTOM ERRORS
    //////////////////////////////////////////////////////////////*/
    error OdysseyERC721_AlreadyInit();
    error OdysseyERC721_Unauthorized();
    error OdysseyERC721_BadAddress();

    /*///////////////////////////////////////////////////////////////
                            METADATA STORAGE
    //////////////////////////////////////////////////////////////*/
    address launcher;
    address public owner;
    bool initialized;
    string public baseURI;
    uint256 public royaltyFeeInBips; // 1% = 100
    address public royaltyReceiver;
    string public contractURI;

    /*///////////////////////////////////////////////////////////////
                              METADATA LOGIC
    //////////////////////////////////////////////////////////////*/

    function tokenURI(uint256 id)
        public
        view
        virtual
        override
        returns (string memory)
    {
        return string(abi.encodePacked(baseURI, id.uint2str()));
    }

    /*///////////////////////////////////////////////////////////////
                              FACTORY LOGIC
    //////////////////////////////////////////////////////////////*/

    function initialize(
        address _launcher,
        address _owner,
        string calldata _name,
        string calldata _symbol,
        string calldata _baseURI
    ) external {
        if (initialized) {
            revert OdysseyERC721_AlreadyInit();
        }
        initialized = true;
        launcher = _launcher;
        owner = _owner;
        name = _name;
        symbol = _symbol;
        baseURI = _baseURI;
    }

    /*///////////////////////////////////////////////////////////////
                              ERC721 LOGIC
    //////////////////////////////////////////////////////////////*/

    function transferOwnership(address newOwner) public virtual {
        if (newOwner == address(0)) {
            revert OdysseyERC721_BadAddress();
        }
        if (msg.sender != owner) {
            revert OdysseyERC721_Unauthorized();
        }
        owner = newOwner;
    }

    function mint(address user, uint256 id) external {
        if (msg.sender != launcher) {
            revert OdysseyERC721_Unauthorized();
        }
        _mint(user, id);
    }

    /*///////////////////////////////////////////////////////////////
                              EIP2981 LOGIC
    //////////////////////////////////////////////////////////////*/

    function royaltyInfo(uint256, uint256 _salePrice)
        external
        view
        returns (address receiver, uint256 royaltyAmount)
    {
        return (royaltyReceiver, (_salePrice / 10000) * royaltyFeeInBips);
    }

    function setRoyaltyInfo(address _royaltyReceiver, uint256 _royaltyFeeInBips)
        external
    {
        if (_royaltyReceiver == address(0)) {
            revert OdysseyERC721_BadAddress();
        }
        if (msg.sender != owner) {
            revert OdysseyERC721_Unauthorized();
        }
        royaltyReceiver = _royaltyReceiver;
        royaltyFeeInBips = _royaltyFeeInBips;
    }

    function setContractURI(string memory _uri) public {
        if (msg.sender != owner) {
            revert OdysseyERC721_Unauthorized();
        }
        contractURI = _uri;
    }

    /*///////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceID)
        public
        pure
        override(ERC721)
        returns (bool)
    {
        return
            bytes4(keccak256("royaltyInfo(uint256,uint256)")) == interfaceID ||
            super.supportsInterface(interfaceID);
    }
} /// @notice Minimalist and gas efficient standard ERC1155 implementation.

/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC1155.sol)
abstract contract ERC1155 {
    /*///////////////////////////////////////////////////////////////
                                EVENTS
    //////////////////////////////////////////////////////////////*/

    event TransferSingle(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256 id,
        uint256 amount
    );

    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] amounts
    );

    event ApprovalForAll(
        address indexed owner,
        address indexed operator,
        bool approved
    );

    event URI(string value, uint256 indexed id);

    /*///////////////////////////////////////////////////////////////
                            ERC1155 STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(address => mapping(uint256 => uint256)) public balanceOf;

    mapping(address => mapping(address => bool)) public isApprovedForAll;

    /*///////////////////////////////////////////////////////////////
                             METADATA LOGIC
    //////////////////////////////////////////////////////////////*/

    function uri(uint256 id) public view virtual returns (string memory);

    /*///////////////////////////////////////////////////////////////
                             ERC1155 LOGIC
    //////////////////////////////////////////////////////////////*/

    function setApprovalForAll(address operator, bool approved) public virtual {
        isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) public virtual {
        require(
            msg.sender == from || isApprovedForAll[from][msg.sender],
            "NOT_AUTHORIZED"
        );

        balanceOf[from][id] -= amount;
        balanceOf[to][id] += amount;

        emit TransferSingle(msg.sender, from, to, id, amount);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiver(to).onERC1155Received(
                    msg.sender,
                    from,
                    id,
                    amount,
                    data
                ) == ERC1155TokenReceiver.onERC1155Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) public virtual {
        uint256 idsLength = ids.length; // Saves MLOADs.

        require(idsLength == amounts.length, "LENGTH_MISMATCH");

        require(
            msg.sender == from || isApprovedForAll[from][msg.sender],
            "NOT_AUTHORIZED"
        );

        for (uint256 i = 0; i < idsLength; ) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            balanceOf[from][id] -= amount;
            balanceOf[to][id] += amount;

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                i++;
            }
        }

        emit TransferBatch(msg.sender, from, to, ids, amounts);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiver(to).onERC1155BatchReceived(
                    msg.sender,
                    from,
                    ids,
                    amounts,
                    data
                ) == ERC1155TokenReceiver.onERC1155BatchReceived.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function balanceOfBatch(address[] memory owners, uint256[] memory ids)
        public
        view
        virtual
        returns (uint256[] memory balances)
    {
        uint256 ownersLength = owners.length; // Saves MLOADs.

        require(ownersLength == ids.length, "LENGTH_MISMATCH");

        balances = new uint256[](owners.length);

        // Unchecked because the only math done is incrementing
        // the array index counter which cannot possibly overflow.
        unchecked {
            for (uint256 i = 0; i < ownersLength; i++) {
                balances[i] = balanceOf[owners[i]][ids[i]];
            }
        }
    }

    /*///////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId)
        public
        pure
        virtual
        returns (bool)
    {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155
            interfaceId == 0x0e89341c; // ERC165 Interface ID for ERC1155MetadataURI
    }

    /*///////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal {
        balanceOf[to][id] += amount;

        emit TransferSingle(msg.sender, address(0), to, id, amount);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiver(to).onERC1155Received(
                    msg.sender,
                    address(0),
                    id,
                    amount,
                    data
                ) == ERC1155TokenReceiver.onERC1155Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _batchMint(
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal {
        uint256 idsLength = ids.length; // Saves MLOADs.

        require(idsLength == amounts.length, "LENGTH_MISMATCH");

        for (uint256 i = 0; i < idsLength; ) {
            balanceOf[to][ids[i]] += amounts[i];

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                i++;
            }
        }

        emit TransferBatch(msg.sender, address(0), to, ids, amounts);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiver(to).onERC1155BatchReceived(
                    msg.sender,
                    address(0),
                    ids,
                    amounts,
                    data
                ) == ERC1155TokenReceiver.onERC1155BatchReceived.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _batchBurn(
        address from,
        uint256[] memory ids,
        uint256[] memory amounts
    ) internal {
        uint256 idsLength = ids.length; // Saves MLOADs.

        require(idsLength == amounts.length, "LENGTH_MISMATCH");

        for (uint256 i = 0; i < idsLength; ) {
            balanceOf[from][ids[i]] -= amounts[i];

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                i++;
            }
        }

        emit TransferBatch(msg.sender, from, address(0), ids, amounts);
    }

    function _burn(
        address from,
        uint256 id,
        uint256 amount
    ) internal {
        balanceOf[from][id] -= amount;

        emit TransferSingle(msg.sender, from, address(0), id, amount);
    }
}

/// @notice A generic interface for a contract which properly accepts ERC1155 tokens.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC1155.sol)
interface ERC1155TokenReceiver {
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) external returns (bytes4);

    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external returns (bytes4);
}

contract OdysseyERC1155 is ERC1155 {
    using UInt2Str for uint256;

    /*///////////////////////////////////////////////////////////////
                            CUSTOM ERRORS
    //////////////////////////////////////////////////////////////*/
    error OdysseyERC1155_AlreadyInit();
    error OdysseyERC1155_Unauthorized();
    error OdysseyERC1155_BadAddress();

    /*///////////////////////////////////////////////////////////////
                            METADATA STORAGE
    //////////////////////////////////////////////////////////////*/
    address launcher;
    address public owner;
    string public name;
    string public symbol;
    string public baseURI;
    bool initialized;
    uint256 public royaltyFeeInBips; // 1% = 100
    address public royaltyReceiver;
    string public contractURI;

    /*///////////////////////////////////////////////////////////////
                             METADATA LOGIC
    //////////////////////////////////////////////////////////////*/

    function uri(uint256 id)
        public
        view
        virtual
        override
        returns (string memory)
    {
        return string(abi.encodePacked(baseURI, id.uint2str()));
    }

    /*///////////////////////////////////////////////////////////////
                              FACTORY LOGIC
    //////////////////////////////////////////////////////////////*/

    function initialize(
        address _launcher,
        address _owner,
        string calldata _name,
        string calldata _symbol,
        string calldata _baseURI
    ) external {
        if (isInit()) {
            revert OdysseyERC1155_AlreadyInit();
        }
        initialized = true;
        launcher = _launcher;
        owner = _owner;
        name = _name;
        symbol = _symbol;
        baseURI = _baseURI;
    }

    function isInit() internal view returns (bool) {
        return initialized;
    }

    /*///////////////////////////////////////////////////////////////
                              ERC1155 LOGIC
    //////////////////////////////////////////////////////////////*/

    function transferOwnership(address newOwner) public virtual {
        if (newOwner == address(0)) {
            revert OdysseyERC1155_BadAddress();
        }
        if (msg.sender != owner) {
            revert OdysseyERC1155_Unauthorized();
        }
        owner = newOwner;
    }

    function mint(address user, uint256 id) external {
        if (msg.sender != launcher) {
            revert OdysseyERC1155_Unauthorized();
        }
        _mint(user, id, 1, "");
    }

    function mintBatch(
        address user,
        uint256 id,
        uint256 amount
    ) external {
        if (msg.sender != launcher) {
            revert OdysseyERC1155_Unauthorized();
        }
        _mint(user, id, amount, "");
    }

    /*///////////////////////////////////////////////////////////////
                              EIP2981 LOGIC
    //////////////////////////////////////////////////////////////*/

    function royaltyInfo(uint256, uint256 _salePrice)
        external
        view
        returns (address receiver, uint256 royaltyAmount)
    {
        return (royaltyReceiver, (_salePrice / 10000) * royaltyFeeInBips);
    }

    function setRoyaltyInfo(address _royaltyReceiver, uint256 _royaltyFeeInBips)
        external
    {
        if (_royaltyReceiver == address(0)) {
            revert OdysseyERC1155_BadAddress();
        }
        if (msg.sender != owner) {
            revert OdysseyERC1155_Unauthorized();
        }
        royaltyReceiver = _royaltyReceiver;
        royaltyFeeInBips = _royaltyFeeInBips;
    }

    function setContractURI(string memory _uri) public {
        if (msg.sender != owner) {
            revert OdysseyERC1155_Unauthorized();
        }
        contractURI = _uri;
    }

    /*///////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceID)
        public
        pure
        override(ERC1155)
        returns (bool)
    {
        return
            bytes4(keccak256("royaltyInfo(uint256,uint256)")) == interfaceID ||
            super.supportsInterface(interfaceID);
    }
}

contract OdysseyTokenFactory {
    /*///////////////////////////////////////////////////////////////
                            CUSTOM ERRORS
    //////////////////////////////////////////////////////////////*/
    error OdysseyTokenFactory_TokenAlreadyExists();
    /*///////////////////////////////////////////////////////////////
                                EVENTS
    //////////////////////////////////////////////////////////////*/

    event TokenCreated(
        string indexed name,
        string indexed symbol,
        address addr,
        bool isERC721,
        uint256 length
    );

    /*///////////////////////////////////////////////////////////////
                            FACTORY STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(string => mapping(string => address)) public getToken;
    mapping(address => uint256) public tokenExists;
    address[] public allTokens;

    /*///////////////////////////////////////////////////////////////
                            FACTORY LOGIC
    //////////////////////////////////////////////////////////////*/

    function allTokensLength() external view returns (uint256) {
        return allTokens.length;
    }

    function create1155(
        address owner,
        string calldata name,
        string calldata symbol,
        string calldata baseURI
    ) external returns (address token) {
        if (getToken[name][symbol] != address(0)) {
            revert OdysseyTokenFactory_TokenAlreadyExists();
        }
        bytes memory bytecode = type(OdysseyERC1155).creationCode;
        bytes32 salt = keccak256(abi.encodePacked(name, symbol));
        assembly {
            token := create2(0, add(bytecode, 32), mload(bytecode), salt)
        }
        getToken[name][symbol] = token;
        tokenExists[token] = 1;
        // Run the proper initialize function
        OdysseyERC1155(token).initialize(
            msg.sender,
            owner,
            name,
            symbol,
            string(
                abi.encodePacked(
                    baseURI,
                    Strings.toString(block.chainid),
                    "/",
                    Strings.toHexString(uint160(token)),
                    "/"
                )
            )
        );
        emit TokenCreated(name, symbol, token, false, allTokens.length);
        return token;
    }

    function create721(
        address owner,
        string calldata name,
        string calldata symbol,
        string calldata baseURI
    ) external returns (address token) {
        if (getToken[name][symbol] != address(0)) {
            revert OdysseyTokenFactory_TokenAlreadyExists();
        }
        bytes memory bytecode = type(OdysseyERC721).creationCode;
        bytes32 salt = keccak256(abi.encodePacked(name, symbol));
        assembly {
            token := create2(0, add(bytecode, 32), mload(bytecode), salt)
        }
        getToken[name][symbol] = token;
        tokenExists[token] = 1;
        // Run the proper initialize function
        OdysseyERC721(token).initialize(
            msg.sender,
            owner,
            name,
            symbol,
            string(
                abi.encodePacked(
                    baseURI,
                    Strings.toString(block.chainid),
                    "/",
                    Strings.toHexString(uint160(token)),
                    "/"
                )
            )
        );
        emit TokenCreated(name, symbol, token, true, allTokens.length);
    }
}

library OdysseyLib {
    struct Odyssey1155Info {
        uint256[] maxSupply;
        uint256[] tokenIds;
        uint256[] reserveAmounts;
    }

    struct BatchMint {
        bytes32[][] merkleProof;
        bytes32[] merkleRoot;
        uint256[] minPrice;
        uint256[] mintsPerUser;
        uint256[] tokenId;
        address[] tokenAddress;
        address[] currency;
        uint8[] v;
        bytes32[] r;
        bytes32[] s;
    }

    struct Percentage {
        uint256 numerator;
        uint256 denominator;
    }

    function compareDefaultPercentage(OdysseyLib.Percentage calldata percent)
        internal
        pure
        returns (bool result)
    {
        if (percent.numerator > percent.denominator) {
            // Can't have a percent greater than 100
            return false;
        }

        if (percent.numerator == 0 || percent.denominator == 0) {
            // Can't use 0 in percentage
            return false;
        }

        //Check cross multiplication of 3/100
        uint256 crossMultiple1 = percent.numerator * 100;
        uint256 crossMultiple2 = percent.denominator * 3;
        if (crossMultiple1 < crossMultiple2) {
            return false;
        }
        return true;
    }
}

abstract contract OdysseyDatabase {
    // Custom Errors
    error OdysseyLaunchPlatform_TokenDoesNotExist();
    error OdysseyLaunchPlatform_AlreadyClaimed();
    error OdysseyLaunchPlatform_MaxSupplyCap();
    error OdysseyLaunchPlatform_InsufficientFunds();
    error OdysseyLaunchPlatform_TreasuryPayFailure();
    error OdysseyLaunchPlatform_FailedToPayEther();
    error OdysseyLaunchPlatform_FailedToPayERC20();
    error OdysseyLaunchPlatform_ReservedOrClaimedMax();

    // Constants
    // keccak256("whitelistMint721(bytes32 merkleRoot,uint256 minPrice,uint256 mintsPerUser,address tokenAddress,address currency)").toString('hex')
    bytes32 public constant MERKLE_TREE_ROOT_ERC721_TYPEHASH =
        0xf0f6f256599682b9387f45fc268ed696625f835d98d64b8967134239e103fc6c;
    // keccak256("whitelistMint1155(bytes32 merkleRoot,uint256 minPrice,uint256 mintsPerUser,uint256 tokenId,address tokenAddress,address currency)").toString('hex')
    bytes32 public constant MERKLE_TREE_ROOT_ERC1155_TYPEHASH =
        0x0a52f6e0133eadd055cc5703844e676242c3b461d85fb7ce7f74becd7e40edd1;

    // Def understand this before writing code:
    // https://docs.soliditylang.org/en/v0.8.12/internals/layout_in_storage.html
    //--------------------------------------------------------------------------------//
    // Slot       |  Type                  | Description                              //
    //--------------------------------------------------------------------------------//
    // 0x00       |  address               | OdysseyLaunchPlatform.sol                //
    // 0x01       |  address               | OdysseyFactory.sol                       //
    // 0x02       |  address               | Treasury Multisig                        //
    // 0x03       |  address               | Admin Address                            //
    // 0x04       |  address               | OdysseyXp.sol                            //
    //--------------------------------------------------------------------------------//
    // Slot storage
    address launchPlatform; // slot 0
    address factory; // slot 1
    address treasury; // slot 2
    address admin; //slot 3
    address xp; //slot 4

    // Common Storage
    mapping(address => bytes32) public domainSeparator;
    mapping(address => uint256) public whitelistActive;
    mapping(address => address) public ownerOf;
    mapping(address => address) public royaltyRecipient;
    mapping(address => OdysseyLib.Percentage) public treasuryCommission;
    mapping(address => uint256) public ohmFamilyCurrencies;
    // ERC721 Storage
    mapping(address => mapping(address => uint256)) public whitelistClaimed721;
    mapping(address => mapping(address => uint256)) public isReserved721;
    mapping(address => uint256) public cumulativeSupply721;
    mapping(address => uint256) public mintedSupply721;
    mapping(address => uint256) public maxSupply721;
    // ERC1155 Storage
    mapping(address => mapping(address => mapping(uint256 => uint256)))
        public whitelistClaimed1155;
    mapping(address => mapping(address => mapping(uint256 => uint256)))
        public isReserved1155;
    mapping(address => mapping(uint256 => uint256)) public cumulativeSupply1155;
    mapping(address => mapping(uint256 => uint256)) public maxSupply1155;

    function readSlotAsAddress(uint256 slot)
        public
        view
        returns (address data)
    {
        assembly {
            data := sload(slot)
        }
    }
} /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.

/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Gnosis (https://github.com/gnosis/gp-v2-contracts/blob/main/src/contracts/libraries/GPv2SafeERC20.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
library SafeTransferLib {
    /*///////////////////////////////////////////////////////////////
                            ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferETH(address to, uint256 amount) internal {
        bool callStatus;

        assembly {
            // Transfer the ETH and store if it succeeded or not.
            callStatus := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(callStatus, "ETH_TRANSFER_FAILED");
    }

    /*///////////////////////////////////////////////////////////////
                           ERC20 OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferFrom(
        ERC20 token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bool callStatus;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata to memory piece by piece:
            mstore(
                freeMemoryPointer,
                0x23b872dd00000000000000000000000000000000000000000000000000000000
            ) // Begin with the function selector.
            mstore(
                add(freeMemoryPointer, 4),
                and(from, 0xffffffffffffffffffffffffffffffffffffffff)
            ) // Mask and append the "from" argument.
            mstore(
                add(freeMemoryPointer, 36),
                and(to, 0xffffffffffffffffffffffffffffffffffffffff)
            ) // Mask and append the "to" argument.
            mstore(add(freeMemoryPointer, 68), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value.

            // Call the token and store if it succeeded or not.
            // We use 100 because the calldata length is 4 + 32 * 3.
            callStatus := call(gas(), token, 0, freeMemoryPointer, 100, 0, 0)
        }

        require(
            didLastOptionalReturnCallSucceed(callStatus),
            "TRANSFER_FROM_FAILED"
        );
    }

    function safeTransfer(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool callStatus;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata to memory piece by piece:
            mstore(
                freeMemoryPointer,
                0xa9059cbb00000000000000000000000000000000000000000000000000000000
            ) // Begin with the function selector.
            mstore(
                add(freeMemoryPointer, 4),
                and(to, 0xffffffffffffffffffffffffffffffffffffffff)
            ) // Mask and append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value.

            // Call the token and store if it succeeded or not.
            // We use 68 because the calldata length is 4 + 32 * 2.
            callStatus := call(gas(), token, 0, freeMemoryPointer, 68, 0, 0)
        }

        require(
            didLastOptionalReturnCallSucceed(callStatus),
            "TRANSFER_FAILED"
        );
    }

    function safeApprove(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool callStatus;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata to memory piece by piece:
            mstore(
                freeMemoryPointer,
                0x095ea7b300000000000000000000000000000000000000000000000000000000
            ) // Begin with the function selector.
            mstore(
                add(freeMemoryPointer, 4),
                and(to, 0xffffffffffffffffffffffffffffffffffffffff)
            ) // Mask and append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value.

            // Call the token and store if it succeeded or not.
            // We use 68 because the calldata length is 4 + 32 * 2.
            callStatus := call(gas(), token, 0, freeMemoryPointer, 68, 0, 0)
        }

        require(didLastOptionalReturnCallSucceed(callStatus), "APPROVE_FAILED");
    }

    /*///////////////////////////////////////////////////////////////
                         INTERNAL HELPER LOGIC
    //////////////////////////////////////////////////////////////*/

    function didLastOptionalReturnCallSucceed(bool callStatus)
        private
        pure
        returns (bool success)
    {
        assembly {
            // Get how many bytes the call returned.
            let returnDataSize := returndatasize()

            // If the call reverted:
            if iszero(callStatus) {
                // Copy the revert message into memory.
                returndatacopy(0, 0, returnDataSize)

                // Revert with the same message.
                revert(0, returnDataSize)
            }

            switch returnDataSize
            case 32 {
                // Copy the return data into memory.
                returndatacopy(0, 0, returnDataSize)

                // Set success to whether it returned true.
                success := iszero(iszero(mload(0)))
            }
            case 0 {
                // There was no return data.
                success := 1
            }
            default {
                // It returned some malformed input.
                success := 0
            }
        }
    }
} /// @notice Arithmetic library with operations for fixed-point numbers.

/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
    /*//////////////////////////////////////////////////////////////
                    SIMPLIFIED FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.

    function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
    }

    function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
    }

    function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
    }

    function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
    }

    /*//////////////////////////////////////////////////////////////
                    LOW LEVEL FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function mulDivDown(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        assembly {
            // Store x * y in z for now.
            z := mul(x, y)

            // Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
            if iszero(
                and(
                    iszero(iszero(denominator)),
                    or(iszero(x), eq(div(z, x), y))
                )
            ) {
                revert(0, 0)
            }

            // Divide z by the denominator.
            z := div(z, denominator)
        }
    }

    function mulDivUp(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        assembly {
            // Store x * y in z for now.
            z := mul(x, y)

            // Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
            if iszero(
                and(
                    iszero(iszero(denominator)),
                    or(iszero(x), eq(div(z, x), y))
                )
            ) {
                revert(0, 0)
            }

            // First, divide z - 1 by the denominator and add 1.
            // We allow z - 1 to underflow if z is 0, because we multiply the
            // end result by 0 if z is zero, ensuring we return 0 if z is zero.
            z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1))
        }
    }

    function rpow(
        uint256 x,
        uint256 n,
        uint256 scalar
    ) internal pure returns (uint256 z) {
        assembly {
            switch x
            case 0 {
                switch n
                case 0 {
                    // 0 ** 0 = 1
                    z := scalar
                }
                default {
                    // 0 ** n = 0
                    z := 0
                }
            }
            default {
                switch mod(n, 2)
                case 0 {
                    // If n is even, store scalar in z for now.
                    z := scalar
                }
                default {
                    // If n is odd, store x in z for now.
                    z := x
                }

                // Shifting right by 1 is like dividing by 2.
                let half := shr(1, scalar)

                for {
                    // Shift n right by 1 before looping to halve it.
                    n := shr(1, n)
                } n {
                    // Shift n right by 1 each iteration to halve it.
                    n := shr(1, n)
                } {
                    // Revert immediately if x ** 2 would overflow.
                    // Equivalent to iszero(eq(div(xx, x), x)) here.
                    if shr(128, x) {
                        revert(0, 0)
                    }

                    // Store x squared.
                    let xx := mul(x, x)

                    // Round to the nearest number.
                    let xxRound := add(xx, half)

                    // Revert if xx + half overflowed.
                    if lt(xxRound, xx) {
                        revert(0, 0)
                    }

                    // Set x to scaled xxRound.
                    x := div(xxRound, scalar)

                    // If n is even:
                    if mod(n, 2) {
                        // Compute z * x.
                        let zx := mul(z, x)

                        // If z * x overflowed:
                        if iszero(eq(div(zx, x), z)) {
                            // Revert if x is non-zero.
                            if iszero(iszero(x)) {
                                revert(0, 0)
                            }
                        }

                        // Round to the nearest number.
                        let zxRound := add(zx, half)

                        // Revert if zx + half overflowed.
                        if lt(zxRound, zx) {
                            revert(0, 0)
                        }

                        // Return properly scaled zxRound.
                        z := div(zxRound, scalar)
                    }
                }
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
                        GENERAL NUMBER UTILITIES
    //////////////////////////////////////////////////////////////*/

    function sqrt(uint256 x) internal pure returns (uint256 z) {
        assembly {
            // Start off with z at 1.
            z := 1

            // Used below to help find a nearby power of 2.
            let y := x

            // Find the lowest power of 2 that is at least sqrt(x).
            if iszero(lt(y, 0x100000000000000000000000000000000)) {
                y := shr(128, y) // Like dividing by 2 ** 128.
                z := shl(64, z) // Like multiplying by 2 ** 64.
            }
            if iszero(lt(y, 0x10000000000000000)) {
                y := shr(64, y) // Like dividing by 2 ** 64.
                z := shl(32, z) // Like multiplying by 2 ** 32.
            }
            if iszero(lt(y, 0x100000000)) {
                y := shr(32, y) // Like dividing by 2 ** 32.
                z := shl(16, z) // Like multiplying by 2 ** 16.
            }
            if iszero(lt(y, 0x10000)) {
                y := shr(16, y) // Like dividing by 2 ** 16.
                z := shl(8, z) // Like multiplying by 2 ** 8.
            }
            if iszero(lt(y, 0x100)) {
                y := shr(8, y) // Like dividing by 2 ** 8.
                z := shl(4, z) // Like multiplying by 2 ** 4.
            }
            if iszero(lt(y, 0x10)) {
                y := shr(4, y) // Like dividing by 2 ** 4.
                z := shl(2, z) // Like multiplying by 2 ** 2.
            }
            if iszero(lt(y, 0x8)) {
                // Equivalent to 2 ** z.
                z := shl(1, z)
            }

            // Shifting right by 1 is like dividing by 2.
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))

            // Compute a rounded down version of z.
            let zRoundDown := div(x, z)

            // If zRoundDown is smaller, use it.
            if lt(zRoundDown, z) {
                z := zRoundDown
            }
        }
    }
} // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165Checker.sol)

// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

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

/**
 * @dev Library used to query support of an interface declared via {IERC165}.
 *
 * Note that these functions return the actual result of the query: they do not
 * `revert` if an interface is not supported. It is up to the caller to decide
 * what to do in these cases.
 */
library ERC165Checker {
    // As per the EIP-165 spec, no interface should ever match 0xffffffff
    bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff;

    /**
     * @dev Returns true if `account` supports the {IERC165} interface,
     */
    function supportsERC165(address account) internal view returns (bool) {
        // Any contract that implements ERC165 must explicitly indicate support of
        // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
        return
            _supportsERC165Interface(account, type(IERC165).interfaceId) &&
            !_supportsERC165Interface(account, _INTERFACE_ID_INVALID);
    }

    /**
     * @dev Returns true if `account` supports the interface defined by
     * `interfaceId`. Support for {IERC165} itself is queried automatically.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsInterface(address account, bytes4 interfaceId)
        internal
        view
        returns (bool)
    {
        // query support of both ERC165 as per the spec and support of _interfaceId
        return
            supportsERC165(account) &&
            _supportsERC165Interface(account, interfaceId);
    }

    /**
     * @dev Returns a boolean array where each value corresponds to the
     * interfaces passed in and whether they're supported or not. This allows
     * you to batch check interfaces for a contract where your expectation
     * is that some interfaces may not be supported.
     *
     * See {IERC165-supportsInterface}.
     *
     * _Available since v3.4._
     */
    function getSupportedInterfaces(
        address account,
        bytes4[] memory interfaceIds
    ) internal view returns (bool[] memory) {
        // an array of booleans corresponding to interfaceIds and whether they're supported or not
        bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);

        // query support of ERC165 itself
        if (supportsERC165(account)) {
            // query support of each interface in interfaceIds
            for (uint256 i = 0; i < interfaceIds.length; i++) {
                interfaceIdsSupported[i] = _supportsERC165Interface(
                    account,
                    interfaceIds[i]
                );
            }
        }

        return interfaceIdsSupported;
    }

    /**
     * @dev Returns true if `account` supports all the interfaces defined in
     * `interfaceIds`. Support for {IERC165} itself is queried automatically.
     *
     * Batch-querying can lead to gas savings by skipping repeated checks for
     * {IERC165} support.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsAllInterfaces(
        address account,
        bytes4[] memory interfaceIds
    ) internal view returns (bool) {
        // query support of ERC165 itself
        if (!supportsERC165(account)) {
            return false;
        }

        // query support of each interface in _interfaceIds
        for (uint256 i = 0; i < interfaceIds.length; i++) {
            if (!_supportsERC165Interface(account, interfaceIds[i])) {
                return false;
            }
        }

        // all interfaces supported
        return true;
    }

    /**
     * @notice Query if a contract implements an interface, does not check ERC165 support
     * @param account The address of the contract to query for support of an interface
     * @param interfaceId The interface identifier, as specified in ERC-165
     * @return true if the contract at account indicates support of the interface with
     * identifier interfaceId, false otherwise
     * @dev Assumes that account contains a contract that supports ERC165, otherwise
     * the behavior of this method is undefined. This precondition can be checked
     * with {supportsERC165}.
     * Interface identification is specified in ERC-165.
     */
    function _supportsERC165Interface(address account, bytes4 interfaceId)
        private
        view
        returns (bool)
    {
        bytes memory encodedParams = abi.encodeWithSelector(
            IERC165.supportsInterface.selector,
            interfaceId
        );
        (bool success, bytes memory result) = account.staticcall{gas: 30000}(
            encodedParams
        );
        if (result.length < 32) return false;
        return success && abi.decode(result, (bool));
    }
}
struct Rewards {
    uint256 sale;
    uint256 purchase;
    uint256 mint;
    uint256 ohmPurchase;
    uint256 ohmMint;
    uint256 multiplier;
}

struct NFT {
    address contractAddress;
    uint256 id;
}

enum NftType {
    ERC721,
    ERC1155
}

error OdysseyXpDirectory_Unauthorized();

contract OdysseyXpDirectory {
    using ERC165Checker for address;

    Rewards public defaultRewards;
    mapping(address => Rewards) public erc721rewards;
    mapping(address => mapping(uint256 => Rewards)) public erc1155rewards;
    NFT[] public customRewardTokens;
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    // modifier substitute
    function notOwner() internal view returns (bool) {
        return msg.sender != owner;
    }

    function transferOwnership(address newOwner) external {
        if (notOwner()) revert OdysseyXpDirectory_Unauthorized();
        owner = newOwner;
    }

    /*///////////////////////////////////////////////////////////////
                            Reward Setters
    //////////////////////////////////////////////////////////////*/

    /// @notice Set default rewards for contracts without a custom reward set
    /// @param sale XP reward for selling an NFT
    /// @param purchase XP reward for purchasing an NFT
    /// @param mint XP reward for minting an NFT
    /// @param ohmPurchase XP reward for purchasing an NFT with OHM
    /// @param ohmMint XP reward for minting an NFT with OHM
    /// @param multiplier XP reward multiplier for wallets holding an NFT
    function setDefaultRewards(
        uint256 sale,
        uint256 purchase,
        uint256 mint,
        uint256 ohmPurchase,
        uint256 ohmMint,
        uint256 multiplier
    ) public {
        if (notOwner()) revert OdysseyXpDirectory_Unauthorized();
        defaultRewards = Rewards(
            sale,
            purchase,
            mint,
            ohmPurchase,
            ohmMint,
            multiplier
        );
    }

    /// @notice Set custom rewards for an ERC721 contract
    /// @param sale XP reward for selling this NFT
    /// @param purchase XP reward for purchasing this NFT
    /// @param mint XP reward for minting this NFT
    /// @param ohmPurchase XP reward for purchasing this NFT with OHM
    /// @param ohmMint XP reward for minting this NFT with OHM
    /// @param multiplier XP reward multiplier for wallets holding this NFT
    function setErc721CustomRewards(
        address tokenAddress,
        uint256 sale,
        uint256 purchase,
        uint256 mint,
        uint256 ohmPurchase,
        uint256 ohmMint,
        uint256 multiplier
    ) public {
        if (notOwner()) revert OdysseyXpDirectory_Unauthorized();
        customRewardTokens.push(NFT(tokenAddress, 0));
        erc721rewards[tokenAddress] = Rewards(
            sale,
            purchase,
            mint,
            ohmPurchase,
            ohmMint,
            multiplier
        );
    }

    /// @notice Set custom rewards for an ERC1155 contract and token ID
    /// @param sale XP reward for selling this NFT
    /// @param purchase XP reward for purchasing this NFT
    /// @param mint XP reward for minting this NFT
    /// @param ohmPurchase XP reward for purchasing this NFT with OHM
    /// @param ohmMint XP reward for minting this NFT with OHM
    /// @param multiplier XP reward multiplier for wallets holding this NFT
    function setErc1155CustomRewards(
        address tokenAddress,
        uint256 tokenId,
        uint256 sale,
        uint256 purchase,
        uint256 mint,
        uint256 ohmPurchase,
        uint256 ohmMint,
        uint256 multiplier
    ) public {
        if (notOwner()) revert OdysseyXpDirectory_Unauthorized();
        customRewardTokens.push(NFT(tokenAddress, tokenId));
        erc1155rewards[tokenAddress][tokenId] = Rewards(
            sale,
            purchase,
            mint,
            ohmPurchase,
            ohmMint,
            multiplier
        );
    }

    /*///////////////////////////////////////////////////////////////
                            Reward Getters
    //////////////////////////////////////////////////////////////*/

    /// @notice Get the XP reward for selling an NFT
    /// @param seller Seller of the NFT
    /// @param contractAddress Address of the NFT being sold
    /// @param tokenId ID of the NFT being sold
    function getSaleReward(
        address seller,
        address contractAddress,
        uint256 tokenId
    ) public view returns (uint256) {
        (
            bool isCustomErc721,
            bool isCustomErc1155,
            uint256 multiplier
        ) = _getRewardDetails(seller, contractAddress, tokenId);
        if (isCustomErc721) {
            return erc721rewards[contractAddress].sale * multiplier;
        } else if (isCustomErc1155) {
            return erc1155rewards[contractAddress][tokenId].sale * multiplier;
        } else {
            return defaultRewards.sale * multiplier;
        }
    }

    /// @notice Get the XP reward for buying an NFT
    /// @param buyer Buyer of the NFT
    /// @param contractAddress Address of the NFT being sold
    /// @param tokenId ID of the NFT being sold
    function getPurchaseReward(
        address buyer,
        address contractAddress,
        uint256 tokenId
    ) public view returns (uint256) {
        (
            bool isCustomErc721,
            bool isCustomErc1155,
            uint256 multiplier
        ) = _getRewardDetails(buyer, contractAddress, tokenId);
        if (isCustomErc721) {
            return erc721rewards[contractAddress].purchase * multiplier;
        } else if (isCustomErc1155) {
            return
                erc1155rewards[contractAddress][tokenId].purchase * multiplier;
        } else {
            return defaultRewards.purchase * multiplier;
        }
    }

    /// @notice Get the XP reward for minting an NFT
    /// @param buyer Buyer of the NFT
    /// @param contractAddress Address of the NFT being sold
    /// @param tokenId ID of the NFT being sold
    function getMintReward(
        address buyer,
        address contractAddress,
        uint256 tokenId
    ) public view returns (uint256) {
        (
            bool isCustomErc721,
            bool isCustomErc1155,
            uint256 multiplier
        ) = _getRewardDetails(buyer, contractAddress, tokenId);
        if (isCustomErc721) {
            return erc721rewards[contractAddress].mint * multiplier;
        } else if (isCustomErc1155) {
            return erc1155rewards[contractAddress][tokenId].mint * multiplier;
        } else {
            return defaultRewards.mint * multiplier;
        }
    }

    /// @notice Get the XP reward for buying an NFT with OHM
    /// @param buyer Buyer of the NFT
    /// @param contractAddress Address of the NFT being sold
    /// @param tokenId ID of the NFT being sold
    function getOhmPurchaseReward(
        address buyer,
        address contractAddress,
        uint256 tokenId
    ) public view returns (uint256) {
        (
            bool isCustomErc721,
            bool isCustomErc1155,
            uint256 multiplier
        ) = _getRewardDetails(buyer, contractAddress, tokenId);
        if (isCustomErc721) {
            return erc721rewards[contractAddress].ohmPurchase * multiplier;
        } else if (isCustomErc1155) {
            return
                erc1155rewards[contractAddress][tokenId].ohmPurchase *
                multiplier;
        } else {
            return defaultRewards.ohmPurchase * multiplier;
        }
    }

    /// @notice Get the XP reward for minting an NFT with OHM
    /// @param buyer Buyer of the NFT
    /// @param contractAddress Address of the NFT being sold
    /// @param tokenId ID of the NFT being sold
    function getOhmMintReward(
        address buyer,
        address contractAddress,
        uint256 tokenId
    ) public view returns (uint256) {
        (
            bool isCustomErc721,
            bool isCustomErc1155,
            uint256 multiplier
        ) = _getRewardDetails(buyer, contractAddress, tokenId);
        if (isCustomErc721) {
            return erc721rewards[contractAddress].ohmMint * multiplier;
        } else if (isCustomErc1155) {
            return
                erc1155rewards[contractAddress][tokenId].ohmMint * multiplier;
        } else {
            return defaultRewards.ohmMint * multiplier;
        }
    }

    /// @notice Determine if an NFT has custom rewards and any multiplier based on the user's held NFTs
    /// @dev The multiplier and custom rewards are determined simultaneously to save on gas costs of iteration
    /// @param user Wallet address with potential multiplier NFTs
    /// @param contractAddress Address of the NFT being sold
    /// @param tokenId ID of the NFT being sold
    function _getRewardDetails(
        address user,
        address contractAddress,
        uint256 tokenId
    )
        internal
        view
        returns (
            bool isCustomErc721,
            bool isCustomErc1155,
            uint256 multiplier
        )
    {
        NFT[] memory _customRewardTokens = customRewardTokens; // save an SLOAD from length reading
        for (uint256 i = 0; i < _customRewardTokens.length; i++) {
            NFT memory token = _customRewardTokens[i];
            if (token.contractAddress.supportsInterface(0x80ac58cd)) {
                // is ERC721
                if (OdysseyERC721(token.contractAddress).balanceOf(user) > 0) {
                    uint256 reward = erc721rewards[token.contractAddress]
                        .multiplier;
                    multiplier = reward > 1 ? multiplier + reward : multiplier; // only increment if multiplier is non-one
                }
                if (contractAddress == token.contractAddress) {
                    isCustomErc721 = true;
                }
            } else if (token.contractAddress.supportsInterface(0xd9b67a26)) {
                // is isERC1155
                if (
                    OdysseyERC1155(token.contractAddress).balanceOf(
                        user,
                        token.id
                    ) > 0
                ) {
                    uint256 reward = erc1155rewards[token.contractAddress][
                        token.id
                    ].multiplier;
                    multiplier = reward > 1 ? multiplier + reward : multiplier; // only increment if multiplier is non-one
                    if (
                        contractAddress == token.contractAddress &&
                        tokenId == token.id
                    ) {
                        isCustomErc1155 = true;
                    }
                }
            }
        }
        multiplier = multiplier == 0 ? defaultRewards.multiplier : multiplier; // if no custom multiplier, use default
        multiplier = multiplier > 4 ? 4 : multiplier; // multiplier caps at 4
    }
}
error OdysseyXp_Unauthorized();
error OdysseyXp_NonTransferable();
error OdysseyXp_ZeroAssets();

contract OdysseyXp is ERC20 {
    using SafeTransferLib for ERC20;
    using FixedPointMathLib for uint256;

    struct UserHistory {
        uint256 balanceAtLastRedeem;
        uint256 globallyWithdrawnAtLastRedeem;
    }

    /*///////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Mint(address indexed owner, uint256 assets, uint256 xp);

    event Redeem(address indexed owner, uint256 assets, uint256 xp);

    /*///////////////////////////////////////////////////////////////
                            STATE VARIABLES
    //////////////////////////////////////////////////////////////*/

    address public router;
    address public exchange;
    address public owner;
    uint256 public globallyWithdrawn;
    ERC20 public immutable asset;
    OdysseyXpDirectory public directory;
    mapping(address => UserHistory) public userHistories;

    constructor(
        ERC20 _asset,
        OdysseyXpDirectory _directory,
        address _router,
        address _exchange,
        address _owner
    ) ERC20("Odyssey XP", "XP", 0) {
        asset = _asset;
        directory = _directory;
        router = _router;
        exchange = _exchange;
        owner = _owner;
    }

    /*///////////////////////////////////////////////////////////////
                            MODIFIERS
    //////////////////////////////////////////////////////////////*/

    function notOwner() internal view returns (bool) {
        return msg.sender != owner;
    }

    function notRouter() internal view returns (bool) {
        return msg.sender != router;
    }

    function notExchange() internal view returns (bool) {
        return msg.sender != exchange;
    }

    /*///////////////////////////////////////////////////////////////
                        RESTRICTED SETTERS
    //////////////////////////////////////////////////////////////*/

    function setExchange(address _exchange) external {
        if (notOwner()) revert OdysseyXp_Unauthorized();
        exchange = _exchange;
    }

    function setRouter(address _router) external {
        if (notOwner()) revert OdysseyXp_Unauthorized();
        router = _router;
    }

    function setDirectory(address _directory) external {
        if (notOwner()) revert OdysseyXp_Unauthorized();
        directory = OdysseyXpDirectory(_directory);
    }

    function transferOwnership(address _newOwner) external {
        if (notOwner()) revert OdysseyXp_Unauthorized();
        owner = _newOwner;
    }

    /*///////////////////////////////////////////////////////////////
                        XP Granting Methods
    //////////////////////////////////////////////////////////////*/

    function saleReward(
        address seller,
        address contractAddress,
        uint256 tokenId
    ) external {
        if (notExchange()) revert OdysseyXp_Unauthorized();
        _grantXP(
            seller,
            directory.getSaleReward(seller, contractAddress, tokenId)
        );
    }

    function purchaseReward(
        address buyer,
        address contractAddress,
        uint256 tokenId
    ) external {
        if (notExchange()) revert OdysseyXp_Unauthorized();
        _grantXP(
            buyer,
            directory.getPurchaseReward(buyer, contractAddress, tokenId)
        );
    }

    function mintReward(
        address buyer,
        address contractAddress,
        uint256 tokenId
    ) external {
        if (notRouter()) revert OdysseyXp_Unauthorized();
        _grantXP(
            buyer,
            directory.getMintReward(buyer, contractAddress, tokenId)
        );
    }

    function ohmPurchaseReward(
        address buyer,
        address contractAddress,
        uint256 tokenId
    ) external {
        if (notExchange()) revert OdysseyXp_Unauthorized();
        _grantXP(
            buyer,
            directory.getOhmPurchaseReward(buyer, contractAddress, tokenId)
        );
    }

    function ohmMintReward(
        address buyer,
        address contractAddress,
        uint256 tokenId
    ) external {
        if (notRouter()) revert OdysseyXp_Unauthorized();
        _grantXP(
            buyer,
            directory.getOhmMintReward(buyer, contractAddress, tokenId)
        );
    }

    /*///////////////////////////////////////////////////////////////
                            MINT LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Grants the receiver the given amount of XP
    /// @dev Forces the receiver to redeem if they have rewards available
    /// @param receiver The address to grant XP to
    /// @param xp The amount of XP to grant
    function _grantXP(address receiver, uint256 xp)
        internal
        returns (uint256 assets)
    {
        uint256 currentXp = balanceOf[receiver];
        if ((assets = previewRedeem(receiver, currentXp)) > 0)
            _redeem(receiver, assets, currentXp); // force redeeming to keep portions in line
        else if (currentXp == 0)
            userHistories[receiver]
                .globallyWithdrawnAtLastRedeem = globallyWithdrawn; // if a new user, adjust their history to calculate withdrawn at their first redeem
        _mint(receiver, xp);

        emit Mint(msg.sender, assets, xp);

        afterMint(assets, xp);
    }

    /*///////////////////////////////////////////////////////////////
                        REDEEM LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice external redeem method
    /// @dev will revert if there is nothing to redeem
    function redeem() public returns (uint256 assets) {
        uint256 xp = balanceOf[msg.sender];
        if ((assets = previewRedeem(msg.sender, xp)) == 0)
            revert OdysseyXp_ZeroAssets();
        _redeem(msg.sender, assets, xp);
    }

    /// @notice Internal logic for redeeming rewards
    /// @param receiver The receiver of rewards
    /// @param assets The amount of assets to grant
    /// @param xp The amount of XP the user is redeeming with
    function _redeem(
        address receiver,
        uint256 assets,
        uint256 xp
    ) internal virtual {
        beforeRedeem(assets, xp);

        userHistories[receiver].balanceAtLastRedeem =
            asset.balanceOf(address(this)) -
            assets;
        userHistories[receiver].globallyWithdrawnAtLastRedeem =
            globallyWithdrawn +
            assets;
        globallyWithdrawn += assets;

        asset.safeTransfer(receiver, assets);

        emit Redeem(receiver, assets, xp);
    }

    /*///////////////////////////////////////////////////////////////
                           ACCOUNTING LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Preview the result of a redeem for the given user with the given XP amount
    /// @param recipient The user to check potential rewards for
    /// @param xp The amount of XP the user is previewing a redeem for
    function previewRedeem(address recipient, uint256 xp)
        public
        view
        virtual
        returns (uint256)
    {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return
            supply == 0 || xp == 0
                ? 0
                : xp.mulDivDown(totalAssets(recipient), supply);
    }

    /// @notice The total amount of available assets for the user, adjusted based on their history
    /// @param user The user to check assets for
    function totalAssets(address user) internal view returns (uint256) {
        uint256 balance = asset.balanceOf(address(this)); // Saves an extra SLOAD if balance is non-zero.
        return
            balance +
            (globallyWithdrawn -
                userHistories[user].globallyWithdrawnAtLastRedeem) -
            userHistories[user].balanceAtLastRedeem;
    }

    /*///////////////////////////////////////////////////////////////
                       OVERRIDE TRANSFERABILITY
    //////////////////////////////////////////////////////////////*/

    function transfer(address to, uint256 amount)
        public
        override
        returns (bool)
    {
        revert OdysseyXp_NonTransferable();
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public override returns (bool) {
        revert OdysseyXp_NonTransferable();
    }

    /*///////////////////////////////////////////////////////////////
                         INTERNAL HOOKS LOGIC
    //////////////////////////////////////////////////////////////*/

    function beforeRedeem(uint256 assets, uint256 xp) internal virtual {}

    function afterMint(uint256 assets, uint256 xp) internal virtual {}
}

contract OdysseyLaunchPlatform is OdysseyDatabase, ReentrancyGuard {
    /*///////////////////////////////////////////////////////////////
                                ACTIONS
    //////////////////////////////////////////////////////////////*/
    function mintERC721(
        bytes32[] calldata merkleProof,
        bytes32 merkleRoot,
        uint256 minPrice,
        uint256 mintsPerUser,
        address tokenAddress,
        address currency,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external payable nonReentrant {
        if (OdysseyTokenFactory(factory).tokenExists(tokenAddress) == 0) {
            revert OdysseyLaunchPlatform_TokenDoesNotExist();
        }
        if (whitelistClaimed721[tokenAddress][msg.sender] >= mintsPerUser) {
            revert OdysseyLaunchPlatform_AlreadyClaimed();
        }
        // Check if user is already reserved + paid
        if (isReserved721[tokenAddress][msg.sender] == 0) {
            if (
                cumulativeSupply721[tokenAddress] >= maxSupply721[tokenAddress]
            ) {
                revert OdysseyLaunchPlatform_MaxSupplyCap();
            }
            {
                // Verify merkle root and minPrice signed by owner (all id's have same min price)
                bytes32 hash = keccak256(
                    abi.encode(
                        MERKLE_TREE_ROOT_ERC721_TYPEHASH,
                        merkleRoot,
                        minPrice,
                        mintsPerUser,
                        tokenAddress,
                        currency
                    )
                );
                Signature.verify(
                    hash,
                    ownerOf[tokenAddress],
                    v,
                    r,
                    s,
                    domainSeparator[tokenAddress]
                );
            }
            if (whitelistActive[tokenAddress] == 1) {
                // Verify user whitelisted
                MerkleWhiteList.verify(msg.sender, merkleProof, merkleRoot);
            }
            cumulativeSupply721[tokenAddress]++;

            OdysseyLib.Percentage storage percent = treasuryCommission[
                tokenAddress
            ];
            uint256 commission = (minPrice * percent.numerator) /
                percent.denominator;

            if (currency == address(0)) {
                if (msg.value < minPrice) {
                    revert OdysseyLaunchPlatform_InsufficientFunds();
                }
                (bool treasurySuccess, ) = treasury.call{value: commission}("");
                if (!treasurySuccess) {
                    revert OdysseyLaunchPlatform_TreasuryPayFailure();
                }
                (bool success, ) = royaltyRecipient[tokenAddress].call{
                    value: minPrice - commission
                }("");
                if (!success) {
                    revert OdysseyLaunchPlatform_FailedToPayEther();
                }
            } else {
                if (
                    ERC20(currency).allowance(msg.sender, address(this)) <
                    minPrice
                ) {
                    revert OdysseyLaunchPlatform_InsufficientFunds();
                }
                bool result = ERC20(currency).transferFrom(
                    msg.sender,
                    treasury,
                    commission
                );
                if (!result) {
                    revert OdysseyLaunchPlatform_TreasuryPayFailure();
                }
                result = ERC20(currency).transferFrom(
                    msg.sender,
                    royaltyRecipient[tokenAddress],
                    minPrice - commission
                );
                if (!result) {
                    revert OdysseyLaunchPlatform_FailedToPayERC20();
                }
                if (ohmFamilyCurrencies[currency] == 1) {
                    OdysseyXp(xp).ohmMintReward(msg.sender, tokenAddress, 0);
                }
            }
        } else {
            isReserved721[tokenAddress][msg.sender]--;
        }
        // Update State
        whitelistClaimed721[tokenAddress][msg.sender]++;
        OdysseyERC721(tokenAddress).mint(
            msg.sender,
            mintedSupply721[tokenAddress]++
        );
    }

    function reserveERC721(
        bytes32[] calldata merkleProof,
        bytes32 merkleRoot,
        uint256 minPrice,
        uint256 mintsPerUser,
        address tokenAddress,
        address currency,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external payable nonReentrant {
        if (OdysseyTokenFactory(factory).tokenExists(tokenAddress) == 0) {
            revert OdysseyLaunchPlatform_TokenDoesNotExist();
        }
        if (cumulativeSupply721[tokenAddress] >= maxSupply721[tokenAddress]) {
            revert OdysseyLaunchPlatform_MaxSupplyCap();
        }
        if (
            isReserved721[tokenAddress][msg.sender] +
                whitelistClaimed721[tokenAddress][msg.sender] >=
            mintsPerUser
        ) {
            revert OdysseyLaunchPlatform_ReservedOrClaimedMax();
        }
        {
            // Verify merkle root and minPrice signed by owner (all id's have same min price)
            bytes32 hash = keccak256(
                abi.encode(
                    MERKLE_TREE_ROOT_ERC721_TYPEHASH,
                    merkleRoot,
                    minPrice,
                    mintsPerUser,
                    tokenAddress,
                    currency
                )
            );
            Signature.verify(
                hash,
                ownerOf[tokenAddress],
                v,
                r,
                s,
                domainSeparator[tokenAddress]
            );
        }
        if (whitelistActive[tokenAddress] == 1) {
            // Verify user whitelisted
            MerkleWhiteList.verify(msg.sender, merkleProof, merkleRoot);
        }

        // Set user is reserved
        isReserved721[tokenAddress][msg.sender]++;
        // Increate Reserved + minted supply
        cumulativeSupply721[tokenAddress]++;

        OdysseyLib.Percentage storage percent = treasuryCommission[
            tokenAddress
        ];
        uint256 commission = (minPrice * percent.numerator) /
            percent.denominator;

        if (currency == address(0)) {
            if (msg.value < minPrice) {
                revert OdysseyLaunchPlatform_InsufficientFunds();
            }
            (bool treasurySuccess, ) = treasury.call{value: commission}("");
            if (!treasurySuccess) {
                revert OdysseyLaunchPlatform_TreasuryPayFailure();
            }
            (bool success, ) = royaltyRecipient[tokenAddress].call{
                value: minPrice - commission
            }("");
            if (!success) {
                revert OdysseyLaunchPlatform_FailedToPayEther();
            }
        } else {
            if (
                ERC20(currency).allowance(msg.sender, address(this)) < minPrice
            ) {
                revert OdysseyLaunchPlatform_InsufficientFunds();
            }
            bool result = ERC20(currency).transferFrom(
                msg.sender,
                treasury,
                commission
            );
            if (!result) {
                revert OdysseyLaunchPlatform_TreasuryPayFailure();
            }
            result = ERC20(currency).transferFrom(
                msg.sender,
                royaltyRecipient[tokenAddress],
                minPrice - commission
            );
            if (!result) {
                revert OdysseyLaunchPlatform_FailedToPayERC20();
            }
            if (ohmFamilyCurrencies[currency] == 1) {
                OdysseyXp(xp).ohmMintReward(msg.sender, tokenAddress, 0);
            }
        }
    }

    function mintERC1155(
        bytes32[] calldata merkleProof,
        bytes32 merkleRoot,
        uint256 minPrice,
        uint256 mintsPerUser,
        uint256 tokenId,
        address tokenAddress,
        address currency,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external payable nonReentrant {
        if (OdysseyTokenFactory(factory).tokenExists(tokenAddress) == 0) {
            revert OdysseyLaunchPlatform_TokenDoesNotExist();
        }
        if (
            whitelistClaimed1155[tokenAddress][msg.sender][tokenId] >=
            mintsPerUser
        ) {
            revert OdysseyLaunchPlatform_AlreadyClaimed();
        }
        // Check if user is already reserved + paid
        if (isReserved1155[tokenAddress][msg.sender][tokenId] == 0) {
            if (
                cumulativeSupply1155[tokenAddress][tokenId] >=
                maxSupply1155[tokenAddress][tokenId]
            ) {
                revert OdysseyLaunchPlatform_MaxSupplyCap();
            }
            {
                // Verify merkle root and minPrice signed by owner (all id's have same min price)
                bytes32 hash = keccak256(
                    abi.encode(
                        MERKLE_TREE_ROOT_ERC1155_TYPEHASH,
                        merkleRoot,
                        minPrice,
                        mintsPerUser,
                        tokenId,
                        tokenAddress,
                        currency
                    )
                );
                Signature.verify(
                    hash,
                    ownerOf[tokenAddress],
                    v,
                    r,
                    s,
                    domainSeparator[tokenAddress]
                );
            }

            if (whitelistActive[tokenAddress] == 1) {
                // Verify user whitelisted
                MerkleWhiteList.verify(msg.sender, merkleProof, merkleRoot);
            }
            cumulativeSupply1155[tokenAddress][tokenId]++;

            OdysseyLib.Percentage storage percent = treasuryCommission[
                tokenAddress
            ];
            uint256 commission = (minPrice * percent.numerator) /
                percent.denominator;

            if (currency == address(0)) {
                if (msg.value < minPrice) {
                    revert OdysseyLaunchPlatform_InsufficientFunds();
                }
                (bool treasurySuccess, ) = treasury.call{value: commission}("");
                if (!treasurySuccess) {
                    revert OdysseyLaunchPlatform_TreasuryPayFailure();
                }
                (bool success, ) = royaltyRecipient[tokenAddress].call{
                    value: minPrice - commission
                }("");
                if (!success) {
                    revert OdysseyLaunchPlatform_FailedToPayEther();
                }
            } else {
                if (
                    ERC20(currency).allowance(msg.sender, address(this)) <
                    minPrice
                ) {
                    revert OdysseyLaunchPlatform_InsufficientFunds();
                }
                bool result = ERC20(currency).transferFrom(
                    msg.sender,
                    treasury,
                    commission
                );
                if (!result) {
                    revert OdysseyLaunchPlatform_TreasuryPayFailure();
                }
                result = ERC20(currency).transferFrom(
                    msg.sender,
                    royaltyRecipient[tokenAddress],
                    minPrice - commission
                );
                if (!result) {
                    revert OdysseyLaunchPlatform_FailedToPayERC20();
                }
                if (ohmFamilyCurrencies[currency] == 1) {
                    OdysseyXp(xp).ohmMintReward(
                        msg.sender,
                        tokenAddress,
                        tokenId
                    );
                }
            }
        } else {
            isReserved1155[tokenAddress][msg.sender][tokenId]--;
        }
        // Update State
        whitelistClaimed1155[tokenAddress][msg.sender][tokenId]++;

        OdysseyERC1155(tokenAddress).mint(msg.sender, tokenId);
    }

    function reserveERC1155(
        bytes32[] calldata merkleProof,
        bytes32 merkleRoot,
        uint256 minPrice,
        uint256 mintsPerUser,
        uint256 tokenId,
        address tokenAddress,
        address currency,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external payable nonReentrant {
        if (OdysseyTokenFactory(factory).tokenExists(tokenAddress) == 0) {
            revert OdysseyLaunchPlatform_TokenDoesNotExist();
        }
        if (
            cumulativeSupply1155[tokenAddress][tokenId] >=
            maxSupply1155[tokenAddress][tokenId]
        ) {
            revert OdysseyLaunchPlatform_MaxSupplyCap();
        }
        if (
            isReserved1155[tokenAddress][msg.sender][tokenId] +
                whitelistClaimed1155[tokenAddress][msg.sender][tokenId] >=
            mintsPerUser
        ) {
            revert OdysseyLaunchPlatform_ReservedOrClaimedMax();
        }
        {
            // Verify merkle root and minPrice signed by owner (all id's have same min price)
            bytes32 hash = keccak256(
                abi.encode(
                    MERKLE_TREE_ROOT_ERC1155_TYPEHASH,
                    merkleRoot,
                    minPrice,
                    mintsPerUser,
                    tokenId,
                    tokenAddress,
                    currency
                )
            );
            Signature.verify(
                hash,
                ownerOf[tokenAddress],
                v,
                r,
                s,
                domainSeparator[tokenAddress]
            );
        }

        if (whitelistActive[tokenAddress] == 1) {
            // Verify user whitelisted
            MerkleWhiteList.verify(msg.sender, merkleProof, merkleRoot);
        }

        // Set user is reserved
        isReserved1155[tokenAddress][msg.sender][tokenId]++;
        // Increase Reserved + minted supply
        cumulativeSupply1155[tokenAddress][tokenId]++;

        OdysseyLib.Percentage storage percent = treasuryCommission[
            tokenAddress
        ];
        uint256 commission = (minPrice * percent.numerator) /
            percent.denominator;

        if (currency == address(0)) {
            if (msg.value < minPrice) {
                revert OdysseyLaunchPlatform_InsufficientFunds();
            }
            (bool treasurySuccess, ) = treasury.call{value: commission}("");
            if (!treasurySuccess) {
                revert OdysseyLaunchPlatform_TreasuryPayFailure();
            }
            (bool success, ) = royaltyRecipient[tokenAddress].call{
                value: minPrice - commission
            }("");
            if (!success) {
                revert OdysseyLaunchPlatform_FailedToPayEther();
            }
        } else {
            if (
                ERC20(currency).allowance(msg.sender, address(this)) < minPrice
            ) {
                revert OdysseyLaunchPlatform_InsufficientFunds();
            }
            bool result = ERC20(currency).transferFrom(
                msg.sender,
                treasury,
                commission
            );
            if (!result) {
                revert OdysseyLaunchPlatform_TreasuryPayFailure();
            }
            result = ERC20(currency).transferFrom(
                msg.sender,
                royaltyRecipient[tokenAddress],
                minPrice - commission
            );
            if (!result) {
                revert OdysseyLaunchPlatform_FailedToPayERC20();
            }
            if (ohmFamilyCurrencies[currency] == 1) {
                OdysseyXp(xp).ohmMintReward(msg.sender, tokenAddress, tokenId);
            }
        }
    }

    function setWhitelistStatus(address addr, bool active)
        external
        nonReentrant
    {
        if (OdysseyTokenFactory(factory).tokenExists(addr) == 0) {
            revert OdysseyLaunchPlatform_TokenDoesNotExist();
        }
        whitelistActive[addr] = active ? 1 : 0;
    }

    function mint721OnCreate(uint256 amount, address token)
        external
        nonReentrant
    {
        cumulativeSupply721[token] = amount;
        mintedSupply721[token] = amount;
        uint256 i;
        for (; i < amount; ++i) {
            OdysseyERC721(token).mint(msg.sender, i);
        }
    }

    function mint1155OnCreate(
        uint256[] calldata tokenIds,
        uint256[] calldata amounts,
        address token
    ) external nonReentrant {
        uint256 i;
        for (; i < tokenIds.length; ++i) {
            cumulativeSupply1155[token][tokenIds[i]] = amounts[i];
            OdysseyERC1155(token).mintBatch(
                msg.sender,
                tokenIds[i],
                amounts[i]
            );
        }
    }

    function ownerMint721(address token, address to) external nonReentrant {
        if (cumulativeSupply721[token] >= maxSupply721[token]) {
            revert OdysseyLaunchPlatform_MaxSupplyCap();
        }
        cumulativeSupply721[token]++;
        OdysseyERC721(token).mint(to, mintedSupply721[token]++);
    }

    function ownerMint1155(
        uint256 id,
        uint256 amount,
        address token,
        address to
    ) external nonReentrant {
        if (
            cumulativeSupply1155[token][id] + amount > maxSupply1155[token][id]
        ) {
            revert OdysseyLaunchPlatform_MaxSupplyCap();
        }
        cumulativeSupply1155[token][id] += amount;
        OdysseyERC1155(token).mintBatch(to, id, amount);
    }
}

contract OdysseyRouter is OdysseyDatabase, ReentrancyGuard {
    error OdysseyRouter_TokenIDSupplyMismatch();
    error OdysseyRouter_WhitelistUpdateFail();
    error OdysseyRouter_Unauthorized();
    error OdysseyRouter_OwnerMintFailure();
    error OdysseyRouter_BadTokenAddress();
    error OdysseyRouter_BadOwnerAddress();
    error OdysseyRouter_BadSenderAddress();
    error OdysseyRouter_BadRecipientAddress();
    error OdysseyRouter_BadTreasuryAddress();
    error OdysseyRouter_BadAdminAddress();

    constructor(
        address treasury_,
        address xpDirectory_,
        address xp_,
        address[] memory ohmCurrencies_
    ) {
        launchPlatform = address(new OdysseyLaunchPlatform());
        factory = address(new OdysseyTokenFactory());
        treasury = treasury_;
        admin = msg.sender;
        uint256 i;
        for (; i < ohmCurrencies_.length; i++) {
            ohmFamilyCurrencies[ohmCurrencies_[i]] = 1;
        }
        if (xp_ == address(0)) {
            if (xpDirectory_ == address(0)) {
                xpDirectory_ = address(new OdysseyXpDirectory());
                OdysseyXpDirectory(xpDirectory_).setDefaultRewards(
                    1,
                    1,
                    1,
                    3,
                    3,
                    1
                );
                OdysseyXpDirectory(xpDirectory_).transferOwnership(admin);
            }
            xp_ = address(
                new OdysseyXp(
                    ERC20(ohmCurrencies_[0]),
                    OdysseyXpDirectory(xpDirectory_),
                    address(this),
                    address(this),
                    admin
                )
            );
        }
        xp = xp_;
    }

    function Factory() public view returns (OdysseyTokenFactory) {
        return OdysseyTokenFactory(readSlotAsAddress(1));
    }

    function create1155(
        string calldata name,
        string calldata symbol,
        string calldata baseURI,
        OdysseyLib.Odyssey1155Info calldata info,
        OdysseyLib.Percentage calldata treasuryPercentage,
        address royaltyReceiver,
        bool whitelist
    ) external returns (address token) {
        if (info.maxSupply.length != info.tokenIds.length) {
            revert OdysseyRouter_TokenIDSupplyMismatch();
        }
        token = Factory().create1155(msg.sender, name, symbol, baseURI);
        ownerOf[token] = msg.sender;
        whitelistActive[token] = whitelist ? 1 : 0;
        royaltyRecipient[token] = royaltyReceiver;
        uint256 i;
        for (; i < info.tokenIds.length; ++i) {
            maxSupply1155[token][info.tokenIds[i]] = (info.maxSupply[i] == 0)
                ? type(uint256).max
                : info.maxSupply[i];
        }

        domainSeparator[token] = keccak256(
            abi.encode(
                // keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)')
                0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
                keccak256(bytes(Strings.toHexString(uint160(token)))),
                0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6, // keccak256(bytes("1"))
                block.chainid,
                token
            )
        );

        if (OdysseyLib.compareDefaultPercentage(treasuryPercentage)) {
            // Treasury % was greater than 3/100
            treasuryCommission[token] = treasuryPercentage;
        } else {
            // Treasury % was less than 3/100, using 3/100 as default
            treasuryCommission[token] = OdysseyLib.Percentage(3, 100);
        }

        if (info.reserveAmounts.length > 0) {
            (bool success, bytes memory data) = launchPlatform.delegatecall(
                abi.encodeWithSignature(
                    "mint1155OnCreate(uint256[],uint256[],address)",
                    info.tokenIds,
                    info.reserveAmounts,
                    token
                )
            );
            if (!success) {
                if (data.length == 0) revert();
                assembly {
                    revert(add(32, data), mload(data))
                }
            }
        }
        return token;
    }

    function create721(
        string calldata name,
        string calldata symbol,
        string calldata baseURI,
        uint256 maxSupply,
        uint256 reserveAmount,
        OdysseyLib.Percentage calldata treasuryPercentage,
        address royaltyReceiver,
        bool whitelist
    ) external returns (address token) {
        token = Factory().create721(msg.sender, name, symbol, baseURI);
        ownerOf[token] = msg.sender;
        maxSupply721[token] = (maxSupply == 0) ? type(uint256).max : maxSupply;
        whitelistActive[token] = whitelist ? 1 : 0;
        royaltyRecipient[token] = royaltyReceiver;
        domainSeparator[token] = keccak256(
            abi.encode(
                // keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)')
                0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
                keccak256(bytes(Strings.toHexString(uint160(token)))),
                0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6, // keccak256(bytes("1"))
                block.chainid,
                token
            )
        );

        if (OdysseyLib.compareDefaultPercentage(treasuryPercentage)) {
            // Treasury % was greater than 3/100
            treasuryCommission[token] = treasuryPercentage;
        } else {
            // Treasury % was less than 3/100, using 3/100 as default
            treasuryCommission[token] = OdysseyLib.Percentage(3, 100);
        }

        if (reserveAmount > 0) {
            (bool success, bytes memory data) = launchPlatform.delegatecall(
                abi.encodeWithSignature(
                    "mint721OnCreate(uint256,address)",
                    reserveAmount,
                    token
                )
            );
            if (!success) {
                if (data.length == 0) revert();
                assembly {
                    revert(add(32, data), mload(data))
                }
            }
        }

        return token;
    }

    function mintERC721(
        bytes32[] calldata merkleProof,
        bytes32 merkleRoot,
        uint256 minPrice,
        uint256 mintsPerUser,
        address tokenAddress,
        address currency,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public payable {
        (bool success, bytes memory data) = launchPlatform.delegatecall(
            abi.encodeWithSignature(
                "mintERC721(bytes32[],bytes32,uint256,uint256,address,address,uint8,bytes32,bytes32)",
                merkleProof,
                merkleRoot,
                minPrice,
                mintsPerUser,
                tokenAddress,
                currency,
                v,
                r,
                s
            )
        );
        if (!success) {
            if (data.length == 0) revert();
            assembly {
                revert(add(32, data), mload(data))
            }
        }
    }

    function batchMintERC721(OdysseyLib.BatchMint calldata batch)
        public
        payable
    {
        for (uint256 i = 0; i < batch.tokenAddress.length; i++) {
            (bool success, bytes memory data) = launchPlatform.delegatecall(
                abi.encodeWithSignature(
                    "mintERC721(bytes32[],bytes32,uint256,uint256,address,address,uint8,bytes32,bytes32)",
                    batch.merkleProof[i],
                    batch.merkleRoot[i],
                    batch.minPrice[i],
                    batch.mintsPerUser[i],
                    batch.tokenAddress[i],
                    batch.currency[i],
                    batch.v[i],
                    batch.r[i],
                    batch.s[i]
                )
            );
            if (!success) {
                if (data.length == 0) revert();
                assembly {
                    revert(add(32, data), mload(data))
                }
            }
        }
    }

    function reserveERC721(
        bytes32[] calldata merkleProof,
        bytes32 merkleRoot,
        uint256 minPrice,
        uint256 mintsPerUser,
        address tokenAddress,
        address currency,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public payable {
        (bool success, bytes memory data) = launchPlatform.delegatecall(
            abi.encodeWithSignature(
                "reserveERC721(bytes32[],bytes32,uint256,uint256,address,address,uint8,bytes32,bytes32)",
                merkleProof,
                merkleRoot,
                minPrice,
                mintsPerUser,
                tokenAddress,
                currency,
                v,
                r,
                s
            )
        );
        if (!success) {
            if (data.length == 0) revert();
            assembly {
                revert(add(32, data), mload(data))
            }
        }
    }

    function batchReserveERC721(OdysseyLib.BatchMint calldata batch)
        public
        payable
    {
        for (uint256 i = 0; i < batch.tokenAddress.length; i++) {
            (bool success, bytes memory data) = launchPlatform.delegatecall(
                abi.encodeWithSignature(
                    "reserveERC721(bytes32[],bytes32,uint256,uint256,address,address,uint8,bytes32,bytes32)",
                    batch.merkleProof[i],
                    batch.merkleRoot[i],
                    batch.minPrice[i],
                    batch.mintsPerUser[i],
                    batch.tokenAddress[i],
                    batch.currency[i],
                    batch.v[i],
                    batch.r[i],
                    batch.s[i]
                )
            );
            if (!success) {
                if (data.length == 0) revert();
                assembly {
                    revert(add(32, data), mload(data))
                }
            }
        }
    }

    function mintERC1155(
        bytes32[] calldata merkleProof,
        bytes32 merkleRoot,
        uint256 minPrice,
        uint256 mintsPerUser,
        uint256 tokenId,
        address tokenAddress,
        address currency,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public payable {
        (bool success, bytes memory data) = launchPlatform.delegatecall(
            abi.encodeWithSignature(
                "mintERC1155(bytes32[],bytes32,uint256,uint256,uint256,address,address,uint8,bytes32,bytes32)",
                merkleProof,
                merkleRoot,
                minPrice,
                mintsPerUser,
                tokenId,
                tokenAddress,
                currency,
                v,
                r,
                s
            )
        );
        if (!success) {
            if (data.length == 0) revert();
            assembly {
                revert(add(32, data), mload(data))
            }
        }
    }

    function batchMintERC1155(OdysseyLib.BatchMint calldata batch)
        public
        payable
    {
        for (uint256 i = 0; i < batch.tokenAddress.length; i++) {
            (bool success, bytes memory data) = launchPlatform.delegatecall(
                abi.encodeWithSignature(
                    "mintERC1155(bytes32[],bytes32,uint256,uint256,uint256,address,address,uint8,bytes32,bytes32)",
                    batch.merkleProof[i],
                    batch.merkleRoot[i],
                    batch.minPrice[i],
                    batch.mintsPerUser[i],
                    batch.tokenId[i],
                    batch.tokenAddress[i],
                    batch.currency[i],
                    batch.v[i],
                    batch.r[i],
                    batch.s[i]
                )
            );
            if (!success) {
                if (data.length == 0) revert();
                assembly {
                    revert(add(32, data), mload(data))
                }
            }
        }
    }

    function reserveERC1155(
        bytes32[] calldata merkleProof,
        bytes32 merkleRoot,
        uint256 minPrice,
        uint256 mintsPerUser,
        uint256 tokenId,
        address tokenAddress,
        address currency,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public payable {
        (bool success, bytes memory data) = launchPlatform.delegatecall(
            abi.encodeWithSignature(
                "reserveERC1155(bytes32[],bytes32,uint256,uint256,uint256,address,address,uint8,bytes32,bytes32)",
                merkleProof,
                merkleRoot,
                minPrice,
                mintsPerUser,
                tokenId,
                tokenAddress,
                currency,
                v,
                r,
                s
            )
        );
        if (!success) {
            if (data.length == 0) revert();
            assembly {
                revert(add(32, data), mload(data))
            }
        }
    }

    function batchReserveERC1155(OdysseyLib.BatchMint calldata batch)
        public
        payable
    {
        for (uint256 i = 0; i < batch.tokenAddress.length; i++) {
            (bool success, bytes memory data) = launchPlatform.delegatecall(
                abi.encodeWithSignature(
                    "reserveERC1155(bytes32[],bytes32,uint256,uint256,uint256,address,address,uint8,bytes32,bytes32)",
                    batch.merkleProof[i],
                    batch.merkleRoot[i],
                    batch.minPrice[i],
                    batch.mintsPerUser[i],
                    batch.tokenId[i],
                    batch.tokenAddress[i],
                    batch.currency[i],
                    batch.v[i],
                    batch.r[i],
                    batch.s[i]
                )
            );
            if (!success) {
                if (data.length == 0) revert();
                assembly {
                    revert(add(32, data), mload(data))
                }
            }
        }
    }

    function setWhitelistStatus(address addr, bool active) public {
        if (msg.sender != ownerOf[addr]) {
            revert OdysseyRouter_Unauthorized();
        }
        (bool success, ) = launchPlatform.delegatecall(
            abi.encodeWithSignature(
                "setWhitelistStatus(address,bool)",
                addr,
                active
            )
        );
        if (!success) {
            revert OdysseyRouter_WhitelistUpdateFail();
        }
    }

    function ownerMint721(address token, address to) public {
        if (ownerOf[token] != msg.sender) {
            revert OdysseyRouter_Unauthorized();
        }
        (bool success, ) = launchPlatform.delegatecall(
            abi.encodeWithSignature("ownerMint721(address,address)", token, to)
        );
        if (!success) {
            revert OdysseyRouter_OwnerMintFailure();
        }
    }

    function ownerMint1155(
        uint256 id,
        uint256 amount,
        address token,
        address to
    ) public {
        if (ownerOf[token] != msg.sender) {
            revert OdysseyRouter_Unauthorized();
        }
        (bool success, ) = launchPlatform.delegatecall(
            abi.encodeWithSignature(
                "ownerMint1155(uint256,uint256,address,address)",
                id,
                amount,
                token,
                to
            )
        );
        if (!success) {
            revert OdysseyRouter_OwnerMintFailure();
        }
    }

    function setOwnerShip(address token, address newOwner) public {
        if (token == address(0)) {
            revert OdysseyRouter_BadTokenAddress();
        }
        if (newOwner == address(0)) {
            revert OdysseyRouter_BadOwnerAddress();
        }
        if (msg.sender == address(0)) {
            revert OdysseyRouter_BadSenderAddress();
        }
        if (ownerOf[token] != msg.sender) {
            revert OdysseyRouter_Unauthorized();
        }
        ownerOf[token] = newOwner;
    }

    function setRoyaltyRecipient(address token, address recipient) public {
        if (token == address(0)) {
            revert OdysseyRouter_BadTokenAddress();
        }
        if (recipient == address(0)) {
            revert OdysseyRouter_BadRecipientAddress();
        }
        if (msg.sender == address(0)) {
            revert OdysseyRouter_BadSenderAddress();
        }
        if (ownerOf[token] != msg.sender) {
            revert OdysseyRouter_Unauthorized();
        }
        royaltyRecipient[token] = recipient;
    }

    function setTreasury(address newTreasury) public {
        if (msg.sender != admin) {
            revert OdysseyRouter_Unauthorized();
        }
        if (msg.sender == address(0)) {
            revert OdysseyRouter_BadSenderAddress();
        }
        if (newTreasury == address(0)) {
            revert OdysseyRouter_BadTreasuryAddress();
        }
        treasury = newTreasury;
    }

    function setXP(address newXp) public {
        if (msg.sender != admin) {
            revert OdysseyRouter_Unauthorized();
        }
        if (msg.sender == address(0)) {
            revert OdysseyRouter_BadSenderAddress();
        }
        if (newXp == address(0)) {
            revert OdysseyRouter_BadTokenAddress();
        }
        xp = newXp;
    }

    function setAdmin(address newAdmin) public {
        if (msg.sender != admin) {
            revert OdysseyRouter_Unauthorized();
        }
        if (msg.sender == address(0)) {
            revert OdysseyRouter_BadSenderAddress();
        }
        if (newAdmin == address(0)) {
            revert OdysseyRouter_BadAdminAddress();
        }
        admin = newAdmin;
    }

    function setMaxSupply721(address token, uint256 amount) public {
        if (ownerOf[token] != msg.sender) {
            revert OdysseyRouter_Unauthorized();
        }
        maxSupply721[token] = amount;
    }

    function setMaxSupply1155(
        address token,
        uint256[] calldata tokenIds,
        uint256[] calldata amounts
    ) public {
        if (ownerOf[token] != msg.sender) {
            revert OdysseyRouter_Unauthorized();
        }
        uint256 i;
        for (; i < tokenIds.length; ++i) {
            maxSupply1155[token][tokenIds[i]] = amounts[i];
        }
    }
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address","name":"treasury_","type":"address"},{"internalType":"address","name":"xpDirectory_","type":"address"},{"internalType":"address","name":"xp_","type":"address"},{"internalType":"address[]","name":"ohmCurrencies_","type":"address[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"OdysseyLaunchPlatform_AlreadyClaimed","type":"error"},{"inputs":[],"name":"OdysseyLaunchPlatform_FailedToPayERC20","type":"error"},{"inputs":[],"name":"OdysseyLaunchPlatform_FailedToPayEther","type":"error"},{"inputs":[],"name":"OdysseyLaunchPlatform_InsufficientFunds","type":"error"},{"inputs":[],"name":"OdysseyLaunchPlatform_MaxSupplyCap","type":"error"},{"inputs":[],"name":"OdysseyLaunchPlatform_ReservedOrClaimedMax","type":"error"},{"inputs":[],"name":"OdysseyLaunchPlatform_TokenDoesNotExist","type":"error"},{"inputs":[],"name":"OdysseyLaunchPlatform_TreasuryPayFailure","type":"error"},{"inputs":[],"name":"OdysseyRouter_BadAdminAddress","type":"error"},{"inputs":[],"name":"OdysseyRouter_BadOwnerAddress","type":"error"},{"inputs":[],"name":"OdysseyRouter_BadRecipientAddress","type":"error"},{"inputs":[],"name":"OdysseyRouter_BadSenderAddress","type":"error"},{"inputs":[],"name":"OdysseyRouter_BadTokenAddress","type":"error"},{"inputs":[],"name":"OdysseyRouter_BadTreasuryAddress","type":"error"},{"inputs":[],"name":"OdysseyRouter_OwnerMintFailure","type":"error"},{"inputs":[],"name":"OdysseyRouter_TokenIDSupplyMismatch","type":"error"},{"inputs":[],"name":"OdysseyRouter_Unauthorized","type":"error"},{"inputs":[],"name":"OdysseyRouter_WhitelistUpdateFail","type":"error"},{"inputs":[],"name":"Factory","outputs":[{"internalType":"contract OdysseyTokenFactory","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MERKLE_TREE_ROOT_ERC1155_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MERKLE_TREE_ROOT_ERC721_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32[][]","name":"merkleProof","type":"bytes32[][]"},{"internalType":"bytes32[]","name":"merkleRoot","type":"bytes32[]"},{"internalType":"uint256[]","name":"minPrice","type":"uint256[]"},{"internalType":"uint256[]","name":"mintsPerUser","type":"uint256[]"},{"internalType":"uint256[]","name":"tokenId","type":"uint256[]"},{"internalType":"address[]","name":"tokenAddress","type":"address[]"},{"internalType":"address[]","name":"currency","type":"address[]"},{"internalType":"uint8[]","name":"v","type":"uint8[]"},{"internalType":"bytes32[]","name":"r","type":"bytes32[]"},{"internalType":"bytes32[]","name":"s","type":"bytes32[]"}],"internalType":"struct OdysseyLib.BatchMint","name":"batch","type":"tuple"}],"name":"batchMintERC1155","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32[][]","name":"merkleProof","type":"bytes32[][]"},{"internalType":"bytes32[]","name":"merkleRoot","type":"bytes32[]"},{"internalType":"uint256[]","name":"minPrice","type":"uint256[]"},{"internalType":"uint256[]","name":"mintsPerUser","type":"uint256[]"},{"internalType":"uint256[]","name":"tokenId","type":"uint256[]"},{"internalType":"address[]","name":"tokenAddress","type":"address[]"},{"internalType":"address[]","name":"currency","type":"address[]"},{"internalType":"uint8[]","name":"v","type":"uint8[]"},{"internalType":"bytes32[]","name":"r","type":"bytes32[]"},{"internalType":"bytes32[]","name":"s","type":"bytes32[]"}],"internalType":"struct OdysseyLib.BatchMint","name":"batch","type":"tuple"}],"name":"batchMintERC721","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32[][]","name":"merkleProof","type":"bytes32[][]"},{"internalType":"bytes32[]","name":"merkleRoot","type":"bytes32[]"},{"internalType":"uint256[]","name":"minPrice","type":"uint256[]"},{"internalType":"uint256[]","name":"mintsPerUser","type":"uint256[]"},{"internalType":"uint256[]","name":"tokenId","type":"uint256[]"},{"internalType":"address[]","name":"tokenAddress","type":"address[]"},{"internalType":"address[]","name":"currency","type":"address[]"},{"internalType":"uint8[]","name":"v","type":"uint8[]"},{"internalType":"bytes32[]","name":"r","type":"bytes32[]"},{"internalType":"bytes32[]","name":"s","type":"bytes32[]"}],"internalType":"struct OdysseyLib.BatchMint","name":"batch","type":"tuple"}],"name":"batchReserveERC1155","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32[][]","name":"merkleProof","type":"bytes32[][]"},{"internalType":"bytes32[]","name":"merkleRoot","type":"bytes32[]"},{"internalType":"uint256[]","name":"minPrice","type":"uint256[]"},{"internalType":"uint256[]","name":"mintsPerUser","type":"uint256[]"},{"internalType":"uint256[]","name":"tokenId","type":"uint256[]"},{"internalType":"address[]","name":"tokenAddress","type":"address[]"},{"internalType":"address[]","name":"currency","type":"address[]"},{"internalType":"uint8[]","name":"v","type":"uint8[]"},{"internalType":"bytes32[]","name":"r","type":"bytes32[]"},{"internalType":"bytes32[]","name":"s","type":"bytes32[]"}],"internalType":"struct OdysseyLib.BatchMint","name":"batch","type":"tuple"}],"name":"batchReserveERC721","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"string","name":"baseURI","type":"string"},{"components":[{"internalType":"uint256[]","name":"maxSupply","type":"uint256[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"reserveAmounts","type":"uint256[]"}],"internalType":"struct OdysseyLib.Odyssey1155Info","name":"info","type":"tuple"},{"components":[{"internalType":"uint256","name":"numerator","type":"uint256"},{"internalType":"uint256","name":"denominator","type":"uint256"}],"internalType":"struct OdysseyLib.Percentage","name":"treasuryPercentage","type":"tuple"},{"internalType":"address","name":"royaltyReceiver","type":"address"},{"internalType":"bool","name":"whitelist","type":"bool"}],"name":"create1155","outputs":[{"internalType":"address","name":"token","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"string","name":"baseURI","type":"string"},{"internalType":"uint256","name":"maxSupply","type":"uint256"},{"internalType":"uint256","name":"reserveAmount","type":"uint256"},{"components":[{"internalType":"uint256","name":"numerator","type":"uint256"},{"internalType":"uint256","name":"denominator","type":"uint256"}],"internalType":"struct OdysseyLib.Percentage","name":"treasuryPercentage","type":"tuple"},{"internalType":"address","name":"royaltyReceiver","type":"address"},{"internalType":"bool","name":"whitelist","type":"bool"}],"name":"create721","outputs":[{"internalType":"address","name":"token","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"cumulativeSupply1155","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"cumulativeSupply721","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"domainSeparator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"isReserved1155","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"isReserved721","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"maxSupply1155","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxSupply721","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"},{"internalType":"bytes32","name":"merkleRoot","type":"bytes32"},{"internalType":"uint256","name":"minPrice","type":"uint256"},{"internalType":"uint256","name":"mintsPerUser","type":"uint256"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"currency","type":"address"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"mintERC1155","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"},{"internalType":"bytes32","name":"merkleRoot","type":"bytes32"},{"internalType":"uint256","name":"minPrice","type":"uint256"},{"internalType":"uint256","name":"mintsPerUser","type":"uint256"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"currency","type":"address"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"mintERC721","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"mintedSupply721","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"ohmFamilyCurrencies","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"}],"name":"ownerMint1155","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"}],"name":"ownerMint721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"slot","type":"uint256"}],"name":"readSlotAsAddress","outputs":[{"internalType":"address","name":"data","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"},{"internalType":"bytes32","name":"merkleRoot","type":"bytes32"},{"internalType":"uint256","name":"minPrice","type":"uint256"},{"internalType":"uint256","name":"mintsPerUser","type":"uint256"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"currency","type":"address"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"reserveERC1155","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"},{"internalType":"bytes32","name":"merkleRoot","type":"bytes32"},{"internalType":"uint256","name":"minPrice","type":"uint256"},{"internalType":"uint256","name":"mintsPerUser","type":"uint256"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"currency","type":"address"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"reserveERC721","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"royaltyRecipient","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newAdmin","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"setMaxSupply1155","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"setMaxSupply721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"newOwner","type":"address"}],"name":"setOwnerShip","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"recipient","type":"address"}],"name":"setRoyaltyRecipient","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newTreasury","type":"address"}],"name":"setTreasury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"bool","name":"active","type":"bool"}],"name":"setWhitelistStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newXp","type":"address"}],"name":"setXP","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"treasuryCommission","outputs":[{"internalType":"uint256","name":"numerator","type":"uint256"},{"internalType":"uint256","name":"denominator","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whitelistActive","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"whitelistClaimed1155","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"whitelistClaimed721","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]

608060405260016014553480156200001657600080fd5b506040516200d1cd3803806200d1cd8339810160408190526200003991620003af565b604051620000479062000344565b604051809103906000f08015801562000064573d6000803e3d6000fd5b50600080546001600160a01b0319166001600160a01b0392909216919091179055604051620000939062000352565b604051809103906000f080158015620000b0573d6000803e3d6000fd5b50600180546001600160a01b039283166001600160a01b0319918216179091556002805492871692821692909217909155600380549091163317905560005b815181101562000156576001600a6000848481518110620001145762000114620004ba565b60200260200101516001600160a01b03166001600160a01b031681526020019081526020016000208190555080806200014d90620004d0565b915050620000ef565b6001600160a01b03831662000319576001600160a01b0384166200028457604051620001829062000360565b604051809103906000f0801580156200019f573d6000803e3d6000fd5b5060405163ee2f70a960e01b81526001600482018190526024820181905260448201819052600360648301819052608483015260a48201529094506001600160a01b0385169063ee2f70a99060c401600060405180830381600087803b1580156200020957600080fd5b505af11580156200021e573d6000803e3d6000fd5b505060035460405163f2fde38b60e01b81526001600160a01b039182166004820152908716925063f2fde38b9150602401600060405180830381600087803b1580156200026a57600080fd5b505af11580156200027f573d6000803e3d6000fd5b505050505b816000815181106200029a576200029a620004ba565b6020026020010151843030600360009054906101000a90046001600160a01b0316604051620002c9906200036e565b6001600160a01b0395861681529385166020850152918416604084015283166060830152909116608082015260a001604051809103906000f08015801562000315573d6000803e3d6000fd5b5092505b5050600480546001600160a01b0319166001600160a01b039290921691909117905550620004fa9050565b61329e806200348a83390190565b614155806200672883390190565b611082806200a87d83390190565b6118ce806200b8ff83390190565b80516001600160a01b03811681146200039457600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60008060008060808587031215620003c657600080fd5b620003d1856200037c565b93506020620003e28187016200037c565b9350620003f2604087016200037c565b60608701519093506001600160401b03808211156200041057600080fd5b818801915088601f8301126200042557600080fd5b8151818111156200043a576200043a62000399565b8060051b604051601f19603f8301168101818110858211171562000462576200046262000399565b60405291825284820192508381018501918b8311156200048157600080fd5b938501935b82851015620004aa576200049a856200037c565b8452938501939285019262000486565b989b979a50959850505050505050565b634e487b7160e01b600052603260045260246000fd5b6000600019821415620004f357634e487b7160e01b600052601160045260246000fd5b5060010190565b612f80806200050a6000396000f3fe6080604052600436106102305760003560e01c8063a41a964a1161012e578063c5936954116100ab578063d425f3cf1161006f578063d425f3cf1461077b578063dd55ebdc146107a8578063de8f21db146107d5578063e3d9a96d146107e8578063f0f442601461081557600080fd5b8063c5936954146106c1578063c67596f2146106e1578063c6aa2fb614610719578063c83dd23114610746578063cf6344391461075b57600080fd5b8063af91e8c9116100f2578063af91e8c91461063b578063b82263ca1461065b578063b98a44521461066e578063c188e3951461069b578063c4dd9ab7146106ae57600080fd5b8063a41a964a1461053d578063a51ffa4c14610571578063a88e6ce9146105ba578063aaa91a9f146105cd578063ac6390a41461060557600080fd5b80635f90e51a116101bc578063809a9f2f11610180578063809a9f2f1461048657806384f6f617146104a65780638bbdc1dd146104c55780638eb346c1146104d857806390135fe41461051057600080fd5b80635f90e51a146103b05780636dc05dbc146103d05780636e6e13ac146103f0578063704b6c02146104285780637823d3151461044857600080fd5b80633bf95ee8116102035780633bf95ee8146102ea578063474361331461032c5780634d32ce2c1461033f5780634d9e7059146103525780635b8540611461037257600080fd5b80630c424284146102355780630f7c4bfc1461025757806314afd79e146102945780632157e77e146102ca575b600080fd5b34801561024157600080fd5b50610255610250366004612604565b610835565b005b34801561026357600080fd5b5061027761027236600461269a565b610931565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156102a057600080fd5b506102776102af36600461277c565b6007602052600090815260409020546001600160a01b031681565b3480156102d657600080fd5b506102776102e53660046127ab565b610c52565b3480156102f657600080fd5b5061031e7f0a52f6e0133eadd055cc5703844e676242c3b461d85fb7ce7f74becd7e40edd181565b60405190815260200161028b565b61025561033a36600461289c565b611077565b61025561034d36600461289c565b611307565b34801561035e57600080fd5b5061025561036d36600461291d565b61156b565b34801561037e57600080fd5b5061031e61038d3660046129a0565b601060209081526000938452604080852082529284528284209052825290205481565b3480156103bc57600080fd5b506102556103cb3660046129e1565b611634565b3480156103dc57600080fd5b506102556103eb36600461277c565b61168a565b3480156103fc57600080fd5b5061031e61040b3660046129e1565b601360209081526000928352604080842090915290825290205481565b34801561043457600080fd5b5061025561044336600461277c565b61171c565b34801561045457600080fd5b5061031e6104633660046129a0565b601160209081526000938452604080852082529284528284209052825290205481565b34801561049257600080fd5b506102556104a1366004612a0d565b6117ae565b3480156104b257600080fd5b506102776104c1366004612a46565b5490565b6102556104d336600461289c565b6118a5565b3480156104e457600080fd5b5061031e6104f3366004612a0d565b600b60209081526000928352604080842090915290825290205481565b34801561051c57600080fd5b5061031e61052b36600461277c565b60056020526000908152604090205481565b34801561054957600080fd5b5061031e7ff0f6f256599682b9387f45fc268ed696625f835d98d64b8967134239e103fc6c81565b34801561057d57600080fd5b506105a561058c36600461277c565b6009602052600090815260409020805460019091015482565b6040805192835260208301919091520161028b565b6102556105c8366004612a70565b611b09565b3480156105d957600080fd5b5061031e6105e83660046129e1565b601260209081526000928352604080842090915290825290205481565b34801561061157600080fd5b5061027761062036600461277c565b6008602052600090815260409020546001600160a01b031681565b34801561064757600080fd5b50610255610656366004612b1a565b611bd4565b61025561066936600461289c565b611ce0565b34801561067a57600080fd5b5061031e61068936600461277c565b600e6020526000908152604090205481565b6102556106a9366004612a70565b611f6c565b6102556106bc366004612b64565b611fd4565b3480156106cd57600080fd5b506102556106dc366004612a0d565b6120a2565b3480156106ed57600080fd5b5061031e6106fc366004612a0d565b600c60209081526000928352604080842090915290825290205481565b34801561072557600080fd5b5061031e61073436600461277c565b60066020526000908152604090205481565b34801561075257600080fd5b50610277612176565b34801561076757600080fd5b50610255610776366004612a0d565b612186565b34801561078757600080fd5b5061031e61079636600461277c565b600d6020526000908152604090205481565b3480156107b457600080fd5b5061031e6107c336600461277c565b600f6020526000908152604090205481565b6102556107e3366004612b64565b61225a565b3480156107f457600080fd5b5061031e61080336600461277c565b600a6020526000908152604090205481565b34801561082157600080fd5b5061025561083036600461277c565b6122c4565b6001600160a01b0382811660009081526007602052604090205416331461086f57604051630862a88160e21b815260040160405180910390fd5b600080546040516001600160a01b03858116602483015284151560448301529091169060640160408051601f198184030181529181526020820180516001600160e01b031663031090a160e21b179052516108ca9190612c19565b600060405180830381855af49150503d8060008114610905576040519150601f19603f3d011682016040523d82523d6000602084013e61090a565b606091505b505090508061092c57604051637f77015160e01b815260040160405180910390fd5b505050565b600061093b612176565b6001600160a01b031663c8c43967338e8e8e8e8e8e6040518863ffffffff1660e01b81526004016109729796959493929190612c7d565b6020604051808303816000875af1158015610991573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109b59190612cd8565b6001600160a01b038116600090815260076020526040902080546001600160a01b03191633179055905085156109eb57856109ef565b6000195b6001600160a01b0382166000908152600f602052604090205581610a14576000610a17565b60015b6001600160a01b03828116600081815260066020908152604080832060ff96909616909555600890529290922080546001600160a01b0319169186169190911790557f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f90610a8490612356565b80516020918201206040805192830193909352918101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201526001600160a01b03821660a082015260c00160408051601f1981840301815291815281516020928301206001600160a01b03841660009081526005909352912055610b14846123b2565b15610b47576001600160a01b03811660009081526009602090815260409091208535815590850135600182015550610b83565b60408051808201825260038152606460208083019182526001600160a01b03851660009081526009909152929092209051815590516001909101555b8415610c435760008054604051602481018890526001600160a01b0384811660448301528392169060640160408051601f198184030181529181526020820180516001600160e01b0316630bc2051f60e41b17905251610be39190612c19565b600060405180830381855af49150503d8060008114610c1e576040519150601f19603f3d011682016040523d82523d6000602084013e610c23565b606091505b509150915081610c40578051610c3857600080fd5b805181602001fd5b50505b9b9a5050505050505050505050565b6000610c616020860186612cf5565b9050610c6d8680612cf5565b905014610c8d5760405163161b1f5b60e01b815260040160405180910390fd5b610c95612176565b6001600160a01b03166360596ba0338d8d8d8d8d8d6040518863ffffffff1660e01b8152600401610ccc9796959493929190612c7d565b6020604051808303816000875af1158015610ceb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d0f9190612cd8565b6001600160a01b038116600090815260076020526040902080546001600160a01b03191633179055905081610d45576000610d48565b60015b6001600160a01b03828116600090815260066020908152604080832060ff959095169094556008905291822080546001600160a01b0319169186169190911790555b610d976020870187612cf5565b9050811015610e5457610daa8680612cf5565b82818110610dba57610dba612d3f565b90506020020135600014610dee57610dd28680612cf5565b82818110610de257610de2612d3f565b90506020020135610df2565b6000195b6001600160a01b038316600090815260136020908152604082209190610e1a908a018a612cf5565b85818110610e2a57610e2a612d3f565b9050602002013581526020019081526020016000208190555080610e4d90612d6b565b9050610d8a565b7f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f610e87836001600160a01b0316612356565b80516020918201206040805192830193909352918101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201526001600160a01b03831660a082015260c00160408051601f1981840301815291815281516020928301206001600160a01b03851660009081526005909352912055610f17856123b2565b15610f4a576001600160a01b03821660009081526009602090815260409091208635815590860135600182015550610f86565b60408051808201825260038152606460208083019182526001600160a01b03861660009081526009909152929092209051815590516001909101555b6000610f956040880188612cf5565b90501115611068576000805481906001600160a01b0316610fb960208a018a612cf5565b610fc660408c018c612cf5565b88604051602401610fdb959493929190612dbc565b60408051601f198184030181529181526020820180516001600160e01b031663b5afec9560e01b179052516110109190612c19565b600060405180830381855af49150503d806000811461104b576040519150601f19603f3d011682016040523d82523d6000602084013e611050565b606091505b509150915081611065578051610c3857600080fd5b50505b509a9950505050505050505050565b60005b61108760a0830183612cf5565b9050811015611303576000805481906001600160a01b03166110a98580612cf5565b858181106110b9576110b9612d3f565b90506020028101906110cb9190612cf5565b6110d86020880188612cf5565b878181106110e8576110e8612d3f565b905060200201358780604001906110ff9190612cf5565b8881811061110f5761110f612d3f565b905060200201358880606001906111269190612cf5565b8981811061113657611136612d3f565b9050602002013589806080019061114d9190612cf5565b8a81811061115d5761115d612d3f565b905060200201358a8060a001906111749190612cf5565b8b81811061118457611184612d3f565b9050602002016020810190611199919061277c565b6111a660c08d018d612cf5565b8c8181106111b6576111b6612d3f565b90506020020160208101906111cb919061277c565b6111d860e08e018e612cf5565b8d8181106111e8576111e8612d3f565b90506020020160208101906111fd9190612dfe565b61120b6101008f018f612cf5565b8e81811061121b5761121b612d3f565b905060200201358e8061012001906112339190612cf5565b8f81811061124357611243612d3f565b905060200201356040516024016112649b9a99989796959493929190612e19565b60408051601f198184030181529181526020820180516001600160e01b031663de8f21db60e01b179052516112999190612c19565b600060405180830381855af49150503d80600081146112d4576040519150601f19603f3d011682016040523d82523d6000602084013e6112d9565b606091505b5091509150816112ee578051610c3857600080fd5b505080806112fb90612d6b565b91505061107a565b5050565b60005b61131760a0830183612cf5565b9050811015611303576000805481906001600160a01b03166113398580612cf5565b8581811061134957611349612d3f565b905060200281019061135b9190612cf5565b6113686020880188612cf5565b8781811061137857611378612d3f565b9050602002013587806040019061138f9190612cf5565b8881811061139f5761139f612d3f565b905060200201358880606001906113b69190612cf5565b898181106113c6576113c6612d3f565b90506020020135898060a001906113dd9190612cf5565b8a8181106113ed576113ed612d3f565b9050602002016020810190611402919061277c565b61140f60c08c018c612cf5565b8b81811061141f5761141f612d3f565b9050602002016020810190611434919061277c565b61144160e08d018d612cf5565b8c81811061145157611451612d3f565b90506020020160208101906114669190612dfe565b6114746101008e018e612cf5565b8d81811061148457611484612d3f565b905060200201358d80610120019061149c9190612cf5565b8e8181106114ac576114ac612d3f565b905060200201356040516024016114cc9a99989796959493929190612e82565b60408051601f198184030181529181526020820180516001600160e01b031663a88e6ce960e01b179052516115019190612c19565b600060405180830381855af49150503d806000811461153c576040519150601f19603f3d011682016040523d82523d6000602084013e611541565b606091505b509150915081611556578051610c3857600080fd5b5050808061156390612d6b565b91505061130a565b6001600160a01b038581166000908152600760205260409020541633146115a557604051630862a88160e21b815260040160405180910390fd5b60005b8381101561162c578282828181106115c2576115c2612d3f565b9050602002013560136000886001600160a01b03166001600160a01b03168152602001908152602001600020600087878581811061160257611602612d3f565b905060200201358152602001908152602001600020819055508061162590612d6b565b90506115a8565b505050505050565b6001600160a01b0382811660009081526007602052604090205416331461166e57604051630862a88160e21b815260040160405180910390fd5b6001600160a01b039091166000908152600f6020526040902055565b6003546001600160a01b031633146116b557604051630862a88160e21b815260040160405180910390fd5b336116d357604051633c0a853160e11b815260040160405180910390fd5b6001600160a01b0381166116fa57604051637a381aaf60e01b815260040160405180910390fd5b600480546001600160a01b0319166001600160a01b0392909216919091179055565b6003546001600160a01b0316331461174757604051630862a88160e21b815260040160405180910390fd5b3361176557604051633c0a853160e11b815260040160405180910390fd5b6001600160a01b03811661178c5760405163035745df60e51b815260040160405180910390fd5b600380546001600160a01b0319166001600160a01b0392909216919091179055565b6001600160a01b038281166000908152600760205260409020541633146117e857604051630862a88160e21b815260040160405180910390fd5b600080546040516001600160a01b03858116602483015284811660448301529091169060640160408051601f198184030181529181526020820180516001600160e01b031663809a9f2f60e01b179052516118439190612c19565b600060405180830381855af49150503d806000811461187e576040519150601f19603f3d011682016040523d82523d6000602084013e611883565b606091505b505090508061092c5760405163786fb38760e01b815260040160405180910390fd5b60005b6118b560a0830183612cf5565b9050811015611303576000805481906001600160a01b03166118d78580612cf5565b858181106118e7576118e7612d3f565b90506020028101906118f99190612cf5565b6119066020880188612cf5565b8781811061191657611916612d3f565b9050602002013587806040019061192d9190612cf5565b8881811061193d5761193d612d3f565b905060200201358880606001906119549190612cf5565b8981811061196457611964612d3f565b90506020020135898060a0019061197b9190612cf5565b8a81811061198b5761198b612d3f565b90506020020160208101906119a0919061277c565b6119ad60c08c018c612cf5565b8b8181106119bd576119bd612d3f565b90506020020160208101906119d2919061277c565b6119df60e08d018d612cf5565b8c8181106119ef576119ef612d3f565b9050602002016020810190611a049190612dfe565b611a126101008e018e612cf5565b8d818110611a2257611a22612d3f565b905060200201358d806101200190611a3a9190612cf5565b8e818110611a4a57611a4a612d3f565b90506020020135604051602401611a6a9a99989796959493929190612e82565b60408051601f198184030181529181526020820180516001600160e01b031663c188e39560e01b17905251611a9f9190612c19565b600060405180830381855af49150503d8060008114611ada576040519150601f19603f3d011682016040523d82523d6000602084013e611adf565b606091505b509150915081611af4578051610c3857600080fd5b50508080611b0190612d6b565b9150506118a8565b6000805460405182916001600160a01b031690611b3c908e908e908e908e908e908e908e908e908e908e90602401612e82565b60408051601f198184030181529181526020820180516001600160e01b031663a88e6ce960e01b17905251611b719190612c19565b600060405180830381855af49150503d8060008114611bac576040519150601f19603f3d011682016040523d82523d6000602084013e611bb1565b606091505b509150915081611bc6578051610c3857600080fd5b505050505050505050505050565b6001600160a01b03828116600090815260076020526040902054163314611c0e57604051630862a88160e21b815260040160405180910390fd5b6000805460405160248101879052604481018690526001600160a01b03858116606483015284811660848301529091169060a40160408051601f198184030181529181526020820180516001600160e01b031663af91e8c960e01b17905251611c779190612c19565b600060405180830381855af49150503d8060008114611cb2576040519150601f19603f3d011682016040523d82523d6000602084013e611cb7565b606091505b5050905080611cd95760405163786fb38760e01b815260040160405180910390fd5b5050505050565b60005b611cf060a0830183612cf5565b9050811015611303576000805481906001600160a01b0316611d128580612cf5565b85818110611d2257611d22612d3f565b9050602002810190611d349190612cf5565b611d416020880188612cf5565b87818110611d5157611d51612d3f565b90506020020135878060400190611d689190612cf5565b88818110611d7857611d78612d3f565b90506020020135888060600190611d8f9190612cf5565b89818110611d9f57611d9f612d3f565b90506020020135898060800190611db69190612cf5565b8a818110611dc657611dc6612d3f565b905060200201358a8060a00190611ddd9190612cf5565b8b818110611ded57611ded612d3f565b9050602002016020810190611e02919061277c565b611e0f60c08d018d612cf5565b8c818110611e1f57611e1f612d3f565b9050602002016020810190611e34919061277c565b611e4160e08e018e612cf5565b8d818110611e5157611e51612d3f565b9050602002016020810190611e669190612dfe565b611e746101008f018f612cf5565b8e818110611e8457611e84612d3f565b905060200201358e806101200190611e9c9190612cf5565b8f818110611eac57611eac612d3f565b90506020020135604051602401611ecd9b9a99989796959493929190612e19565b60408051601f198184030181529181526020820180516001600160e01b031663c4dd9ab760e01b17905251611f029190612c19565b600060405180830381855af49150503d8060008114611f3d576040519150601f19603f3d011682016040523d82523d6000602084013e611f42565b606091505b509150915081611f57578051610c3857600080fd5b50508080611f6490612d6b565b915050611ce3565b6000805460405182916001600160a01b031690611f9f908e908e908e908e908e908e908e908e908e908e90602401612e82565b60408051601f198184030181529181526020820180516001600160e01b031663c188e39560e01b17905251611b719190612c19565b6000805460405182916001600160a01b031690612009908f908f908f908f908f908f908f908f908f908f908f90602401612e19565b60408051601f198184030181529181526020820180516001600160e01b031663c4dd9ab760e01b1790525161203e9190612c19565b600060405180830381855af49150503d8060008114612079576040519150601f19603f3d011682016040523d82523d6000602084013e61207e565b606091505b509150915081612093578051610c3857600080fd5b50505050505050505050505050565b6001600160a01b0382166120c957604051637a381aaf60e01b815260040160405180910390fd5b6001600160a01b0381166120f057604051630572b44f60e41b815260040160405180910390fd5b3361210e57604051633c0a853160e11b815260040160405180910390fd5b6001600160a01b0382811660009081526007602052604090205416331461214857604051630862a88160e21b815260040160405180910390fd5b6001600160a01b03918216600090815260086020526040902080546001600160a01b03191691909216179055565b600061218160015490565b905090565b6001600160a01b0382166121ad57604051637a381aaf60e01b815260040160405180910390fd5b6001600160a01b0381166121d457604051630b07403f60e21b815260040160405180910390fd5b336121f257604051633c0a853160e11b815260040160405180910390fd5b6001600160a01b0382811660009081526007602052604090205416331461222c57604051630862a88160e21b815260040160405180910390fd5b6001600160a01b03918216600090815260076020526040902080546001600160a01b03191691909216179055565b6000805460405182916001600160a01b03169061228f908f908f908f908f908f908f908f908f908f908f908f90602401612e19565b60408051601f198184030181529181526020820180516001600160e01b031663de8f21db60e01b1790525161203e9190612c19565b6003546001600160a01b031633146122ef57604051630862a88160e21b815260040160405180910390fd5b3361230d57604051633c0a853160e11b815260040160405180910390fd5b6001600160a01b038116612334576040516322f85dbf60e01b815260040160405180910390fd5b600280546001600160a01b0319166001600160a01b0392909216919091179055565b60608161237d5750506040805180820190915260048152630307830360e41b602082015290565b8160005b81156123a0578061239181612d6b565b915050600882901c9150612381565b6123aa8482612425565b949350505050565b60006020820135823511156123c957506000919050565b813515806123d957506020820135155b156123e657506000919050565b60006123f483356064612ee6565b9050600061240760208501356003612ee6565b90508082101561241b575060009392505050565b5060019392505050565b60606000612434836002612ee6565b61243f906002612f05565b67ffffffffffffffff81111561245757612457612f1d565b6040519080825280601f01601f191660200182016040528015612481576020820181803683370190505b509050600360fc1b8160008151811061249c5761249c612d3f565b60200101906001600160f81b031916908160001a905350600f60fb1b816001815181106124cb576124cb612d3f565b60200101906001600160f81b031916908160001a90535060006124ef846002612ee6565b6124fa906001612f05565b90505b6001811115612572576f181899199a1a9b1b9c1cb0b131b232b360811b85600f166010811061252e5761252e612d3f565b1a60f81b82828151811061254457612544612d3f565b60200101906001600160f81b031916908160001a90535060049490941c9361256b81612f33565b90506124fd565b5083156125c55760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e74604482015260640160405180910390fd5b9392505050565b6001600160a01b03811681146125e157600080fd5b50565b80356125ef816125cc565b919050565b803580151581146125ef57600080fd5b6000806040838503121561261757600080fd5b8235612622816125cc565b9150612630602084016125f4565b90509250929050565b60008083601f84011261264b57600080fd5b50813567ffffffffffffffff81111561266357600080fd5b60208301915083602082850101111561267b57600080fd5b9250929050565b60006040828403121561269457600080fd5b50919050565b60008060008060008060008060008060006101208c8e0312156126bc57600080fd5b67ffffffffffffffff808d3511156126d357600080fd5b6126e08e8e358f01612639565b909c509a5060208d01358110156126f657600080fd5b6127068e60208f01358f01612639565b909a50985060408d013581101561271c57600080fd5b5061272d8d60408e01358e01612639565b909750955060608c0135945060808c0135935061274d8d60a08e01612682565b925061275b60e08d016125e4565b915061276a6101008d016125f4565b90509295989b509295989b9093969950565b60006020828403121561278e57600080fd5b81356125c5816125cc565b60006060828403121561269457600080fd5b6000806000806000806000806000806101008b8d0312156127cb57600080fd5b8a3567ffffffffffffffff808211156127e357600080fd5b6127ef8e838f01612639565b909c509a5060208d013591508082111561280857600080fd5b6128148e838f01612639565b909a50985060408d013591508082111561282d57600080fd5b6128398e838f01612639565b909850965060608d013591508082111561285257600080fd5b5061285f8d828e01612799565b94505061286f8c60808d01612682565b925061287d60c08c016125e4565b915061288b60e08c016125f4565b90509295989b9194979a5092959850565b6000602082840312156128ae57600080fd5b813567ffffffffffffffff8111156128c557600080fd5b820161014081850312156125c557600080fd5b60008083601f8401126128ea57600080fd5b50813567ffffffffffffffff81111561290257600080fd5b6020830191508360208260051b850101111561267b57600080fd5b60008060008060006060868803121561293557600080fd5b8535612940816125cc565b9450602086013567ffffffffffffffff8082111561295d57600080fd5b61296989838a016128d8565b9096509450604088013591508082111561298257600080fd5b5061298f888289016128d8565b969995985093965092949392505050565b6000806000606084860312156129b557600080fd5b83356129c0816125cc565b925060208401356129d0816125cc565b929592945050506040919091013590565b600080604083850312156129f457600080fd5b82356129ff816125cc565b946020939093013593505050565b60008060408385031215612a2057600080fd5b8235612a2b816125cc565b91506020830135612a3b816125cc565b809150509250929050565b600060208284031215612a5857600080fd5b5035919050565b803560ff811681146125ef57600080fd5b6000806000806000806000806000806101208b8d031215612a9057600080fd5b8a3567ffffffffffffffff811115612aa757600080fd5b612ab38d828e016128d8565b909b5099505060208b0135975060408b0135965060608b0135955060808b0135612adc816125cc565b945060a08b0135612aec816125cc565b9350612afa60c08c01612a5f565b925060e08b013591506101008b013590509295989b9194979a5092959850565b60008060008060808587031215612b3057600080fd5b84359350602085013592506040850135612b49816125cc565b91506060850135612b59816125cc565b939692955090935050565b60008060008060008060008060008060006101408c8e031215612b8657600080fd5b8b3567ffffffffffffffff811115612b9d57600080fd5b612ba98e828f016128d8565b909c509a505060208c0135985060408c0135975060608c0135965060808c0135955060a08c0135612bd9816125cc565b945060c08c0135612be9816125cc565b9350612bf760e08d01612a5f565b92506101008c013591506101208c013590509295989b509295989b9093969950565b6000825160005b81811015612c3a5760208186018101518583015201612c20565b81811115612c49576000828501525b509190910192915050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6001600160a01b0388168152608060208201819052600090612ca2908301888a612c54565b8281036040840152612cb5818789612c54565b90508281036060840152612cca818587612c54565b9a9950505050505050505050565b600060208284031215612cea57600080fd5b81516125c5816125cc565b6000808335601e19843603018112612d0c57600080fd5b83018035915067ffffffffffffffff821115612d2757600080fd5b6020019150600581901b360382131561267b57600080fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b6000600019821415612d7f57612d7f612d55565b5060010190565b81835260006001600160fb1b03831115612d9f57600080fd5b8260051b8083602087013760009401602001938452509192915050565b606081526000612dd0606083018789612d86565b8281036020840152612de3818688612d86565b91505060018060a01b03831660408301529695505050505050565b600060208284031215612e1057600080fd5b6125c582612a5f565b61014081526000612e2f61014083018d8f612d86565b602083019b909b52506040810198909852606088019690965260808701949094526001600160a01b0392831660a0870152911660c085015260ff1660e08401526101008301526101209091015292915050565b6000610120808352612e978184018d8f612d86565b602084019b909b525050604081019790975260608701959095526001600160a01b0393841660808701529190921660a085015260ff90911660c084015260e08301526101009091015292915050565b6000816000190483118215151615612f0057612f00612d55565b500290565b60008219821115612f1857612f18612d55565b500190565b634e487b7160e01b600052604160045260246000fd5b600081612f4257612f42612d55565b50600019019056fea264697066735822122023ed71495e38f66a1d455443cde842982c3b06bad6b09689e2facfe108ef187364736f6c634300080c00336080604052600160145534801561001557600080fd5b50613279806100256000396000f3fe60806040526004361061019c5760003560e01c8063aaa91a9f116100ec578063c4dd9ab71161008a578063d425f3cf11610064578063d425f3cf146105c6578063dd55ebdc146105f3578063de8f21db14610620578063e3d9a96d1461063357600080fd5b8063c4dd9ab71461054e578063c67596f214610561578063c6aa2fb61461059957600080fd5b8063b5afec95116100c6578063b5afec95146104ce578063b98a4452146104ee578063bc2051f01461051b578063c188e3951461053b57600080fd5b8063aaa91a9f14610440578063ac6390a414610478578063af91e8c9146104ae57600080fd5b8063809a9f2f1161015957806390135fe41161013357806390135fe414610383578063a41a964a146103b0578063a51ffa4c146103e4578063a88e6ce91461042d57600080fd5b8063809a9f2f1461030c57806384f6f6171461032c5780638eb346c11461034b57600080fd5b80630c424284146101a157806314afd79e146101c35780633bf95ee8146102165780635b854061146102585780636e6e13ac146102965780637823d315146102ce575b600080fd5b3480156101ad57600080fd5b506101c16101bc366004612c8b565b610660565b005b3480156101cf57600080fd5b506101f96101de366004612cc2565b6007602052600090815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561022257600080fd5b5061024a7f0a52f6e0133eadd055cc5703844e676242c3b461d85fb7ce7f74becd7e40edd181565b60405190815260200161020d565b34801561026457600080fd5b5061024a610273366004612ce4565b601060209081526000938452604080852082529284528284209052825290205481565b3480156102a257600080fd5b5061024a6102b1366004612d20565b601360209081526000928352604080842090915290825290205481565b3480156102da57600080fd5b5061024a6102e9366004612ce4565b601160209081526000938452604080852082529284528284209052825290205481565b34801561031857600080fd5b506101c1610327366004612d4a565b610754565b34801561033857600080fd5b506101f9610347366004612d7d565b5490565b34801561035757600080fd5b5061024a610366366004612d4a565b600b60209081526000928352604080842090915290825290205481565b34801561038f57600080fd5b5061024a61039e366004612cc2565b60056020526000908152604090205481565b3480156103bc57600080fd5b5061024a7ff0f6f256599682b9387f45fc268ed696625f835d98d64b8967134239e103fc6c81565b3480156103f057600080fd5b506104186103ff366004612cc2565b6009602052600090815260409020805460019091015482565b6040805192835260208301919091520161020d565b6101c161043b366004612df3565b61087e565b34801561044c57600080fd5b5061024a61045b366004612d20565b601260209081526000928352604080842090915290825290205481565b34801561048457600080fd5b506101f9610493366004612cc2565b6008602052600090815260409020546001600160a01b031681565b3480156104ba57600080fd5b506101c16104c9366004612e99565b610fc1565b3480156104da57600080fd5b506101c16104e9366004612edf565b6110f5565b3480156104fa57600080fd5b5061024a610509366004612cc2565b600e6020526000908152604090205481565b34801561052757600080fd5b506101c1610536366004612f60565b61124c565b6101c1610549366004612df3565b61131f565b6101c161055c366004612f83565b611991565b34801561056d57600080fd5b5061024a61057c366004612d4a565b600c60209081526000928352604080842090915290825290205481565b3480156105a557600080fd5b5061024a6105b4366004612cc2565b60066020526000908152604090205481565b3480156105d257600080fd5b5061024a6105e1366004612cc2565b600d6020526000908152604090205481565b3480156105ff57600080fd5b5061024a61060e366004612cc2565b600f6020526000908152604090205481565b6101c161062e366004612f83565b612041565b34801561063f57600080fd5b5061024a61064e366004612cc2565b600a6020526000908152604090205481565b60145460011461068b5760405162461bcd60e51b815260040161068290613034565b60405180910390fd5b600260145560015460405163599fbc6560e11b81526001600160a01b0384811660048301529091169063b33f78ca90602401602060405180830381865afa1580156106da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106fe9190613058565b61071b5760405163c043671760e01b815260040160405180910390fd5b8061072757600061072a565b60015b6001600160a01b03909216600090815260066020526040902060ff92909216909155506001601455565b6014546001146107765760405162461bcd60e51b815260040161068290613034565b60026014556001600160a01b0382166000908152600f6020908152604080832054600d90925290912054106107be57604051634e3e4dad60e11b815260040160405180910390fd5b6001600160a01b0382166000908152600d602052604081208054916107e283613087565b90915550506001600160a01b0382166000818152600e6020526040812080546340c10f199285929061081383613087565b909155506040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b15801561085d57600080fd5b505af1158015610871573d6000803e3d6000fd5b5050600160145550505050565b6014546001146108a05760405162461bcd60e51b815260040161068290613034565b600260145560015460405163599fbc6560e11b81526001600160a01b0387811660048301529091169063b33f78ca90602401602060405180830381865afa1580156108ef573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109139190613058565b6109305760405163c043671760e01b815260040160405180910390fd5b6001600160a01b0385166000908152600b6020908152604080832033845290915290205486116109735760405163ed39ba7f60e01b815260040160405180910390fd5b6001600160a01b0385166000908152600c60209081526040808320338452909152902054610eb9576001600160a01b0385166000908152600f6020908152604080832054600d90925290912054106109de57604051634e3e4dad60e11b815260040160405180910390fd5b604080517ff0f6f256599682b9387f45fc268ed696625f835d98d64b8967134239e103fc6c602082015290810189905260608101889052608081018790526001600160a01b0380871660a0830152851660c082015260009060e00160408051601f1981840301815291815281516020928301206001600160a01b03808a166000908152600785528381205460059095529290922054909350610a89928492169087908790879061279c565b506001600160a01b03851660009081526006602052604090205460011415610ab757610ab7338b8b8b612959565b6001600160a01b0385166000908152600d60205260408120805491610adb83613087565b90915550506001600160a01b038516600090815260096020526040812060018101548154919291610b0c908b6130a2565b610b1691906130c1565b90506001600160a01b038616610c4c5788341015610b4757604051633f943cc760e21b815260040160405180910390fd5b6002546040516000916001600160a01b03169083908381818185875af1925050503d8060008114610b94576040519150601f19603f3d011682016040523d82523d6000602084013e610b99565b606091505b5050905080610bbb57604051630303e72760e41b815260040160405180910390fd5b6001600160a01b03808916600090815260086020526040812054909116610be2848d6130e3565b604051600081818185875af1925050503d8060008114610c1e576040519150601f19603f3d011682016040523d82523d6000602084013e610c23565b606091505b5050905080610c455760405163799cbfed60e11b815260040160405180910390fd5b5050610eb2565b604051636eb1769f60e11b815233600482015230602482015289906001600160a01b0388169063dd62ed3e90604401602060405180830381865afa158015610c98573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cbc9190613058565b1015610cdb57604051633f943cc760e21b815260040160405180910390fd5b6002546040516323b872dd60e01b81526000916001600160a01b03808a16926323b872dd92610d1392339291169087906004016130fa565b6020604051808303816000875af1158015610d32573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d56919061311e565b905080610d7657604051630303e72760e41b815260040160405180910390fd5b6001600160a01b03888116600090815260086020526040902054818916916323b872dd91339116610da7868f6130e3565b6040518463ffffffff1660e01b8152600401610dc5939291906130fa565b6020604051808303816000875af1158015610de4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e08919061311e565b905080610e2857604051634191f0ed60e11b815260040160405180910390fd5b6001600160a01b0387166000908152600a602052604090205460011415610eb05760048054604051636da2661b60e01b81526001600160a01b0390911691636da2661b91610e7d9133918d91600091016130fa565b600060405180830381600087803b158015610e9757600080fd5b505af1158015610eab573d6000803e3d6000fd5b505050505b505b5050610eee565b6001600160a01b0385166000908152600c602090815260408083203384529091528120805491610ee88361313b565b91905055505b6001600160a01b0385166000908152600b602090815260408083203384529091528120805491610f1d83613087565b90915550506001600160a01b0385166000818152600e6020526040812080546340c10f1992339290610f4e83613087565b909155506040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b158015610f9857600080fd5b505af1158015610fac573d6000803e3d6000fd5b50506001601455505050505050505050505050565b601454600114610fe35760405162461bcd60e51b815260040161068290613034565b60026014556001600160a01b03821660008181526013602090815260408083208884528252808320549383526012825280832088845290915290205461102a908590613152565b111561104957604051634e3e4dad60e11b815260040160405180910390fd5b6001600160a01b03821660009081526012602090815260408083208784529091528120805485929061107c908490613152565b9091555050604051631740d57560e11b81526001600160a01b0382811660048301526024820186905260448201859052831690632e81aaea90606401600060405180830381600087803b1580156110d257600080fd5b505af11580156110e6573d6000803e3d6000fd5b50506001601455505050505050565b6014546001146111175760405162461bcd60e51b815260040161068290613034565b600260145560005b84811015610871578383828181106111395761113961316a565b9050602002013560126000846001600160a01b03166001600160a01b0316815260200190815260200160002060008888858181106111795761117961316a565b90506020020135815260200190815260200160002081905550816001600160a01b0316632e81aaea338888858181106111b4576111b461316a565b905060200201358787868181106111cd576111cd61316a565b6040516001600160e01b031960e088901b1681526001600160a01b039095166004860152602485019390935250602090910201356044820152606401600060405180830381600087803b15801561122357600080fd5b505af1158015611237573d6000803e3d6000fd5b505050508061124590613087565b905061111f565b60145460011461126e5760405162461bcd60e51b815260040161068290613034565b60026014556001600160a01b0381166000908152600d60209081526040808320859055600e90915281208390555b82811015611315576040516340c10f1960e01b8152336004820152602481018290526001600160a01b038316906340c10f1990604401600060405180830381600087803b1580156112ec57600080fd5b505af1158015611300573d6000803e3d6000fd5b505050508061130e90613087565b905061129c565b5050600160145550565b6014546001146113415760405162461bcd60e51b815260040161068290613034565b600260145560015460405163599fbc6560e11b81526001600160a01b0387811660048301529091169063b33f78ca90602401602060405180830381865afa158015611390573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113b49190613058565b6113d15760405163c043671760e01b815260040160405180910390fd5b6001600160a01b0385166000908152600f6020908152604080832054600d909252909120541061141457604051634e3e4dad60e11b815260040160405180910390fd5b6001600160a01b0385166000818152600b602090815260408083203380855290835281842054948452600c8352818420908452909152902054879161145891613152565b1061147657604051636e6d1a4560e01b815260040160405180910390fd5b604080517ff0f6f256599682b9387f45fc268ed696625f835d98d64b8967134239e103fc6c602082015290810189905260608101889052608081018790526001600160a01b0380871660a0830152851660c082015260009060e00160408051601f1981840301815291815281516020928301206001600160a01b03808a166000908152600785528381205460059095529290922054909350611521928492169087908790879061279c565b506001600160a01b0385166000908152600660205260409020546001141561154f5761154f338b8b8b612959565b6001600160a01b0385166000908152600c60209081526040808320338452909152812080549161157e83613087565b90915550506001600160a01b0385166000908152600d602052604081208054916115a783613087565b90915550506001600160a01b0385166000908152600960205260408120600181015481549192916115d8908b6130a2565b6115e291906130c1565b90506001600160a01b038616611718578834101561161357604051633f943cc760e21b815260040160405180910390fd5b6002546040516000916001600160a01b03169083908381818185875af1925050503d8060008114611660576040519150601f19603f3d011682016040523d82523d6000602084013e611665565b606091505b505090508061168757604051630303e72760e41b815260040160405180910390fd5b6001600160a01b038089166000908152600860205260408120549091166116ae848d6130e3565b604051600081818185875af1925050503d80600081146116ea576040519150601f19603f3d011682016040523d82523d6000602084013e6116ef565b606091505b50509050806117115760405163799cbfed60e11b815260040160405180910390fd5b505061197e565b604051636eb1769f60e11b815233600482015230602482015289906001600160a01b0388169063dd62ed3e90604401602060405180830381865afa158015611764573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117889190613058565b10156117a757604051633f943cc760e21b815260040160405180910390fd5b6002546040516323b872dd60e01b81526000916001600160a01b03808a16926323b872dd926117df92339291169087906004016130fa565b6020604051808303816000875af11580156117fe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611822919061311e565b90508061184257604051630303e72760e41b815260040160405180910390fd5b6001600160a01b03888116600090815260086020526040902054818916916323b872dd91339116611873868f6130e3565b6040518463ffffffff1660e01b8152600401611891939291906130fa565b6020604051808303816000875af11580156118b0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118d4919061311e565b9050806118f457604051634191f0ed60e11b815260040160405180910390fd5b6001600160a01b0387166000908152600a60205260409020546001141561197c5760048054604051636da2661b60e01b81526001600160a01b0390911691636da2661b916119499133918d91600091016130fa565b600060405180830381600087803b15801561196357600080fd5b505af1158015611977573d6000803e3d6000fd5b505050505b505b5050600160145550505050505050505050565b6014546001146119b35760405162461bcd60e51b815260040161068290613034565b600260145560015460405163599fbc6560e11b81526001600160a01b0387811660048301529091169063b33f78ca90602401602060405180830381865afa158015611a02573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a269190613058565b611a435760405163c043671760e01b815260040160405180910390fd5b6001600160a01b03851660008181526013602090815260408083208a8452825280832054938352601282528083208a845290915290205410611a9857604051634e3e4dad60e11b815260040160405180910390fd5b6001600160a01b0385166000818152601060209081526040808320338085529083528184208b85528352818420549484526011835281842090845282528083208a84529091529020548891611aec91613152565b10611b0a57604051636e6d1a4560e01b815260040160405180910390fd5b604051600090611b4a907f0a52f6e0133eadd055cc5703844e676242c3b461d85fb7ce7f74becd7e40edd1908c908c908c908c908c908c90602001613180565b60408051601f1981840301815291815281516020928301206001600160a01b03808a166000908152600785528381205460059095529290922054909350611b9a928492169087908790879061279c565b506001600160a01b03851660009081526006602052604090205460011415611bc857611bc8338c8c8c612959565b6001600160a01b038516600090815260116020908152604080832033845282528083208984529091528120805491611bff83613087565b90915550506001600160a01b03851660009081526012602090815260408083208984529091528120805491611c3383613087565b90915550506001600160a01b038516600090815260096020526040812060018101548154919291611c64908c6130a2565b611c6e91906130c1565b90506001600160a01b038616611da45789341015611c9f57604051633f943cc760e21b815260040160405180910390fd5b6002546040516000916001600160a01b03169083908381818185875af1925050503d8060008114611cec576040519150601f19603f3d011682016040523d82523d6000602084013e611cf1565b606091505b5050905080611d1357604051630303e72760e41b815260040160405180910390fd5b6001600160a01b03808916600090815260086020526040812054909116611d3a848e6130e3565b604051600081818185875af1925050503d8060008114611d76576040519150601f19603f3d011682016040523d82523d6000602084013e611d7b565b606091505b5050905080611d9d5760405163799cbfed60e11b815260040160405180910390fd5b505061202d565b604051636eb1769f60e11b81523360048201523060248201528a906001600160a01b0388169063dd62ed3e90604401602060405180830381865afa158015611df0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e149190613058565b1015611e3357604051633f943cc760e21b815260040160405180910390fd5b6002546040516323b872dd60e01b81526000916001600160a01b03808a16926323b872dd92611e6b92339291169087906004016130fa565b6020604051808303816000875af1158015611e8a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611eae919061311e565b905080611ece57604051630303e72760e41b815260040160405180910390fd5b866001600160a01b03166323b872dd33600860008c6001600160a01b03166001600160a01b0316815260200190815260200160002060009054906101000a90046001600160a01b0316858f611f2391906130e3565b6040518463ffffffff1660e01b8152600401611f41939291906130fa565b6020604051808303816000875af1158015611f60573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f84919061311e565b905080611fa457604051634191f0ed60e11b815260040160405180910390fd5b6001600160a01b0387166000908152600a60205260409020546001141561202b5760048054604051636da2661b60e01b81526001600160a01b0390911691636da2661b91611ff89133918d918f91016130fa565b600060405180830381600087803b15801561201257600080fd5b505af1158015612026573d6000803e3d6000fd5b505050505b505b505060016014555050505050505050505050565b6014546001146120635760405162461bcd60e51b815260040161068290613034565b600260145560015460405163599fbc6560e11b81526001600160a01b0387811660048301529091169063b33f78ca90602401602060405180830381865afa1580156120b2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120d69190613058565b6120f35760405163c043671760e01b815260040160405180910390fd5b6001600160a01b03851660009081526010602090815260408083203384528252808320898452909152902054871161213e5760405163ed39ba7f60e01b815260040160405180910390fd5b6001600160a01b038516600090815260116020908152604080832033845282528083208984529091529020546126b1576001600160a01b03851660008181526013602090815260408083208a8452825280832054938352601282528083208a8452909152902054106121c357604051634e3e4dad60e11b815260040160405180910390fd5b604051600090612203907f0a52f6e0133eadd055cc5703844e676242c3b461d85fb7ce7f74becd7e40edd1908c908c908c908c908c908c90602001613180565b60408051601f1981840301815291815281516020928301206001600160a01b03808a166000908152600785528381205460059095529290922054909350612253928492169087908790879061279c565b506001600160a01b0385166000908152600660205260409020546001141561228157612281338c8c8c612959565b6001600160a01b038516600090815260126020908152604080832089845290915281208054916122b083613087565b90915550506001600160a01b0385166000908152600960205260408120600181015481549192916122e1908c6130a2565b6122eb91906130c1565b90506001600160a01b038616612421578934101561231c57604051633f943cc760e21b815260040160405180910390fd5b6002546040516000916001600160a01b03169083908381818185875af1925050503d8060008114612369576040519150601f19603f3d011682016040523d82523d6000602084013e61236e565b606091505b505090508061239057604051630303e72760e41b815260040160405180910390fd5b6001600160a01b038089166000908152600860205260408120549091166123b7848e6130e3565b604051600081818185875af1925050503d80600081146123f3576040519150601f19603f3d011682016040523d82523d6000602084013e6123f8565b606091505b505090508061241a5760405163799cbfed60e11b815260040160405180910390fd5b50506126aa565b604051636eb1769f60e11b81523360048201523060248201528a906001600160a01b0388169063dd62ed3e90604401602060405180830381865afa15801561246d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124919190613058565b10156124b057604051633f943cc760e21b815260040160405180910390fd5b6002546040516323b872dd60e01b81526000916001600160a01b03808a16926323b872dd926124e892339291169087906004016130fa565b6020604051808303816000875af1158015612507573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061252b919061311e565b90508061254b57604051630303e72760e41b815260040160405180910390fd5b866001600160a01b03166323b872dd33600860008c6001600160a01b03166001600160a01b0316815260200190815260200160002060009054906101000a90046001600160a01b0316858f6125a091906130e3565b6040518463ffffffff1660e01b81526004016125be939291906130fa565b6020604051808303816000875af11580156125dd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612601919061311e565b90508061262157604051634191f0ed60e11b815260040160405180910390fd5b6001600160a01b0387166000908152600a6020526040902054600114156126a85760048054604051636da2661b60e01b81526001600160a01b0390911691636da2661b916126759133918d918f91016130fa565b600060405180830381600087803b15801561268f57600080fd5b505af11580156126a3573d6000803e3d6000fd5b505050505b505b50506126ee565b6001600160a01b0385166000908152601160209081526040808320338452825280832089845290915281208054916126e88361313b565b91905055505b6001600160a01b03851660009081526010602090815260408083203384528252808320898452909152812080549161272583613087565b90915550506040516340c10f1960e01b8152336004820152602481018790526001600160a01b038616906340c10f1990604401600060405180830381600087803b15801561277257600080fd5b505af1158015612786573d6000803e3d6000fd5b5050600160145550505050505050505050505050565b60405161190160f01b602082015260228101829052604281018790526000906062016040516020818303038152906040528051906020012090506127e9866001600160a01b03163b151590565b156128ec57604080516020810186905280820185905260f887901b6001600160f81b0319166060820152815160418183030181526061820192839052630b135d3f60e11b9092526001600160a01b03881691631626ba7e9161284f9185916065016131bc565b602060405180830381865afa15801561286c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128909190613219565b6001600160e01b031916631626ba7e60e01b146128e75760405162461bcd60e51b815260206004820152601560248201527413d11654d4d1564e8815539055551213d492569151605a1b6044820152606401610682565b612950565b856001600160a01b031661290282878787612a2b565b6001600160a01b0316146129505760405162461bcd60e51b815260206004820152601560248201527413d11654d4d1564e8815539055551213d492569151605a1b6044820152606401610682565b50505050505050565b6001600160a01b03841661296c57600080fd5b6040516bffffffffffffffffffffffff19606086901b1660208201526000906034016040516020818303038152906040528051906020012090506129e6848480806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250869250859150612bd49050565b612a245760405162461bcd60e51b815260206004820152600f60248201526e139bdd081dda1a5d195b1a5cdd1959608a1b6044820152606401610682565b5050505050565b60007f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0821115612aa85760405162461bcd60e51b815260206004820152602260248201527f4f4459535345593a20494e56414c49445f5349474e41545552455f535f56414c604482015261554560f01b6064820152608401610682565b8360ff16601b1480612abd57508360ff16601c145b612b145760405162461bcd60e51b815260206004820152602260248201527f4f4459535345593a20494e56414c49445f5349474e41545552455f565f56414c604482015261554560f01b6064820152608401610682565b6040805160008082526020820180845288905260ff871692820192909252606081018590526080810184905260019060a0016020604051602081039080840390855afa158015612b68573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116612bcb5760405162461bcd60e51b815260206004820152601a60248201527f4f4459535345593a20494e56414c49445f5349474e41545552450000000000006044820152606401610682565b95945050505050565b600082612be18584612bea565b14949350505050565b600081815b8451811015612c56576000858281518110612c0c57612c0c61316a565b60200260200101519050808311612c325760008381526020829052604090209250612c43565b600081815260208490526040902092505b5080612c4e81613087565b915050612bef565b509392505050565b80356001600160a01b0381168114612c7557600080fd5b919050565b8015158114612c8857600080fd5b50565b60008060408385031215612c9e57600080fd5b612ca783612c5e565b91506020830135612cb781612c7a565b809150509250929050565b600060208284031215612cd457600080fd5b612cdd82612c5e565b9392505050565b600080600060608486031215612cf957600080fd5b612d0284612c5e565b9250612d1060208501612c5e565b9150604084013590509250925092565b60008060408385031215612d3357600080fd5b612d3c83612c5e565b946020939093013593505050565b60008060408385031215612d5d57600080fd5b612d6683612c5e565b9150612d7460208401612c5e565b90509250929050565b600060208284031215612d8f57600080fd5b5035919050565b60008083601f840112612da857600080fd5b50813567ffffffffffffffff811115612dc057600080fd5b6020830191508360208260051b8501011115612ddb57600080fd5b9250929050565b803560ff81168114612c7557600080fd5b6000806000806000806000806000806101208b8d031215612e1357600080fd5b8a3567ffffffffffffffff811115612e2a57600080fd5b612e368d828e01612d96565b909b5099505060208b0135975060408b0135965060608b01359550612e5d60808c01612c5e565b9450612e6b60a08c01612c5e565b9350612e7960c08c01612de2565b925060e08b013591506101008b013590509295989b9194979a5092959850565b60008060008060808587031215612eaf57600080fd5b8435935060208501359250612ec660408601612c5e565b9150612ed460608601612c5e565b905092959194509250565b600080600080600060608688031215612ef757600080fd5b853567ffffffffffffffff80821115612f0f57600080fd5b612f1b89838a01612d96565b90975095506020880135915080821115612f3457600080fd5b50612f4188828901612d96565b9094509250612f54905060408701612c5e565b90509295509295909350565b60008060408385031215612f7357600080fd5b82359150612d7460208401612c5e565b60008060008060008060008060008060006101408c8e031215612fa557600080fd5b8b3567ffffffffffffffff811115612fbc57600080fd5b612fc88e828f01612d96565b909c509a505060208c0135985060408c0135975060608c0135965060808c01359550612ff660a08d01612c5e565b945061300460c08d01612c5e565b935061301260e08d01612de2565b92506101008c013591506101208c013590509295989b509295989b9093969950565b6020808252600a90820152695245454e5452414e435960b01b604082015260600190565b60006020828403121561306a57600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b600060001982141561309b5761309b613071565b5060010190565b60008160001904831182151516156130bc576130bc613071565b500290565b6000826130de57634e487b7160e01b600052601260045260246000fd5b500490565b6000828210156130f5576130f5613071565b500390565b6001600160a01b039384168152919092166020820152604081019190915260600190565b60006020828403121561313057600080fd5b8151612cdd81612c7a565b60008161314a5761314a613071565b506000190190565b6000821982111561316557613165613071565b500190565b634e487b7160e01b600052603260045260246000fd5b96875260208701959095526040860193909352606085019190915260808401526001600160a01b0390811660a08401521660c082015260e00190565b82815260006020604081840152835180604085015260005b818110156131f0578581018301518582016060015282016131d4565b81811115613202576000606083870101525b50601f01601f191692909201606001949350505050565b60006020828403121561322b57600080fd5b81516001600160e01b031981168114612cdd57600080fdfea264697066735822122044c5f5e798bd7853471e45cc2e5a71b3d9322893f9a1acc7ad46a540c467698164736f6c634300080c0033608060405234801561001057600080fd5b50614135806100206000396000f3fe60806040523480156200001157600080fd5b50600436106200006a5760003560e01c806360596ba0146200006f578063634282af14620000a3578063b33f78ca14620000ba578063c8c4396714620000ec578063ce5539d21462000103578063dbb80e421462000157575b600080fd5b620000866200008036600462000a4d565b62000160565b6040516001600160a01b0390911681526020015b60405180910390f35b62000086620000b436600462000b04565b620003eb565b620000dd620000cb36600462000b1e565b60016020526000908152604090205481565b6040519081526020016200009a565b62000086620000fd36600462000a4d565b62000416565b620000866200011436600462000be7565b815160208184018101805160008252928201948201949094209190935281518083018401805192815290840192909301919091209152546001600160a01b031681565b600254620000dd565b6000806001600160a01b0316600088886040516200018092919062000c52565b90815260200160405180910390208686604051620001a092919062000c52565b908152604051908190036020019020546001600160a01b031614620001d8576040516359294bdd60e11b815260040160405180910390fd5b600060405180602001620001ec90620009c8565b6020820181038252601f19601f8201166040525090506000888888886040516020016200021d949392919062000c62565b604051602081830303815290604052805190602001209050808251602084016000f592508260008a8a6040516200025692919062000c52565b908152602001604051809103902088886040516200027692919062000c52565b908152604080516020928190038301902080546001600160a01b0319166001600160a01b0394851617905591851660008181526001928390529290922055633f2f5ee2338c8c8c8c8c8c8c620002cc4662000691565b620002e08e6001600160a01b0316620007af565b604051602001620002f5949392919062000cb7565b6040516020818303038152906040526040518863ffffffff1660e01b815260040162000328979695949392919062000d36565b600060405180830381600087803b1580156200034357600080fd5b505af115801562000358573d6000803e3d6000fd5b5050505086866040516200036e92919062000c52565b604051809103902089896040516200038892919062000c52565b604080519182900382206002546001600160a01b03881684526000602085015291830191909152907f4d436b5f0e18ba082306559a4bd1e56cf927a6b69716fa45737f88089d7e233c906060015b60405180910390a35050979650505050505050565b60028181548110620003fc57600080fd5b6000918252602090912001546001600160a01b0316905081565b6000806001600160a01b0316600088886040516200043692919062000c52565b908152602001604051809103902086866040516200045692919062000c52565b908152604051908190036020019020546001600160a01b0316146200048e576040516359294bdd60e11b815260040160405180910390fd5b600060405180602001620004a290620009d6565b6020820181038252601f19601f820116604052509050600088888888604051602001620004d3949392919062000c62565b604051602081830303815290604052805190602001209050808251602084016000f592508260008a8a6040516200050c92919062000c52565b908152602001604051809103902088886040516200052c92919062000c52565b908152604080516020928190038301902080546001600160a01b0319166001600160a01b0394851617905591851660008181526001928390529290922055633f2f5ee2338c8c8c8c8c8c8c620005824662000691565b620005968e6001600160a01b0316620007af565b604051602001620005ab949392919062000cb7565b6040516020818303038152906040526040518863ffffffff1660e01b8152600401620005de979695949392919062000d36565b600060405180830381600087803b158015620005f957600080fd5b505af11580156200060e573d6000803e3d6000fd5b5050505086866040516200062492919062000c52565b604051809103902089896040516200063e92919062000c52565b604080519182900382206002546001600160a01b03881684526001602085015291830191909152907f4d436b5f0e18ba082306559a4bd1e56cf927a6b69716fa45737f88089d7e233c90606001620003d6565b606081620006b65750506040805180820190915260018152600360fc1b602082015290565b8160005b8115620006e65780620006cd8162000dca565b9150620006de9050600a8362000dfe565b9150620006ba565b60008167ffffffffffffffff81111562000704576200070462000b3c565b6040519080825280601f01601f1916602001820160405280156200072f576020820181803683370190505b5090505b8415620007a7576200074760018362000e15565b915062000756600a8662000e2f565b6200076390603062000e46565b60f81b8183815181106200077b576200077b62000e61565b60200101906001600160f81b031916908160001a9053506200079f600a8662000dfe565b945062000733565b949350505050565b606081620007d75750506040805180820190915260048152630307830360e41b602082015290565b8160005b8115620007fe5780620007ee8162000dca565b915050600882901c9150620007db565b620007a78482606060006200081583600262000e77565b6200082290600262000e46565b67ffffffffffffffff8111156200083d576200083d62000b3c565b6040519080825280601f01601f19166020018201604052801562000868576020820181803683370190505b509050600360fc1b8160008151811062000886576200088662000e61565b60200101906001600160f81b031916908160001a905350600f60fb1b81600181518110620008b857620008b862000e61565b60200101906001600160f81b031916908160001a9053506000620008de84600262000e77565b620008eb90600162000e46565b90505b60018111156200096d576f181899199a1a9b1b9c1cb0b131b232b360811b85600f166010811062000923576200092362000e61565b1a60f81b8282815181106200093c576200093c62000e61565b60200101906001600160f81b031916908160001a90535060049490941c93620009658162000e99565b9050620008ee565b508315620009c15760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e74604482015260640160405180910390fd5b9392505050565b611ae88062000eb483390190565b611764806200299c83390190565b80356001600160a01b0381168114620009fc57600080fd5b919050565b60008083601f84011262000a1457600080fd5b50813567ffffffffffffffff81111562000a2d57600080fd5b60208301915083602082850101111562000a4657600080fd5b9250929050565b60008060008060008060006080888a03121562000a6957600080fd5b62000a7488620009e4565b9650602088013567ffffffffffffffff8082111562000a9257600080fd5b62000aa08b838c0162000a01565b909850965060408a013591508082111562000aba57600080fd5b62000ac88b838c0162000a01565b909650945060608a013591508082111562000ae257600080fd5b5062000af18a828b0162000a01565b989b979a50959850939692959293505050565b60006020828403121562000b1757600080fd5b5035919050565b60006020828403121562000b3157600080fd5b620009c182620009e4565b634e487b7160e01b600052604160045260246000fd5b600082601f83011262000b6457600080fd5b813567ffffffffffffffff8082111562000b825762000b8262000b3c565b604051601f8301601f19908116603f0116810190828211818310171562000bad5762000bad62000b3c565b8160405283815286602085880101111562000bc757600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000806040838503121562000bfb57600080fd5b823567ffffffffffffffff8082111562000c1457600080fd5b62000c228683870162000b52565b9350602085013591508082111562000c3957600080fd5b5062000c488582860162000b52565b9150509250929050565b8183823760009101908152919050565b8385823760008482016000815283858237600093019283525090949350505050565b60005b8381101562000ca157818101518382015260200162000c87565b8381111562000cb1576000848401525b50505050565b83858237600084820160008152845162000cd681836020890162000c84565b602f60f81b9101818152845190919062000cf881600185016020890162000c84565b60019201918201526002019695505050505050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6001600160a01b0388811682528716602082015260a06040820181905260009062000d65908301878962000d0d565b828103606084015262000d7a81868862000d0d565b90508281036080840152835180825262000d9c81602084016020880162000c84565b601f01601f1916016020019998505050505050505050565b634e487b7160e01b600052601160045260246000fd5b600060001982141562000de15762000de162000db4565b5060010190565b634e487b7160e01b600052601260045260246000fd5b60008262000e105762000e1062000de8565b500490565b60008282101562000e2a5762000e2a62000db4565b500390565b60008262000e415762000e4162000de8565b500690565b6000821982111562000e5c5762000e5c62000db4565b500190565b634e487b7160e01b600052603260045260246000fd5b600081600019048311821515161562000e945762000e9462000db4565b500290565b60008162000eab5762000eab62000db4565b50600019019056fe608060405234801561001057600080fd5b50611ac8806100206000396000f3fe608060405234801561001057600080fd5b50600436106101415760003560e01c80636c0360eb116100b8578063a22cb4651161007c578063a22cb465146102d6578063e2e784d5146102e9578063e8a3d485146102fc578063e985e9c514610304578063f242432a14610332578063f2fde38b1461034557600080fd5b80636c0360eb146102755780638da5cb5b1461027d578063938e3d7b146102a857806395d89b41146102bb5780639fbc8713146102c357600080fd5b80632e81aaea1161010a5780632e81aaea146101fe5780632eb2c2d6146102135780633f2f5ee21461022657806340c10f19146102395780634e1273f41461024c5780635df478381461026c57600080fd5b8062fdd58e1461014657806301ffc9a71461018157806306fdde03146101a45780630e89341c146101b95780632a55205a146101cc575b600080fd5b61016e610154366004611126565b600060208181529281526040808220909352908152205481565b6040519081526020015b60405180910390f35b61019461018f366004611169565b610358565b6040519015158152602001610178565b6101ac610383565b60405161017891906111e5565b6101ac6101c73660046111f8565b610411565b6101df6101da366004611211565b610445565b604080516001600160a01b039093168352602083019190915201610178565b61021161020c366004611233565b61047e565b005b6102116102213660046113b4565b6104c9565b6102116102343660046114a0565b610780565b610211610247366004611126565b610811565b61025f61025a36600461155b565b61085c565b6040516101789190611656565b61016e60085481565b6101ac61098a565b600354610290906001600160a01b031681565b6040516001600160a01b039091168152602001610178565b6102116102b6366004611669565b610997565b6101ac6109d5565b600954610290906001600160a01b031681565b6102116102e43660046116ba565b6109e2565b6102116102f7366004611126565b610a4e565b6101ac610ac6565b6101946103123660046116f6565b600160209081526000928352604080842090915290825290205460ff1681565b610211610340366004611729565b610ad3565b61021161035336600461178e565b610cca565b600063152a902d60e11b6001600160e01b03198316148061037d575061037d82610d3e565b92915050565b60048054610390906117a9565b80601f01602080910402602001604051908101604052809291908181526020018280546103bc906117a9565b80156104095780601f106103de57610100808354040283529160200191610409565b820191906000526020600020905b8154815290600101906020018083116103ec57829003601f168201915b505050505081565b6060600661041e83610d8c565b60405160200161042f929190611800565b6040516020818303038152906040529050919050565b60095460085460009182916001600160a01b0390911690610468612710866118bd565b61047291906118df565b915091505b9250929050565b6002546001600160a01b031633146104a95760405163154cdd6360e31b815260040160405180910390fd5b6104c483838360405180602001604052806000815250610eb5565b505050565b8251825181146105125760405162461bcd60e51b815260206004820152600f60248201526e0988a9c8ea890be9a92a69a82a8869608b1b60448201526064015b60405180910390fd5b336001600160a01b038716148061054c57506001600160a01b038616600090815260016020908152604080832033845290915290205460ff165b6105895760405162461bcd60e51b815260206004820152600e60248201526d1393d517d055551213d49256915160921b6044820152606401610509565b60005b8181101561065e5760008582815181106105a8576105a86118fe565b6020026020010151905060008583815181106105c6576105c66118fe565b60200260200101519050806000808b6001600160a01b03166001600160a01b03168152602001908152602001600020600084815260200190815260200160002060008282546106159190611914565b90915550506001600160a01b0388166000908152602081815260408083208584529091528120805483929061064b90849061192b565b90915550506001909201915061058c9050565b50846001600160a01b0316866001600160a01b0316336001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb87876040516106ae929190611943565b60405180910390a46001600160a01b0385163b1561074f5760405163bc197c8160e01b808252906001600160a01b0387169063bc197c81906106fc9033908b908a908a908a90600401611968565b6020604051808303816000875af115801561071b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061073f91906119c6565b6001600160e01b0319161461075c565b6001600160a01b03851615155b6107785760405162461bcd60e51b8152600401610509906119e3565b505050505050565b60075460ff16156107a357604051620489d760e91b815260040160405180910390fd5b6007805460ff19166001179055600280546001600160a01b03808b166001600160a01b03199283161790925560038054928a16929091169190911790556107ec60048787610ffd565b506107f960058585610ffd565b5061080660068383610ffd565b505050505050505050565b6002546001600160a01b0316331461083c5760405163154cdd6360e31b815260040160405180910390fd5b6108588282600160405180602001604052806000815250610eb5565b5050565b815181516060919081146108a45760405162461bcd60e51b815260206004820152600f60248201526e0988a9c8ea890be9a92a69a82a8869608b1b6044820152606401610509565b835167ffffffffffffffff8111156108be576108be611266565b6040519080825280602002602001820160405280156108e7578160200160208202803683370190505b50915060005b818110156109825760008086838151811061090a5761090a6118fe565b60200260200101516001600160a01b03166001600160a01b031681526020019081526020016000206000858381518110610946576109466118fe565b602002602001015181526020019081526020016000205483828151811061096f5761096f6118fe565b60209081029190910101526001016108ed565b505092915050565b60068054610390906117a9565b6003546001600160a01b031633146109c25760405163154cdd6360e31b815260040160405180910390fd5b805161085890600a906020840190611081565b60058054610390906117a9565b3360008181526001602090815260408083206001600160a01b03871680855290835292819020805460ff191686151590811790915590519081529192917f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a35050565b6001600160a01b038216610a7557604051634ba7322360e11b815260040160405180910390fd5b6003546001600160a01b03163314610aa05760405163154cdd6360e31b815260040160405180910390fd5b600980546001600160a01b0319166001600160a01b039390931692909217909155600855565b600a8054610390906117a9565b336001600160a01b0386161480610b0d57506001600160a01b038516600090815260016020908152604080832033845290915290205460ff165b610b4a5760405162461bcd60e51b815260206004820152600e60248201526d1393d517d055551213d49256915160921b6044820152606401610509565b6001600160a01b03851660009081526020818152604080832086845290915281208054849290610b7b908490611914565b90915550506001600160a01b03841660009081526020818152604080832086845290915281208054849290610bb190849061192b565b909155505060408051848152602081018490526001600160a01b03808716929088169133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a46001600160a01b0384163b15610c9a5760405163f23a6e6160e01b808252906001600160a01b0386169063f23a6e6190610c479033908a90899089908990600401611a0d565b6020604051808303816000875af1158015610c66573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c8a91906119c6565b6001600160e01b03191614610ca7565b6001600160a01b03841615155b610cc35760405162461bcd60e51b8152600401610509906119e3565b5050505050565b6001600160a01b038116610cf157604051634ba7322360e11b815260040160405180910390fd5b6003546001600160a01b03163314610d1c5760405163154cdd6360e31b815260040160405180910390fd5b600380546001600160a01b0319166001600160a01b0392909216919091179055565b60006301ffc9a760e01b6001600160e01b031983161480610d6f5750636cdb3d1360e11b6001600160e01b03198316145b8061037d5750506001600160e01b0319166303a24d0760e21b1490565b606081610db05750506040805180820190915260018152600360fc1b602082015290565b8160005b8115610dda5780610dc481611a52565b9150610dd39050600a836118bd565b9150610db4565b60008167ffffffffffffffff811115610df557610df5611266565b6040519080825280601f01601f191660200182016040528015610e1f576020820181803683370190505b509050815b8515610eac57610e35600182611914565b90506000610e44600a886118bd565b610e4f90600a6118df565b610e599088611914565b610e64906030611a6d565b905060008160f81b905080848481518110610e8157610e816118fe565b60200101906001600160f81b031916908160001a905350610ea3600a896118bd565b97505050610e24565b50949350505050565b6001600160a01b03841660009081526020818152604080832086845290915281208054849290610ee690849061192b565b909155505060408051848152602081018490526001600160a01b0386169160009133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a46001600160a01b0384163b15610fce5760405163f23a6e6160e01b808252906001600160a01b0386169063f23a6e6190610f7b903390600090899089908990600401611a0d565b6020604051808303816000875af1158015610f9a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fbe91906119c6565b6001600160e01b03191614610fdb565b6001600160a01b03841615155b610ff75760405162461bcd60e51b8152600401610509906119e3565b50505050565b828054611009906117a9565b90600052602060002090601f01602090048101928261102b5760008555611071565b82601f106110445782800160ff19823516178555611071565b82800160010185558215611071579182015b82811115611071578235825591602001919060010190611056565b5061107d9291506110f5565b5090565b82805461108d906117a9565b90600052602060002090601f0160209004810192826110af5760008555611071565b82601f106110c857805160ff1916838001178555611071565b82800160010185558215611071579182015b828111156110715782518255916020019190600101906110da565b5b8082111561107d57600081556001016110f6565b80356001600160a01b038116811461112157600080fd5b919050565b6000806040838503121561113957600080fd5b6111428361110a565b946020939093013593505050565b6001600160e01b03198116811461116657600080fd5b50565b60006020828403121561117b57600080fd5b813561118681611150565b9392505050565b60005b838110156111a8578181015183820152602001611190565b83811115610ff75750506000910152565b600081518084526111d181602086016020860161118d565b601f01601f19169290920160200192915050565b60208152600061118660208301846111b9565b60006020828403121561120a57600080fd5b5035919050565b6000806040838503121561122457600080fd5b50508035926020909101359150565b60008060006060848603121561124857600080fd5b6112518461110a565b95602085013595506040909401359392505050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff811182821017156112a5576112a5611266565b604052919050565b600067ffffffffffffffff8211156112c7576112c7611266565b5060051b60200190565b600082601f8301126112e257600080fd5b813560206112f76112f2836112ad565b61127c565b82815260059290921b8401810191818101908684111561131657600080fd5b8286015b84811015611331578035835291830191830161131a565b509695505050505050565b600067ffffffffffffffff83111561135657611356611266565b611369601f8401601f191660200161127c565b905082815283838301111561137d57600080fd5b828260208301376000602084830101529392505050565b600082601f8301126113a557600080fd5b6111868383356020850161133c565b600080600080600060a086880312156113cc57600080fd5b6113d58661110a565b94506113e36020870161110a565b9350604086013567ffffffffffffffff8082111561140057600080fd5b61140c89838a016112d1565b9450606088013591508082111561142257600080fd5b61142e89838a016112d1565b9350608088013591508082111561144457600080fd5b5061145188828901611394565b9150509295509295909350565b60008083601f84011261147057600080fd5b50813567ffffffffffffffff81111561148857600080fd5b60208301915083602082850101111561047757600080fd5b60008060008060008060008060a0898b0312156114bc57600080fd5b6114c58961110a565b97506114d360208a0161110a565b9650604089013567ffffffffffffffff808211156114f057600080fd5b6114fc8c838d0161145e565b909850965060608b013591508082111561151557600080fd5b6115218c838d0161145e565b909650945060808b013591508082111561153a57600080fd5b506115478b828c0161145e565b999c989b5096995094979396929594505050565b6000806040838503121561156e57600080fd5b823567ffffffffffffffff8082111561158657600080fd5b818501915085601f83011261159a57600080fd5b813560206115aa6112f2836112ad565b82815260059290921b840181019181810190898411156115c957600080fd5b948201945b838610156115ee576115df8661110a565b825294820194908201906115ce565b9650508601359250508082111561160457600080fd5b50611611858286016112d1565b9150509250929050565b600081518084526020808501945080840160005b8381101561164b5781518752958201959082019060010161162f565b509495945050505050565b602081526000611186602083018461161b565b60006020828403121561167b57600080fd5b813567ffffffffffffffff81111561169257600080fd5b8201601f810184136116a357600080fd5b6116b28482356020840161133c565b949350505050565b600080604083850312156116cd57600080fd5b6116d68361110a565b9150602083013580151581146116eb57600080fd5b809150509250929050565b6000806040838503121561170957600080fd5b6117128361110a565b91506117206020840161110a565b90509250929050565b600080600080600060a0868803121561174157600080fd5b61174a8661110a565b94506117586020870161110a565b93506040860135925060608601359150608086013567ffffffffffffffff81111561178257600080fd5b61145188828901611394565b6000602082840312156117a057600080fd5b6111868261110a565b600181811c908216806117bd57607f821691505b602082108114156117de57634e487b7160e01b600052602260045260246000fd5b50919050565b600081516117f681856020860161118d565b9290920192915050565b600080845481600182811c91508083168061181c57607f831692505b602080841082141561183c57634e487b7160e01b86526022600452602486fd5b81801561185057600181146118615761188e565b60ff1986168952848901965061188e565b60008b81526020902060005b868110156118865781548b82015290850190830161186d565b505084890196505b50505050505061189e81856117e4565b95945050505050565b634e487b7160e01b600052601160045260246000fd5b6000826118da57634e487b7160e01b600052601260045260246000fd5b500490565b60008160001904831182151516156118f9576118f96118a7565b500290565b634e487b7160e01b600052603260045260246000fd5b600082821015611926576119266118a7565b500390565b6000821982111561193e5761193e6118a7565b500190565b604081526000611956604083018561161b565b828103602084015261189e818561161b565b6001600160a01b0386811682528516602082015260a0604082018190526000906119949083018661161b565b82810360608401526119a6818661161b565b905082810360808401526119ba81856111b9565b98975050505050505050565b6000602082840312156119d857600080fd5b815161118681611150565b60208082526010908201526f155394d0519157d49150d2541251539560821b604082015260600190565b6001600160a01b03868116825285166020820152604081018490526060810183905260a060808201819052600090611a47908301846111b9565b979650505050505050565b6000600019821415611a6657611a666118a7565b5060010190565b600060ff821660ff84168060ff03821115611a8a57611a8a6118a7565b01939250505056fea2646970667358221220b4968124272ff911d5ec7df4207835b0ca22b335b4cbed407e8b89ccef6260a364736f6c634300080c003360806040523480156200001157600080fd5b50604080516020808201808452600080845284519283019094528382528251929391926200004192919062000060565b5080516200005790600190602084019062000060565b50505062000143565b8280546200006e9062000106565b90600052602060002090601f016020900481019282620000925760008555620000dd565b82601f10620000ad57805160ff1916838001178555620000dd565b82800160010185558215620000dd579182015b82811115620000dd578251825591602001919060010190620000c0565b50620000eb929150620000ef565b5090565b5b80821115620000eb5760008155600101620000f0565b600181811c908216806200011b57607f821691505b602082108114156200013d57634e487b7160e01b600052602260045260246000fd5b50919050565b61161180620001536000396000f3fe608060405234801561001057600080fd5b50600436106101585760003560e01c806370a08231116100c3578063b88d4fde1161007c578063b88d4fde1461032a578063c87b56dd1461033d578063e2e784d514610350578063e8a3d48514610363578063e985e9c51461036b578063f2fde38b1461039957600080fd5b806370a08231146102b65780638da5cb5b146102d6578063938e3d7b146102e957806395d89b41146102fc5780639fbc871314610304578063a22cb4651461031757600080fd5b80633f2f5ee2116101155780633f2f5ee21461023557806340c10f191461024857806342842e0e1461025b5780635df478381461026e5780636352211e146102855780636c0360eb146102ae57600080fd5b806301ffc9a71461015d57806306fdde0314610185578063081812fc1461019a578063095ea7b3146101db57806323b872dd146101f05780632a55205a14610203575b600080fd5b61017061016b366004610f93565b6103ac565b60405190151581526020015b60405180910390f35b61018d6103d7565b60405161017c919061100f565b6101c36101a8366004611022565b6004602052600090815260409020546001600160a01b031681565b6040516001600160a01b03909116815260200161017c565b6101ee6101e9366004611057565b610465565b005b6101ee6101fe366004611081565b61054c565b6102166102113660046110bd565b610713565b604080516001600160a01b03909316835260208301919091520161017c565b6101ee610243366004611121565b61074c565b6101ee610256366004611057565b6107e4565b6101ee610269366004611081565b61081d565b61027760095481565b60405190815260200161017c565b6101c3610293366004611022565b6003602052600090815260409020546001600160a01b031681565b61018d610915565b6102776102c43660046111dc565b60026020526000908152604090205481565b6007546101c3906001600160a01b031681565b6101ee6102f7366004611283565b610922565b61018d610960565b600a546101c3906001600160a01b031681565b6101ee6103253660046112d4565b61096d565b6101ee610338366004611310565b6109d9565b61018d61034b366004611022565b610abe565b6101ee61035e366004611057565b610af2565b61018d610b6a565b61017061037936600461138c565b600560209081526000928352604080842090915290825290205460ff1681565b6101ee6103a73660046111dc565b610b77565b600063152a902d60e11b6001600160e01b0319831614806103d157506103d182610beb565b92915050565b600080546103e4906113bf565b80601f0160208091040260200160405190810160405280929190818152602001828054610410906113bf565b801561045d5780601f106104325761010080835404028352916020019161045d565b820191906000526020600020905b81548152906001019060200180831161044057829003601f168201915b505050505081565b6000818152600360205260409020546001600160a01b0316338114806104ae57506001600160a01b038116600090815260056020908152604080832033845290915290205460ff165b6104f05760405162461bcd60e51b815260206004820152600e60248201526d1393d517d055551213d49256915160921b60448201526064015b60405180910390fd5b60008281526004602052604080822080546001600160a01b0319166001600160a01b0387811691821790925591518593918516917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a4505050565b6000818152600360205260409020546001600160a01b038481169116146105a25760405162461bcd60e51b815260206004820152600a60248201526957524f4e475f46524f4d60b01b60448201526064016104e7565b6001600160a01b0382166105ec5760405162461bcd60e51b81526020600482015260116024820152701253959053125117d49150d25412515395607a1b60448201526064016104e7565b336001600160a01b038416148061061957506000818152600460205260409020546001600160a01b031633145b8061064757506001600160a01b038316600090815260056020908152604080832033845290915290205460ff165b6106845760405162461bcd60e51b815260206004820152600e60248201526d1393d517d055551213d49256915160921b60448201526064016104e7565b6001600160a01b0380841660008181526002602090815260408083208054600019019055938616808352848320805460010190558583526003825284832080546001600160a01b03199081168317909155600490925284832080549092169091559251849392917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4505050565b600a5460095460009182916001600160a01b039091169061073661271086611410565b6107409190611432565b915091505b9250929050565b600754600160a01b900460ff16156107775760405163017ea83f60e61b815260040160405180910390fd5b60078054600680546001600160a01b0319166001600160a01b038c811691909117909155600160a01b6001600160a81b0319909216908a16171790556107bf60008787610e6d565b506107cc60018585610e6d565b506107d960088383610e6d565b505050505050505050565b6006546001600160a01b0316331461080f5760405163661af03760e11b815260040160405180910390fd5b6108198282610c39565b5050565b61082883838361054c565b6001600160a01b0382163b15806108d15750604051630a85bd0160e11b8082523360048301526001600160a01b03858116602484015260448301849052608060648401526000608484015290919084169063150b7a029060a4016020604051808303816000875af11580156108a1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108c59190611451565b6001600160e01b031916145b6109105760405162461bcd60e51b815260206004820152601060248201526f155394d0519157d49150d2541251539560821b60448201526064016104e7565b505050565b600880546103e4906113bf565b6007546001600160a01b0316331461094d5760405163661af03760e11b815260040160405180910390fd5b805161081990600b906020840190610ef1565b600180546103e4906113bf565b3360008181526005602090815260408083206001600160a01b03871680855290835292819020805460ff191686151590811790915590519081529192917f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a35050565b6109e484848461054c565b6001600160a01b0383163b1580610a795750604051630a85bd0160e11b808252906001600160a01b0385169063150b7a0290610a2a90339089908890889060040161146e565b6020604051808303816000875af1158015610a49573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a6d9190611451565b6001600160e01b031916145b610ab85760405162461bcd60e51b815260206004820152601060248201526f155394d0519157d49150d2541251539560821b60448201526064016104e7565b50505050565b60606008610acb83610d44565b604051602001610adc9291906114c7565b6040516020818303038152906040529050919050565b6001600160a01b038216610b1957604051634cc66cdd60e11b815260040160405180910390fd5b6007546001600160a01b03163314610b445760405163661af03760e11b815260040160405180910390fd5b600a80546001600160a01b0319166001600160a01b039390931692909217909155600955565b600b80546103e4906113bf565b6001600160a01b038116610b9e57604051634cc66cdd60e11b815260040160405180910390fd5b6007546001600160a01b03163314610bc95760405163661af03760e11b815260040160405180910390fd5b600780546001600160a01b0319166001600160a01b0392909216919091179055565b60006301ffc9a760e01b6001600160e01b031983161480610c1c57506380ac58cd60e01b6001600160e01b03198316145b806103d15750506001600160e01b031916635b5e139f60e01b1490565b6001600160a01b038216610c835760405162461bcd60e51b81526020600482015260116024820152701253959053125117d49150d25412515395607a1b60448201526064016104e7565b6000818152600360205260409020546001600160a01b031615610cd95760405162461bcd60e51b815260206004820152600e60248201526d1053149150511657d3525395115160921b60448201526064016104e7565b6001600160a01b038216600081815260026020908152604080832080546001019055848352600390915280822080546001600160a01b0319168417905551839291907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b606081610d685750506040805180820190915260018152600360fc1b602082015290565b8160005b8115610d925780610d7c8161156e565b9150610d8b9050600a83611410565b9150610d6c565b60008167ffffffffffffffff811115610dad57610dad6111f7565b6040519080825280601f01601f191660200182016040528015610dd7576020820181803683370190505b509050815b8515610e6457610ded600182611589565b90506000610dfc600a88611410565b610e0790600a611432565b610e119088611589565b610e1c9060306115a0565b905060008160f81b905080848481518110610e3957610e396115c5565b60200101906001600160f81b031916908160001a905350610e5b600a89611410565b97505050610ddc565b50949350505050565b828054610e79906113bf565b90600052602060002090601f016020900481019282610e9b5760008555610ee1565b82601f10610eb45782800160ff19823516178555610ee1565b82800160010185558215610ee1579182015b82811115610ee1578235825591602001919060010190610ec6565b50610eed929150610f65565b5090565b828054610efd906113bf565b90600052602060002090601f016020900481019282610f1f5760008555610ee1565b82601f10610f3857805160ff1916838001178555610ee1565b82800160010185558215610ee1579182015b82811115610ee1578251825591602001919060010190610f4a565b5b80821115610eed5760008155600101610f66565b6001600160e01b031981168114610f9057600080fd5b50565b600060208284031215610fa557600080fd5b8135610fb081610f7a565b9392505050565b60005b83811015610fd2578181015183820152602001610fba565b83811115610ab85750506000910152565b60008151808452610ffb816020860160208601610fb7565b601f01601f19169290920160200192915050565b602081526000610fb06020830184610fe3565b60006020828403121561103457600080fd5b5035919050565b80356001600160a01b038116811461105257600080fd5b919050565b6000806040838503121561106a57600080fd5b6110738361103b565b946020939093013593505050565b60008060006060848603121561109657600080fd5b61109f8461103b565b92506110ad6020850161103b565b9150604084013590509250925092565b600080604083850312156110d057600080fd5b50508035926020909101359150565b60008083601f8401126110f157600080fd5b50813567ffffffffffffffff81111561110957600080fd5b60208301915083602082850101111561074557600080fd5b60008060008060008060008060a0898b03121561113d57600080fd5b6111468961103b565b975061115460208a0161103b565b9650604089013567ffffffffffffffff8082111561117157600080fd5b61117d8c838d016110df565b909850965060608b013591508082111561119657600080fd5b6111a28c838d016110df565b909650945060808b01359150808211156111bb57600080fd5b506111c88b828c016110df565b999c989b5096995094979396929594505050565b6000602082840312156111ee57600080fd5b610fb08261103b565b634e487b7160e01b600052604160045260246000fd5b600067ffffffffffffffff80841115611228576112286111f7565b604051601f8501601f19908116603f01168101908282118183101715611250576112506111f7565b8160405280935085815286868601111561126957600080fd5b858560208301376000602087830101525050509392505050565b60006020828403121561129557600080fd5b813567ffffffffffffffff8111156112ac57600080fd5b8201601f810184136112bd57600080fd5b6112cc8482356020840161120d565b949350505050565b600080604083850312156112e757600080fd5b6112f08361103b565b91506020830135801515811461130557600080fd5b809150509250929050565b6000806000806080858703121561132657600080fd5b61132f8561103b565b935061133d6020860161103b565b925060408501359150606085013567ffffffffffffffff81111561136057600080fd5b8501601f8101871361137157600080fd5b6113808782356020840161120d565b91505092959194509250565b6000806040838503121561139f57600080fd5b6113a88361103b565b91506113b66020840161103b565b90509250929050565b600181811c908216806113d357607f821691505b602082108114156113f457634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b60008261142d57634e487b7160e01b600052601260045260246000fd5b500490565b600081600019048311821515161561144c5761144c6113fa565b500290565b60006020828403121561146357600080fd5b8151610fb081610f7a565b6001600160a01b03858116825284166020820152604081018390526080606082018190526000906114a190830184610fe3565b9695505050505050565b600081516114bd818560208601610fb7565b9290920192915050565b600080845481600182811c9150808316806114e357607f831692505b602080841082141561150357634e487b7160e01b86526022600452602486fd5b818015611517576001811461152857611555565b60ff19861689528489019650611555565b60008b81526020902060005b8681101561154d5781548b820152908501908301611534565b505084890196505b50505050505061156581856114ab565b95945050505050565b6000600019821415611582576115826113fa565b5060010190565b60008282101561159b5761159b6113fa565b500390565b600060ff821660ff84168060ff038211156115bd576115bd6113fa565b019392505050565b634e487b7160e01b600052603260045260246000fdfea2646970667358221220656bcabb0c05365279531b6b2a8081bea28afcf6dca5cc18b1039fc9f513235964736f6c634300080c0033a2646970667358221220e3622b1f7a6e62df610252e2880c4e4d0ffa1828c6336a86a99b29c293e70e1664736f6c634300080c0033608060405234801561001057600080fd5b50600980546001600160a01b03191633179055611050806100326000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c8063a5a57d211161008c578063d20fea2511610066578063d20fea251461029c578063ee2f70a9146102af578063f2fde38b146102c2578063f60ba1b0146102d557600080fd5b8063a5a57d2114610214578063b03d554714610257578063d0c52c451461028957600080fd5b806326903838116100c8578063269038381461013b578063347f73a11461018657806360cf6c4c146101d45780638da5cb5b146101e957600080fd5b806304c15e50146100ef5780630db9a5d5146101155780631f4042a214610128575b600080fd5b6101026100fd366004610d9a565b6102e8565b6040519081526020015b60405180910390f35b610102610123366004610d9a565b61037d565b610102610136366004610d9a565b610407565b60005460015460025460035460045460055461015995949392919086565b604080519687526020870195909552938501929092526060840152608083015260a082015260c00161010c565b610159610194366004610dd6565b6007602090815260009283526040808420909152908252902080546001820154600283015460038401546004850154600590950154939492939192909186565b6101e76101e2366004610e00565b610491565b005b6009546101fc906001600160a01b031681565b6040516001600160a01b03909116815260200161010c565b610159610222366004610e5c565b600660205260009081526040902080546001820154600283015460038401546004850154600590950154939492939192909186565b61026a610265366004610e77565b6105ea565b604080516001600160a01b03909316835260208301919091520161010c565b6101e7610297366004610e90565b610622565b6101026102aa366004610d9a565b61076a565b6101e76102bd366004610ee3565b6107f4565b6101e76102d0366004610e5c565b61086a565b6101026102e3366004610d9a565b6108b7565b6000806000806102f9878787610941565b9250925092508215610333576001600160a01b038616600090815260066020526040902054610329908290610f3c565b9350505050610376565b8115610368576001600160a01b0386166000908152600760209081526040808320888452909152902054610329908290610f3c565b600054610329908290610f3c565b9392505050565b60008060008061038e878787610941565b92509250925082156103c1576001600160a01b038616600090815260066020526040902060030154610329908290610f3c565b81156103f9576001600160a01b0386166000908152600760209081526040808320888452909152902060030154610329908290610f3c565b600354610329908290610f3c565b600080600080610418878787610941565b925092509250821561044b576001600160a01b038616600090815260066020526040902060040154610329908290610f3c565b8115610483576001600160a01b0386166000908152600760209081526040808320888452909152902060040154610329908290610f3c565b600454610329908290610f3c565b6009546001600160a01b031633146104bc57604051633d5d2fc360e21b815260040160405180910390fd5b600860405180604001604052808a6001600160a01b0316815260200189815250908060018154018082558091505060019003906000526020600020906002020160009091909190915060008201518160000160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506020820151816001015550506040518060c0016040528087815260200186815260200185815260200184815260200183815260200182815250600760008a6001600160a01b03166001600160a01b031681526020019081526020016000206000898152602001908152602001600020600082015181600001556020820151816001015560408201518160020155606082015181600301556080820151816004015560a082015181600501559050505050505050505050565b600881815481106105fa57600080fd5b6000918252602090912060029091020180546001909101546001600160a01b03909116915082565b6009546001600160a01b0316331461064d57604051633d5d2fc360e21b815260040160405180910390fd5b60086040518060400160405280896001600160a01b031681526020016000815250908060018154018082558091505060019003906000526020600020906002020160009091909190915060008201518160000160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506020820151816001015550506040518060c001604052808781526020018681526020018581526020018481526020018381526020018281525060066000896001600160a01b03166001600160a01b03168152602001908152602001600020600082015181600001556020820151816001015560408201518160020155606082015181600301556080820151816004015560a0820151816005015590505050505050505050565b60008060008061077b878787610941565b92509250925082156107ae576001600160a01b038616600090815260066020526040902060010154610329908290610f3c565b81156107e6576001600160a01b0386166000908152600760209081526040808320888452909152902060010154610329908290610f3c565b600154610329908290610f3c565b6009546001600160a01b0316331461081f57604051633d5d2fc360e21b815260040160405180910390fd5b6040805160c08101825287815260208101879052908101859052606081018490526080810183905260a001819052600095909555600193909355600291909155600355600455600555565b6009546001600160a01b0316331461089557604051633d5d2fc360e21b815260040160405180910390fd5b600980546001600160a01b0319166001600160a01b0392909216919091179055565b6000806000806108c8878787610941565b92509250925082156108fb576001600160a01b038616600090815260066020526040902060020154610329908290610f3c565b8115610933576001600160a01b0386166000908152600760209081526040808320888452909152902060020154610329908290610f3c565b600254610329908290610f3c565b6000806000806008805480602002602001604051908101604052809291908181526020016000905b828210156109b1576000848152602090819020604080518082019091526002850290910180546001600160a01b03168252600190810154828401529083529092019101610969565b50505050905060005b8151811015610c0d5760008282815181106109d7576109d7610f5b565b60209081029190910101518051909150610a01906001600160a01b03166380ac58cd60e01b610c3d565b15610ad25780516040516370a0823160e01b81526001600160a01b038b8116600483015260009216906370a0823190602401602060405180830381865afa158015610a50573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a749190610f71565b1115610ab35780516001600160a01b031660009081526006602052604090206005015460018111610aa55784610aaf565b610aaf8186610f8a565b9450505b80516001600160a01b0389811691161415610acd57600195505b610bfa565b8051610aee906001600160a01b0316636cdb3d1360e11b610c3d565b15610bfa5780516020820151604051627eeac760e11b81526000926001600160a01b03169162fdd58e91610b3a918e916004016001600160a01b03929092168252602082015260400190565b602060405180830381865afa158015610b57573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b7b9190610f71565b1115610bfa5780516001600160a01b0316600090815260076020908152604080832082850151845290915290206005015460018111610bba5784610bc4565b610bc48186610f8a565b945081600001516001600160a01b0316896001600160a01b0316148015610bee5750816020015188145b15610bf857600195505b505b5080610c0581610fa2565b9150506109ba565b508115610c1a5781610c1e565b6005545b915060048211610c2e5781610c31565b60045b91505093509350939050565b6000610c4883610c62565b8015610c595750610c598383610c95565b90505b92915050565b6000610c75826301ffc9a760e01b610c95565b8015610c5c5750610c8e826001600160e01b0319610c95565b1592915050565b604080516001600160e01b0319831660248083019190915282518083039091018152604490910182526020810180516001600160e01b03166301ffc9a760e01b179052905160009190829081906001600160a01b0387169061753090610cfc908690610fbd565b6000604051808303818686fa925050503d8060008114610d38576040519150601f19603f3d011682016040523d82523d6000602084013e610d3d565b606091505b5091509150602081511015610d585760009350505050610c5c565b818015610d74575080806020019051810190610d749190610ff8565b9695505050505050565b80356001600160a01b0381168114610d9557600080fd5b919050565b600080600060608486031215610daf57600080fd5b610db884610d7e565b9250610dc660208501610d7e565b9150604084013590509250925092565b60008060408385031215610de957600080fd5b610df283610d7e565b946020939093013593505050565b600080600080600080600080610100898b031215610e1d57600080fd5b610e2689610d7e565b9a60208a01359a5060408a013599606081013599506080810135985060a0810135975060c0810135965060e00135945092505050565b600060208284031215610e6e57600080fd5b610c5982610d7e565b600060208284031215610e8957600080fd5b5035919050565b600080600080600080600060e0888a031215610eab57600080fd5b610eb488610d7e565b9960208901359950604089013598606081013598506080810135975060a0810135965060c00135945092505050565b60008060008060008060c08789031215610efc57600080fd5b505084359660208601359650604086013595606081013595506080810135945060a0013592509050565b634e487b7160e01b600052601160045260246000fd5b6000816000190483118215151615610f5657610f56610f26565b500290565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610f8357600080fd5b5051919050565b60008219821115610f9d57610f9d610f26565b500190565b6000600019821415610fb657610fb6610f26565b5060010190565b6000825160005b81811015610fde5760208186018101518583015201610fc4565b81811115610fed576000828501525b509190910192915050565b60006020828403121561100a57600080fd5b8151801515811461037657600080fdfea264697066735822122009a080f3be075aa0df4961e80e47ff436674980a2de4f97601da32398f8865ea64736f6c634300080c00336101006040523480156200001257600080fd5b50604051620018ce380380620018ce833981016040819052620000359162000269565b604080518082018252600a81526904f6479737365792058560b41b602080830191825283518085019094526002845261058560f41b9084015281519192916000916200008491839190620001aa565b5081516200009a906001906020850190620001aa565b5060ff81166080524660a052620000b06200010e565b60c0525050506001600160a01b0394851660e052600a80549486166001600160a01b031995861617905560068054938616938516939093179092556007805491851691841691909117905560088054919093169116179055620003ca565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f600060405162000142919062000326565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b828054620001b890620002e9565b90600052602060002090601f016020900481019282620001dc576000855562000227565b82601f10620001f757805160ff191683800117855562000227565b8280016001018555821562000227579182015b82811115620002275782518255916020019190600101906200020a565b506200023592915062000239565b5090565b5b808211156200023557600081556001016200023a565b6001600160a01b03811681146200026657600080fd5b50565b600080600080600060a086880312156200028257600080fd5b85516200028f8162000250565b6020870151909550620002a28162000250565b6040870151909450620002b58162000250565b6060870151909350620002c88162000250565b6080870151909250620002db8162000250565b809150509295509295909350565b600181811c90821680620002fe57607f821691505b602082108114156200032057634e487b7160e01b600052602260045260246000fd5b50919050565b600080835481600182811c9150808316806200034357607f831692505b60208084108214156200036457634e487b7160e01b86526022600452602486fd5b8180156200037b57600181146200038d57620003bc565b60ff19861689528489019650620003bc565b60008a81526020902060005b86811015620003b45781548b82015290850190830162000399565b505084890196505b509498975050505050505050565b60805160a05160c05160e0516114b562000419600039600081816102c701528181610e1201528181610efa0152610f8c01526000610632015260006105fd0152600061028601526114b56000f3fe608060405234801561001057600080fd5b50600436106101e55760003560e01c80637b626f2c1161010f578063c41c2f24116100a2578063dd62ed3e11610071578063dd62ed3e14610458578063f11a3fcb14610483578063f2fde38b146104bf578063f887ea40146104d257600080fd5b8063c41c2f241461040c578063cbe52ae31461041f578063d2f7265a14610432578063d505accf1461044557600080fd5b8063a9059cbb116100de578063a9059cbb146103d0578063be040fb0146103de578063be204492146103e6578063c0d78655146103f957600080fd5b80637b626f2c146103825780637ecebe00146103955780638da5cb5b146103b557806395d89b41146103c857600080fd5b80633644e5151161018757806367b1f5df1161015657806367b1f5df146103295780636da2661b1461033c5780636e91818a1461034f57806370a082311461036257600080fd5b80633644e515146102ba57806338d52e0f146102c257806343c60544146103015780634c50f2841461031657600080fd5b806318160ddd116101c357806318160ddd1461023e57806323b872dd1461024757806330adf81f1461025a578063313ce5671461028157600080fd5b8063041874c6146101ea57806306fdde0314610206578063095ea7b31461021b575b600080fd5b6101f360095481565b6040519081526020015b60405180910390f35b61020e6104e5565b6040516101fd919061118e565b61022e6102293660046111ff565b610573565b60405190151581526020016101fd565b6101f360025481565b61022e610255366004611229565b6105df565b6101f37f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b6102a87f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff90911681526020016101fd565b6101f36105f9565b6102e97f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016101fd565b61031461030f366004611229565b610654565b005b610314610324366004611229565b610705565b610314610337366004611265565b61076a565b61031461034a366004611229565b6107b7565b61031461035d366004611265565b61081c565b6101f3610370366004611265565b60036020526000908152604090205481565b610314610390366004611229565b610869565b6101f36103a3366004611265565b60056020526000908152604090205481565b6008546102e9906001600160a01b031681565b61020e6108cd565b61022e6102553660046111ff565b6101f36108da565b6103146103f4366004611229565b610925565b610314610407366004611265565b61098a565b600a546102e9906001600160a01b031681565b6101f361042d3660046111ff565b6109d7565b6007546102e9906001600160a01b031681565b610314610453366004611280565b610a10565b6101f36104663660046112f3565b600460209081526000928352604080842090915290825290205481565b6104aa610491366004611265565b600b602052600090815260409020805460019091015482565b604080519283526020830191909152016101fd565b6103146104cd366004611265565b610c66565b6006546102e9906001600160a01b031681565b600080546104f290611326565b80601f016020809104026020016040519081016040528092919081815260200182805461051e90611326565b801561056b5780601f106105405761010080835404028352916020019161056b565b820191906000526020600020905b81548152906001019060200180831161054e57829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906105ce9086815260200190565b60405180910390a350600192915050565b600060405162db810f60e11b815260040160405180910390fd5b60007f0000000000000000000000000000000000000000000000000000000000000000461461062f5761062a610cb3565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b6006546001600160a01b0316331461067f57604051637f7f876960e01b815260040160405180910390fd5b600a54604051630f60ba1b60e41b81526106ff9185916001600160a01b039091169063f60ba1b0906106b990849088908890600401611361565b602060405180830381865afa1580156106d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106fa9190611385565b610d4d565b50505050565b6007546001600160a01b0316331461073057604051637f7f876960e01b815260040160405180910390fd5b600a5460405163d20fea2560e01b81526106ff9185916001600160a01b039091169063d20fea25906106b990849088908890600401611361565b6008546001600160a01b0316331461079557604051637f7f876960e01b815260040160405180910390fd5b600780546001600160a01b0319166001600160a01b0392909216919091179055565b6006546001600160a01b031633146107e257604051637f7f876960e01b815260040160405180910390fd5b600a54604051630fa0215160e11b81526106ff9185916001600160a01b0390911690631f4042a2906106b990849088908890600401611361565b6008546001600160a01b0316331461084757604051637f7f876960e01b815260040160405180910390fd5b600a80546001600160a01b0319166001600160a01b0392909216919091179055565b6007546001600160a01b0316331461089457604051637f7f876960e01b815260040160405180910390fd5b600a54604051624c15e560e41b81526106ff9185916001600160a01b03909116906304c15e50906106b990849088908890600401611361565b600180546104f290611326565b3360008181526003602052604081205490916108f690826109d7565b9150816109165760405163385f6b1160e11b815260040160405180910390fd5b610921338383610dfb565b5090565b6007546001600160a01b0316331461095057604051637f7f876960e01b815260040160405180910390fd5b600a54604051630db9a5d560e01b81526106ff9185916001600160a01b0390911690630db9a5d5906106b990849088908890600401611361565b6008546001600160a01b031633146109b557604051637f7f876960e01b815260040160405180910390fd5b600680546001600160a01b0319166001600160a01b0392909216919091179055565b6002546000908015806109e8575082155b610a0557610a006109f885610f6a565b849083611044565b610a08565b60005b949350505050565b42841015610a655760405162461bcd60e51b815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b6000610a6f6105f9565b6001600160a01b0389811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938c166060840152608083018b905260a083019390935260c08083018a90528151808403909101815260e08301909152805192019190912061190160f01b6101008301526101028201929092526101228101919091526101420160408051601f198184030181528282528051602091820120600080855291840180845281905260ff88169284019290925260608301869052608083018590529092509060019060a0016020604051602081039080840390855afa158015610b88573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811615801590610bbe5750886001600160a01b0316816001600160a01b0316145b610bfb5760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b6044820152606401610a5c565b6001600160a01b0390811660009081526004602090815260408083208b8516808552908352928190208a905551898152919350918a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b6008546001600160a01b03163314610c9157604051637f7f876960e01b815260040160405180910390fd5b600880546001600160a01b0319166001600160a01b0392909216919091179055565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6000604051610ce5919061139e565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b6001600160a01b03821660009081526003602052604081205481610d7185836109d7565b9250821115610d8a57610d85848383610dfb565b610daf565b80610daf576009546001600160a01b0385166000908152600b60205260409020600101555b610db98484611063565b604080518381526020810185905233917f4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f910160405180910390a25092915050565b6040516370a0823160e01b815230600482015282907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015610e61573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e859190611385565b610e8f9190611450565b6001600160a01b0384166000908152600b6020526040902055600954610eb6908390611467565b6001600160a01b0384166000908152600b602052604081206001019190915560098054849290610ee7908490611467565b90915550610f2190506001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001684846110ce565b60408051838152602081018390526001600160a01b038516917fe5b754fb1abb7f01b499791d0b820ae3b6af3424ac1c59768edb53f4ec31a929910160405180910390a2505050565b6040516370a0823160e01b815230600482015260009081906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa158015610fd3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ff79190611385565b6001600160a01b0384166000908152600b60205260409020805460019091015460095492935090916110299190611450565b6110339083611467565b61103d9190611450565b9392505050565b82820281151584158583048514171661105c57600080fd5b0492915050565b80600260008282546110759190611467565b90915550506001600160a01b0382166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060405163a9059cbb60e01b81526001600160a01b03841660048201528260248201526000806044836000895af191505061110981611147565b6106ff5760405162461bcd60e51b815260206004820152600f60248201526e1514905394d1915497d19052531151608a1b6044820152606401610a5c565b60003d8261115957806000803e806000fd5b80602081146111715780156111825760009250611187565b816000803e60005115159250611187565b600192505b5050919050565b600060208083528351808285015260005b818110156111bb5785810183015185820160400152820161119f565b818111156111cd576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b03811681146111fa57600080fd5b919050565b6000806040838503121561121257600080fd5b61121b836111e3565b946020939093013593505050565b60008060006060848603121561123e57600080fd5b611247846111e3565b9250611255602085016111e3565b9150604084013590509250925092565b60006020828403121561127757600080fd5b61103d826111e3565b600080600080600080600060e0888a03121561129b57600080fd5b6112a4886111e3565b96506112b2602089016111e3565b95506040880135945060608801359350608088013560ff811681146112d657600080fd5b9699959850939692959460a0840135945060c09093013592915050565b6000806040838503121561130657600080fd5b61130f836111e3565b915061131d602084016111e3565b90509250929050565b600181811c9082168061133a57607f821691505b6020821081141561135b57634e487b7160e01b600052602260045260246000fd5b50919050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b60006020828403121561139757600080fd5b5051919050565b600080835481600182811c9150808316806113ba57607f831692505b60208084108214156113da57634e487b7160e01b86526022600452602486fd5b8180156113ee57600181146113ff5761142c565b60ff1986168952848901965061142c565b60008a81526020902060005b868110156114245781548b82015290850190830161140b565b505084890196505b509498975050505050505050565b634e487b7160e01b600052601160045260246000fd5b6000828210156114625761146261143a565b500390565b6000821982111561147a5761147a61143a565b50019056fea2646970667358221220a19885a55e8b0c417655f6a05466c45748cc67f3b8912f000ff31a4443743de164736f6c634300080c0033000000000000000000000000cb7ec73fd2bca2596794804a6ef5475d0dc4f42700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000ab87046fbb341d058f17cbc4c1133f25a20a52f00000000000000000000000064aa3364f17a4d01c6f1751fd97c2bd3d7e7f1d500000000000000000000000004906695d6d12cf5459975d7c3c03356e4ccd460

Deployed Bytecode

0x6080604052600436106102305760003560e01c8063a41a964a1161012e578063c5936954116100ab578063d425f3cf1161006f578063d425f3cf1461077b578063dd55ebdc146107a8578063de8f21db146107d5578063e3d9a96d146107e8578063f0f442601461081557600080fd5b8063c5936954146106c1578063c67596f2146106e1578063c6aa2fb614610719578063c83dd23114610746578063cf6344391461075b57600080fd5b8063af91e8c9116100f2578063af91e8c91461063b578063b82263ca1461065b578063b98a44521461066e578063c188e3951461069b578063c4dd9ab7146106ae57600080fd5b8063a41a964a1461053d578063a51ffa4c14610571578063a88e6ce9146105ba578063aaa91a9f146105cd578063ac6390a41461060557600080fd5b80635f90e51a116101bc578063809a9f2f11610180578063809a9f2f1461048657806384f6f617146104a65780638bbdc1dd146104c55780638eb346c1146104d857806390135fe41461051057600080fd5b80635f90e51a146103b05780636dc05dbc146103d05780636e6e13ac146103f0578063704b6c02146104285780637823d3151461044857600080fd5b80633bf95ee8116102035780633bf95ee8146102ea578063474361331461032c5780634d32ce2c1461033f5780634d9e7059146103525780635b8540611461037257600080fd5b80630c424284146102355780630f7c4bfc1461025757806314afd79e146102945780632157e77e146102ca575b600080fd5b34801561024157600080fd5b50610255610250366004612604565b610835565b005b34801561026357600080fd5b5061027761027236600461269a565b610931565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156102a057600080fd5b506102776102af36600461277c565b6007602052600090815260409020546001600160a01b031681565b3480156102d657600080fd5b506102776102e53660046127ab565b610c52565b3480156102f657600080fd5b5061031e7f0a52f6e0133eadd055cc5703844e676242c3b461d85fb7ce7f74becd7e40edd181565b60405190815260200161028b565b61025561033a36600461289c565b611077565b61025561034d36600461289c565b611307565b34801561035e57600080fd5b5061025561036d36600461291d565b61156b565b34801561037e57600080fd5b5061031e61038d3660046129a0565b601060209081526000938452604080852082529284528284209052825290205481565b3480156103bc57600080fd5b506102556103cb3660046129e1565b611634565b3480156103dc57600080fd5b506102556103eb36600461277c565b61168a565b3480156103fc57600080fd5b5061031e61040b3660046129e1565b601360209081526000928352604080842090915290825290205481565b34801561043457600080fd5b5061025561044336600461277c565b61171c565b34801561045457600080fd5b5061031e6104633660046129a0565b601160209081526000938452604080852082529284528284209052825290205481565b34801561049257600080fd5b506102556104a1366004612a0d565b6117ae565b3480156104b257600080fd5b506102776104c1366004612a46565b5490565b6102556104d336600461289c565b6118a5565b3480156104e457600080fd5b5061031e6104f3366004612a0d565b600b60209081526000928352604080842090915290825290205481565b34801561051c57600080fd5b5061031e61052b36600461277c565b60056020526000908152604090205481565b34801561054957600080fd5b5061031e7ff0f6f256599682b9387f45fc268ed696625f835d98d64b8967134239e103fc6c81565b34801561057d57600080fd5b506105a561058c36600461277c565b6009602052600090815260409020805460019091015482565b6040805192835260208301919091520161028b565b6102556105c8366004612a70565b611b09565b3480156105d957600080fd5b5061031e6105e83660046129e1565b601260209081526000928352604080842090915290825290205481565b34801561061157600080fd5b5061027761062036600461277c565b6008602052600090815260409020546001600160a01b031681565b34801561064757600080fd5b50610255610656366004612b1a565b611bd4565b61025561066936600461289c565b611ce0565b34801561067a57600080fd5b5061031e61068936600461277c565b600e6020526000908152604090205481565b6102556106a9366004612a70565b611f6c565b6102556106bc366004612b64565b611fd4565b3480156106cd57600080fd5b506102556106dc366004612a0d565b6120a2565b3480156106ed57600080fd5b5061031e6106fc366004612a0d565b600c60209081526000928352604080842090915290825290205481565b34801561072557600080fd5b5061031e61073436600461277c565b60066020526000908152604090205481565b34801561075257600080fd5b50610277612176565b34801561076757600080fd5b50610255610776366004612a0d565b612186565b34801561078757600080fd5b5061031e61079636600461277c565b600d6020526000908152604090205481565b3480156107b457600080fd5b5061031e6107c336600461277c565b600f6020526000908152604090205481565b6102556107e3366004612b64565b61225a565b3480156107f457600080fd5b5061031e61080336600461277c565b600a6020526000908152604090205481565b34801561082157600080fd5b5061025561083036600461277c565b6122c4565b6001600160a01b0382811660009081526007602052604090205416331461086f57604051630862a88160e21b815260040160405180910390fd5b600080546040516001600160a01b03858116602483015284151560448301529091169060640160408051601f198184030181529181526020820180516001600160e01b031663031090a160e21b179052516108ca9190612c19565b600060405180830381855af49150503d8060008114610905576040519150601f19603f3d011682016040523d82523d6000602084013e61090a565b606091505b505090508061092c57604051637f77015160e01b815260040160405180910390fd5b505050565b600061093b612176565b6001600160a01b031663c8c43967338e8e8e8e8e8e6040518863ffffffff1660e01b81526004016109729796959493929190612c7d565b6020604051808303816000875af1158015610991573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109b59190612cd8565b6001600160a01b038116600090815260076020526040902080546001600160a01b03191633179055905085156109eb57856109ef565b6000195b6001600160a01b0382166000908152600f602052604090205581610a14576000610a17565b60015b6001600160a01b03828116600081815260066020908152604080832060ff96909616909555600890529290922080546001600160a01b0319169186169190911790557f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f90610a8490612356565b80516020918201206040805192830193909352918101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201526001600160a01b03821660a082015260c00160408051601f1981840301815291815281516020928301206001600160a01b03841660009081526005909352912055610b14846123b2565b15610b47576001600160a01b03811660009081526009602090815260409091208535815590850135600182015550610b83565b60408051808201825260038152606460208083019182526001600160a01b03851660009081526009909152929092209051815590516001909101555b8415610c435760008054604051602481018890526001600160a01b0384811660448301528392169060640160408051601f198184030181529181526020820180516001600160e01b0316630bc2051f60e41b17905251610be39190612c19565b600060405180830381855af49150503d8060008114610c1e576040519150601f19603f3d011682016040523d82523d6000602084013e610c23565b606091505b509150915081610c40578051610c3857600080fd5b805181602001fd5b50505b9b9a5050505050505050505050565b6000610c616020860186612cf5565b9050610c6d8680612cf5565b905014610c8d5760405163161b1f5b60e01b815260040160405180910390fd5b610c95612176565b6001600160a01b03166360596ba0338d8d8d8d8d8d6040518863ffffffff1660e01b8152600401610ccc9796959493929190612c7d565b6020604051808303816000875af1158015610ceb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d0f9190612cd8565b6001600160a01b038116600090815260076020526040902080546001600160a01b03191633179055905081610d45576000610d48565b60015b6001600160a01b03828116600090815260066020908152604080832060ff959095169094556008905291822080546001600160a01b0319169186169190911790555b610d976020870187612cf5565b9050811015610e5457610daa8680612cf5565b82818110610dba57610dba612d3f565b90506020020135600014610dee57610dd28680612cf5565b82818110610de257610de2612d3f565b90506020020135610df2565b6000195b6001600160a01b038316600090815260136020908152604082209190610e1a908a018a612cf5565b85818110610e2a57610e2a612d3f565b9050602002013581526020019081526020016000208190555080610e4d90612d6b565b9050610d8a565b7f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f610e87836001600160a01b0316612356565b80516020918201206040805192830193909352918101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201526001600160a01b03831660a082015260c00160408051601f1981840301815291815281516020928301206001600160a01b03851660009081526005909352912055610f17856123b2565b15610f4a576001600160a01b03821660009081526009602090815260409091208635815590860135600182015550610f86565b60408051808201825260038152606460208083019182526001600160a01b03861660009081526009909152929092209051815590516001909101555b6000610f956040880188612cf5565b90501115611068576000805481906001600160a01b0316610fb960208a018a612cf5565b610fc660408c018c612cf5565b88604051602401610fdb959493929190612dbc565b60408051601f198184030181529181526020820180516001600160e01b031663b5afec9560e01b179052516110109190612c19565b600060405180830381855af49150503d806000811461104b576040519150601f19603f3d011682016040523d82523d6000602084013e611050565b606091505b509150915081611065578051610c3857600080fd5b50505b509a9950505050505050505050565b60005b61108760a0830183612cf5565b9050811015611303576000805481906001600160a01b03166110a98580612cf5565b858181106110b9576110b9612d3f565b90506020028101906110cb9190612cf5565b6110d86020880188612cf5565b878181106110e8576110e8612d3f565b905060200201358780604001906110ff9190612cf5565b8881811061110f5761110f612d3f565b905060200201358880606001906111269190612cf5565b8981811061113657611136612d3f565b9050602002013589806080019061114d9190612cf5565b8a81811061115d5761115d612d3f565b905060200201358a8060a001906111749190612cf5565b8b81811061118457611184612d3f565b9050602002016020810190611199919061277c565b6111a660c08d018d612cf5565b8c8181106111b6576111b6612d3f565b90506020020160208101906111cb919061277c565b6111d860e08e018e612cf5565b8d8181106111e8576111e8612d3f565b90506020020160208101906111fd9190612dfe565b61120b6101008f018f612cf5565b8e81811061121b5761121b612d3f565b905060200201358e8061012001906112339190612cf5565b8f81811061124357611243612d3f565b905060200201356040516024016112649b9a99989796959493929190612e19565b60408051601f198184030181529181526020820180516001600160e01b031663de8f21db60e01b179052516112999190612c19565b600060405180830381855af49150503d80600081146112d4576040519150601f19603f3d011682016040523d82523d6000602084013e6112d9565b606091505b5091509150816112ee578051610c3857600080fd5b505080806112fb90612d6b565b91505061107a565b5050565b60005b61131760a0830183612cf5565b9050811015611303576000805481906001600160a01b03166113398580612cf5565b8581811061134957611349612d3f565b905060200281019061135b9190612cf5565b6113686020880188612cf5565b8781811061137857611378612d3f565b9050602002013587806040019061138f9190612cf5565b8881811061139f5761139f612d3f565b905060200201358880606001906113b69190612cf5565b898181106113c6576113c6612d3f565b90506020020135898060a001906113dd9190612cf5565b8a8181106113ed576113ed612d3f565b9050602002016020810190611402919061277c565b61140f60c08c018c612cf5565b8b81811061141f5761141f612d3f565b9050602002016020810190611434919061277c565b61144160e08d018d612cf5565b8c81811061145157611451612d3f565b90506020020160208101906114669190612dfe565b6114746101008e018e612cf5565b8d81811061148457611484612d3f565b905060200201358d80610120019061149c9190612cf5565b8e8181106114ac576114ac612d3f565b905060200201356040516024016114cc9a99989796959493929190612e82565b60408051601f198184030181529181526020820180516001600160e01b031663a88e6ce960e01b179052516115019190612c19565b600060405180830381855af49150503d806000811461153c576040519150601f19603f3d011682016040523d82523d6000602084013e611541565b606091505b509150915081611556578051610c3857600080fd5b5050808061156390612d6b565b91505061130a565b6001600160a01b038581166000908152600760205260409020541633146115a557604051630862a88160e21b815260040160405180910390fd5b60005b8381101561162c578282828181106115c2576115c2612d3f565b9050602002013560136000886001600160a01b03166001600160a01b03168152602001908152602001600020600087878581811061160257611602612d3f565b905060200201358152602001908152602001600020819055508061162590612d6b565b90506115a8565b505050505050565b6001600160a01b0382811660009081526007602052604090205416331461166e57604051630862a88160e21b815260040160405180910390fd5b6001600160a01b039091166000908152600f6020526040902055565b6003546001600160a01b031633146116b557604051630862a88160e21b815260040160405180910390fd5b336116d357604051633c0a853160e11b815260040160405180910390fd5b6001600160a01b0381166116fa57604051637a381aaf60e01b815260040160405180910390fd5b600480546001600160a01b0319166001600160a01b0392909216919091179055565b6003546001600160a01b0316331461174757604051630862a88160e21b815260040160405180910390fd5b3361176557604051633c0a853160e11b815260040160405180910390fd5b6001600160a01b03811661178c5760405163035745df60e51b815260040160405180910390fd5b600380546001600160a01b0319166001600160a01b0392909216919091179055565b6001600160a01b038281166000908152600760205260409020541633146117e857604051630862a88160e21b815260040160405180910390fd5b600080546040516001600160a01b03858116602483015284811660448301529091169060640160408051601f198184030181529181526020820180516001600160e01b031663809a9f2f60e01b179052516118439190612c19565b600060405180830381855af49150503d806000811461187e576040519150601f19603f3d011682016040523d82523d6000602084013e611883565b606091505b505090508061092c5760405163786fb38760e01b815260040160405180910390fd5b60005b6118b560a0830183612cf5565b9050811015611303576000805481906001600160a01b03166118d78580612cf5565b858181106118e7576118e7612d3f565b90506020028101906118f99190612cf5565b6119066020880188612cf5565b8781811061191657611916612d3f565b9050602002013587806040019061192d9190612cf5565b8881811061193d5761193d612d3f565b905060200201358880606001906119549190612cf5565b8981811061196457611964612d3f565b90506020020135898060a0019061197b9190612cf5565b8a81811061198b5761198b612d3f565b90506020020160208101906119a0919061277c565b6119ad60c08c018c612cf5565b8b8181106119bd576119bd612d3f565b90506020020160208101906119d2919061277c565b6119df60e08d018d612cf5565b8c8181106119ef576119ef612d3f565b9050602002016020810190611a049190612dfe565b611a126101008e018e612cf5565b8d818110611a2257611a22612d3f565b905060200201358d806101200190611a3a9190612cf5565b8e818110611a4a57611a4a612d3f565b90506020020135604051602401611a6a9a99989796959493929190612e82565b60408051601f198184030181529181526020820180516001600160e01b031663c188e39560e01b17905251611a9f9190612c19565b600060405180830381855af49150503d8060008114611ada576040519150601f19603f3d011682016040523d82523d6000602084013e611adf565b606091505b509150915081611af4578051610c3857600080fd5b50508080611b0190612d6b565b9150506118a8565b6000805460405182916001600160a01b031690611b3c908e908e908e908e908e908e908e908e908e908e90602401612e82565b60408051601f198184030181529181526020820180516001600160e01b031663a88e6ce960e01b17905251611b719190612c19565b600060405180830381855af49150503d8060008114611bac576040519150601f19603f3d011682016040523d82523d6000602084013e611bb1565b606091505b509150915081611bc6578051610c3857600080fd5b505050505050505050505050565b6001600160a01b03828116600090815260076020526040902054163314611c0e57604051630862a88160e21b815260040160405180910390fd5b6000805460405160248101879052604481018690526001600160a01b03858116606483015284811660848301529091169060a40160408051601f198184030181529181526020820180516001600160e01b031663af91e8c960e01b17905251611c779190612c19565b600060405180830381855af49150503d8060008114611cb2576040519150601f19603f3d011682016040523d82523d6000602084013e611cb7565b606091505b5050905080611cd95760405163786fb38760e01b815260040160405180910390fd5b5050505050565b60005b611cf060a0830183612cf5565b9050811015611303576000805481906001600160a01b0316611d128580612cf5565b85818110611d2257611d22612d3f565b9050602002810190611d349190612cf5565b611d416020880188612cf5565b87818110611d5157611d51612d3f565b90506020020135878060400190611d689190612cf5565b88818110611d7857611d78612d3f565b90506020020135888060600190611d8f9190612cf5565b89818110611d9f57611d9f612d3f565b90506020020135898060800190611db69190612cf5565b8a818110611dc657611dc6612d3f565b905060200201358a8060a00190611ddd9190612cf5565b8b818110611ded57611ded612d3f565b9050602002016020810190611e02919061277c565b611e0f60c08d018d612cf5565b8c818110611e1f57611e1f612d3f565b9050602002016020810190611e34919061277c565b611e4160e08e018e612cf5565b8d818110611e5157611e51612d3f565b9050602002016020810190611e669190612dfe565b611e746101008f018f612cf5565b8e818110611e8457611e84612d3f565b905060200201358e806101200190611e9c9190612cf5565b8f818110611eac57611eac612d3f565b90506020020135604051602401611ecd9b9a99989796959493929190612e19565b60408051601f198184030181529181526020820180516001600160e01b031663c4dd9ab760e01b17905251611f029190612c19565b600060405180830381855af49150503d8060008114611f3d576040519150601f19603f3d011682016040523d82523d6000602084013e611f42565b606091505b509150915081611f57578051610c3857600080fd5b50508080611f6490612d6b565b915050611ce3565b6000805460405182916001600160a01b031690611f9f908e908e908e908e908e908e908e908e908e908e90602401612e82565b60408051601f198184030181529181526020820180516001600160e01b031663c188e39560e01b17905251611b719190612c19565b6000805460405182916001600160a01b031690612009908f908f908f908f908f908f908f908f908f908f908f90602401612e19565b60408051601f198184030181529181526020820180516001600160e01b031663c4dd9ab760e01b1790525161203e9190612c19565b600060405180830381855af49150503d8060008114612079576040519150601f19603f3d011682016040523d82523d6000602084013e61207e565b606091505b509150915081612093578051610c3857600080fd5b50505050505050505050505050565b6001600160a01b0382166120c957604051637a381aaf60e01b815260040160405180910390fd5b6001600160a01b0381166120f057604051630572b44f60e41b815260040160405180910390fd5b3361210e57604051633c0a853160e11b815260040160405180910390fd5b6001600160a01b0382811660009081526007602052604090205416331461214857604051630862a88160e21b815260040160405180910390fd5b6001600160a01b03918216600090815260086020526040902080546001600160a01b03191691909216179055565b600061218160015490565b905090565b6001600160a01b0382166121ad57604051637a381aaf60e01b815260040160405180910390fd5b6001600160a01b0381166121d457604051630b07403f60e21b815260040160405180910390fd5b336121f257604051633c0a853160e11b815260040160405180910390fd5b6001600160a01b0382811660009081526007602052604090205416331461222c57604051630862a88160e21b815260040160405180910390fd5b6001600160a01b03918216600090815260076020526040902080546001600160a01b03191691909216179055565b6000805460405182916001600160a01b03169061228f908f908f908f908f908f908f908f908f908f908f908f90602401612e19565b60408051601f198184030181529181526020820180516001600160e01b031663de8f21db60e01b1790525161203e9190612c19565b6003546001600160a01b031633146122ef57604051630862a88160e21b815260040160405180910390fd5b3361230d57604051633c0a853160e11b815260040160405180910390fd5b6001600160a01b038116612334576040516322f85dbf60e01b815260040160405180910390fd5b600280546001600160a01b0319166001600160a01b0392909216919091179055565b60608161237d5750506040805180820190915260048152630307830360e41b602082015290565b8160005b81156123a0578061239181612d6b565b915050600882901c9150612381565b6123aa8482612425565b949350505050565b60006020820135823511156123c957506000919050565b813515806123d957506020820135155b156123e657506000919050565b60006123f483356064612ee6565b9050600061240760208501356003612ee6565b90508082101561241b575060009392505050565b5060019392505050565b60606000612434836002612ee6565b61243f906002612f05565b67ffffffffffffffff81111561245757612457612f1d565b6040519080825280601f01601f191660200182016040528015612481576020820181803683370190505b509050600360fc1b8160008151811061249c5761249c612d3f565b60200101906001600160f81b031916908160001a905350600f60fb1b816001815181106124cb576124cb612d3f565b60200101906001600160f81b031916908160001a90535060006124ef846002612ee6565b6124fa906001612f05565b90505b6001811115612572576f181899199a1a9b1b9c1cb0b131b232b360811b85600f166010811061252e5761252e612d3f565b1a60f81b82828151811061254457612544612d3f565b60200101906001600160f81b031916908160001a90535060049490941c9361256b81612f33565b90506124fd565b5083156125c55760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e74604482015260640160405180910390fd5b9392505050565b6001600160a01b03811681146125e157600080fd5b50565b80356125ef816125cc565b919050565b803580151581146125ef57600080fd5b6000806040838503121561261757600080fd5b8235612622816125cc565b9150612630602084016125f4565b90509250929050565b60008083601f84011261264b57600080fd5b50813567ffffffffffffffff81111561266357600080fd5b60208301915083602082850101111561267b57600080fd5b9250929050565b60006040828403121561269457600080fd5b50919050565b60008060008060008060008060008060006101208c8e0312156126bc57600080fd5b67ffffffffffffffff808d3511156126d357600080fd5b6126e08e8e358f01612639565b909c509a5060208d01358110156126f657600080fd5b6127068e60208f01358f01612639565b909a50985060408d013581101561271c57600080fd5b5061272d8d60408e01358e01612639565b909750955060608c0135945060808c0135935061274d8d60a08e01612682565b925061275b60e08d016125e4565b915061276a6101008d016125f4565b90509295989b509295989b9093969950565b60006020828403121561278e57600080fd5b81356125c5816125cc565b60006060828403121561269457600080fd5b6000806000806000806000806000806101008b8d0312156127cb57600080fd5b8a3567ffffffffffffffff808211156127e357600080fd5b6127ef8e838f01612639565b909c509a5060208d013591508082111561280857600080fd5b6128148e838f01612639565b909a50985060408d013591508082111561282d57600080fd5b6128398e838f01612639565b909850965060608d013591508082111561285257600080fd5b5061285f8d828e01612799565b94505061286f8c60808d01612682565b925061287d60c08c016125e4565b915061288b60e08c016125f4565b90509295989b9194979a5092959850565b6000602082840312156128ae57600080fd5b813567ffffffffffffffff8111156128c557600080fd5b820161014081850312156125c557600080fd5b60008083601f8401126128ea57600080fd5b50813567ffffffffffffffff81111561290257600080fd5b6020830191508360208260051b850101111561267b57600080fd5b60008060008060006060868803121561293557600080fd5b8535612940816125cc565b9450602086013567ffffffffffffffff8082111561295d57600080fd5b61296989838a016128d8565b9096509450604088013591508082111561298257600080fd5b5061298f888289016128d8565b969995985093965092949392505050565b6000806000606084860312156129b557600080fd5b83356129c0816125cc565b925060208401356129d0816125cc565b929592945050506040919091013590565b600080604083850312156129f457600080fd5b82356129ff816125cc565b946020939093013593505050565b60008060408385031215612a2057600080fd5b8235612a2b816125cc565b91506020830135612a3b816125cc565b809150509250929050565b600060208284031215612a5857600080fd5b5035919050565b803560ff811681146125ef57600080fd5b6000806000806000806000806000806101208b8d031215612a9057600080fd5b8a3567ffffffffffffffff811115612aa757600080fd5b612ab38d828e016128d8565b909b5099505060208b0135975060408b0135965060608b0135955060808b0135612adc816125cc565b945060a08b0135612aec816125cc565b9350612afa60c08c01612a5f565b925060e08b013591506101008b013590509295989b9194979a5092959850565b60008060008060808587031215612b3057600080fd5b84359350602085013592506040850135612b49816125cc565b91506060850135612b59816125cc565b939692955090935050565b60008060008060008060008060008060006101408c8e031215612b8657600080fd5b8b3567ffffffffffffffff811115612b9d57600080fd5b612ba98e828f016128d8565b909c509a505060208c0135985060408c0135975060608c0135965060808c0135955060a08c0135612bd9816125cc565b945060c08c0135612be9816125cc565b9350612bf760e08d01612a5f565b92506101008c013591506101208c013590509295989b509295989b9093969950565b6000825160005b81811015612c3a5760208186018101518583015201612c20565b81811115612c49576000828501525b509190910192915050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6001600160a01b0388168152608060208201819052600090612ca2908301888a612c54565b8281036040840152612cb5818789612c54565b90508281036060840152612cca818587612c54565b9a9950505050505050505050565b600060208284031215612cea57600080fd5b81516125c5816125cc565b6000808335601e19843603018112612d0c57600080fd5b83018035915067ffffffffffffffff821115612d2757600080fd5b6020019150600581901b360382131561267b57600080fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b6000600019821415612d7f57612d7f612d55565b5060010190565b81835260006001600160fb1b03831115612d9f57600080fd5b8260051b8083602087013760009401602001938452509192915050565b606081526000612dd0606083018789612d86565b8281036020840152612de3818688612d86565b91505060018060a01b03831660408301529695505050505050565b600060208284031215612e1057600080fd5b6125c582612a5f565b61014081526000612e2f61014083018d8f612d86565b602083019b909b52506040810198909852606088019690965260808701949094526001600160a01b0392831660a0870152911660c085015260ff1660e08401526101008301526101209091015292915050565b6000610120808352612e978184018d8f612d86565b602084019b909b525050604081019790975260608701959095526001600160a01b0393841660808701529190921660a085015260ff90911660c084015260e08301526101009091015292915050565b6000816000190483118215151615612f0057612f00612d55565b500290565b60008219821115612f1857612f18612d55565b500190565b634e487b7160e01b600052604160045260246000fd5b600081612f4257612f42612d55565b50600019019056fea264697066735822122023ed71495e38f66a1d455443cde842982c3b06bad6b09689e2facfe108ef187364736f6c634300080c0033

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

000000000000000000000000cb7ec73fd2bca2596794804a6ef5475d0dc4f42700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000ab87046fbb341d058f17cbc4c1133f25a20a52f00000000000000000000000064aa3364f17a4d01c6f1751fd97c2bd3d7e7f1d500000000000000000000000004906695d6d12cf5459975d7c3c03356e4ccd460

-----Decoded View---------------
Arg [0] : treasury_ (address): 0xCB7ec73Fd2bca2596794804a6eF5475d0DC4F427
Arg [1] : xpDirectory_ (address): 0x0000000000000000000000000000000000000000
Arg [2] : xp_ (address): 0x0000000000000000000000000000000000000000
Arg [3] : ohmCurrencies_ (address[]): 0x0ab87046fBb341D058F17CBC4c1133F25a20a52f,0x64aa3364F17a4D01c6f1751Fd97C2BD3D7e7f1D5,0x04906695D6D12CF5459975d7C3C03356E4Ccd460

-----Encoded View---------------
8 Constructor Arguments found :
Arg [0] : 000000000000000000000000cb7ec73fd2bca2596794804a6ef5475d0dc4f427
Arg [1] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [2] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [3] : 0000000000000000000000000000000000000000000000000000000000000080
Arg [4] : 0000000000000000000000000000000000000000000000000000000000000003
Arg [5] : 0000000000000000000000000ab87046fbb341d058f17cbc4c1133f25a20a52f
Arg [6] : 00000000000000000000000064aa3364f17a4d01c6f1751fd97c2bd3d7e7f1d5
Arg [7] : 00000000000000000000000004906695d6d12cf5459975d7c3c03356e4ccd460


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

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

Validator Index Block Amount
View All Withdrawals

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

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