ETH Price: $3,388.38 (+1.26%)

TetraSpektra (TETRA)
 

Overview

TokenID

49

Total Transfers

-

Market

Onchain Market Cap

$0.00

Circulating Supply Market Cap

-

Other Info

Loading...
Loading
Loading...
Loading
Loading...
Loading

Click here to update the token information / general information
# Exchange Pair Price  24H Volume % Volume
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.

Contract Source Code Verified (Exact Match)

Contract Name:
TetraSpektra

Compiler Version
v0.8.23+commit.f704f362

Optimization Enabled:
Yes with 1000 runs

Other Settings:
paris EvmVersion
File 1 of 52 : TetraSpectra.sol
// SPDX-License-Identifier: MIT

/******* TetraSpektra.sol ******
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 * ░░█████████░░░███████████░░ *
 * ░░██░░░░░░░░░░░░░░░░░░░██░░ *
 * ░░██░░░██████░░░████░░░██░░ *
 * ░░██░░░██░░░░░░░░░██░░░██░░ *
 * ░░██░░░░░░░▒▒░██░░██░░░░░░░ *
 * ░░░░░░░██░░▓▓░▒▒░░░░░░░██░░ *
 * ░░██░░░██░░░░░░░░░██░░░██░░ *
 * ░░██░░░████░░░██████░░░██░░ *
 * ░░██░░░░░░░░░░░░░░░░░░░██░░ *
 * ░░███████████░░░█████████░░ *
 * ░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
 ******************************/

/*
  ___                           _            _    _   
 / _ \__  __/\   /\___ _ __ ___| |_ ___  ___| | _| |_ 
| | | \ \/ /\ \ / / _ \ '__/ __| __/ _ \/ __| |/ / __|
| |_| |>  <  \ V /  __/ |  \__ \ ||  __/ (__|   <| |_ 
 \___//_/\_\  \_/ \___|_|  |___/\__\___|\___|_|\_\\__|
 
 */

pragma solidity >=0.7.0 <0.9.0;

import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";

import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import {Excavator} from "./Excavator.sol";
import "./interfaces/ISwapRouter.sol";
import "./interfaces/IClaimsManager.sol";
import "./interfaces/IRandomizer.sol";
import "./interfaces/IPriceOracle.sol";
import "./interfaces/IBountyVault.sol";
import "./interfaces/IWETH.sol";
import "./interfaces/IProspektorFundManager.sol";

import "./utils/TransferHelper.sol";
import "./utils/RenderHelper.sol";
import "./utils/ExecutorManager.sol";

enum CreatorBountyTier {
    None,
    Low,
    Medium,
    High,
    Ultra
}

enum BountyTier {
    Silver,
    Gold,
    Platinum
}

enum DiscoveryType {
    Prospekted,
    Reforged,
    Refined
}

struct BountySwapConfig {
    uint256 tokenCount;
    uint256 innerCount;
    uint256 outerCount;
}

struct TetraSpektraConfig {
    uint256 maxSupply;
    uint256 prospektPrice;
    uint256 refreshPrice;
    uint256 flashBountyValue;
    uint8 excavatorFundingPercentage;
    uint16 maxSilver2Swaps;
    uint8 silver2UnlockCadence;
    uint8 silver2UnlockAmount;
    string[] colorHexCodes;
    string[] colorNames;
}

struct AddressConfig {
    address randomizerAddress;
    address claimsManagerAddress;
    address priceOracleAddress;
    address bountyVaultAddress;
    address uniswapRouterAddress;
}

struct ColorConfig {
    string[] colorHexCodes;
    string[] colorNames;
    uint8 maxColors;
}

struct ExcavatorConfig {
    address vrfCoordinatorAddress;
    bytes32 vrfKeyHash;
    address linkTokenAddress;
}

struct TokenConfig {
    address wethTokenAddress;
    address porkTokenAddress;
}

/**
 * @title TetraSpektra
 * @author 0xVersteckt
 * @notice Hello prospektor...
 */
contract TetraSpektra is
    ERC721,
    IERC721Receiver,
    ExecutorManager,
    Pausable,
    ReentrancyGuard
{
    error InsufficientPayment();
    error NotSoldOut();
    error InvalidTokenOwner();
    error InvalidToken();
    error NotMatched();
    error InvalidTokenCompare();
    error TooManySilver2Swaps();
    error InvalidTokenCount();
    error ExcavationInProgress();
    error ExcavationGroupNotReady();

    event Swapped(
        address indexed prospektor,
        uint256 indexed tier,
        uint256 bountyValue,
        uint256 tokenId_0,
        uint256 tokenId_1,
        uint256 tokenId_2,
        uint256 tokenId_3
    );

    event Discovery(
        address indexed prospektor,
        uint256 indexed tokenId,
        DiscoveryType discoveryType
    );

    event Excavated(
        address indexed prospektor,
        uint256 indexed tokenId,
        bytes32 silverTetraKey,
        bytes32 goldTetraKey,
        bytes32 platinumTetraKey
    );

    event AddedLiquidityForBounty(
        address indexed prospektor,
        uint256 interactionCount,
        uint256 porkAmount,
        uint256 wethAmount,
        uint256 canAddLiquidityAfter
    );

    event CollectedFeesForBounty(
        address indexed prospektor,
        uint256 interactionCount,
        uint256 porkAmount,
        uint256 canCollectFeesAfter
    );

    event ExcavationComplete(
        uint256 indexed excavationId,
        uint256 indexed value
    );

    event ExcavationContracted(
        uint256 excavationId,
        uint256 tokenId_0,
        uint256 tokenId_1,
        uint256 tokenId_2,
        uint256 tokenId_3,
        uint256 tokenId_4
    );

    event ExcavatedFlashBounty(
        bytes32 silverTetraKey,
        bytes32 goldTetraKey,
        bytes32 platinumTetraKey
    );

    event ClaimedFlashBounty(
        address indexed prospektor,
        uint256 indexed tokenId,
        bytes32 tetraKey,
        uint256 bountyValue
    );

    event Claimed(
        address indexed prospektor,
        uint256 claimsSessionId,
        uint256 claimedTokenId,
        uint256 tokenId
    );

    IProspektorFundManager public prospektorFundManager;
    IClaimsManager public claimsManager;
    IRandomizer public randomizer;
    IPriceOracle public priceOracle;
    IBountyVault public bountyVault;
    Excavator public excavator;

    IUniswapV3Pool public uniswapPool;
    ISwapRouter public uniswapRouter;

    address public immutable WETH;
    address public immutable PORK;

    uint256 public immutable maxSupply;

    /// @notice Used to prevent underflow
    uint256 public constant MIN_WETH_AMOUNT = 1_000_000_000_000_000; // .001 ether
    uint256 public constant MIN_PORK_AMOUNT = 100_000_000;

    bytes32 public immutable BASE_TETRA_KEY = getTetraKey(0, BountyTier.Silver);
    uint24 public constant PORK_WETH_POOL_FEE = 10_000;
    uint24 public constant DEFAULT_POOL_FEE = 3_000;
    uint8 public constant MAX_TO_PROSPEKT = 5;
    uint8 private constant CADENCE = 5;
    uint8 public constant SWAP_PERCENTAGE_INCREMENT = 8;

    uint8 public fundingPercentage;

    uint16 public maxSilver2Swaps;
    uint8 public silver2UnlockCadence;
    uint8 public silver2UnlockAmount;

    uint256 public silver2Swaps = 0;

    uint256 public canCollectFeesAfter = 20;
    uint256 public feeCollectorBounty = 20_000_000_000_000_000;
    uint256 public feeCollectorCadence = 20;

    uint256 public canAddLiquidityAfter = 10;
    uint256 public liquidityAdderBounty = 25_000_000_000_000_000;
    uint256 public liquidityAdderCadence = 12;

    uint256 private _prospektCount = 1;
    uint256 public interactionCount = 0;
    uint256 public swapPercentage = 55;
    uint256 public prospektPrice;
    uint256 public refreshPrice;

    uint256 public refreshCount = 0;

    bool public isInitialized;

    uint256 public flashBountyValue;
    bool public flashBountyEnabled;

    mapping(uint256 => uint256) private _tetraSeeds;
    mapping(uint256 => uint256) private _excavationResults;

    mapping(uint256 => uint256) public tetraExcavationIds;
    mapping(uint256 => uint256) public excavationTracker;

    uint256 public excavationGroup;

    uint256[3][3] private _bounties;

    ColorConfig public colorConfig;

    uint24[5][2] private _priceHistory;
    uint8 private _priceHistoryIndex;

    constructor(
        AddressConfig memory addressConfig,
        TokenConfig memory tokenConfig,
        ExcavatorConfig memory excavatorConfig,
        TetraSpektraConfig memory tetraSpektraConfig,
        uint256[3][3] memory bounties
    ) payable ExecutorManager(msg.sender) ERC721("TetraSpektra", "TETRA") {
        require(
            tetraSpektraConfig.colorHexCodes.length ==
                tetraSpektraConfig.colorNames.length,
            "0"
        );
        claimsManager = IClaimsManager(addressConfig.claimsManagerAddress);
        randomizer = IRandomizer(addressConfig.randomizerAddress);
        priceOracle = IPriceOracle(addressConfig.priceOracleAddress);
        bountyVault = IBountyVault(addressConfig.bountyVaultAddress);
        uniswapRouter = ISwapRouter(addressConfig.uniswapRouterAddress);

        WETH = tokenConfig.wethTokenAddress;
        PORK = tokenConfig.porkTokenAddress;

        maxSupply = tetraSpektraConfig.maxSupply;
        prospektPrice = tetraSpektraConfig.prospektPrice;
        refreshPrice = tetraSpektraConfig.refreshPrice;

        flashBountyValue = tetraSpektraConfig.flashBountyValue == 0
            ? _percentage(tetraSpektraConfig.prospektPrice, 70) * 10
            : tetraSpektraConfig.flashBountyValue;

        fundingPercentage = tetraSpektraConfig.excavatorFundingPercentage;

        maxSilver2Swaps = tetraSpektraConfig.maxSilver2Swaps;
        silver2UnlockCadence = tetraSpektraConfig.silver2UnlockCadence;
        silver2UnlockAmount = tetraSpektraConfig.silver2UnlockAmount;

        colorConfig = ColorConfig({
            colorHexCodes: tetraSpektraConfig.colorHexCodes,
            colorNames: tetraSpektraConfig.colorNames,
            maxColors: uint8(tetraSpektraConfig.colorNames.length)
        });

        excavator = new Excavator(
            msg.sender,
            address(this),
            address(uniswapRouter),
            excavatorConfig.vrfCoordinatorAddress,
            excavatorConfig.vrfKeyHash,
            WETH,
            excavatorConfig.linkTokenAddress
        );

        TransferHelper.safeApprove(WETH, address(excavator), type(uint256).max);

        IWETH(WETH).deposit{value: _percentage(msg.value, 20)}();
        excavator.initialize{value: _percentage(msg.value, 80)}();

        _bounties = bounties;
    }

    function _unsafeIncrement(uint256 x) private pure returns (uint256) {
        unchecked {
            return x + 1;
        }
    }

    modifier mustOwnToken(address addr, uint256 tokenId) {
        if (ownerOf(tokenId) != addr) revert InvalidTokenOwner();
        _;
    }

    modifier checkPayment(uint256 value) {
        if (msg.value < value) revert InsufficientPayment();
        _;
    }

    modifier mustBeSoldOut() {
        if (totalSupply() != maxSupply) revert NotSoldOut();
        _;
    }

    function getBounties() public view returns (uint256[3][3] memory) {
        return _bounties;
    }

    function totalSupply() public view returns (uint256) {
        return _prospektCount - 1;
    }

    function getTetrasRemaining() public view returns (uint256) {
        return maxSupply - totalSupply();
    }

    function claimVaultBounty(
        uint256 tokenId
    ) public mustOwnToken(msg.sender, tokenId) {
        bountyVault.claimBounty(msg.sender, tokenId);
    }

    function getTotalBounty(
        uint256 tierIndex,
        uint256 countIndex
    ) private view returns (uint256) {
        return
            address(this).balance >= _bounties[tierIndex][countIndex]
                ? _bounties[tierIndex][countIndex]
                : address(this).balance;
    }

    function isFlashBountyEnabled() public view returns (bool) {
        return flashBountyEnabled && isExcavated(0);
    }

    function isExcavated(uint256 tokenId) public view returns (bool) {
        return getTetraKey(tokenId, BountyTier.Silver) != BASE_TETRA_KEY;
    }

    function _percentage(
        uint256 value,
        uint256 percentage
    ) private pure returns (uint256) {
        return (value * percentage) / 100;
    }

    function _fundExcavatorWhenReady() internal {
        if (
            fundingPercentage > 0 &&
            interactionCount > 0 &&
            interactionCount % CADENCE == 0
        ) {
            uint256 fundAmount = (_percentage(
                excavator.getLinkPrice(),
                fundingPercentage
            ) * CADENCE);
            require(
                IERC20(WETH).balanceOf(address(this)) > fundAmount,
                "INVALID_WETH"
            );
            excavator.fundWithWeth(fundAmount);
        }
    }

    function updateExcavatorFundingPercentage(
        uint8 percentage
    ) public onlyExecutor {
        require(percentage >= 0 && percentage <= 100, "0");
        fundingPercentage = percentage;
    }

    function prospekt(
        uint256 count
    )
        external
        payable
        whenNotPaused
        nonReentrant
        checkPayment(prospektPrice * count)
        returns (uint256[MAX_TO_PROSPEKT] memory tokenIds)
    {
        require(count > 0 && count <= MAX_TO_PROSPEKT, "1");
        require(totalSupply() + count <= maxSupply, "2");

        for (uint256 i = 0; i < count; i = _unsafeIncrement(i))
            tokenIds[i] = _prospekt(msg.sender);

        IWETH(WETH).deposit{value: msg.value}();

        _fundExcavatorWhenReady();

        /// @dev Attempt to buffer for liquidity adder
        ///      and fee collector bounty
        uint256 wethToSwap = IERC20(WETH).balanceOf(address(this));
        if (wethToSwap / liquidityAdderBounty == 1)
            wethToSwap -= liquidityAdderBounty;
        if (wethToSwap > 0)
            _swapWethForToken(
                PORK,
                _percentage(wethToSwap, swapPercentage),
                address(this)
            );

        return tokenIds;
    }

    /// @dev User pays to re-excavate and claim tokens owned by contract
    function refine(
        uint256[] memory tokenIds
    )
        external
        payable
        whenNotPaused
        nonReentrant
        checkPayment(refreshPrice * tokenIds.length)
        mustBeSoldOut
    {
        IWETH(WETH).deposit{value: msg.value}();

        for (uint256 i = 0; i < tokenIds.length; i = _unsafeIncrement(i)) {
            if (ownerOf(tokenIds[i]) != address(this))
                revert InvalidTokenOwner();

            _transfer(address(this), msg.sender, tokenIds[i]);

            _processRefresh(DiscoveryType.Refined, tokenIds[i]);
        }

        /// @dev Attempt to buffer for liquidity adder
        ///      and fee collector bounty
        uint256 wethToSwap = IERC20(WETH).balanceOf(address(this));
        if (wethToSwap / liquidityAdderBounty == 1)
            wethToSwap -= liquidityAdderBounty;
        if (wethToSwap > 0)
            _swapWethForToken(
                PORK,
                _percentage(wethToSwap, swapPercentage),
                address(this)
            );
    }

    /// @dev User pays to re-excavate tokens they own
    function reforge(
        uint256[] memory tokenIds
    )
        external
        payable
        whenNotPaused
        nonReentrant
        checkPayment(refreshPrice * tokenIds.length)
        mustBeSoldOut
    {
        IWETH(WETH).deposit{value: msg.value}();

        for (uint256 i = 0; i < tokenIds.length; i = _unsafeIncrement(i)) {
            if (ownerOf(tokenIds[i]) != msg.sender) revert InvalidTokenOwner();

            _resetTetra(tokenIds[i]);

            _processRefresh(DiscoveryType.Reforged, tokenIds[i]);
        }

        /// @dev Attempt to buffer for liquidity adder
        ///      and fee collector bounty
        uint256 wethToSwap = IERC20(WETH).balanceOf(address(this));
        if (wethToSwap / liquidityAdderBounty == 1)
            wethToSwap -= liquidityAdderBounty;
        if (wethToSwap > 0)
            _swapWethForToken(
                PORK,
                _percentage(wethToSwap, swapPercentage),
                address(this)
            );
    }

    /// @dev Swap matching owned tokens for bounty
    function swapForBounty(
        uint256[4] memory tokenIds,
        CreatorBountyTier creatorBountyTier
    ) external whenNotPaused nonReentrant {
        BountySwapConfig memory bountySwapConfig;
        bountySwapConfig.tokenCount = 0;

        uint8[7] memory tetra0 = _getTetra(tokenIds[0]);
        for (uint256 i = 0; i < tokenIds.length; i = _unsafeIncrement(i)) {
            if (tokenIds[i] == 0) break;
            else bountySwapConfig.tokenCount++;
            if (ownerOf(tokenIds[i]) != msg.sender) revert InvalidTokenOwner();

            uint8[7] memory currentTetra = _getTetra(tokenIds[i]);

            if (i < tokenIds.length - 1)
                for (
                    uint256 k = i + 1;
                    k < tokenIds.length;
                    k = _unsafeIncrement(k)
                ) {
                    if (tokenIds[k] == 0) break;
                    if (tokenIds[i] == tokenIds[k])
                        revert InvalidTokenCompare();
                    if (
                        getTetraKey(tokenIds[i], BountyTier.Silver) !=
                        getTetraKey(tokenIds[k], BountyTier.Silver)
                    ) revert NotMatched();
                }
            if (i != 0) {
                if (
                    tetra0[5] != 0 &&
                    currentTetra[5] != 0 &&
                    tetra0[5] == currentTetra[5]
                )
                    bountySwapConfig.innerCount = _unsafeIncrement(
                        bountySwapConfig.innerCount
                    );
                if (
                    /// @notice Cant compare if not excavated
                    tetra0[6] != 0 &&
                    currentTetra[6] != 0 &&
                    tetra0[6] == currentTetra[6]
                )
                    bountySwapConfig.outerCount = _unsafeIncrement(
                        bountySwapConfig.outerCount
                    );
            }
        }
        if (bountySwapConfig.tokenCount < 2) revert InvalidTokenCount();

        uint256 tierIndex = 0;
        /// @notice Check to see if shell_0 matches
        if (bountySwapConfig.innerCount == bountySwapConfig.tokenCount - 1) {
            tierIndex = _unsafeIncrement(tierIndex);

            /// @notice Check to see if shell_1 matches
            if (bountySwapConfig.outerCount == bountySwapConfig.tokenCount - 1)
                tierIndex = _unsafeIncrement(tierIndex);
        }

        /// @notice Limit silver2Swaps
        if (tierIndex == 0 && bountySwapConfig.tokenCount == 2) {
            if (silver2Swaps == maxSilver2Swaps) revert TooManySilver2Swaps();
            silver2Swaps = _unsafeIncrement(silver2Swaps);
        }

        for (
            uint256 i = 0;
            i < bountySwapConfig.tokenCount;
            i = _unsafeIncrement(i)
        ) {
            _resetTetra(tokenIds[i]);
            safeTransferFrom(msg.sender, address(this), tokenIds[i]);
        }

        uint256 totalValueEth = getTotalBounty(
            tierIndex,
            bountySwapConfig.tokenCount - 2
        );

        uint256 bountyValue = priceOracle.getPrice(
            WETH,
            PORK,
            uint128(totalValueEth),
            PORK_WETH_POOL_FEE
        );

        require(
            IERC20(PORK).balanceOf(address(bountyVault)) > bountyValue,
            "NOT_ENOUGH_BALANCE"
        );

        uint256 creatorBountyPercentage = uint256(
            creatorBountyTier == CreatorBountyTier.Low
                ? 5
                : creatorBountyTier == CreatorBountyTier.Medium
                ? 10
                : creatorBountyTier == CreatorBountyTier.High
                ? 20
                : creatorBountyTier == CreatorBountyTier.Ultra
                ? 40
                : 0
        );

        bountyVault.distribute(
            msg.sender,
            _percentage(bountyValue, 100 - creatorBountyPercentage),
            _percentage(bountyValue, creatorBountyPercentage)
        );

        emit Swapped(
            msg.sender,
            tierIndex,
            bountyValue,
            tokenIds[0],
            tokenIds[1],
            tokenIds[2],
            /// @notice Make sure that token[2] exists so tokenId isn't "spoofed"
            bountySwapConfig.tokenCount > 3 ? tokenIds[3] : 0
        );
    }

    function _processRefresh(
        DiscoveryType discoveryType,
        uint256 tokenId
    ) private {
        _excavateWhenReady(tokenId);

        _fundExcavatorWhenReady();

        if (
            ++refreshCount % silver2UnlockCadence == 0 &&
            silver2Swaps > silver2UnlockAmount
        ) silver2Swaps -= silver2UnlockAmount;

        emit Discovery(msg.sender, tokenId, discoveryType);
    }

    function _swapWethForToken(
        address token,
        uint256 ethAmount,
        address recipient
    ) internal returns (uint256 amount) {
        uint256 currWethBalance = IWETH(WETH).balanceOf(address(this));
        if (ethAmount > currWethBalance) ethAmount = currWethBalance;

        // TODO: Set min value req
        if (ethAmount > 0) {
            amount = _swapTokens(WETH, token, ethAmount, recipient);
        }
    }

    function _resetTetra(uint256 tokenId) private {
        delete _tetraSeeds[tokenId];
        delete tetraExcavationIds[tokenId];
    }

    function _beginGroupExcavation() private {
        require(excavator.isInitialized(), "NO_SUBSCRIPTION");
        uint256 excavationId = excavator.beginExcavation();

        require(excavationId != 0, "EXCAVATION_FAILED");
        excavationTracker[excavationId] = excavationGroup;

        for (uint256 i = 0; i < CADENCE; i = _unsafeIncrement(i))
            tetraExcavationIds[
                uint256(uint16(excavationTracker[excavationId] >> (16 * i)))
            ] = excavationId;

        emit ExcavationContracted(
            excavationId,
            uint256(uint16(excavationGroup >> 0)),
            uint256(uint16(excavationGroup >> 16)),
            uint256(uint16(excavationGroup >> 32)),
            uint256(uint16(excavationGroup >> 48)),
            uint256(uint16(excavationGroup >> 64))
        );

        delete excavationGroup;
    }

    function excavate(uint256 excavationId, uint256 value) external {
        require(
            msg.sender == address(excavator.vrfCoordinator()) ||
                msg.sender == address(excavator),
            "INVALID_SENDER"
        );

        _excavate(excavationId, value);

        emit ExcavationComplete(excavationId, value);
    }

    function forceExcavate(uint256 excavationId) public onlyExecutor {
        /// @notice Prevents executor from breaking unfilled excavation groups
        for (uint256 i = 0; i < CADENCE; i = _unsafeIncrement(i)) {
            uint256 tokenId = uint256(
                uint16(excavationTracker[excavationId] >> (16 * i))
            );
            if (tokenId == 0) revert ExcavationGroupNotReady();
        }
        excavator.forceExcavate(excavationId, randomizer.random());
    }

    function _prospekt(address recipient) private returns (uint256) {
        require(totalSupply() < maxSupply);

        uint256 tokenId = _prospektCount;
        _prospektCount = _unsafeIncrement(_prospektCount);
        _safeMint(recipient, tokenId);

        _excavateWhenReady(tokenId);

        emit Discovery(msg.sender, tokenId, DiscoveryType.Prospekted);

        return tokenId;
    }

    function collectFeesForBounty() external nonReentrant {
        require(balanceOf(msg.sender) > 0, "NOT_TETRA_OWNER");
        require(canCollectFees(), "NOT_READY");
        uint256 preBalance = IERC20(PORK).balanceOf(address(bountyVault));
        prospektorFundManager.collectLPFees();
        uint256 postBalance = IERC20(PORK).balanceOf(address(bountyVault));

        emit CollectedFeesForBounty(
            msg.sender,
            interactionCount,
            postBalance - preBalance,
            canCollectFeesAfter
        );

        canCollectFeesAfter = interactionCount + feeCollectorCadence;
        IERC20(WETH).approve(msg.sender, feeCollectorBounty);
        IERC20(WETH).transfer(msg.sender, feeCollectorBounty);
    }

    function addLiquidityForBounty() external nonReentrant {
        require(balanceOf(msg.sender) > 0, "NOT_TETRA_OWNER");
        require(canAddLiquidity(), "NOT_READY");
        (uint256 addedPorkAmount, uint256 addedWethAmount) = _addLiquidity();
        canAddLiquidityAfter = interactionCount + liquidityAdderCadence;

        emit AddedLiquidityForBounty(
            msg.sender,
            interactionCount,
            addedPorkAmount,
            addedWethAmount,
            canAddLiquidityAfter
        );

        IERC20(WETH).approve(msg.sender, liquidityAdderBounty);
        IERC20(WETH).transfer(msg.sender, liquidityAdderBounty);
    }

    function canAddLiquidity() public view returns (bool) {
        return
            interactionCount >= canAddLiquidityAfter &&
            IERC20(WETH).balanceOf(address(this)) > liquidityAdderBounty;
    }

    function canCollectFees() public view returns (bool) {
        return
            interactionCount >= canCollectFeesAfter &&
            IERC20(WETH).balanceOf(address(this)) > feeCollectorBounty;
    }

    function _addLiquidity()
        private
        returns (uint256 addedPorkAmount, uint256 addedWethAmount)
    {
        uint256 initialPorkBalance = _getBalanceMinusSaver(PORK);
        uint256 initialWethBalance = _getBalanceMinusSaver(WETH) -
            liquidityAdderBounty;

        (, addedPorkAmount, addedWethAmount) = prospektorFundManager
            .addLiquidity(initialPorkBalance, initialWethBalance);

        uint256 remainingPorkAmount = initialPorkBalance - addedPorkAmount;
        uint256 remainingWethAmount = initialWethBalance - addedWethAmount;

        uint256 porkWethValue = priceOracle.getPrice(
            PORK,
            WETH,
            uint128(remainingPorkAmount),
            PORK_WETH_POOL_FEE
        );

        if (
            porkWethValue > remainingWethAmount &&
            swapPercentage - SWAP_PERCENTAGE_INCREMENT > 0
        ) swapPercentage -= SWAP_PERCENTAGE_INCREMENT;

        if (
            porkWethValue < remainingWethAmount &&
            swapPercentage + SWAP_PERCENTAGE_INCREMENT < 90
        ) swapPercentage += SWAP_PERCENTAGE_INCREMENT;
    }

    function _getBalanceMinusSaver(
        address erc20Address
    ) internal view returns (uint256 balance) {
        balance = IERC20(erc20Address).balanceOf(address(this));
        if (erc20Address == WETH) balance -= MIN_WETH_AMOUNT;
        else if (erc20Address == PORK) balance -= MIN_PORK_AMOUNT;
    }

    function getTetraExcavationId(
        uint256 tokenId
    ) external view returns (uint256) {
        return tetraExcavationIds[tokenId];
    }

    function isExcavating(uint256 tokenId) public view returns (bool) {
        return
            tetraExcavationIds[tokenId] != 0 &&
            _excavationResults[tetraExcavationIds[tokenId]] == 0;
    }

    function _excavateWhenReady(uint256 tokenId) internal {
        if (isExcavating(tokenId)) revert ExcavationInProgress();

        if (interactionCount % CADENCE == 0) excavationGroup = tokenId;
        else excavationGroup |= tokenId << (16 * (interactionCount % CADENCE));

        /// @notice Temporarily set to value
        tetraExcavationIds[tokenId] = 1;

        _createTetraSeed(tokenId);

        if ((++interactionCount) % CADENCE == 0) _beginGroupExcavation();
    }

    /**
     * Flash bounties
     */
    function enableFlashBounty() public onlyExecutor {
        require(!isFlashBountyEnabled());
        flashBountyEnabled = true;
        _createTetraSeed(0);
        _excavate(0, randomizer.random());
    }

    function _disableFlashBounty() private {
        require(isFlashBountyEnabled(), "NOT_ENABLED");
        flashBountyEnabled = false;
        delete _excavationResults[0];
    }

    function isFlashBountyMatch(uint256 tokenId) public view returns (bool) {
        return
            flashBountyEnabled
                ? getTetraKey(0, BountyTier.Silver) ==
                    getTetraKey(tokenId, BountyTier.Silver)
                : false;
    }

    function claimFlashBounty(
        uint256 tokenId
    ) public nonReentrant mustOwnToken(msg.sender, tokenId) {
        require(flashBountyEnabled && isFlashBountyMatch(tokenId), "0");
        _disableFlashBounty();

        uint256 bountyValue = priceOracle.getPrice(
            WETH,
            PORK,
            uint128(flashBountyValue),
            PORK_WETH_POOL_FEE
        );

        bountyVault.distribute(msg.sender, bountyValue, 0);

        emit ClaimedFlashBounty(
            msg.sender,
            tokenId,
            getTetraKey(tokenId, BountyTier.Silver),
            bountyValue
        );
    }

    function claim(uint256 claimTokenId) public nonReentrant returns (uint256) {
        claimsManager.claim(msg.sender, claimTokenId);
        uint256 tokenId = _prospekt(msg.sender);
        emit Claimed(
            msg.sender,
            claimsManager.getClaimsSessionId(),
            claimTokenId,
            tokenId
        );
        return tokenId;
    }

    function getTetraKey(
        uint256 tokenId,
        BountyTier tier
    ) public view returns (bytes32) {
        if (tier == BountyTier.Silver)
            return _getTetraKey(tokenId, false, false);
        if (tier == BountyTier.Gold) {
            return _getTetraKey(tokenId, true, false);
        }
        if (tier == BountyTier.Platinum)
            return _getTetraKey(tokenId, true, true);
        return 0;
    }

    function getTetraKeys(
        uint256 tokenId
    ) public view returns (bytes32[3] memory) {
        bytes32[3] memory tetraKeys = [
            getTetraKey(tokenId, BountyTier.Silver),
            getTetraKey(tokenId, BountyTier.Gold),
            getTetraKey(tokenId, BountyTier.Platinum)
        ];
        return tetraKeys;
    }

    function _getTetra(uint256 tokenId) public view returns (uint8[7] memory) {
        if (
            isExcavating(tokenId) ||
            (tokenId == 0 && !flashBountyEnabled) ||
            /// @notice Check if slot is uninitialized
            uint256(uint16(_tetraSeeds[tokenId] >> 16)) == 0
        ) return [0, 0, 0, 0, 0, 0, 0];
        return [
            _modToColorIndex(
                _excavationResults[tetraExcavationIds[tokenId]],
                uint256(uint16(_tetraSeeds[tokenId] >> 16))
            ),
            _modToColorIndex(
                _excavationResults[tetraExcavationIds[tokenId]],
                uint256(uint16(_tetraSeeds[tokenId] >> 32))
            ),
            _modToColorIndex(
                _excavationResults[tetraExcavationIds[tokenId]],
                uint256(uint16(_tetraSeeds[tokenId] >> 48))
            ),
            _modToColorIndex(
                _excavationResults[tetraExcavationIds[tokenId]],
                uint256(uint16(_tetraSeeds[tokenId] >> 64))
            ),
            _modToColorIndex(
                _excavationResults[tetraExcavationIds[tokenId]],
                uint256(uint16(_tetraSeeds[tokenId] >> 80))
            ),
            _modToColorIndex(
                _excavationResults[tetraExcavationIds[tokenId]],
                uint256(uint16(_tetraSeeds[tokenId] >> 96))
            ),
            _modToColorIndex(
                _excavationResults[tetraExcavationIds[tokenId]],
                uint256(uint16(_tetraSeeds[tokenId] >> 112))
            )
        ];
    }

    function _getTetraKey(
        uint256 tokenId,
        bool includeMantle,
        bool includeKrust
    ) private view returns (bytes32) {
        uint8[7] memory tetra = _getTetra(tokenId);
        return
            bytes32(
                (uint256(tetra[0]) + 1) *
                    10 ** 12 +
                    (uint256(tetra[1]) + 1) *
                    10 ** 10 +
                    (uint256(tetra[2]) + 1) *
                    10 ** 8 +
                    (uint256(tetra[3]) + 1) *
                    10 ** 6 +
                    (uint256(tetra[4]) + 1) *
                    10 ** 4 +
                    (includeMantle ? (uint256(tetra[5]) + 1) * 10 ** 2 : 0) +
                    (includeKrust ? (uint256(tetra[6]) + 1) : 0)
            );
    }

    function tokenURI(
        uint256 tokenId
    ) public view override returns (string memory) {
        return
            RenderHelper.getJson(
                tokenId,
                _getTetra(tokenId),
                colorConfig.colorNames,
                colorConfig.colorHexCodes
            );
    }

    function _modToColorIndex(
        uint256 value,
        uint256 seed
    ) public view returns (uint8) {
        unchecked {
            return uint8((value / seed) % (colorConfig.maxColors - 1)) + 1;
        }
    }

    function _createTetraSeed(uint256 tokenId) private {
        uint256 tetra;
        uint256 rand = randomizer.random();

        for (uint256 i = 0; i < 7; i = _unsafeIncrement(i)) tetra |= rand << 16;

        _tetraSeeds[tokenId] = tetra;
    }

    function _excavate(uint256 excavationId, uint256 value) internal {
        _excavationResults[excavationId] = value;

        /// @dev Is Flash Bounty
        if (excavationId == 0) {
            emit ExcavatedFlashBounty(
                getTetraKey(0, BountyTier.Silver),
                getTetraKey(0, BountyTier.Gold),
                getTetraKey(0, BountyTier.Platinum)
            );
        } else {
            for (uint256 i = 0; i < CADENCE; i = _unsafeIncrement(i)) {
                uint256 tokenId = uint256(
                    uint16(excavationTracker[excavationId] >> (16 * i))
                );
                emit Excavated(
                    ownerOf(tokenId),
                    tokenId,
                    getTetraKey(tokenId, BountyTier.Silver),
                    getTetraKey(tokenId, BountyTier.Gold),
                    getTetraKey(tokenId, BountyTier.Platinum)
                );
            }
            delete excavationTracker[excavationId];
        }
    }

    function _swapTokens(
        address fromToken,
        address toToken,
        uint256 amountIn,
        address recipient
    ) internal returns (uint256) {
        uint24 _poolFee = fromToken == PORK || toToken == PORK
            ? PORK_WETH_POOL_FEE
            : DEFAULT_POOL_FEE;
        return
            uniswapRouter.exactInputSingle(
                ISwapRouter.ExactInputSingleParams({
                    tokenIn: fromToken,
                    tokenOut: toToken,
                    fee: _poolFee,
                    recipient: recipient,
                    amountIn: amountIn,
                    amountOutMinimum: 0,
                    sqrtPriceLimitX96: 0
                })
            );
    }

    function initialize(
        address prospektorFundManagerAddress
    ) public onlyExecutor {
        require(!isInitialized, "ALREADY_INITIALIZED");
        isInitialized = true;
        /// @notice Setup ProspektorFundManager
        prospektorFundManager = IProspektorFundManager(
            prospektorFundManagerAddress
        );

        TransferHelper.safeApprove(
            WETH,
            prospektorFundManagerAddress,
            type(uint256).max
        );

        TransferHelper.safeApprove(
            PORK,
            prospektorFundManagerAddress,
            type(uint256).max
        );

        /// @notice Setup Uniswap Router
        TransferHelper.safeApprove(
            WETH,
            address(uniswapRouter),
            type(uint256).max
        );

        TransferHelper.safeApprove(
            PORK,
            address(uniswapRouter),
            type(uint256).max
        );
    }

    function pause() public onlyExecutor {
        _pause();
    }

    function unpause() public onlyExecutor {
        _unpause();
    }

    function onERC721Received(
        address,
        address,
        uint256,
        bytes calldata
    ) external pure returns (bytes4) {
        return IERC721Receiver.onERC721Received.selector;
    }
}

File 2 of 52 : ChainSpecificUtil.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import {ArbSys} from "./vendor/@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol";
import {ArbGasInfo} from "./vendor/@arbitrum/nitro-contracts/src/precompiles/ArbGasInfo.sol";

//@dev A library that abstracts out opcodes that behave differently across chains.
//@dev The methods below return values that are pertinent to the given chain.
//@dev For instance, ChainSpecificUtil.getBlockNumber() returns L2 block number in L2 chains
library ChainSpecificUtil {
  address private constant ARBSYS_ADDR = address(0x0000000000000000000000000000000000000064);
  ArbSys private constant ARBSYS = ArbSys(ARBSYS_ADDR);
  address private constant ARBGAS_ADDR = address(0x000000000000000000000000000000000000006C);
  ArbGasInfo private constant ARBGAS = ArbGasInfo(ARBGAS_ADDR);
  uint256 private constant ARB_MAINNET_CHAIN_ID = 42161;
  uint256 private constant ARB_GOERLI_TESTNET_CHAIN_ID = 421613;
  uint256 private constant ARB_SEPOLIA_TESTNET_CHAIN_ID = 421614;

  function getBlockhash(uint64 blockNumber) internal view returns (bytes32) {
    uint256 chainid = block.chainid;
    if (
      chainid == ARB_MAINNET_CHAIN_ID ||
      chainid == ARB_GOERLI_TESTNET_CHAIN_ID ||
      chainid == ARB_SEPOLIA_TESTNET_CHAIN_ID
    ) {
      if ((getBlockNumber() - blockNumber) > 256 || blockNumber >= getBlockNumber()) {
        return "";
      }
      return ARBSYS.arbBlockHash(blockNumber);
    }
    return blockhash(blockNumber);
  }

  function getBlockNumber() internal view returns (uint256) {
    uint256 chainid = block.chainid;
    if (chainid == ARB_MAINNET_CHAIN_ID || chainid == ARB_GOERLI_TESTNET_CHAIN_ID) {
      return ARBSYS.arbBlockNumber();
    }
    return block.number;
  }

  function getCurrentTxL1GasFees() internal view returns (uint256) {
    uint256 chainid = block.chainid;
    if (chainid == ARB_MAINNET_CHAIN_ID || chainid == ARB_GOERLI_TESTNET_CHAIN_ID) {
      return ARBGAS.getCurrentTxL1GasFees();
    }
    return 0;
  }

  /**
   * @notice Returns the gas cost in wei of calldataSizeBytes of calldata being posted
   * @notice to L1.
   */
  function getL1CalldataGasCost(uint256 calldataSizeBytes) internal view returns (uint256) {
    uint256 chainid = block.chainid;
    if (chainid == ARB_MAINNET_CHAIN_ID || chainid == ARB_GOERLI_TESTNET_CHAIN_ID) {
      (, uint256 l1PricePerByte, , , , ) = ARBGAS.getPricesInWei();
      // see https://developer.arbitrum.io/devs-how-tos/how-to-estimate-gas#where-do-we-get-all-this-information-from
      // for the justification behind the 140 number.
      return l1PricePerByte * (calldataSizeBytes + 140);
    }
    return 0;
  }
}

File 3 of 52 : AggregatorV3Interface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface AggregatorV3Interface {
  function decimals() external view returns (uint8);

  function description() external view returns (string memory);

  function version() external view returns (uint256);

  function getRoundData(
    uint80 _roundId
  ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

  function latestRoundData()
    external
    view
    returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}

File 4 of 52 : BlockhashStoreInterface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface BlockhashStoreInterface {
  function getBlockhash(uint256 number) external view returns (bytes32);
}

File 5 of 52 : TypeAndVersionInterface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

abstract contract TypeAndVersionInterface {
  function typeAndVersion() external pure virtual returns (string memory);
}

File 6 of 52 : VRFCoordinatorV2Interface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface VRFCoordinatorV2Interface {
  /**
   * @notice Get configuration relevant for making requests
   * @return minimumRequestConfirmations global min for request confirmations
   * @return maxGasLimit global max for request gas limit
   * @return s_provingKeyHashes list of registered key hashes
   */
  function getRequestConfig() external view returns (uint16, uint32, bytes32[] memory);

  /**
   * @notice Request a set of random words.
   * @param keyHash - Corresponds to a particular oracle job which uses
   * that key for generating the VRF proof. Different keyHash's have different gas price
   * ceilings, so you can select a specific one to bound your maximum per request cost.
   * @param subId  - The ID of the VRF subscription. Must be funded
   * with the minimum subscription balance required for the selected keyHash.
   * @param minimumRequestConfirmations - How many blocks you'd like the
   * oracle to wait before responding to the request. See SECURITY CONSIDERATIONS
   * for why you may want to request more. The acceptable range is
   * [minimumRequestBlockConfirmations, 200].
   * @param callbackGasLimit - How much gas you'd like to receive in your
   * fulfillRandomWords callback. Note that gasleft() inside fulfillRandomWords
   * may be slightly less than this amount because of gas used calling the function
   * (argument decoding etc.), so you may need to request slightly more than you expect
   * to have inside fulfillRandomWords. The acceptable range is
   * [0, maxGasLimit]
   * @param numWords - The number of uint256 random values you'd like to receive
   * in your fulfillRandomWords callback. Note these numbers are expanded in a
   * secure way by the VRFCoordinator from a single random value supplied by the oracle.
   * @return requestId - A unique identifier of the request. Can be used to match
   * a request to a response in fulfillRandomWords.
   */
  function requestRandomWords(
    bytes32 keyHash,
    uint64 subId,
    uint16 minimumRequestConfirmations,
    uint32 callbackGasLimit,
    uint32 numWords
  ) external returns (uint256 requestId);

  /**
   * @notice Create a VRF subscription.
   * @return subId - A unique subscription id.
   * @dev You can manage the consumer set dynamically with addConsumer/removeConsumer.
   * @dev Note to fund the subscription, use transferAndCall. For example
   * @dev  LINKTOKEN.transferAndCall(
   * @dev    address(COORDINATOR),
   * @dev    amount,
   * @dev    abi.encode(subId));
   */
  function createSubscription() external returns (uint64 subId);

  /**
   * @notice Get a VRF subscription.
   * @param subId - ID of the subscription
   * @return balance - LINK balance of the subscription in juels.
   * @return reqCount - number of requests for this subscription, determines fee tier.
   * @return owner - owner of the subscription.
   * @return consumers - list of consumer address which are able to use this subscription.
   */
  function getSubscription(
    uint64 subId
  ) external view returns (uint96 balance, uint64 reqCount, address owner, address[] memory consumers);

  /**
   * @notice Request subscription owner transfer.
   * @param subId - ID of the subscription
   * @param newOwner - proposed new owner of the subscription
   */
  function requestSubscriptionOwnerTransfer(uint64 subId, address newOwner) external;

  /**
   * @notice Request subscription owner transfer.
   * @param subId - ID of the subscription
   * @dev will revert if original owner of subId has
   * not requested that msg.sender become the new owner.
   */
  function acceptSubscriptionOwnerTransfer(uint64 subId) external;

  /**
   * @notice Add a consumer to a VRF subscription.
   * @param subId - ID of the subscription
   * @param consumer - New consumer which can use the subscription
   */
  function addConsumer(uint64 subId, address consumer) external;

  /**
   * @notice Remove a consumer from a VRF subscription.
   * @param subId - ID of the subscription
   * @param consumer - Consumer to remove from the subscription
   */
  function removeConsumer(uint64 subId, address consumer) external;

  /**
   * @notice Cancel a subscription
   * @param subId - ID of the subscription
   * @param to - Where to send the remaining LINK to
   */
  function cancelSubscription(uint64 subId, address to) external;

  /*
   * @notice Check to see if there exists a request commitment consumers
   * for all consumers and keyhashes for a given sub.
   * @param subId - ID of the subscription
   * @return true if there exists at least one unfulfilled request for the subscription, false
   * otherwise.
   */
  function pendingRequestExists(uint64 subId) external view returns (bool);
}

File 7 of 52 : ConfirmedOwner.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./ConfirmedOwnerWithProposal.sol";

/**
 * @title The ConfirmedOwner contract
 * @notice A contract with helpers for basic contract ownership.
 */
contract ConfirmedOwner is ConfirmedOwnerWithProposal {
  constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
}

File 8 of 52 : ConfirmedOwnerWithProposal.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../interfaces/IOwnable.sol";

/**
 * @title The ConfirmedOwner contract
 * @notice A contract with helpers for basic contract ownership.
 */
contract ConfirmedOwnerWithProposal is IOwnable {
  address private s_owner;
  address private s_pendingOwner;

  event OwnershipTransferRequested(address indexed from, address indexed to);
  event OwnershipTransferred(address indexed from, address indexed to);

  constructor(address newOwner, address pendingOwner) {
    require(newOwner != address(0), "Cannot set owner to zero");

    s_owner = newOwner;
    if (pendingOwner != address(0)) {
      _transferOwnership(pendingOwner);
    }
  }

  /**
   * @notice Allows an owner to begin transferring ownership to a new address,
   * pending.
   */
  function transferOwnership(address to) public override onlyOwner {
    _transferOwnership(to);
  }

  /**
   * @notice Allows an ownership transfer to be completed by the recipient.
   */
  function acceptOwnership() external override {
    require(msg.sender == s_pendingOwner, "Must be proposed owner");

    address oldOwner = s_owner;
    s_owner = msg.sender;
    s_pendingOwner = address(0);

    emit OwnershipTransferred(oldOwner, msg.sender);
  }

  /**
   * @notice Get the current owner
   */
  function owner() public view override returns (address) {
    return s_owner;
  }

  /**
   * @notice validate, transfer ownership, and emit relevant events
   */
  function _transferOwnership(address to) private {
    require(to != msg.sender, "Cannot transfer to self");

    s_pendingOwner = to;

    emit OwnershipTransferRequested(s_owner, to);
  }

  /**
   * @notice validate access
   */
  function _validateOwnership() internal view {
    require(msg.sender == s_owner, "Only callable by owner");
  }

  /**
   * @notice Reverts if called by anyone other than the contract owner.
   */
  modifier onlyOwner() {
    _validateOwnership();
    _;
  }
}

File 9 of 52 : IERC677Receiver.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;

interface IERC677Receiver {
  function onTokenTransfer(address sender, uint256 amount, bytes calldata data) external;
}

File 10 of 52 : IOwnable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IOwnable {
  function owner() external returns (address);

  function transferOwnership(address recipient) external;

  function acceptOwnership() external;
}

File 11 of 52 : LinkTokenInterface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface LinkTokenInterface {
  function allowance(address owner, address spender) external view returns (uint256 remaining);

  function approve(address spender, uint256 value) external returns (bool success);

  function balanceOf(address owner) external view returns (uint256 balance);

  function decimals() external view returns (uint8 decimalPlaces);

  function decreaseApproval(address spender, uint256 addedValue) external returns (bool success);

  function increaseApproval(address spender, uint256 subtractedValue) external;

  function name() external view returns (string memory tokenName);

  function symbol() external view returns (string memory tokenSymbol);

  function totalSupply() external view returns (uint256 totalTokensIssued);

  function transfer(address to, uint256 value) external returns (bool success);

  function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool success);

  function transferFrom(address from, address to, uint256 value) external returns (bool success);
}

File 12 of 52 : ArbGasInfo.sol
// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1

pragma solidity >=0.4.21 <0.9.0;

interface ArbGasInfo {
    // return gas prices in wei, assuming the specified aggregator is used
    //        (
    //            per L2 tx,
    //            per L1 calldata unit, (zero byte = 4 units, nonzero byte = 16 units)
    //            per storage allocation,
    //            per ArbGas base,
    //            per ArbGas congestion,
    //            per ArbGas total
    //        )
    function getPricesInWeiWithAggregator(address aggregator) external view returns (uint, uint, uint, uint, uint, uint);

    // return gas prices in wei, as described above, assuming the caller's preferred aggregator is used
    //     if the caller hasn't specified a preferred aggregator, the default aggregator is assumed
    function getPricesInWei() external view returns (uint, uint, uint, uint, uint, uint);

    // return prices in ArbGas (per L2 tx, per L1 calldata unit, per storage allocation),
    //       assuming the specified aggregator is used
    function getPricesInArbGasWithAggregator(address aggregator) external view returns (uint, uint, uint);

    // return gas prices in ArbGas, as described above, assuming the caller's preferred aggregator is used
    //     if the caller hasn't specified a preferred aggregator, the default aggregator is assumed
    function getPricesInArbGas() external view returns (uint, uint, uint);

    // return gas accounting parameters (speedLimitPerSecond, gasPoolMax, maxTxGasLimit)
    function getGasAccountingParams() external view returns (uint, uint, uint);

    // get ArbOS's estimate of the L1 gas price in wei
    function getL1GasPriceEstimate() external view returns(uint);

    // set ArbOS's estimate of the L1 gas price in wei
    // reverts unless called by chain owner or designated gas oracle (if any)
    function setL1GasPriceEstimate(uint priceInWei) external;

    // get L1 gas fees paid by the current transaction (txBaseFeeWei, calldataFeeWei)
    function getCurrentTxL1GasFees() external view returns(uint);
}

File 13 of 52 : ArbSys.sol
// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1

pragma solidity >=0.4.21 <0.9.0;

/**
 * @title System level functionality
 * @notice For use by contracts to interact with core L2-specific functionality.
 * Precompiled contract that exists in every Arbitrum chain at address(100), 0x0000000000000000000000000000000000000064.
 */
interface ArbSys {
    /**
     * @notice Get Arbitrum block number (distinct from L1 block number; Arbitrum genesis block has block number 0)
     * @return block number as int
     */
    function arbBlockNumber() external view returns (uint256);

    /**
     * @notice Get Arbitrum block hash (reverts unless currentBlockNum-256 <= arbBlockNum < currentBlockNum)
     * @return block hash
     */
    function arbBlockHash(uint256 arbBlockNum) external view returns (bytes32);

    /**
     * @notice Gets the rollup's unique chain identifier
     * @return Chain identifier as int
     */
    function arbChainID() external view returns (uint256);

    /**
     * @notice Get internal version number identifying an ArbOS build
     * @return version number as int
     */
    function arbOSVersion() external view returns (uint256);

    /**
     * @notice Returns 0 since Nitro has no concept of storage gas
     * @return uint 0
     */
    function getStorageGasAvailable() external view returns (uint256);

    /**
     * @notice (deprecated) check if current call is top level (meaning it was triggered by an EoA or a L1 contract)
     * @dev this call has been deprecated and may be removed in a future release
     * @return true if current execution frame is not a call by another L2 contract
     */
    function isTopLevelCall() external view returns (bool);

    /**
     * @notice map L1 sender contract address to its L2 alias
     * @param sender sender address
     * @param unused argument no longer used
     * @return aliased sender address
     */
    function mapL1SenderContractAddressToL2Alias(address sender, address unused)
        external
        pure
        returns (address);

    /**
     * @notice check if the caller (of this caller of this) is an aliased L1 contract address
     * @return true iff the caller's address is an alias for an L1 contract address
     */
    function wasMyCallersAddressAliased() external view returns (bool);

    /**
     * @notice return the address of the caller (of this caller of this), without applying L1 contract address aliasing
     * @return address of the caller's caller, without applying L1 contract address aliasing
     */
    function myCallersAddressWithoutAliasing() external view returns (address);

    /**
     * @notice Send given amount of Eth to dest from sender.
     * This is a convenience function, which is equivalent to calling sendTxToL1 with empty data.
     * @param destination recipient address on L1
     * @return unique identifier for this L2-to-L1 transaction.
     */
    function withdrawEth(address destination)
        external
        payable
        returns (uint256);

    /**
     * @notice Send a transaction to L1
     * @dev it is not possible to execute on the L1 any L2-to-L1 transaction which contains data
     * to a contract address without any code (as enforced by the Bridge contract).
     * @param destination recipient address on L1
     * @param data (optional) calldata for L1 contract call
     * @return a unique identifier for this L2-to-L1 transaction.
     */
    function sendTxToL1(address destination, bytes calldata data)
        external
        payable
        returns (uint256);

    /**
     * @notice Get send Merkle tree state
     * @return size number of sends in the history
     * @return root root hash of the send history
     * @return partials hashes of partial subtrees in the send history tree
     */
    function sendMerkleTreeState()
        external
        view
        returns (
            uint256 size,
            bytes32 root,
            bytes32[] memory partials
        );

    /**
     * @notice creates a send txn from L2 to L1
     * @param position = (level << 192) + leaf = (0 << 192) + leaf = leaf
     */
    event L2ToL1Tx(
        address caller,
        address indexed destination,
        uint256 indexed hash,
        uint256 indexed position,
        uint256 arbBlockNum,
        uint256 ethBlockNum,
        uint256 timestamp,
        uint256 callvalue,
        bytes data
    );

    /// @dev DEPRECATED in favour of the new L2ToL1Tx event above after the nitro upgrade
    event L2ToL1Transaction(
        address caller,
        address indexed destination,
        uint256 indexed uniqueId,
        uint256 indexed batchNumber,
        uint256 indexInBatch,
        uint256 arbBlockNum,
        uint256 ethBlockNum,
        uint256 timestamp,
        uint256 callvalue,
        bytes data
    );

    /**
     * @notice logs a merkle branch for proof synthesis
     * @param reserved an index meant only to align the 4th index with L2ToL1Transaction's 4th event
     * @param hash the merkle hash
     * @param position = (level << 192) + leaf
     */
    event SendMerkleUpdate(
        uint256 indexed reserved,
        bytes32 indexed hash,
        uint256 indexed position
    );
}

File 14 of 52 : VRF.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/** ****************************************************************************
  * @notice Verification of verifiable-random-function (VRF) proofs, following
  * @notice https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.3
  * @notice See https://eprint.iacr.org/2017/099.pdf for security proofs.

  * @dev Bibliographic references:

  * @dev Goldberg, et al., "Verifiable Random Functions (VRFs)", Internet Draft
  * @dev draft-irtf-cfrg-vrf-05, IETF, Aug 11 2019,
  * @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05

  * @dev Papadopoulos, et al., "Making NSEC5 Practical for DNSSEC", Cryptology
  * @dev ePrint Archive, Report 2017/099, https://eprint.iacr.org/2017/099.pdf
  * ****************************************************************************
  * @dev USAGE

  * @dev The main entry point is randomValueFromVRFProof. See its docstring.
  * ****************************************************************************
  * @dev PURPOSE

  * @dev Reggie the Random Oracle (not his real job) wants to provide randomness
  * @dev to Vera the verifier in such a way that Vera can be sure he's not
  * @dev making his output up to suit himself. Reggie provides Vera a public key
  * @dev to which he knows the secret key. Each time Vera provides a seed to
  * @dev Reggie, he gives back a value which is computed completely
  * @dev deterministically from the seed and the secret key.

  * @dev Reggie provides a proof by which Vera can verify that the output was
  * @dev correctly computed once Reggie tells it to her, but without that proof,
  * @dev the output is computationally indistinguishable to her from a uniform
  * @dev random sample from the output space.

  * @dev The purpose of this contract is to perform that verification.
  * ****************************************************************************
  * @dev DESIGN NOTES

  * @dev The VRF algorithm verified here satisfies the full uniqueness, full
  * @dev collision resistance, and full pseudo-randomness security properties.
  * @dev See "SECURITY PROPERTIES" below, and
  * @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-3

  * @dev An elliptic curve point is generally represented in the solidity code
  * @dev as a uint256[2], corresponding to its affine coordinates in
  * @dev GF(FIELD_SIZE).

  * @dev For the sake of efficiency, this implementation deviates from the spec
  * @dev in some minor ways:

  * @dev - Keccak hash rather than the SHA256 hash recommended in
  * @dev   https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.5
  * @dev   Keccak costs much less gas on the EVM, and provides similar security.

  * @dev - Secp256k1 curve instead of the P-256 or ED25519 curves recommended in
  * @dev   https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.5
  * @dev   For curve-point multiplication, it's much cheaper to abuse ECRECOVER

  * @dev - hashToCurve recursively hashes until it finds a curve x-ordinate. On
  * @dev   the EVM, this is slightly more efficient than the recommendation in
  * @dev   https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.4.1.1
  * @dev   step 5, to concatenate with a nonce then hash, and rehash with the
  * @dev   nonce updated until a valid x-ordinate is found.

  * @dev - hashToCurve does not include a cipher version string or the byte 0x1
  * @dev   in the hash message, as recommended in step 5.B of the draft
  * @dev   standard. They are unnecessary here because no variation in the
  * @dev   cipher suite is allowed.

  * @dev - Similarly, the hash input in scalarFromCurvePoints does not include a
  * @dev   commitment to the cipher suite, either, which differs from step 2 of
  * @dev   https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.4.3
  * @dev   . Also, the hash input is the concatenation of the uncompressed
  * @dev   points, not the compressed points as recommended in step 3.

  * @dev - In the calculation of the challenge value "c", the "u" value (i.e.
  * @dev   the value computed by Reggie as the nonce times the secp256k1
  * @dev   generator point, see steps 5 and 7 of
  * @dev   https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.3
  * @dev   ) is replaced by its ethereum address, i.e. the lower 160 bits of the
  * @dev   keccak hash of the original u. This is because we only verify the
  * @dev   calculation of u up to its address, by abusing ECRECOVER.
  * ****************************************************************************
  * @dev   SECURITY PROPERTIES

  * @dev Here are the security properties for this VRF:

  * @dev Full uniqueness: For any seed and valid VRF public key, there is
  * @dev   exactly one VRF output which can be proved to come from that seed, in
  * @dev   the sense that the proof will pass verifyVRFProof.

  * @dev Full collision resistance: It's cryptographically infeasible to find
  * @dev   two seeds with same VRF output from a fixed, valid VRF key

  * @dev Full pseudorandomness: Absent the proofs that the VRF outputs are
  * @dev   derived from a given seed, the outputs are computationally
  * @dev   indistinguishable from randomness.

  * @dev https://eprint.iacr.org/2017/099.pdf, Appendix B contains the proofs
  * @dev for these properties.

  * @dev For secp256k1, the key validation described in section
  * @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.6
  * @dev is unnecessary, because secp256k1 has cofactor 1, and the
  * @dev representation of the public key used here (affine x- and y-ordinates
  * @dev of the secp256k1 point on the standard y^2=x^3+7 curve) cannot refer to
  * @dev the point at infinity.
  * ****************************************************************************
  * @dev OTHER SECURITY CONSIDERATIONS
  *
  * @dev The seed input to the VRF could in principle force an arbitrary amount
  * @dev of work in hashToCurve, by requiring extra rounds of hashing and
  * @dev checking whether that's yielded the x ordinate of a secp256k1 point.
  * @dev However, under the Random Oracle Model the probability of choosing a
  * @dev point which forces n extra rounds in hashToCurve is 2⁻ⁿ. The base cost
  * @dev for calling hashToCurve is about 25,000 gas, and each round of checking
  * @dev for a valid x ordinate costs about 15,555 gas, so to find a seed for
  * @dev which hashToCurve would cost more than 2,017,000 gas, one would have to
  * @dev try, in expectation, about 2¹²⁸ seeds, which is infeasible for any
  * @dev foreseeable computational resources. (25,000 + 128 * 15,555 < 2,017,000.)

  * @dev Since the gas block limit for the Ethereum main net is 10,000,000 gas,
  * @dev this means it is infeasible for an adversary to prevent correct
  * @dev operation of this contract by choosing an adverse seed.

  * @dev (See TestMeasureHashToCurveGasCost for verification of the gas cost for
  * @dev hashToCurve.)

  * @dev It may be possible to make a secure constant-time hashToCurve function.
  * @dev See notes in hashToCurve docstring.
*/
contract VRF {
  // See https://www.secg.org/sec2-v2.pdf, section 2.4.1, for these constants.
  // Number of points in Secp256k1
  uint256 private constant GROUP_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
  // Prime characteristic of the galois field over which Secp256k1 is defined
  uint256 private constant FIELD_SIZE =
    // solium-disable-next-line indentation
    0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F;
  uint256 private constant WORD_LENGTH_BYTES = 0x20;

  // (base^exponent) % FIELD_SIZE
  // Cribbed from https://medium.com/@rbkhmrcr/precompiles-solidity-e5d29bd428c4
  function bigModExp(uint256 base, uint256 exponent) internal view returns (uint256 exponentiation) {
    uint256 callResult;
    uint256[6] memory bigModExpContractInputs;
    bigModExpContractInputs[0] = WORD_LENGTH_BYTES; // Length of base
    bigModExpContractInputs[1] = WORD_LENGTH_BYTES; // Length of exponent
    bigModExpContractInputs[2] = WORD_LENGTH_BYTES; // Length of modulus
    bigModExpContractInputs[3] = base;
    bigModExpContractInputs[4] = exponent;
    bigModExpContractInputs[5] = FIELD_SIZE;
    uint256[1] memory output;
    assembly {
      // solhint-disable-line no-inline-assembly
      callResult := staticcall(
        not(0), // Gas cost: no limit
        0x05, // Bigmodexp contract address
        bigModExpContractInputs,
        0xc0, // Length of input segment: 6*0x20-bytes
        output,
        0x20 // Length of output segment
      )
    }
    if (callResult == 0) {
      revert("bigModExp failure!");
    }
    return output[0];
  }

  // Let q=FIELD_SIZE. q % 4 = 3, ∴ x≡r^2 mod q ⇒ x^SQRT_POWER≡±r mod q.  See
  // https://en.wikipedia.org/wiki/Modular_square_root#Prime_or_prime_power_modulus
  uint256 private constant SQRT_POWER = (FIELD_SIZE + 1) >> 2;

  // Computes a s.t. a^2 = x in the field. Assumes a exists
  function squareRoot(uint256 x) internal view returns (uint256) {
    return bigModExp(x, SQRT_POWER);
  }

  // The value of y^2 given that (x,y) is on secp256k1.
  function ySquared(uint256 x) internal pure returns (uint256) {
    // Curve is y^2=x^3+7. See section 2.4.1 of https://www.secg.org/sec2-v2.pdf
    uint256 xCubed = mulmod(x, mulmod(x, x, FIELD_SIZE), FIELD_SIZE);
    return addmod(xCubed, 7, FIELD_SIZE);
  }

  // True iff p is on secp256k1
  function isOnCurve(uint256[2] memory p) internal pure returns (bool) {
    // Section 2.3.6. in https://www.secg.org/sec1-v2.pdf
    // requires each ordinate to be in [0, ..., FIELD_SIZE-1]
    require(p[0] < FIELD_SIZE, "invalid x-ordinate");
    require(p[1] < FIELD_SIZE, "invalid y-ordinate");
    return ySquared(p[0]) == mulmod(p[1], p[1], FIELD_SIZE);
  }

  // Hash x uniformly into {0, ..., FIELD_SIZE-1}.
  function fieldHash(bytes memory b) internal pure returns (uint256 x_) {
    x_ = uint256(keccak256(b));
    // Rejecting if x >= FIELD_SIZE corresponds to step 2.1 in section 2.3.4 of
    // http://www.secg.org/sec1-v2.pdf , which is part of the definition of
    // string_to_point in the IETF draft
    while (x_ >= FIELD_SIZE) {
      x_ = uint256(keccak256(abi.encodePacked(x_)));
    }
  }

  // Hash b to a random point which hopefully lies on secp256k1. The y ordinate
  // is always even, due to
  // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.4.1.1
  // step 5.C, which references arbitrary_string_to_point, defined in
  // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.5 as
  // returning the point with given x ordinate, and even y ordinate.
  function newCandidateSecp256k1Point(bytes memory b) internal view returns (uint256[2] memory p) {
    unchecked {
      p[0] = fieldHash(b);
      p[1] = squareRoot(ySquared(p[0]));
      if (p[1] % 2 == 1) {
        // Note that 0 <= p[1] < FIELD_SIZE
        // so this cannot wrap, we use unchecked to save gas.
        p[1] = FIELD_SIZE - p[1];
      }
    }
  }

  // Domain-separation tag for initial hash in hashToCurve. Corresponds to
  // vrf.go/hashToCurveHashPrefix
  uint256 internal constant HASH_TO_CURVE_HASH_PREFIX = 1;

  // Cryptographic hash function onto the curve.
  //
  // Corresponds to algorithm in section 5.4.1.1 of the draft standard. (But see
  // DESIGN NOTES above for slight differences.)
  //
  // TODO(alx): Implement a bounded-computation hash-to-curve, as described in
  // "Construction of Rational Points on Elliptic Curves over Finite Fields"
  // http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.831.5299&rep=rep1&type=pdf
  // and suggested by
  // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-01#section-5.2.2
  // (Though we can't used exactly that because secp256k1's j-invariant is 0.)
  //
  // This would greatly simplify the analysis in "OTHER SECURITY CONSIDERATIONS"
  // https://www.pivotaltracker.com/story/show/171120900
  function hashToCurve(uint256[2] memory pk, uint256 input) internal view returns (uint256[2] memory rv) {
    rv = newCandidateSecp256k1Point(abi.encodePacked(HASH_TO_CURVE_HASH_PREFIX, pk, input));
    while (!isOnCurve(rv)) {
      rv = newCandidateSecp256k1Point(abi.encodePacked(rv[0]));
    }
  }

  /** *********************************************************************
   * @notice Check that product==scalar*multiplicand
   *
   * @dev Based on Vitalik Buterin's idea in ethresear.ch post cited below.
   *
   * @param multiplicand: secp256k1 point
   * @param scalar: non-zero GF(GROUP_ORDER) scalar
   * @param product: secp256k1 expected to be multiplier * multiplicand
   * @return verifies true iff product==scalar*multiplicand, with cryptographically high probability
   */
  function ecmulVerify(
    uint256[2] memory multiplicand,
    uint256 scalar,
    uint256[2] memory product
  ) internal pure returns (bool verifies) {
    require(scalar != 0, "zero scalar"); // Rules out an ecrecover failure case
    uint256 x = multiplicand[0]; // x ordinate of multiplicand
    uint8 v = multiplicand[1] % 2 == 0 ? 27 : 28; // parity of y ordinate
    // https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384/9
    // Point corresponding to address ecrecover(0, v, x, s=scalar*x) is
    // (x⁻¹ mod GROUP_ORDER) * (scalar * x * multiplicand - 0 * g), i.e.
    // scalar*multiplicand. See https://crypto.stackexchange.com/a/18106
    bytes32 scalarTimesX = bytes32(mulmod(scalar, x, GROUP_ORDER));
    address actual = ecrecover(bytes32(0), v, bytes32(x), scalarTimesX);
    // Explicit conversion to address takes bottom 160 bits
    address expected = address(uint160(uint256(keccak256(abi.encodePacked(product)))));
    return (actual == expected);
  }

  // Returns x1/z1-x2/z2=(x1z2-x2z1)/(z1z2) in projective coordinates on P¹(𝔽ₙ)
  function projectiveSub(
    uint256 x1,
    uint256 z1,
    uint256 x2,
    uint256 z2
  ) internal pure returns (uint256 x3, uint256 z3) {
    unchecked {
      uint256 num1 = mulmod(z2, x1, FIELD_SIZE);
      // Note this cannot wrap since x2 is a point in [0, FIELD_SIZE-1]
      // we use unchecked to save gas.
      uint256 num2 = mulmod(FIELD_SIZE - x2, z1, FIELD_SIZE);
      (x3, z3) = (addmod(num1, num2, FIELD_SIZE), mulmod(z1, z2, FIELD_SIZE));
    }
  }

  // Returns x1/z1*x2/z2=(x1x2)/(z1z2), in projective coordinates on P¹(𝔽ₙ)
  function projectiveMul(
    uint256 x1,
    uint256 z1,
    uint256 x2,
    uint256 z2
  ) internal pure returns (uint256 x3, uint256 z3) {
    (x3, z3) = (mulmod(x1, x2, FIELD_SIZE), mulmod(z1, z2, FIELD_SIZE));
  }

  /** **************************************************************************
        @notice Computes elliptic-curve sum, in projective co-ordinates

        @dev Using projective coordinates avoids costly divisions

        @dev To use this with p and q in affine coordinates, call
        @dev projectiveECAdd(px, py, qx, qy). This will return
        @dev the addition of (px, py, 1) and (qx, qy, 1), in the
        @dev secp256k1 group.

        @dev This can be used to calculate the z which is the inverse to zInv
        @dev in isValidVRFOutput. But consider using a faster
        @dev re-implementation such as ProjectiveECAdd in the golang vrf package.

        @dev This function assumes [px,py,1],[qx,qy,1] are valid projective
             coordinates of secp256k1 points. That is safe in this contract,
             because this method is only used by linearCombination, which checks
             points are on the curve via ecrecover.
        **************************************************************************
        @param px The first affine coordinate of the first summand
        @param py The second affine coordinate of the first summand
        @param qx The first affine coordinate of the second summand
        @param qy The second affine coordinate of the second summand

        (px,py) and (qx,qy) must be distinct, valid secp256k1 points.
        **************************************************************************
        Return values are projective coordinates of [px,py,1]+[qx,qy,1] as points
        on secp256k1, in P²(𝔽ₙ)
        @return sx
        @return sy
        @return sz
    */
  function projectiveECAdd(
    uint256 px,
    uint256 py,
    uint256 qx,
    uint256 qy
  ) internal pure returns (uint256 sx, uint256 sy, uint256 sz) {
    unchecked {
      // See "Group law for E/K : y^2 = x^3 + ax + b", in section 3.1.2, p. 80,
      // "Guide to Elliptic Curve Cryptography" by Hankerson, Menezes and Vanstone
      // We take the equations there for (sx,sy), and homogenize them to
      // projective coordinates. That way, no inverses are required, here, and we
      // only need the one inverse in affineECAdd.

      // We only need the "point addition" equations from Hankerson et al. Can
      // skip the "point doubling" equations because p1 == p2 is cryptographically
      // impossible, and required not to be the case in linearCombination.

      // Add extra "projective coordinate" to the two points
      (uint256 z1, uint256 z2) = (1, 1);

      // (lx, lz) = (qy-py)/(qx-px), i.e., gradient of secant line.
      // Cannot wrap since px and py are in [0, FIELD_SIZE-1]
      uint256 lx = addmod(qy, FIELD_SIZE - py, FIELD_SIZE);
      uint256 lz = addmod(qx, FIELD_SIZE - px, FIELD_SIZE);

      uint256 dx; // Accumulates denominator from sx calculation
      // sx=((qy-py)/(qx-px))^2-px-qx
      (sx, dx) = projectiveMul(lx, lz, lx, lz); // ((qy-py)/(qx-px))^2
      (sx, dx) = projectiveSub(sx, dx, px, z1); // ((qy-py)/(qx-px))^2-px
      (sx, dx) = projectiveSub(sx, dx, qx, z2); // ((qy-py)/(qx-px))^2-px-qx

      uint256 dy; // Accumulates denominator from sy calculation
      // sy=((qy-py)/(qx-px))(px-sx)-py
      (sy, dy) = projectiveSub(px, z1, sx, dx); // px-sx
      (sy, dy) = projectiveMul(sy, dy, lx, lz); // ((qy-py)/(qx-px))(px-sx)
      (sy, dy) = projectiveSub(sy, dy, py, z1); // ((qy-py)/(qx-px))(px-sx)-py

      if (dx != dy) {
        // Cross-multiply to put everything over a common denominator
        sx = mulmod(sx, dy, FIELD_SIZE);
        sy = mulmod(sy, dx, FIELD_SIZE);
        sz = mulmod(dx, dy, FIELD_SIZE);
      } else {
        // Already over a common denominator, use that for z ordinate
        sz = dx;
      }
    }
  }

  // p1+p2, as affine points on secp256k1.
  //
  // invZ must be the inverse of the z returned by projectiveECAdd(p1, p2).
  // It is computed off-chain to save gas.
  //
  // p1 and p2 must be distinct, because projectiveECAdd doesn't handle
  // point doubling.
  function affineECAdd(
    uint256[2] memory p1,
    uint256[2] memory p2,
    uint256 invZ
  ) internal pure returns (uint256[2] memory) {
    uint256 x;
    uint256 y;
    uint256 z;
    (x, y, z) = projectiveECAdd(p1[0], p1[1], p2[0], p2[1]);
    require(mulmod(z, invZ, FIELD_SIZE) == 1, "invZ must be inverse of z");
    // Clear the z ordinate of the projective representation by dividing through
    // by it, to obtain the affine representation
    return [mulmod(x, invZ, FIELD_SIZE), mulmod(y, invZ, FIELD_SIZE)];
  }

  // True iff address(c*p+s*g) == lcWitness, where g is generator. (With
  // cryptographically high probability.)
  function verifyLinearCombinationWithGenerator(
    uint256 c,
    uint256[2] memory p,
    uint256 s,
    address lcWitness
  ) internal pure returns (bool) {
    // Rule out ecrecover failure modes which return address 0.
    unchecked {
      require(lcWitness != address(0), "bad witness");
      uint8 v = (p[1] % 2 == 0) ? 27 : 28; // parity of y-ordinate of p
      // Note this cannot wrap (X - Y % X), but we use unchecked to save
      // gas.
      bytes32 pseudoHash = bytes32(GROUP_ORDER - mulmod(p[0], s, GROUP_ORDER)); // -s*p[0]
      bytes32 pseudoSignature = bytes32(mulmod(c, p[0], GROUP_ORDER)); // c*p[0]
      // https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384/9
      // The point corresponding to the address returned by
      // ecrecover(-s*p[0],v,p[0],c*p[0]) is
      // (p[0]⁻¹ mod GROUP_ORDER)*(c*p[0]-(-s)*p[0]*g)=c*p+s*g.
      // See https://crypto.stackexchange.com/a/18106
      // https://bitcoin.stackexchange.com/questions/38351/ecdsa-v-r-s-what-is-v
      address computed = ecrecover(pseudoHash, v, bytes32(p[0]), pseudoSignature);
      return computed == lcWitness;
    }
  }

  // c*p1 + s*p2. Requires cp1Witness=c*p1 and sp2Witness=s*p2. Also
  // requires cp1Witness != sp2Witness (which is fine for this application,
  // since it is cryptographically impossible for them to be equal. In the
  // (cryptographically impossible) case that a prover accidentally derives
  // a proof with equal c*p1 and s*p2, they should retry with a different
  // proof nonce.) Assumes that all points are on secp256k1
  // (which is checked in verifyVRFProof below.)
  function linearCombination(
    uint256 c,
    uint256[2] memory p1,
    uint256[2] memory cp1Witness,
    uint256 s,
    uint256[2] memory p2,
    uint256[2] memory sp2Witness,
    uint256 zInv
  ) internal pure returns (uint256[2] memory) {
    unchecked {
      // Note we are relying on the wrap around here
      require((cp1Witness[0] % FIELD_SIZE) != (sp2Witness[0] % FIELD_SIZE), "points in sum must be distinct");
      require(ecmulVerify(p1, c, cp1Witness), "First mul check failed");
      require(ecmulVerify(p2, s, sp2Witness), "Second mul check failed");
      return affineECAdd(cp1Witness, sp2Witness, zInv);
    }
  }

  // Domain-separation tag for the hash taken in scalarFromCurvePoints.
  // Corresponds to scalarFromCurveHashPrefix in vrf.go
  uint256 internal constant SCALAR_FROM_CURVE_POINTS_HASH_PREFIX = 2;

  // Pseudo-random number from inputs. Matches vrf.go/scalarFromCurvePoints, and
  // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.4.3
  // The draft calls (in step 7, via the definition of string_to_int, in
  // https://datatracker.ietf.org/doc/html/rfc8017#section-4.2 ) for taking the
  // first hash without checking that it corresponds to a number less than the
  // group order, which will lead to a slight bias in the sample.
  //
  // TODO(alx): We could save a bit of gas by following the standard here and
  // using the compressed representation of the points, if we collated the y
  // parities into a single bytes32.
  // https://www.pivotaltracker.com/story/show/171120588
  function scalarFromCurvePoints(
    uint256[2] memory hash,
    uint256[2] memory pk,
    uint256[2] memory gamma,
    address uWitness,
    uint256[2] memory v
  ) internal pure returns (uint256 s) {
    return uint256(keccak256(abi.encodePacked(SCALAR_FROM_CURVE_POINTS_HASH_PREFIX, hash, pk, gamma, v, uWitness)));
  }

  // True if (gamma, c, s) is a correctly constructed randomness proof from pk
  // and seed. zInv must be the inverse of the third ordinate from
  // projectiveECAdd applied to cGammaWitness and sHashWitness. Corresponds to
  // section 5.3 of the IETF draft.
  //
  // TODO(alx): Since I'm only using pk in the ecrecover call, I could only pass
  // the x ordinate, and the parity of the y ordinate in the top bit of uWitness
  // (which I could make a uint256 without using any extra space.) Would save
  // about 2000 gas. https://www.pivotaltracker.com/story/show/170828567
  function verifyVRFProof(
    uint256[2] memory pk,
    uint256[2] memory gamma,
    uint256 c,
    uint256 s,
    uint256 seed,
    address uWitness,
    uint256[2] memory cGammaWitness,
    uint256[2] memory sHashWitness,
    uint256 zInv
  ) internal view {
    unchecked {
      require(isOnCurve(pk), "public key is not on curve");
      require(isOnCurve(gamma), "gamma is not on curve");
      require(isOnCurve(cGammaWitness), "cGammaWitness is not on curve");
      require(isOnCurve(sHashWitness), "sHashWitness is not on curve");
      // Step 5. of IETF draft section 5.3 (pk corresponds to 5.3's Y, and here
      // we use the address of u instead of u itself. Also, here we add the
      // terms instead of taking the difference, and in the proof construction in
      // vrf.GenerateProof, we correspondingly take the difference instead of
      // taking the sum as they do in step 7 of section 5.1.)
      require(verifyLinearCombinationWithGenerator(c, pk, s, uWitness), "addr(c*pk+s*g)!=_uWitness");
      // Step 4. of IETF draft section 5.3 (pk corresponds to Y, seed to alpha_string)
      uint256[2] memory hash = hashToCurve(pk, seed);
      // Step 6. of IETF draft section 5.3, but see note for step 5 about +/- terms
      uint256[2] memory v = linearCombination(c, gamma, cGammaWitness, s, hash, sHashWitness, zInv);
      // Steps 7. and 8. of IETF draft section 5.3
      uint256 derivedC = scalarFromCurvePoints(hash, pk, gamma, uWitness, v);
      require(c == derivedC, "invalid proof");
    }
  }

  // Domain-separation tag for the hash used as the final VRF output.
  // Corresponds to vrfRandomOutputHashPrefix in vrf.go
  uint256 internal constant VRF_RANDOM_OUTPUT_HASH_PREFIX = 3;

  struct Proof {
    uint256[2] pk;
    uint256[2] gamma;
    uint256 c;
    uint256 s;
    uint256 seed;
    address uWitness;
    uint256[2] cGammaWitness;
    uint256[2] sHashWitness;
    uint256 zInv;
  }

  /* ***************************************************************************
     * @notice Returns proof's output, if proof is valid. Otherwise reverts

     * @param proof vrf proof components
     * @param seed  seed used to generate the vrf output
     *
     * Throws if proof is invalid, otherwise:
     * @return output i.e., the random output implied by the proof
     * ***************************************************************************
     */
  function randomValueFromVRFProof(Proof memory proof, uint256 seed) internal view returns (uint256 output) {
    verifyVRFProof(
      proof.pk,
      proof.gamma,
      proof.c,
      proof.s,
      seed,
      proof.uWitness,
      proof.cGammaWitness,
      proof.sHashWitness,
      proof.zInv
    );
    output = uint256(keccak256(abi.encode(VRF_RANDOM_OUTPUT_HASH_PREFIX, proof.gamma)));
  }
}

File 15 of 52 : VRFConsumerBaseV2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/** ****************************************************************************
 * @notice Interface for contracts using VRF randomness
 * *****************************************************************************
 * @dev PURPOSE
 *
 * @dev Reggie the Random Oracle (not his real job) wants to provide randomness
 * @dev to Vera the verifier in such a way that Vera can be sure he's not
 * @dev making his output up to suit himself. Reggie provides Vera a public key
 * @dev to which he knows the secret key. Each time Vera provides a seed to
 * @dev Reggie, he gives back a value which is computed completely
 * @dev deterministically from the seed and the secret key.
 *
 * @dev Reggie provides a proof by which Vera can verify that the output was
 * @dev correctly computed once Reggie tells it to her, but without that proof,
 * @dev the output is indistinguishable to her from a uniform random sample
 * @dev from the output space.
 *
 * @dev The purpose of this contract is to make it easy for unrelated contracts
 * @dev to talk to Vera the verifier about the work Reggie is doing, to provide
 * @dev simple access to a verifiable source of randomness. It ensures 2 things:
 * @dev 1. The fulfillment came from the VRFCoordinator
 * @dev 2. The consumer contract implements fulfillRandomWords.
 * *****************************************************************************
 * @dev USAGE
 *
 * @dev Calling contracts must inherit from VRFConsumerBase, and can
 * @dev initialize VRFConsumerBase's attributes in their constructor as
 * @dev shown:
 *
 * @dev   contract VRFConsumer {
 * @dev     constructor(<other arguments>, address _vrfCoordinator, address _link)
 * @dev       VRFConsumerBase(_vrfCoordinator) public {
 * @dev         <initialization with other arguments goes here>
 * @dev       }
 * @dev   }
 *
 * @dev The oracle will have given you an ID for the VRF keypair they have
 * @dev committed to (let's call it keyHash). Create subscription, fund it
 * @dev and your consumer contract as a consumer of it (see VRFCoordinatorInterface
 * @dev subscription management functions).
 * @dev Call requestRandomWords(keyHash, subId, minimumRequestConfirmations,
 * @dev callbackGasLimit, numWords),
 * @dev see (VRFCoordinatorInterface for a description of the arguments).
 *
 * @dev Once the VRFCoordinator has received and validated the oracle's response
 * @dev to your request, it will call your contract's fulfillRandomWords method.
 *
 * @dev The randomness argument to fulfillRandomWords is a set of random words
 * @dev generated from your requestId and the blockHash of the request.
 *
 * @dev If your contract could have concurrent requests open, you can use the
 * @dev requestId returned from requestRandomWords to track which response is associated
 * @dev with which randomness request.
 * @dev See "SECURITY CONSIDERATIONS" for principles to keep in mind,
 * @dev if your contract could have multiple requests in flight simultaneously.
 *
 * @dev Colliding `requestId`s are cryptographically impossible as long as seeds
 * @dev differ.
 *
 * *****************************************************************************
 * @dev SECURITY CONSIDERATIONS
 *
 * @dev A method with the ability to call your fulfillRandomness method directly
 * @dev could spoof a VRF response with any random value, so it's critical that
 * @dev it cannot be directly called by anything other than this base contract
 * @dev (specifically, by the VRFConsumerBase.rawFulfillRandomness method).
 *
 * @dev For your users to trust that your contract's random behavior is free
 * @dev from malicious interference, it's best if you can write it so that all
 * @dev behaviors implied by a VRF response are executed *during* your
 * @dev fulfillRandomness method. If your contract must store the response (or
 * @dev anything derived from it) and use it later, you must ensure that any
 * @dev user-significant behavior which depends on that stored value cannot be
 * @dev manipulated by a subsequent VRF request.
 *
 * @dev Similarly, both miners and the VRF oracle itself have some influence
 * @dev over the order in which VRF responses appear on the blockchain, so if
 * @dev your contract could have multiple VRF requests in flight simultaneously,
 * @dev you must ensure that the order in which the VRF responses arrive cannot
 * @dev be used to manipulate your contract's user-significant behavior.
 *
 * @dev Since the block hash of the block which contains the requestRandomness
 * @dev call is mixed into the input to the VRF *last*, a sufficiently powerful
 * @dev miner could, in principle, fork the blockchain to evict the block
 * @dev containing the request, forcing the request to be included in a
 * @dev different block with a different hash, and therefore a different input
 * @dev to the VRF. However, such an attack would incur a substantial economic
 * @dev cost. This cost scales with the number of blocks the VRF oracle waits
 * @dev until it calls responds to a request. It is for this reason that
 * @dev that you can signal to an oracle you'd like them to wait longer before
 * @dev responding to the request (however this is not enforced in the contract
 * @dev and so remains effective only in the case of unmodified oracle software).
 */
abstract contract VRFConsumerBaseV2 {
  error OnlyCoordinatorCanFulfill(address have, address want);
  address private immutable vrfCoordinator;

  /**
   * @param _vrfCoordinator address of VRFCoordinator contract
   */
  constructor(address _vrfCoordinator) {
    vrfCoordinator = _vrfCoordinator;
  }

  /**
   * @notice fulfillRandomness handles the VRF response. Your contract must
   * @notice implement it. See "SECURITY CONSIDERATIONS" above for important
   * @notice principles to keep in mind when implementing your fulfillRandomness
   * @notice method.
   *
   * @dev VRFConsumerBaseV2 expects its subcontracts to have a method with this
   * @dev signature, and will call it once it has verified the proof
   * @dev associated with the randomness. (It is triggered via a call to
   * @dev rawFulfillRandomness, below.)
   *
   * @param requestId The Id initially returned by requestRandomness
   * @param randomWords the VRF output expanded to the requested number of words
   */
  function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal virtual;

  // rawFulfillRandomness is called by VRFCoordinator when it receives a valid VRF
  // proof. rawFulfillRandomness then calls fulfillRandomness, after validating
  // the origin of the call
  function rawFulfillRandomWords(uint256 requestId, uint256[] memory randomWords) external {
    if (msg.sender != vrfCoordinator) {
      revert OnlyCoordinatorCanFulfill(msg.sender, vrfCoordinator);
    }
    fulfillRandomWords(requestId, randomWords);
  }
}

File 16 of 52 : VRFCoordinatorV2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "../shared/interfaces/LinkTokenInterface.sol";
import "../interfaces/BlockhashStoreInterface.sol";
import "../interfaces/AggregatorV3Interface.sol";
import "../interfaces/VRFCoordinatorV2Interface.sol";
import "../interfaces/TypeAndVersionInterface.sol";
import "../shared/interfaces/IERC677Receiver.sol";
import "./VRF.sol";
import "../shared/access/ConfirmedOwner.sol";
import "./VRFConsumerBaseV2.sol";
import "../ChainSpecificUtil.sol";

contract VRFCoordinatorV2 is VRF, ConfirmedOwner, TypeAndVersionInterface, VRFCoordinatorV2Interface, IERC677Receiver {
  LinkTokenInterface public immutable LINK;
  AggregatorV3Interface public immutable LINK_ETH_FEED;
  BlockhashStoreInterface public immutable BLOCKHASH_STORE;

  // We need to maintain a list of consuming addresses.
  // This bound ensures we are able to loop over them as needed.
  // Should a user require more consumers, they can use multiple subscriptions.
  uint16 public constant MAX_CONSUMERS = 100;
  error TooManyConsumers();
  error InsufficientBalance();
  error InvalidConsumer(uint64 subId, address consumer);
  error InvalidSubscription();
  error OnlyCallableFromLink();
  error InvalidCalldata();
  error MustBeSubOwner(address owner);
  error PendingRequestExists();
  error MustBeRequestedOwner(address proposedOwner);
  error BalanceInvariantViolated(uint256 internalBalance, uint256 externalBalance); // Should never happen
  event FundsRecovered(address to, uint256 amount);
  // We use the subscription struct (1 word)
  // at fulfillment time.
  struct Subscription {
    // There are only 1e9*1e18 = 1e27 juels in existence, so the balance can fit in uint96 (2^96 ~ 7e28)
    uint96 balance; // Common link balance used for all consumer requests.
    uint64 reqCount; // For fee tiers
  }
  // We use the config for the mgmt APIs
  struct SubscriptionConfig {
    address owner; // Owner can fund/withdraw/cancel the sub.
    address requestedOwner; // For safely transferring sub ownership.
    // Maintains the list of keys in s_consumers.
    // We do this for 2 reasons:
    // 1. To be able to clean up all keys from s_consumers when canceling a subscription.
    // 2. To be able to return the list of all consumers in getSubscription.
    // Note that we need the s_consumers map to be able to directly check if a
    // consumer is valid without reading all the consumers from storage.
    address[] consumers;
  }
  // Note a nonce of 0 indicates an the consumer is not assigned to that subscription.
  mapping(address => mapping(uint64 => uint64)) /* consumer */ /* subId */ /* nonce */ private s_consumers;
  mapping(uint64 => SubscriptionConfig) /* subId */ /* subscriptionConfig */ private s_subscriptionConfigs;
  mapping(uint64 => Subscription) /* subId */ /* subscription */ private s_subscriptions;
  // We make the sub count public so that its possible to
  // get all the current subscriptions via getSubscription.
  uint64 private s_currentSubId;
  // s_totalBalance tracks the total link sent to/from
  // this contract through onTokenTransfer, cancelSubscription and oracleWithdraw.
  // A discrepancy with this contract's link balance indicates someone
  // sent tokens using transfer and so we may need to use recoverFunds.
  uint96 private s_totalBalance;
  event SubscriptionCreated(uint64 indexed subId, address owner);
  event SubscriptionFunded(uint64 indexed subId, uint256 oldBalance, uint256 newBalance);
  event SubscriptionConsumerAdded(uint64 indexed subId, address consumer);
  event SubscriptionConsumerRemoved(uint64 indexed subId, address consumer);
  event SubscriptionCanceled(uint64 indexed subId, address to, uint256 amount);
  event SubscriptionOwnerTransferRequested(uint64 indexed subId, address from, address to);
  event SubscriptionOwnerTransferred(uint64 indexed subId, address from, address to);

  // Set this maximum to 200 to give us a 56 block window to fulfill
  // the request before requiring the block hash feeder.
  uint16 public constant MAX_REQUEST_CONFIRMATIONS = 200;
  uint32 public constant MAX_NUM_WORDS = 500;
  // 5k is plenty for an EXTCODESIZE call (2600) + warm CALL (100)
  // and some arithmetic operations.
  uint256 private constant GAS_FOR_CALL_EXACT_CHECK = 5_000;
  error InvalidRequestConfirmations(uint16 have, uint16 min, uint16 max);
  error GasLimitTooBig(uint32 have, uint32 want);
  error NumWordsTooBig(uint32 have, uint32 want);
  error ProvingKeyAlreadyRegistered(bytes32 keyHash);
  error NoSuchProvingKey(bytes32 keyHash);
  error InvalidLinkWeiPrice(int256 linkWei);
  error InsufficientGasForConsumer(uint256 have, uint256 want);
  error NoCorrespondingRequest();
  error IncorrectCommitment();
  error BlockhashNotInStore(uint256 blockNum);
  error PaymentTooLarge();
  error Reentrant();
  struct RequestCommitment {
    uint64 blockNum;
    uint64 subId;
    uint32 callbackGasLimit;
    uint32 numWords;
    address sender;
  }
  mapping(bytes32 => address) /* keyHash */ /* oracle */ private s_provingKeys;
  bytes32[] private s_provingKeyHashes;
  mapping(address => uint96) /* oracle */ /* LINK balance */ private s_withdrawableTokens;
  mapping(uint256 => bytes32) /* requestID */ /* commitment */ private s_requestCommitments;
  event ProvingKeyRegistered(bytes32 keyHash, address indexed oracle);
  event ProvingKeyDeregistered(bytes32 keyHash, address indexed oracle);
  event RandomWordsRequested(
    bytes32 indexed keyHash,
    uint256 requestId,
    uint256 preSeed,
    uint64 indexed subId,
    uint16 minimumRequestConfirmations,
    uint32 callbackGasLimit,
    uint32 numWords,
    address indexed sender
  );
  event RandomWordsFulfilled(uint256 indexed requestId, uint256 outputSeed, uint96 payment, bool success);

  struct Config {
    uint16 minimumRequestConfirmations;
    uint32 maxGasLimit;
    // Reentrancy protection.
    bool reentrancyLock;
    // stalenessSeconds is how long before we consider the feed price to be stale
    // and fallback to fallbackWeiPerUnitLink.
    uint32 stalenessSeconds;
    // Gas to cover oracle payment after we calculate the payment.
    // We make it configurable in case those operations are repriced.
    uint32 gasAfterPaymentCalculation;
  }
  int256 private s_fallbackWeiPerUnitLink;
  Config private s_config;
  FeeConfig private s_feeConfig;
  struct FeeConfig {
    // Flat fee charged per fulfillment in millionths of link
    // So fee range is [0, 2^32/10^6].
    uint32 fulfillmentFlatFeeLinkPPMTier1;
    uint32 fulfillmentFlatFeeLinkPPMTier2;
    uint32 fulfillmentFlatFeeLinkPPMTier3;
    uint32 fulfillmentFlatFeeLinkPPMTier4;
    uint32 fulfillmentFlatFeeLinkPPMTier5;
    uint24 reqsForTier2;
    uint24 reqsForTier3;
    uint24 reqsForTier4;
    uint24 reqsForTier5;
  }
  event ConfigSet(
    uint16 minimumRequestConfirmations,
    uint32 maxGasLimit,
    uint32 stalenessSeconds,
    uint32 gasAfterPaymentCalculation,
    int256 fallbackWeiPerUnitLink,
    FeeConfig feeConfig
  );

  constructor(address link, address blockhashStore, address linkEthFeed) ConfirmedOwner(msg.sender) {
    LINK = LinkTokenInterface(link);
    LINK_ETH_FEED = AggregatorV3Interface(linkEthFeed);
    BLOCKHASH_STORE = BlockhashStoreInterface(blockhashStore);
  }

  /**
   * @notice Registers a proving key to an oracle.
   * @param oracle address of the oracle
   * @param publicProvingKey key that oracle can use to submit vrf fulfillments
   */
  function registerProvingKey(address oracle, uint256[2] calldata publicProvingKey) external onlyOwner {
    bytes32 kh = hashOfKey(publicProvingKey);
    if (s_provingKeys[kh] != address(0)) {
      revert ProvingKeyAlreadyRegistered(kh);
    }
    s_provingKeys[kh] = oracle;
    s_provingKeyHashes.push(kh);
    emit ProvingKeyRegistered(kh, oracle);
  }

  /**
   * @notice Deregisters a proving key to an oracle.
   * @param publicProvingKey key that oracle can use to submit vrf fulfillments
   */
  function deregisterProvingKey(uint256[2] calldata publicProvingKey) external onlyOwner {
    bytes32 kh = hashOfKey(publicProvingKey);
    address oracle = s_provingKeys[kh];
    if (oracle == address(0)) {
      revert NoSuchProvingKey(kh);
    }
    delete s_provingKeys[kh];
    for (uint256 i = 0; i < s_provingKeyHashes.length; i++) {
      if (s_provingKeyHashes[i] == kh) {
        bytes32 last = s_provingKeyHashes[s_provingKeyHashes.length - 1];
        // Copy last element and overwrite kh to be deleted with it
        s_provingKeyHashes[i] = last;
        s_provingKeyHashes.pop();
      }
    }
    emit ProvingKeyDeregistered(kh, oracle);
  }

  /**
   * @notice Returns the proving key hash key associated with this public key
   * @param publicKey the key to return the hash of
   */
  function hashOfKey(uint256[2] memory publicKey) public pure returns (bytes32) {
    return keccak256(abi.encode(publicKey));
  }

  /**
   * @notice Sets the configuration of the vrfv2 coordinator
   * @param minimumRequestConfirmations global min for request confirmations
   * @param maxGasLimit global max for request gas limit
   * @param stalenessSeconds if the eth/link feed is more stale then this, use the fallback price
   * @param gasAfterPaymentCalculation gas used in doing accounting after completing the gas measurement
   * @param fallbackWeiPerUnitLink fallback eth/link price in the case of a stale feed
   * @param feeConfig fee tier configuration
   */
  function setConfig(
    uint16 minimumRequestConfirmations,
    uint32 maxGasLimit,
    uint32 stalenessSeconds,
    uint32 gasAfterPaymentCalculation,
    int256 fallbackWeiPerUnitLink,
    FeeConfig memory feeConfig
  ) external onlyOwner {
    if (minimumRequestConfirmations > MAX_REQUEST_CONFIRMATIONS) {
      revert InvalidRequestConfirmations(
        minimumRequestConfirmations,
        minimumRequestConfirmations,
        MAX_REQUEST_CONFIRMATIONS
      );
    }
    if (fallbackWeiPerUnitLink <= 0) {
      revert InvalidLinkWeiPrice(fallbackWeiPerUnitLink);
    }
    s_config = Config({
      minimumRequestConfirmations: minimumRequestConfirmations,
      maxGasLimit: maxGasLimit,
      stalenessSeconds: stalenessSeconds,
      gasAfterPaymentCalculation: gasAfterPaymentCalculation,
      reentrancyLock: false
    });
    s_feeConfig = feeConfig;
    s_fallbackWeiPerUnitLink = fallbackWeiPerUnitLink;
    emit ConfigSet(
      minimumRequestConfirmations,
      maxGasLimit,
      stalenessSeconds,
      gasAfterPaymentCalculation,
      fallbackWeiPerUnitLink,
      s_feeConfig
    );
  }

  function getConfig()
    external
    view
    returns (
      uint16 minimumRequestConfirmations,
      uint32 maxGasLimit,
      uint32 stalenessSeconds,
      uint32 gasAfterPaymentCalculation
    )
  {
    return (
      s_config.minimumRequestConfirmations,
      s_config.maxGasLimit,
      s_config.stalenessSeconds,
      s_config.gasAfterPaymentCalculation
    );
  }

  function getFeeConfig()
    external
    view
    returns (
      uint32 fulfillmentFlatFeeLinkPPMTier1,
      uint32 fulfillmentFlatFeeLinkPPMTier2,
      uint32 fulfillmentFlatFeeLinkPPMTier3,
      uint32 fulfillmentFlatFeeLinkPPMTier4,
      uint32 fulfillmentFlatFeeLinkPPMTier5,
      uint24 reqsForTier2,
      uint24 reqsForTier3,
      uint24 reqsForTier4,
      uint24 reqsForTier5
    )
  {
    return (
      s_feeConfig.fulfillmentFlatFeeLinkPPMTier1,
      s_feeConfig.fulfillmentFlatFeeLinkPPMTier2,
      s_feeConfig.fulfillmentFlatFeeLinkPPMTier3,
      s_feeConfig.fulfillmentFlatFeeLinkPPMTier4,
      s_feeConfig.fulfillmentFlatFeeLinkPPMTier5,
      s_feeConfig.reqsForTier2,
      s_feeConfig.reqsForTier3,
      s_feeConfig.reqsForTier4,
      s_feeConfig.reqsForTier5
    );
  }

  function getTotalBalance() external view returns (uint256) {
    return s_totalBalance;
  }

  function getFallbackWeiPerUnitLink() external view returns (int256) {
    return s_fallbackWeiPerUnitLink;
  }

  /**
   * @notice Owner cancel subscription, sends remaining link directly to the subscription owner.
   * @param subId subscription id
   * @dev notably can be called even if there are pending requests, outstanding ones may fail onchain
   */
  function ownerCancelSubscription(uint64 subId) external onlyOwner {
    if (s_subscriptionConfigs[subId].owner == address(0)) {
      revert InvalidSubscription();
    }
    cancelSubscriptionHelper(subId, s_subscriptionConfigs[subId].owner);
  }

  /**
   * @notice Recover link sent with transfer instead of transferAndCall.
   * @param to address to send link to
   */
  function recoverFunds(address to) external onlyOwner {
    uint256 externalBalance = LINK.balanceOf(address(this));
    uint256 internalBalance = uint256(s_totalBalance);
    if (internalBalance > externalBalance) {
      revert BalanceInvariantViolated(internalBalance, externalBalance);
    }
    if (internalBalance < externalBalance) {
      uint256 amount = externalBalance - internalBalance;
      LINK.transfer(to, amount);
      emit FundsRecovered(to, amount);
    }
    // If the balances are equal, nothing to be done.
  }

  /**
   * @inheritdoc VRFCoordinatorV2Interface
   */
  function getRequestConfig() external view override returns (uint16, uint32, bytes32[] memory) {
    return (s_config.minimumRequestConfirmations, s_config.maxGasLimit, s_provingKeyHashes);
  }

  /**
   * @inheritdoc VRFCoordinatorV2Interface
   */
  function requestRandomWords(
    bytes32 keyHash,
    uint64 subId,
    uint16 requestConfirmations,
    uint32 callbackGasLimit,
    uint32 numWords
  ) external override nonReentrant returns (uint256) {
    // Input validation using the subscription storage.
    if (s_subscriptionConfigs[subId].owner == address(0)) {
      revert InvalidSubscription();
    }
    // Its important to ensure that the consumer is in fact who they say they
    // are, otherwise they could use someone else's subscription balance.
    // A nonce of 0 indicates consumer is not allocated to the sub.
    uint64 currentNonce = s_consumers[msg.sender][subId];
    if (currentNonce == 0) {
      revert InvalidConsumer(subId, msg.sender);
    }
    // Input validation using the config storage word.
    if (
      requestConfirmations < s_config.minimumRequestConfirmations || requestConfirmations > MAX_REQUEST_CONFIRMATIONS
    ) {
      revert InvalidRequestConfirmations(
        requestConfirmations,
        s_config.minimumRequestConfirmations,
        MAX_REQUEST_CONFIRMATIONS
      );
    }
    // No lower bound on the requested gas limit. A user could request 0
    // and they would simply be billed for the proof verification and wouldn't be
    // able to do anything with the random value.
    if (callbackGasLimit > s_config.maxGasLimit) {
      revert GasLimitTooBig(callbackGasLimit, s_config.maxGasLimit);
    }
    if (numWords > MAX_NUM_WORDS) {
      revert NumWordsTooBig(numWords, MAX_NUM_WORDS);
    }
    // Note we do not check whether the keyHash is valid to save gas.
    // The consequence for users is that they can send requests
    // for invalid keyHashes which will simply not be fulfilled.
    uint64 nonce = currentNonce + 1;
    (uint256 requestId, uint256 preSeed) = computeRequestId(keyHash, msg.sender, subId, nonce);

    s_requestCommitments[requestId] = keccak256(
      abi.encode(requestId, ChainSpecificUtil.getBlockNumber(), subId, callbackGasLimit, numWords, msg.sender)
    );
    emit RandomWordsRequested(
      keyHash,
      requestId,
      preSeed,
      subId,
      requestConfirmations,
      callbackGasLimit,
      numWords,
      msg.sender
    );
    s_consumers[msg.sender][subId] = nonce;

    return requestId;
  }

  /**
   * @notice Get request commitment
   * @param requestId id of request
   * @dev used to determine if a request is fulfilled or not
   */
  function getCommitment(uint256 requestId) external view returns (bytes32) {
    return s_requestCommitments[requestId];
  }

  function computeRequestId(
    bytes32 keyHash,
    address sender,
    uint64 subId,
    uint64 nonce
  ) private pure returns (uint256, uint256) {
    uint256 preSeed = uint256(keccak256(abi.encode(keyHash, sender, subId, nonce)));
    return (uint256(keccak256(abi.encode(keyHash, preSeed))), preSeed);
  }

  /**
   * @dev calls target address with exactly gasAmount gas and data as calldata
   * or reverts if at least gasAmount gas is not available.
   */
  function callWithExactGas(uint256 gasAmount, address target, bytes memory data) private returns (bool success) {
    // solhint-disable-next-line no-inline-assembly
    assembly {
      let g := gas()
      // Compute g -= GAS_FOR_CALL_EXACT_CHECK and check for underflow
      // The gas actually passed to the callee is min(gasAmount, 63//64*gas available).
      // We want to ensure that we revert if gasAmount >  63//64*gas available
      // as we do not want to provide them with less, however that check itself costs
      // gas.  GAS_FOR_CALL_EXACT_CHECK ensures we have at least enough gas to be able
      // to revert if gasAmount >  63//64*gas available.
      if lt(g, GAS_FOR_CALL_EXACT_CHECK) {
        revert(0, 0)
      }
      g := sub(g, GAS_FOR_CALL_EXACT_CHECK)
      // if g - g//64 <= gasAmount, revert
      // (we subtract g//64 because of EIP-150)
      if iszero(gt(sub(g, div(g, 64)), gasAmount)) {
        revert(0, 0)
      }
      // solidity calls check that a contract actually exists at the destination, so we do the same
      if iszero(extcodesize(target)) {
        revert(0, 0)
      }
      // call and return whether we succeeded. ignore return data
      // call(gas,addr,value,argsOffset,argsLength,retOffset,retLength)
      success := call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0)
    }
    return success;
  }

  function getRandomnessFromProof(
    Proof memory proof,
    RequestCommitment memory rc
  ) private view returns (bytes32 keyHash, uint256 requestId, uint256 randomness) {
    keyHash = hashOfKey(proof.pk);
    // Only registered proving keys are permitted.
    address oracle = s_provingKeys[keyHash];
    if (oracle == address(0)) {
      revert NoSuchProvingKey(keyHash);
    }
    requestId = uint256(keccak256(abi.encode(keyHash, proof.seed)));
    bytes32 commitment = s_requestCommitments[requestId];
    if (commitment == 0) {
      revert NoCorrespondingRequest();
    }
    if (
      commitment != keccak256(abi.encode(requestId, rc.blockNum, rc.subId, rc.callbackGasLimit, rc.numWords, rc.sender))
    ) {
      revert IncorrectCommitment();
    }

    bytes32 blockHash = ChainSpecificUtil.getBlockhash(rc.blockNum);
    if (blockHash == bytes32(0)) {
      blockHash = BLOCKHASH_STORE.getBlockhash(rc.blockNum);
      if (blockHash == bytes32(0)) {
        revert BlockhashNotInStore(rc.blockNum);
      }
    }

    // The seed actually used by the VRF machinery, mixing in the blockhash
    uint256 actualSeed = uint256(keccak256(abi.encodePacked(proof.seed, blockHash)));
    randomness = VRF.randomValueFromVRFProof(proof, actualSeed); // Reverts on failure
  }

  /*
   * @notice Compute fee based on the request count
   * @param reqCount number of requests
   * @return feePPM fee in LINK PPM
   */
  function getFeeTier(uint64 reqCount) public view returns (uint32) {
    FeeConfig memory fc = s_feeConfig;
    if (0 <= reqCount && reqCount <= fc.reqsForTier2) {
      return fc.fulfillmentFlatFeeLinkPPMTier1;
    }
    if (fc.reqsForTier2 < reqCount && reqCount <= fc.reqsForTier3) {
      return fc.fulfillmentFlatFeeLinkPPMTier2;
    }
    if (fc.reqsForTier3 < reqCount && reqCount <= fc.reqsForTier4) {
      return fc.fulfillmentFlatFeeLinkPPMTier3;
    }
    if (fc.reqsForTier4 < reqCount && reqCount <= fc.reqsForTier5) {
      return fc.fulfillmentFlatFeeLinkPPMTier4;
    }
    return fc.fulfillmentFlatFeeLinkPPMTier5;
  }

  /*
   * @notice Fulfill a randomness request
   * @param proof contains the proof and randomness
   * @param rc request commitment pre-image, committed to at request time
   * @return payment amount billed to the subscription
   * @dev simulated offchain to determine if sufficient balance is present to fulfill the request
   */
  function fulfillRandomWords(Proof memory proof, RequestCommitment memory rc) external nonReentrant returns (uint96) {
    uint256 startGas = gasleft();
    (bytes32 keyHash, uint256 requestId, uint256 randomness) = getRandomnessFromProof(proof, rc);

    uint256[] memory randomWords = new uint256[](rc.numWords);
    for (uint256 i = 0; i < rc.numWords; i++) {
      randomWords[i] = uint256(keccak256(abi.encode(randomness, i)));
    }

    delete s_requestCommitments[requestId];
    VRFConsumerBaseV2 v;
    bytes memory resp = abi.encodeWithSelector(v.rawFulfillRandomWords.selector, requestId, randomWords);
    // Call with explicitly the amount of callback gas requested
    // Important to not let them exhaust the gas budget and avoid oracle payment.
    // Do not allow any non-view/non-pure coordinator functions to be called
    // during the consumers callback code via reentrancyLock.
    // Note that callWithExactGas will revert if we do not have sufficient gas
    // to give the callee their requested amount.
    s_config.reentrancyLock = true;
    bool success = callWithExactGas(rc.callbackGasLimit, rc.sender, resp);
    s_config.reentrancyLock = false;

    // Increment the req count for fee tier selection.
    uint64 reqCount = s_subscriptions[rc.subId].reqCount;
    s_subscriptions[rc.subId].reqCount += 1;

    // We want to charge users exactly for how much gas they use in their callback.
    // The gasAfterPaymentCalculation is meant to cover these additional operations where we
    // decrement the subscription balance and increment the oracles withdrawable balance.
    // We also add the flat link fee to the payment amount.
    // Its specified in millionths of link, if s_config.fulfillmentFlatFeeLinkPPM = 1
    // 1 link / 1e6 = 1e18 juels / 1e6 = 1e12 juels.
    uint96 payment = calculatePaymentAmount(
      startGas,
      s_config.gasAfterPaymentCalculation,
      getFeeTier(reqCount),
      tx.gasprice
    );
    if (s_subscriptions[rc.subId].balance < payment) {
      revert InsufficientBalance();
    }
    s_subscriptions[rc.subId].balance -= payment;
    s_withdrawableTokens[s_provingKeys[keyHash]] += payment;
    // Include payment in the event for tracking costs.
    emit RandomWordsFulfilled(requestId, randomness, payment, success);
    return payment;
  }

  // Get the amount of gas used for fulfillment
  function calculatePaymentAmount(
    uint256 startGas,
    uint256 gasAfterPaymentCalculation,
    uint32 fulfillmentFlatFeeLinkPPM,
    uint256 weiPerUnitGas
  ) internal view returns (uint96) {
    int256 weiPerUnitLink;
    weiPerUnitLink = getFeedData();
    if (weiPerUnitLink <= 0) {
      revert InvalidLinkWeiPrice(weiPerUnitLink);
    }
    // Will return non-zero on chains that have this enabled
    uint256 l1CostWei = ChainSpecificUtil.getCurrentTxL1GasFees();
    // (1e18 juels/link) ((wei/gas * gas) + l1wei) / (wei/link) = juels
    uint256 paymentNoFee = (1e18 * (weiPerUnitGas * (gasAfterPaymentCalculation + startGas - gasleft()) + l1CostWei)) /
      uint256(weiPerUnitLink);
    uint256 fee = 1e12 * uint256(fulfillmentFlatFeeLinkPPM);
    if (paymentNoFee > (1e27 - fee)) {
      revert PaymentTooLarge(); // Payment + fee cannot be more than all of the link in existence.
    }
    return uint96(paymentNoFee + fee);
  }

  function getFeedData() private view returns (int256) {
    uint32 stalenessSeconds = s_config.stalenessSeconds;
    bool staleFallback = stalenessSeconds > 0;
    uint256 timestamp;
    int256 weiPerUnitLink;
    (, weiPerUnitLink, , timestamp, ) = LINK_ETH_FEED.latestRoundData();
    // solhint-disable-next-line not-rely-on-time
    if (staleFallback && stalenessSeconds < block.timestamp - timestamp) {
      weiPerUnitLink = s_fallbackWeiPerUnitLink;
    }
    return weiPerUnitLink;
  }

  /*
   * @notice Oracle withdraw LINK earned through fulfilling requests
   * @param recipient where to send the funds
   * @param amount amount to withdraw
   */
  function oracleWithdraw(address recipient, uint96 amount) external nonReentrant {
    if (s_withdrawableTokens[msg.sender] < amount) {
      revert InsufficientBalance();
    }
    s_withdrawableTokens[msg.sender] -= amount;
    s_totalBalance -= amount;
    if (!LINK.transfer(recipient, amount)) {
      revert InsufficientBalance();
    }
  }

  function onTokenTransfer(address /* sender */, uint256 amount, bytes calldata data) external override nonReentrant {
    if (msg.sender != address(LINK)) {
      revert OnlyCallableFromLink();
    }
    if (data.length != 32) {
      revert InvalidCalldata();
    }
    uint64 subId = abi.decode(data, (uint64));
    if (s_subscriptionConfigs[subId].owner == address(0)) {
      revert InvalidSubscription();
    }
    // We do not check that the msg.sender is the subscription owner,
    // anyone can fund a subscription.
    uint256 oldBalance = s_subscriptions[subId].balance;
    s_subscriptions[subId].balance += uint96(amount);
    s_totalBalance += uint96(amount);
    emit SubscriptionFunded(subId, oldBalance, oldBalance + amount);
  }

  function getCurrentSubId() external view returns (uint64) {
    return s_currentSubId;
  }

  /**
   * @inheritdoc VRFCoordinatorV2Interface
   */
  function getSubscription(
    uint64 subId
  ) external view override returns (uint96 balance, uint64 reqCount, address owner, address[] memory consumers) {
    if (s_subscriptionConfigs[subId].owner == address(0)) {
      revert InvalidSubscription();
    }
    return (
      s_subscriptions[subId].balance,
      s_subscriptions[subId].reqCount,
      s_subscriptionConfigs[subId].owner,
      s_subscriptionConfigs[subId].consumers
    );
  }

  /**
   * @inheritdoc VRFCoordinatorV2Interface
   */
  function createSubscription() external override nonReentrant returns (uint64) {
    s_currentSubId++;
    uint64 currentSubId = s_currentSubId;
    address[] memory consumers = new address[](0);
    s_subscriptions[currentSubId] = Subscription({balance: 0, reqCount: 0});
    s_subscriptionConfigs[currentSubId] = SubscriptionConfig({
      owner: msg.sender,
      requestedOwner: address(0),
      consumers: consumers
    });

    emit SubscriptionCreated(currentSubId, msg.sender);
    return currentSubId;
  }

  /**
   * @inheritdoc VRFCoordinatorV2Interface
   */
  function requestSubscriptionOwnerTransfer(
    uint64 subId,
    address newOwner
  ) external override onlySubOwner(subId) nonReentrant {
    // Proposing to address(0) would never be claimable so don't need to check.
    if (s_subscriptionConfigs[subId].requestedOwner != newOwner) {
      s_subscriptionConfigs[subId].requestedOwner = newOwner;
      emit SubscriptionOwnerTransferRequested(subId, msg.sender, newOwner);
    }
  }

  /**
   * @inheritdoc VRFCoordinatorV2Interface
   */
  function acceptSubscriptionOwnerTransfer(uint64 subId) external override nonReentrant {
    if (s_subscriptionConfigs[subId].owner == address(0)) {
      revert InvalidSubscription();
    }
    if (s_subscriptionConfigs[subId].requestedOwner != msg.sender) {
      revert MustBeRequestedOwner(s_subscriptionConfigs[subId].requestedOwner);
    }
    address oldOwner = s_subscriptionConfigs[subId].owner;
    s_subscriptionConfigs[subId].owner = msg.sender;
    s_subscriptionConfigs[subId].requestedOwner = address(0);
    emit SubscriptionOwnerTransferred(subId, oldOwner, msg.sender);
  }

  /**
   * @inheritdoc VRFCoordinatorV2Interface
   */
  function removeConsumer(uint64 subId, address consumer) external override onlySubOwner(subId) nonReentrant {
    if (pendingRequestExists(subId)) {
      revert PendingRequestExists();
    }
    if (s_consumers[consumer][subId] == 0) {
      revert InvalidConsumer(subId, consumer);
    }
    // Note bounded by MAX_CONSUMERS
    address[] memory consumers = s_subscriptionConfigs[subId].consumers;
    uint256 lastConsumerIndex = consumers.length - 1;
    for (uint256 i = 0; i < consumers.length; i++) {
      if (consumers[i] == consumer) {
        address last = consumers[lastConsumerIndex];
        // Storage write to preserve last element
        s_subscriptionConfigs[subId].consumers[i] = last;
        // Storage remove last element
        s_subscriptionConfigs[subId].consumers.pop();
        break;
      }
    }
    delete s_consumers[consumer][subId];
    emit SubscriptionConsumerRemoved(subId, consumer);
  }

  /**
   * @inheritdoc VRFCoordinatorV2Interface
   */
  function addConsumer(uint64 subId, address consumer) external override onlySubOwner(subId) nonReentrant {
    // Already maxed, cannot add any more consumers.
    if (s_subscriptionConfigs[subId].consumers.length == MAX_CONSUMERS) {
      revert TooManyConsumers();
    }
    if (s_consumers[consumer][subId] != 0) {
      // Idempotence - do nothing if already added.
      // Ensures uniqueness in s_subscriptions[subId].consumers.
      return;
    }
    // Initialize the nonce to 1, indicating the consumer is allocated.
    s_consumers[consumer][subId] = 1;
    s_subscriptionConfigs[subId].consumers.push(consumer);

    emit SubscriptionConsumerAdded(subId, consumer);
  }

  /**
   * @inheritdoc VRFCoordinatorV2Interface
   */
  function cancelSubscription(uint64 subId, address to) external override onlySubOwner(subId) nonReentrant {
    if (pendingRequestExists(subId)) {
      revert PendingRequestExists();
    }
    cancelSubscriptionHelper(subId, to);
  }

  function cancelSubscriptionHelper(uint64 subId, address to) private nonReentrant {
    SubscriptionConfig memory subConfig = s_subscriptionConfigs[subId];
    Subscription memory sub = s_subscriptions[subId];
    uint96 balance = sub.balance;
    // Note bounded by MAX_CONSUMERS;
    // If no consumers, does nothing.
    for (uint256 i = 0; i < subConfig.consumers.length; i++) {
      delete s_consumers[subConfig.consumers[i]][subId];
    }
    delete s_subscriptionConfigs[subId];
    delete s_subscriptions[subId];
    s_totalBalance -= balance;
    if (!LINK.transfer(to, uint256(balance))) {
      revert InsufficientBalance();
    }
    emit SubscriptionCanceled(subId, to, balance);
  }

  /**
   * @inheritdoc VRFCoordinatorV2Interface
   * @dev Looping is bounded to MAX_CONSUMERS*(number of keyhashes).
   * @dev Used to disable subscription canceling while outstanding request are present.
   */
  function pendingRequestExists(uint64 subId) public view override returns (bool) {
    SubscriptionConfig memory subConfig = s_subscriptionConfigs[subId];
    for (uint256 i = 0; i < subConfig.consumers.length; i++) {
      for (uint256 j = 0; j < s_provingKeyHashes.length; j++) {
        (uint256 reqId, ) = computeRequestId(
          s_provingKeyHashes[j],
          subConfig.consumers[i],
          subId,
          s_consumers[subConfig.consumers[i]][subId]
        );
        if (s_requestCommitments[reqId] != 0) {
          return true;
        }
      }
    }
    return false;
  }

  modifier onlySubOwner(uint64 subId) {
    address owner = s_subscriptionConfigs[subId].owner;
    if (owner == address(0)) {
      revert InvalidSubscription();
    }
    if (msg.sender != owner) {
      revert MustBeSubOwner(owner);
    }
    _;
  }

  modifier nonReentrant() {
    if (s_config.reentrancyLock) {
      revert Reentrant();
    }
    _;
  }

  /**
   * @notice The type and version of this contract
   * @return Type and version string
   */
  function typeAndVersion() external pure virtual override returns (string memory) {
    return "VRFCoordinatorV2 1.0.0";
  }
}

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

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

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

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

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

File 18 of 52 : draft-IERC6093.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;

/**
 * @dev Standard ERC20 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
 */
interface IERC20Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC20InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC20InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     * @param allowance Amount of tokens a `spender` is allowed to operate with.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC20InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `spender` to be approved. Used in approvals.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC20InvalidSpender(address spender);
}

/**
 * @dev Standard ERC721 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
 */
interface IERC721Errors {
    /**
     * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
     * Used in balance queries.
     * @param owner Address of the current owner of a token.
     */
    error ERC721InvalidOwner(address owner);

    /**
     * @dev Indicates a `tokenId` whose `owner` is the zero address.
     * @param tokenId Identifier number of a token.
     */
    error ERC721NonexistentToken(uint256 tokenId);

    /**
     * @dev Indicates an error related to the ownership over a particular token. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param tokenId Identifier number of a token.
     * @param owner Address of the current owner of a token.
     */
    error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC721InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC721InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param tokenId Identifier number of a token.
     */
    error ERC721InsufficientApproval(address operator, uint256 tokenId);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC721InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC721InvalidOperator(address operator);
}

/**
 * @dev Standard ERC1155 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
 */
interface IERC1155Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     * @param tokenId Identifier number of a token.
     */
    error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC1155InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC1155InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param owner Address of the current owner of a token.
     */
    error ERC1155MissingApprovalForAll(address operator, address owner);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC1155InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC1155InvalidOperator(address operator);

    /**
     * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
     * Used in batch transfers.
     * @param idsLength Length of the array of token identifiers
     * @param valuesLength Length of the array of token amounts
     */
    error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}

File 19 of 52 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

File 20 of 52 : ERC721.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/ERC721.sol)

pragma solidity ^0.8.20;

import {IERC721} from "./IERC721.sol";
import {IERC721Receiver} from "./IERC721Receiver.sol";
import {IERC721Metadata} from "./extensions/IERC721Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {Strings} from "../../utils/Strings.sol";
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
import {IERC721Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
 * the Metadata extension, but not including the Enumerable extension, which is available separately as
 * {ERC721Enumerable}.
 */
abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Errors {
    using Strings for uint256;

    // Token name
    string private _name;

    // Token symbol
    string private _symbol;

    mapping(uint256 tokenId => address) private _owners;

    mapping(address owner => uint256) private _balances;

    mapping(uint256 tokenId => address) private _tokenApprovals;

    mapping(address owner => mapping(address operator => bool)) private _operatorApprovals;

    /**
     * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC721-balanceOf}.
     */
    function balanceOf(address owner) public view virtual returns (uint256) {
        if (owner == address(0)) {
            revert ERC721InvalidOwner(address(0));
        }
        return _balances[owner];
    }

    /**
     * @dev See {IERC721-ownerOf}.
     */
    function ownerOf(uint256 tokenId) public view virtual returns (address) {
        return _requireOwned(tokenId);
    }

    /**
     * @dev See {IERC721Metadata-name}.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev See {IERC721Metadata-symbol}.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId) public view virtual returns (string memory) {
        _requireOwned(tokenId);

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string.concat(baseURI, tokenId.toString()) : "";
    }

    /**
     * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
     * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
     * by default, can be overridden in child contracts.
     */
    function _baseURI() internal view virtual returns (string memory) {
        return "";
    }

    /**
     * @dev See {IERC721-approve}.
     */
    function approve(address to, uint256 tokenId) public virtual {
        _approve(to, tokenId, _msgSender());
    }

    /**
     * @dev See {IERC721-getApproved}.
     */
    function getApproved(uint256 tokenId) public view virtual returns (address) {
        _requireOwned(tokenId);

        return _getApproved(tokenId);
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC721-isApprovedForAll}.
     */
    function isApprovedForAll(address owner, address operator) public view virtual returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    function transferFrom(address from, address to, uint256 tokenId) public virtual {
        if (to == address(0)) {
            revert ERC721InvalidReceiver(address(0));
        }
        // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists
        // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here.
        address previousOwner = _update(to, tokenId, _msgSender());
        if (previousOwner != from) {
            revert ERC721IncorrectOwner(from, tokenId, previousOwner);
        }
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) public {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual {
        transferFrom(from, to, tokenId);
        _checkOnERC721Received(from, to, tokenId, data);
    }

    /**
     * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
     *
     * IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the
     * core ERC721 logic MUST be matched with the use of {_increaseBalance} to keep balances
     * consistent with ownership. The invariant to preserve is that for any address `a` the value returned by
     * `balanceOf(a)` must be equal to the number of tokens such that `_ownerOf(tokenId)` is `a`.
     */
    function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
        return _owners[tokenId];
    }

    /**
     * @dev Returns the approved address for `tokenId`. Returns 0 if `tokenId` is not minted.
     */
    function _getApproved(uint256 tokenId) internal view virtual returns (address) {
        return _tokenApprovals[tokenId];
    }

    /**
     * @dev Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in
     * particular (ignoring whether it is owned by `owner`).
     *
     * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
     * assumption.
     */
    function _isAuthorized(address owner, address spender, uint256 tokenId) internal view virtual returns (bool) {
        return
            spender != address(0) &&
            (owner == spender || isApprovedForAll(owner, spender) || _getApproved(tokenId) == spender);
    }

    /**
     * @dev Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner.
     * Reverts if `spender` does not have approval from the provided `owner` for the given token or for all its assets
     * the `spender` for the specific `tokenId`.
     *
     * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
     * assumption.
     */
    function _checkAuthorized(address owner, address spender, uint256 tokenId) internal view virtual {
        if (!_isAuthorized(owner, spender, tokenId)) {
            if (owner == address(0)) {
                revert ERC721NonexistentToken(tokenId);
            } else {
                revert ERC721InsufficientApproval(spender, tokenId);
            }
        }
    }

    /**
     * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
     *
     * NOTE: the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that
     * a uint256 would ever overflow from increments when these increments are bounded to uint128 values.
     *
     * WARNING: Increasing an account's balance using this function tends to be paired with an override of the
     * {_ownerOf} function to resolve the ownership of the corresponding tokens so that balances and ownership
     * remain consistent with one another.
     */
    function _increaseBalance(address account, uint128 value) internal virtual {
        unchecked {
            _balances[account] += value;
        }
    }

    /**
     * @dev Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the current owner
     * (or `to`) is the zero address. Returns the owner of the `tokenId` before the update.
     *
     * The `auth` argument is optional. If the value passed is non 0, then this function will check that
     * `auth` is either the owner of the token, or approved to operate on the token (by the owner).
     *
     * Emits a {Transfer} event.
     *
     * NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}.
     */
    function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) {
        address from = _ownerOf(tokenId);

        // Perform (optional) operator check
        if (auth != address(0)) {
            _checkAuthorized(from, auth, tokenId);
        }

        // Execute the update
        if (from != address(0)) {
            // Clear approval. No need to re-authorize or emit the Approval event
            _approve(address(0), tokenId, address(0), false);

            unchecked {
                _balances[from] -= 1;
            }
        }

        if (to != address(0)) {
            unchecked {
                _balances[to] += 1;
            }
        }

        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);

        return from;
    }

    /**
     * @dev Mints `tokenId` and transfers it to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - `to` cannot be the zero address.
     *
     * Emits a {Transfer} event.
     */
    function _mint(address to, uint256 tokenId) internal {
        if (to == address(0)) {
            revert ERC721InvalidReceiver(address(0));
        }
        address previousOwner = _update(to, tokenId, address(0));
        if (previousOwner != address(0)) {
            revert ERC721InvalidSender(address(0));
        }
    }

    /**
     * @dev Mints `tokenId`, transfers it to `to` and checks for `to` acceptance.
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeMint(address to, uint256 tokenId) internal {
        _safeMint(to, tokenId, "");
    }

    /**
     * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
        _mint(to, tokenId);
        _checkOnERC721Received(address(0), to, tokenId, data);
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     * This is an internal function that does not check if the sender is authorized to operate on the token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId) internal {
        address previousOwner = _update(address(0), tokenId, address(0));
        if (previousOwner == address(0)) {
            revert ERC721NonexistentToken(tokenId);
        }
    }

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     *
     * Emits a {Transfer} event.
     */
    function _transfer(address from, address to, uint256 tokenId) internal {
        if (to == address(0)) {
            revert ERC721InvalidReceiver(address(0));
        }
        address previousOwner = _update(to, tokenId, address(0));
        if (previousOwner == address(0)) {
            revert ERC721NonexistentToken(tokenId);
        } else if (previousOwner != from) {
            revert ERC721IncorrectOwner(from, tokenId, previousOwner);
        }
    }

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients
     * are aware of the ERC721 standard to prevent tokens from being forever locked.
     *
     * `data` is additional data, it has no specified format and it is sent in call to `to`.
     *
     * This internal function is like {safeTransferFrom} in the sense that it invokes
     * {IERC721Receiver-onERC721Received} on the receiver, and can be used to e.g.
     * implement alternative mechanisms to perform token transfer, such as signature-based.
     *
     * Requirements:
     *
     * - `tokenId` token must exist and be owned by `from`.
     * - `to` cannot be the zero address.
     * - `from` cannot be the zero address.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeTransfer(address from, address to, uint256 tokenId) internal {
        _safeTransfer(from, to, tokenId, "");
    }

    /**
     * @dev Same as {xref-ERC721-_safeTransfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
        _transfer(from, to, tokenId);
        _checkOnERC721Received(from, to, tokenId, data);
    }

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is
     * either the owner of the token, or approved to operate on all tokens held by this owner.
     *
     * Emits an {Approval} event.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address to, uint256 tokenId, address auth) internal {
        _approve(to, tokenId, auth, true);
    }

    /**
     * @dev Variant of `_approve` with an optional flag to enable or disable the {Approval} event. The event is not
     * emitted in the context of transfers.
     */
    function _approve(address to, uint256 tokenId, address auth, bool emitEvent) internal virtual {
        // Avoid reading the owner unless necessary
        if (emitEvent || auth != address(0)) {
            address owner = _requireOwned(tokenId);

            // We do not use _isAuthorized because single-token approvals should not be able to call approve
            if (auth != address(0) && owner != auth && !isApprovedForAll(owner, auth)) {
                revert ERC721InvalidApprover(auth);
            }

            if (emitEvent) {
                emit Approval(owner, to, tokenId);
            }
        }

        _tokenApprovals[tokenId] = to;
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Requirements:
     * - operator can't be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
        if (operator == address(0)) {
            revert ERC721InvalidOperator(operator);
        }
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @dev Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned).
     * Returns the owner.
     *
     * Overrides to ownership logic should be done to {_ownerOf}.
     */
    function _requireOwned(uint256 tokenId) internal view returns (address) {
        address owner = _ownerOf(tokenId);
        if (owner == address(0)) {
            revert ERC721NonexistentToken(tokenId);
        }
        return owner;
    }

    /**
     * @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target address. This will revert if the
     * recipient doesn't accept the token transfer. The call is not executed if the target address is not a contract.
     *
     * @param from address representing the previous owner of the given token ID
     * @param to target address that will receive the tokens
     * @param tokenId uint256 ID of the token to be transferred
     * @param data bytes optional data to send along with the call
     */
    function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private {
        if (to.code.length > 0) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                if (retval != IERC721Receiver.onERC721Received.selector) {
                    revert ERC721InvalidReceiver(to);
                }
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert ERC721InvalidReceiver(to);
                } else {
                    /// @solidity memory-safe-assembly
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        }
    }
}

File 21 of 52 : IERC721Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/IERC721Metadata.sol)

pragma solidity ^0.8.20;

import {IERC721} from "../IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Metadata is IERC721 {
    /**
     * @dev Returns the token collection name.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the token collection symbol.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
     */
    function tokenURI(uint256 tokenId) external view returns (string memory);
}

File 22 of 52 : IERC721.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

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

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

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

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

File 23 of 52 : IERC721Receiver.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol)

pragma solidity ^0.8.20;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be
     * reverted.
     *
     * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

File 24 of 52 : Base64.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Base64.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides a set of functions to operate with Base64 strings.
 */
library Base64 {
    /**
     * @dev Base64 Encoding/Decoding Table
     */
    string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    /**
     * @dev Converts a `bytes` to its Bytes64 `string` representation.
     */
    function encode(bytes memory data) internal pure returns (string memory) {
        /**
         * Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
         * https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
         */
        if (data.length == 0) return "";

        // Loads the table into memory
        string memory table = _TABLE;

        // Encoding takes 3 bytes chunks of binary data from `bytes` data parameter
        // and split into 4 numbers of 6 bits.
        // The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up
        // - `data.length + 2`  -> Round up
        // - `/ 3`              -> Number of 3-bytes chunks
        // - `4 *`              -> 4 characters for each chunk
        string memory result = new string(4 * ((data.length + 2) / 3));

        /// @solidity memory-safe-assembly
        assembly {
            // Prepare the lookup table (skip the first "length" byte)
            let tablePtr := add(table, 1)

            // Prepare result pointer, jump over length
            let resultPtr := add(result, 32)

            // Run over the input, 3 bytes at a time
            for {
                let dataPtr := data
                let endPtr := add(data, mload(data))
            } lt(dataPtr, endPtr) {

            } {
                // Advance 3 bytes
                dataPtr := add(dataPtr, 3)
                let input := mload(dataPtr)

                // To write each character, shift the 3 bytes (18 bits) chunk
                // 4 times in blocks of 6 bits for each character (18, 12, 6, 0)
                // and apply logical AND with 0x3F which is the number of
                // the previous character in the ASCII table prior to the Base64 Table
                // The result is then added to the table to get the character to write,
                // and finally write it in the result pointer but with a left shift
                // of 256 (1 byte) - 8 (1 ASCII char) = 248 bits

                mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance
            }

            // When data `bytes` is not exactly 3 bytes long
            // it is padded with `=` characters at the end
            switch mod(mload(data), 3)
            case 1 {
                mstore8(sub(resultPtr, 1), 0x3d)
                mstore8(sub(resultPtr, 2), 0x3d)
            }
            case 2 {
                mstore8(sub(resultPtr, 1), 0x3d)
            }
        }

        return result;
    }
}

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

pragma solidity ^0.8.20;

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

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

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

File 26 of 52 : ERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

File 27 of 52 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @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);
}

File 28 of 52 : Math.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Muldiv operation overflow.
     */
    error MathOverflowedMulDiv();

    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            return a / b;
        }

        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
     * Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0 = x * y; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            if (denominator <= prod1) {
                revert MathOverflowedMulDiv();
            }

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
            // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.

            uint256 twos = denominator & (0 - denominator);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
            // works in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
     * towards zero.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }
}

File 29 of 52 : SignedMath.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

File 30 of 52 : Pausable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)

pragma solidity ^0.8.20;

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

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

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

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

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

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

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor() {
        _paused = false;
    }

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

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

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

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

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

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

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

File 31 of 52 : ReentrancyGuard.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

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

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

    uint256 private _status;

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

    constructor() {
        _status = NOT_ENTERED;
    }

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

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

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

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

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

File 32 of 52 : Strings.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)

pragma solidity ^0.8.20;

import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant HEX_DIGITS = "0123456789abcdef";
    uint8 private constant ADDRESS_LENGTH = 20;

    /**
     * @dev The `value` string doesn't fit in the specified `length`.
     */
    error StringsInsufficientHexLength(uint256 value, uint256 length);

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toStringSigned(int256 value) internal pure returns (string memory) {
        return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        uint256 localValue = value;
        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_DIGITS[localValue & 0xf];
            localValue >>= 4;
        }
        if (localValue != 0) {
            revert StringsInsufficientHexLength(value, length);
        }
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
     * representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

File 33 of 52 : IUniswapV3Pool.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

import './pool/IUniswapV3PoolImmutables.sol';
import './pool/IUniswapV3PoolState.sol';
import './pool/IUniswapV3PoolDerivedState.sol';
import './pool/IUniswapV3PoolActions.sol';
import './pool/IUniswapV3PoolOwnerActions.sol';
import './pool/IUniswapV3PoolEvents.sol';

/// @title The interface for a Uniswap V3 Pool
/// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform
/// to the ERC20 specification
/// @dev The pool interface is broken up into many smaller pieces
interface IUniswapV3Pool is
    IUniswapV3PoolImmutables,
    IUniswapV3PoolState,
    IUniswapV3PoolDerivedState,
    IUniswapV3PoolActions,
    IUniswapV3PoolOwnerActions,
    IUniswapV3PoolEvents
{

}

File 34 of 52 : IUniswapV3PoolActions.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Permissionless pool actions
/// @notice Contains pool methods that can be called by anyone
interface IUniswapV3PoolActions {
    /// @notice Sets the initial price for the pool
    /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value
    /// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96
    function initialize(uint160 sqrtPriceX96) external;

    /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position
    /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback
    /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends
    /// on tickLower, tickUpper, the amount of liquidity, and the current price.
    /// @param recipient The address for which the liquidity will be created
    /// @param tickLower The lower tick of the position in which to add liquidity
    /// @param tickUpper The upper tick of the position in which to add liquidity
    /// @param amount The amount of liquidity to mint
    /// @param data Any data that should be passed through to the callback
    /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback
    /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback
    function mint(
        address recipient,
        int24 tickLower,
        int24 tickUpper,
        uint128 amount,
        bytes calldata data
    ) external returns (uint256 amount0, uint256 amount1);

    /// @notice Collects tokens owed to a position
    /// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity.
    /// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or
    /// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the
    /// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity.
    /// @param recipient The address which should receive the fees collected
    /// @param tickLower The lower tick of the position for which to collect fees
    /// @param tickUpper The upper tick of the position for which to collect fees
    /// @param amount0Requested How much token0 should be withdrawn from the fees owed
    /// @param amount1Requested How much token1 should be withdrawn from the fees owed
    /// @return amount0 The amount of fees collected in token0
    /// @return amount1 The amount of fees collected in token1
    function collect(
        address recipient,
        int24 tickLower,
        int24 tickUpper,
        uint128 amount0Requested,
        uint128 amount1Requested
    ) external returns (uint128 amount0, uint128 amount1);

    /// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position
    /// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0
    /// @dev Fees must be collected separately via a call to #collect
    /// @param tickLower The lower tick of the position for which to burn liquidity
    /// @param tickUpper The upper tick of the position for which to burn liquidity
    /// @param amount How much liquidity to burn
    /// @return amount0 The amount of token0 sent to the recipient
    /// @return amount1 The amount of token1 sent to the recipient
    function burn(
        int24 tickLower,
        int24 tickUpper,
        uint128 amount
    ) external returns (uint256 amount0, uint256 amount1);

    /// @notice Swap token0 for token1, or token1 for token0
    /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback
    /// @param recipient The address to receive the output of the swap
    /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
    /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
    /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
    /// value after the swap. If one for zero, the price cannot be greater than this value after the swap
    /// @param data Any data to be passed through to the callback
    /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
    /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
    function swap(
        address recipient,
        bool zeroForOne,
        int256 amountSpecified,
        uint160 sqrtPriceLimitX96,
        bytes calldata data
    ) external returns (int256 amount0, int256 amount1);

    /// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback
    /// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback
    /// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling
    /// with 0 amount{0,1} and sending the donation amount(s) from the callback
    /// @param recipient The address which will receive the token0 and token1 amounts
    /// @param amount0 The amount of token0 to send
    /// @param amount1 The amount of token1 to send
    /// @param data Any data to be passed through to the callback
    function flash(
        address recipient,
        uint256 amount0,
        uint256 amount1,
        bytes calldata data
    ) external;

    /// @notice Increase the maximum number of price and liquidity observations that this pool will store
    /// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to
    /// the input observationCardinalityNext.
    /// @param observationCardinalityNext The desired minimum number of observations for the pool to store
    function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external;
}

File 35 of 52 : IUniswapV3PoolDerivedState.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Pool state that is not stored
/// @notice Contains view functions to provide information about the pool that is computed rather than stored on the
/// blockchain. The functions here may have variable gas costs.
interface IUniswapV3PoolDerivedState {
    /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp
    /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing
    /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick,
    /// you must call it with secondsAgos = [3600, 0].
    /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in
    /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio.
    /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned
    /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp
    /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block
    /// timestamp
    function observe(uint32[] calldata secondsAgos)
        external
        view
        returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);

    /// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range
    /// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed.
    /// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first
    /// snapshot is taken and the second snapshot is taken.
    /// @param tickLower The lower tick of the range
    /// @param tickUpper The upper tick of the range
    /// @return tickCumulativeInside The snapshot of the tick accumulator for the range
    /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range
    /// @return secondsInside The snapshot of seconds per liquidity for the range
    function snapshotCumulativesInside(int24 tickLower, int24 tickUpper)
        external
        view
        returns (
            int56 tickCumulativeInside,
            uint160 secondsPerLiquidityInsideX128,
            uint32 secondsInside
        );
}

File 36 of 52 : IUniswapV3PoolEvents.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Events emitted by a pool
/// @notice Contains all events emitted by the pool
interface IUniswapV3PoolEvents {
    /// @notice Emitted exactly once by a pool when #initialize is first called on the pool
    /// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize
    /// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96
    /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool
    event Initialize(uint160 sqrtPriceX96, int24 tick);

    /// @notice Emitted when liquidity is minted for a given position
    /// @param sender The address that minted the liquidity
    /// @param owner The owner of the position and recipient of any minted liquidity
    /// @param tickLower The lower tick of the position
    /// @param tickUpper The upper tick of the position
    /// @param amount The amount of liquidity minted to the position range
    /// @param amount0 How much token0 was required for the minted liquidity
    /// @param amount1 How much token1 was required for the minted liquidity
    event Mint(
        address sender,
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint256 amount0,
        uint256 amount1
    );

    /// @notice Emitted when fees are collected by the owner of a position
    /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees
    /// @param owner The owner of the position for which fees are collected
    /// @param tickLower The lower tick of the position
    /// @param tickUpper The upper tick of the position
    /// @param amount0 The amount of token0 fees collected
    /// @param amount1 The amount of token1 fees collected
    event Collect(
        address indexed owner,
        address recipient,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount0,
        uint128 amount1
    );

    /// @notice Emitted when a position's liquidity is removed
    /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect
    /// @param owner The owner of the position for which liquidity is removed
    /// @param tickLower The lower tick of the position
    /// @param tickUpper The upper tick of the position
    /// @param amount The amount of liquidity to remove
    /// @param amount0 The amount of token0 withdrawn
    /// @param amount1 The amount of token1 withdrawn
    event Burn(
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint256 amount0,
        uint256 amount1
    );

    /// @notice Emitted by the pool for any swaps between token0 and token1
    /// @param sender The address that initiated the swap call, and that received the callback
    /// @param recipient The address that received the output of the swap
    /// @param amount0 The delta of the token0 balance of the pool
    /// @param amount1 The delta of the token1 balance of the pool
    /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96
    /// @param liquidity The liquidity of the pool after the swap
    /// @param tick The log base 1.0001 of price of the pool after the swap
    event Swap(
        address indexed sender,
        address indexed recipient,
        int256 amount0,
        int256 amount1,
        uint160 sqrtPriceX96,
        uint128 liquidity,
        int24 tick
    );

    /// @notice Emitted by the pool for any flashes of token0/token1
    /// @param sender The address that initiated the swap call, and that received the callback
    /// @param recipient The address that received the tokens from flash
    /// @param amount0 The amount of token0 that was flashed
    /// @param amount1 The amount of token1 that was flashed
    /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee
    /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee
    event Flash(
        address indexed sender,
        address indexed recipient,
        uint256 amount0,
        uint256 amount1,
        uint256 paid0,
        uint256 paid1
    );

    /// @notice Emitted by the pool for increases to the number of observations that can be stored
    /// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index
    /// just before a mint/swap/burn.
    /// @param observationCardinalityNextOld The previous value of the next observation cardinality
    /// @param observationCardinalityNextNew The updated value of the next observation cardinality
    event IncreaseObservationCardinalityNext(
        uint16 observationCardinalityNextOld,
        uint16 observationCardinalityNextNew
    );

    /// @notice Emitted when the protocol fee is changed by the pool
    /// @param feeProtocol0Old The previous value of the token0 protocol fee
    /// @param feeProtocol1Old The previous value of the token1 protocol fee
    /// @param feeProtocol0New The updated value of the token0 protocol fee
    /// @param feeProtocol1New The updated value of the token1 protocol fee
    event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New);

    /// @notice Emitted when the collected protocol fees are withdrawn by the factory owner
    /// @param sender The address that collects the protocol fees
    /// @param recipient The address that receives the collected protocol fees
    /// @param amount0 The amount of token0 protocol fees that is withdrawn
    /// @param amount0 The amount of token1 protocol fees that is withdrawn
    event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1);
}

File 37 of 52 : IUniswapV3PoolImmutables.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Pool state that never changes
/// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values
interface IUniswapV3PoolImmutables {
    /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface
    /// @return The contract address
    function factory() external view returns (address);

    /// @notice The first of the two tokens of the pool, sorted by address
    /// @return The token contract address
    function token0() external view returns (address);

    /// @notice The second of the two tokens of the pool, sorted by address
    /// @return The token contract address
    function token1() external view returns (address);

    /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6
    /// @return The fee
    function fee() external view returns (uint24);

    /// @notice The pool tick spacing
    /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive
    /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ...
    /// This value is an int24 to avoid casting even though it is always positive.
    /// @return The tick spacing
    function tickSpacing() external view returns (int24);

    /// @notice The maximum amount of position liquidity that can use any tick in the range
    /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and
    /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool
    /// @return The max amount of liquidity per tick
    function maxLiquidityPerTick() external view returns (uint128);
}

File 38 of 52 : IUniswapV3PoolOwnerActions.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Permissioned pool actions
/// @notice Contains pool methods that may only be called by the factory owner
interface IUniswapV3PoolOwnerActions {
    /// @notice Set the denominator of the protocol's % share of the fees
    /// @param feeProtocol0 new protocol fee for token0 of the pool
    /// @param feeProtocol1 new protocol fee for token1 of the pool
    function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external;

    /// @notice Collect the protocol fee accrued to the pool
    /// @param recipient The address to which collected protocol fees should be sent
    /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1
    /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0
    /// @return amount0 The protocol fee collected in token0
    /// @return amount1 The protocol fee collected in token1
    function collectProtocol(
        address recipient,
        uint128 amount0Requested,
        uint128 amount1Requested
    ) external returns (uint128 amount0, uint128 amount1);
}

File 39 of 52 : IUniswapV3PoolState.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Pool state that can change
/// @notice These methods compose the pool's state, and can change with any frequency including multiple times
/// per transaction
interface IUniswapV3PoolState {
    /// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas
    /// when accessed externally.
    /// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value
    /// tick The current tick of the pool, i.e. according to the last tick transition that was run.
    /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick
    /// boundary.
    /// observationIndex The index of the last oracle observation that was written,
    /// observationCardinality The current maximum number of observations stored in the pool,
    /// observationCardinalityNext The next maximum number of observations, to be updated when the observation.
    /// feeProtocol The protocol fee for both tokens of the pool.
    /// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0
    /// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee.
    /// unlocked Whether the pool is currently locked to reentrancy
    function slot0()
        external
        view
        returns (
            uint160 sqrtPriceX96,
            int24 tick,
            uint16 observationIndex,
            uint16 observationCardinality,
            uint16 observationCardinalityNext,
            uint8 feeProtocol,
            bool unlocked
        );

    /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool
    /// @dev This value can overflow the uint256
    function feeGrowthGlobal0X128() external view returns (uint256);

    /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool
    /// @dev This value can overflow the uint256
    function feeGrowthGlobal1X128() external view returns (uint256);

    /// @notice The amounts of token0 and token1 that are owed to the protocol
    /// @dev Protocol fees will never exceed uint128 max in either token
    function protocolFees() external view returns (uint128 token0, uint128 token1);

    /// @notice The currently in range liquidity available to the pool
    /// @dev This value has no relationship to the total liquidity across all ticks
    function liquidity() external view returns (uint128);

    /// @notice Look up information about a specific tick in the pool
    /// @param tick The tick to look up
    /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or
    /// tick upper,
    /// liquidityNet how much liquidity changes when the pool price crosses the tick,
    /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0,
    /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1,
    /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick
    /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick,
    /// secondsOutside the seconds spent on the other side of the tick from the current tick,
    /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false.
    /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0.
    /// In addition, these values are only relative and must be used only in comparison to previous snapshots for
    /// a specific position.
    function ticks(int24 tick)
        external
        view
        returns (
            uint128 liquidityGross,
            int128 liquidityNet,
            uint256 feeGrowthOutside0X128,
            uint256 feeGrowthOutside1X128,
            int56 tickCumulativeOutside,
            uint160 secondsPerLiquidityOutsideX128,
            uint32 secondsOutside,
            bool initialized
        );

    /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information
    function tickBitmap(int16 wordPosition) external view returns (uint256);

    /// @notice Returns the information about a position by the position's key
    /// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper
    /// @return _liquidity The amount of liquidity in the position,
    /// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke,
    /// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke,
    /// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke,
    /// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke
    function positions(bytes32 key)
        external
        view
        returns (
            uint128 _liquidity,
            uint256 feeGrowthInside0LastX128,
            uint256 feeGrowthInside1LastX128,
            uint128 tokensOwed0,
            uint128 tokensOwed1
        );

    /// @notice Returns data about a specific observation index
    /// @param index The element of the observations array to fetch
    /// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time
    /// ago, rather than at a specific index in the array.
    /// @return blockTimestamp The timestamp of the observation,
    /// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp,
    /// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp,
    /// Returns initialized whether the observation has been initialized and the values are safe to use
    function observations(uint256 index)
        external
        view
        returns (
            uint32 blockTimestamp,
            int56 tickCumulative,
            uint160 secondsPerLiquidityCumulativeX128,
            bool initialized
        );
}

File 40 of 52 : Excavator.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;

import "@openzeppelin/contracts/access/Ownable.sol";

import "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol";
import "@chainlink/contracts/src/v0.8/vrf/VRFCoordinatorV2.sol";
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";

import "./utils/ExecutorManager.sol";
import "./utils/TransferHelper.sol";
import "./interfaces/ISwapRouter.sol";
import "./interfaces/IWETH.sol";
import "./interfaces/ITetraSpektra.sol";

contract Excavator is Ownable, ExecutorManager, VRFConsumerBaseV2 {
    ISwapRouter public immutable uniswapRouter;
    VRFCoordinatorV2 public immutable vrfCoordinator;

    address public immutable WETH;
    address public immutable LINK;
    bytes32 public immutable KEY_HASH;

    uint64 public vrfSubscriptionId;
    ITetraSpektra immutable tetraSpektra;

    uint32 _gasFeeLimit = 420_000;

    constructor(
        address executor,
        address tetraSpektraAddress_,
        address uniswapRouterAddress_,
        address vrfCoordinatorAddress_,
        bytes32 vrfKeyHash_,
        address wethTokenAddress_,
        address linkTokenAddress_
    )
        Ownable(msg.sender)
        ExecutorManager(executor)
        VRFConsumerBaseV2(vrfCoordinatorAddress_)
    {
        tetraSpektra = ITetraSpektra(tetraSpektraAddress_);

        uniswapRouter = ISwapRouter(uniswapRouterAddress_);

        vrfCoordinator = VRFCoordinatorV2(vrfCoordinatorAddress_);

        WETH = wethTokenAddress_;
        LINK = linkTokenAddress_;
        KEY_HASH = vrfKeyHash_;

        TransferHelper.safeApprove(
            WETH,
            address(uniswapRouter),
            type(uint256).max
        );

        LinkTokenInterface(LINK).approve(
            address(vrfCoordinator),
            type(uint256).max
        );
    }

    function initialize() public payable onlyOwner {
        vrfSubscriptionId = vrfCoordinator.createSubscription();

        require(isInitialized(), "0");

        vrfCoordinator.addConsumer(vrfSubscriptionId, address(this));

        IWETH(WETH).deposit{value: msg.value}();

        uint256 outputAmount = _swapWethForLink(msg.value, address(this));

        LinkTokenInterface(LINK).transferAndCall(
            address(vrfCoordinator),
            outputAmount,
            abi.encode(vrfSubscriptionId)
        );
    }

    function fund() external payable {
        require(isInitialized(), "0");
        IWETH(WETH).deposit{value: msg.value}();

        uint256 outputAmount = _swapWethForLink(msg.value, address(this));

        LinkTokenInterface(LINK).transferAndCall(
            address(vrfCoordinator),
            outputAmount,
            abi.encode(vrfSubscriptionId)
        );
    }

    function fundWithWeth(uint256 amount) external {
        require(isInitialized(), "0");
        require(amount > 0, "1");
        require(IERC20(WETH).transferFrom(msg.sender, address(this), amount));

        uint256 fundAmount = _swapWethForLink(amount, address(this));
        require(fundAmount > 0, "2");
        LinkTokenInterface(LINK).transferAndCall(
            address(vrfCoordinator),
            fundAmount,
            abi.encode(vrfSubscriptionId)
        );
    }

    function fundWithLink(uint256 amount) external {
        require(isInitialized(), "0");
        require(amount > 0, "1");
        require(IERC20(LINK).transferFrom(msg.sender, address(this), amount));

        uint256 fundAmount = IERC20(LINK).balanceOf(address(this));
        LinkTokenInterface(LINK).transferAndCall(
            address(vrfCoordinator),
            fundAmount,
            abi.encode(vrfSubscriptionId)
        );
    }

    function isInitialized() public view returns (bool) {
        return vrfSubscriptionId != 0;
    }

    function getLinkPrice() external view returns (uint256) {
        (, int answer, , , ) = vrfCoordinator.LINK_ETH_FEED().latestRoundData();
        uint256 linkPrice = (answer <= 0)
            ? 6_500_000_000_000_000
            : uint256(answer);
        return linkPrice;
    }

    function _swapWethForLink(
        uint256 amountIn,
        address recipient
    ) private returns (uint256) {
        return
            uniswapRouter.exactInputSingle(
                ISwapRouter.ExactInputSingleParams({
                    tokenIn: WETH,
                    tokenOut: LINK,
                    fee: 3_000,
                    recipient: recipient,
                    amountIn: amountIn,
                    amountOutMinimum: 2,
                    sqrtPriceLimitX96: 0
                })
            );
    }

    function beginExcavation() public onlyOwner returns (uint256) {
        uint256 excavationId = vrfCoordinator.requestRandomWords(
            KEY_HASH,
            vrfSubscriptionId,
            3,
            _gasFeeLimit,
            1
        );
        return excavationId;
    }

    function updateGasFeeLimit(uint32 gasFeeLimit) external onlyExecutor {
        require(gasFeeLimit > 330_000, "TOO_LOW");
        _gasFeeLimit = gasFeeLimit;
    }

    function fulfillRandomWords(
        uint256 excavationId,
        uint256[] memory values
    ) internal override {
        tetraSpektra.excavate(excavationId, values[0]);
    }

    function forceExcavate(
        uint256 excavationId,
        uint256 value
    ) public onlyOwner {
        tetraSpektra.excavate(excavationId, value);
    }
}

File 41 of 52 : IBountyVault.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;

interface IBountyVault {
    function distribute(
        address recipient,
        uint256 amount,
        uint256 amountForCreator
    ) external;

    function claimBounty(address tokenOwner, uint256 tokenId) external;

    function isVaultUnlocked() external returns (bool);
}

File 42 of 52 : IClaimsManager.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;

interface IClaimsManager {
    function isClaimsSessionExpired() external view returns (bool);

    function getTimeRemainingForClaimsSession() external view returns (uint256);

    function getClaimsSessionId() external view returns (uint256);

    function enable() external;

    function claim(address recipient, uint256 tokenId) external;
}

File 43 of 52 : IPriceOracle.sol
// SPDX-License-Identifier: UNLICENSED

pragma solidity >=0.7.0 <0.9.0;

interface IPriceOracle {
    function getPrice(
        address token0Address,
        address token1Address,
        uint128 amount,
        uint24 poolFee
    ) external view returns (uint256 priceU18);

    function getBasePrice(
        address token0Address,
        address token1Address,
        uint24 poolFee
    ) external view returns (uint256 priceU18);
}

File 44 of 52 : IProspektorFundManager.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;

interface IProspektorFundManager {
    function getTick() external view returns (int24);

    function collectLPFees() external returns (uint256, uint256);

    function addLiquidity(
        uint token0Amount,
        uint token1Amount
    )
        external
        returns (uint128 liquidity, uint256 token0Value, uint256 token1Value);
}

File 45 of 52 : IRandomizer.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;

import "@openzeppelin/contracts/access/Ownable.sol";

interface IRandomizer {
    function random() external returns (uint256);
}

File 46 of 52 : ISwapRouter.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;

interface ISwapRouter {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        // uint deadline;
        uint amountIn;
        uint amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    function exactInputSingle(
        ExactInputSingleParams calldata params
    ) external payable returns (uint amountOut);

    struct ExactInputParams {
        bytes path;
        address recipient;
        //uint deadline;
        uint amountIn;
        uint amountOutMinimum;
    }

    function exactInput(
        ExactInputParams calldata params
    ) external payable returns (uint amountOut);
}

File 47 of 52 : ITetraSpektra.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

interface ITetraSpektra is IERC721 {
    function excavate(uint256 excavationId, uint256 value) external;

    function exchange(uint256[4] memory tokenIds) external;

    function getGlyphKeys(uint256 tokenId) external returns (bytes32[3] memory);
}

File 48 of 52 : IWETH.sol
// SPDX-License-Identifier: UNLICENSED
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

pragma solidity >=0.7.0 <0.9.0;

interface IWETH is IERC20 {
    function deposit() external payable;

    function withdraw(uint amount) external;
}

File 49 of 52 : ExecutorManager.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;

abstract contract ExecutorManager {
    error NotExecutor(address attempted);
    error CannotRemoveSelf();

    mapping(address => bool) public executors;

    constructor(address initialExecutor) {
        _addExecutor(initialExecutor);
    }

    modifier onlyExecutor() {
        if (isExecutor(msg.sender) != true) revert NotExecutor(msg.sender);
        _;
    }

    function isExecutor(address _executor) public view returns (bool) {
        return (executors[_executor] == true);
    }

    function _addExecutor(address _toAdd) internal {
        executors[_toAdd] = true;
    }

    function addExecutor(address _toAdd) external virtual onlyExecutor {
        _addExecutor(_toAdd);
    }

    function _removeExecutor(address _toRemove) internal {
        if (_toRemove == msg.sender) revert CannotRemoveSelf();
        executors[_toRemove] = false;
    }

    function removeExecutor(address _toRemove) external virtual onlyExecutor {
        _removeExecutor(_toRemove);
    }
}

File 50 of 52 : RenderHelper.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;

import "@openzeppelin/contracts/utils/Base64.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "./SvgHelper.sol";

library RenderHelper {
    function getJson(
        uint256 tokenId,
        uint8[7] memory tetra,
        string[] memory colorNames,
        string[] memory colorHexCodes
    ) public pure returns (string memory) {
        bytes memory dataURI = abi.encodePacked(
            "{",
            _prop("description", "TetraSpektra"),
            _prop("name", string.concat("Tetra #", Strings.toString(tokenId))),
            '"attributes": [',
            string(_getAttributes(tetra, colorNames)),
            "],",
            _prop(
                "image",
                _getBase64Image(_render(tetra, colorHexCodes)),
                false
            ),
            "}"
        );

        return
            string(
                abi.encodePacked(
                    "data:application/json;base64,",
                    Base64.encode(dataURI)
                )
            );
    }

    function _getTrait(
        string memory traitType,
        string memory traitValue
    ) private pure returns (string memory) {
        return _getTrait(traitType, traitValue, true);
    }

    function _getTrait(
        string memory traitType,
        string memory traitValue,
        bool addComma
    ) private pure returns (string memory) {
        return
            string.concat(
                "{",
                _prop("trait_type", traitType),
                _prop("value", traitValue, false),
                "}",
                addComma ? "," : ""
            );
    }

    function _prop(
        string memory name,
        string memory value
    ) private pure returns (string memory) {
        return _prop(name, value, false, true);
    }

    function _prop(
        string memory name,
        string memory value,
        bool addComma
    ) private pure returns (string memory) {
        return _prop(name, value, false, addComma);
    }

    function _prop(
        string memory name,
        string memory value,
        bool isNumber,
        bool addComma
    ) private pure returns (string memory) {
        return
            string.concat(
                ' "',
                name,
                '": ',
                isNumber ? "" : '"',
                value,
                isNumber ? "" : '"',
                addComma ? ", " : " "
            );
    }

    function _getBase64Image(
        bytes memory data
    ) private pure returns (string memory) {
        return
            string(
                abi.encodePacked(
                    "data:image/svg+xml;base64,",
                    Base64.encode(data)
                )
            );
    }

    function _getAttributes(
        uint8[7] memory tetra,
        string[] memory colorNames
    ) private pure returns (bytes memory) {
        return
            abi.encodePacked(
                _getTrait("Inner Kore #1", colorNames[tetra[0]]),
                _getTrait("Inner Kore #2", colorNames[tetra[1]]),
                _getTrait("Inner Kore #3", colorNames[tetra[2]]),
                _getTrait("Inner Kore #4", colorNames[tetra[3]]),
                _getTrait("Outer Kore", colorNames[tetra[4]]),
                _getTrait("Mantle", colorNames[tetra[5]]),
                _getTrait("Krust", colorNames[tetra[6]], false)
            );
    }

    function _renderUnexcavated() private pure returns (bytes memory) {
        return
            abi.encodePacked(
                "<svg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='xMinYMin meet' viewBox='0 0 15 15' width='100%' height='auto' shape-rendering='crispEdges' >",
                "<style type='text/css'> @media screen and (-webkit-min-device-pixel-ratio: 0) and (min-resolution: 0.001dpcm) {.static { background: repeating-radial-gradient(#000 0 0.0001%,#fff 0 0.0002%) 50% 0/2500px 2500px,repeating-conic-gradient(#000 0 0.0001%,#fff 0 0.0002%) 60% 60%/2500px 2500px; background-blend-mode: difference; animation: st .2s infinite alternate; mix-blend-mode: multiply;} @keyframes st{ 100% {background-position: 20% 0, 20% 50%} } @media screen and (-webkit-min-device-pixel-ratio: 0) { _::-webkit-full-page-media, .static { background:none; }}</style>",
                "<filter id='g'><feGaussianBlur stdDeviation='.14' result='cb' /><feMerge><feMergeNode in='cb' /><feMergeNode in='SourceGraphic' /></feMerge></filter><rect x='0' y='0' height='100%' width='100%' fill='black' /><g transform='translate(.5 .5)'><rect x='6.125' y='6.125' height='.75' width='.75' stroke='#FFFFFF' stroke-width='.01'><animate attributeName='opacity' values='1;0;1' dur='4s' repeatCount='indefinite' /></rect><rect x='7.125' y='6.125' height='.75' width='.75' stroke='#FFFFFF' stroke-width='.01'><animate attributeName='opacity' values='1;0;1' dur='2s' repeatCount='indefinite' /></rect><rect x='6.125' y='7.125' height='.75' width='.75' stroke='#FFFFFF' stroke-width='.01'><animate attributeName='opacity' values='1;0;1' dur='3s' repeatCount='indefinite' /></rect><rect x='7.125' y='7.125' height='.75' width='.75' stroke='#FFFFFF' stroke-width='.01'><animate attributeName='opacity' values='1;0;1' dur='1s' repeatCount='indefinite' /></rect></g><g transform='translate(.5 .5)'><g><circle cx='7' cy='7' r='2' fill='none' opacity='1' stroke='#FFFFFF' stroke-width='0.1' filter='' ><animate attributeName='r' values='2.5;6.9;' dur='6s' repeatCount='indefinite' /><animate attributeName='stroke-width' values='0;.1;' dur='6s' repeatCount='indefinite' /><animate attributeName='opacity' values='0;1;0;' dur='6s' repeatCount='indefinite' /></circle></g></g>",
                "<foreignObject class='static' x='0' y='0' width='100' height='100'><div class='logoGradient' xmlns='http://www.w3.org/1999/xhtml'></div></foreignObject></svg>"
            );
    }

    function _renderKrust(
        uint8[7] memory tetra,
        string[] memory colorHexCodes
    ) private pure returns (string memory) {
        return
            string.concat(
                SvgHelper.rectangle(
                    "0",
                    "0",
                    "6",
                    ".1",
                    colorHexCodes[tetra[6]]
                ),
                SvgHelper.rectangle(
                    "0",
                    "0",
                    ".1",
                    "7",
                    colorHexCodes[tetra[6]]
                ),
                SvgHelper.rectangle(
                    "0",
                    "7",
                    "7",
                    ".1",
                    colorHexCodes[tetra[6]]
                ),
                SvgHelper.rectangle(
                    "0",
                    "13.9",
                    ".1",
                    "6",
                    colorHexCodes[tetra[6]]
                ),
                SvgHelper.rectangle(
                    "7",
                    "13.9",
                    ".1",
                    "7",
                    colorHexCodes[tetra[6]]
                ),
                SvgHelper.rectangle(
                    "13.9",
                    "8",
                    "6",
                    ".1",
                    colorHexCodes[tetra[6]]
                ),
                SvgHelper.rectangle(
                    "13.9",
                    "0",
                    "7",
                    ".1",
                    colorHexCodes[tetra[6]]
                ),
                SvgHelper.rectangle(
                    "8",
                    "0",
                    ".1",
                    "6",
                    colorHexCodes[tetra[6]]
                )
            );
    }

    function _renderMantle(
        uint8[7] memory tetra,
        string[] memory colorHexCodes
    ) private pure returns (string memory) {
        return
            string.concat(
                SvgHelper.rectangle(
                    "2",
                    "2",
                    ".1",
                    "7",
                    colorHexCodes[tetra[5]]
                ),
                SvgHelper.rectangle(
                    "2",
                    "2",
                    "1",
                    ".1",
                    colorHexCodes[tetra[5]]
                ),
                //
                SvgHelper.rectangle(
                    "2",
                    "5",
                    "7",
                    ".1",
                    colorHexCodes[tetra[5]]
                ),
                SvgHelper.rectangle(
                    "2",
                    "11.9",
                    ".1",
                    "1",
                    colorHexCodes[tetra[5]]
                ),
                //
                SvgHelper.rectangle(
                    "5",
                    "11.9",
                    ".1",
                    "7",
                    colorHexCodes[tetra[5]]
                ),
                SvgHelper.rectangle(
                    "11.9",
                    "10.9",
                    "1",
                    ".1",
                    colorHexCodes[tetra[5]]
                ),
                //
                SvgHelper.rectangle(
                    "11.9",
                    "2",
                    "7",
                    ".1",
                    colorHexCodes[tetra[5]]
                ),
                SvgHelper.rectangle(
                    "10.9",
                    "2",
                    ".1",
                    "1",
                    colorHexCodes[tetra[5]]
                )
            );
    }

    function _renderNucleus(
        uint8[7] memory tetra,
        string[] memory colorHexCodes
    ) private pure returns (string memory) {
        return
            string.concat(
                SvgHelper.square(
                    "6.125",
                    "6.125",
                    ".75",
                    colorHexCodes[tetra[0]],
                    SvgHelper.animate("opacity", "1;0.2;1;", "3s")
                ),
                SvgHelper.square(
                    "7.125",
                    "6.125",
                    ".75",
                    colorHexCodes[tetra[1]],
                    SvgHelper.animate("opacity", "1;0.2;1;", "2s")
                ),
                SvgHelper.square(
                    "6.125",
                    "7.125",
                    ".75",
                    colorHexCodes[tetra[2]],
                    SvgHelper.animate("opacity", "1;0.2;1;", "4s")
                ),
                SvgHelper.square(
                    "7.125",
                    "7.125",
                    ".75",
                    colorHexCodes[tetra[3]],
                    SvgHelper.animate("opacity", "1;0.2;1;", "5s")
                )
            );
    }

    function _render(
        uint8[7] memory tetra,
        string[] memory colorHexCodes
    ) private pure returns (bytes memory) {
        if (tetra[0] == 0) return _renderUnexcavated();
        string memory str = string.concat(
            "<g filter='url(#g)'>",
            _renderKrust(tetra, colorHexCodes),
            _renderMantle(tetra, colorHexCodes),
            SvgHelper.circle(
                "7",
                "7",
                "2.5",
                "1",
                "none",
                colorHexCodes[tetra[4]],
                "0.1"
            ),
            "</g>",
            "<g>",
            SvgHelper.circle(
                "7",
                "7",
                "2",
                "1",
                "none",
                colorHexCodes[tetra[4]],
                "0.1",
                SvgHelper.getStandardAnimationGroup("8s")
            ),
            SvgHelper.circle(
                "7",
                "7",
                "2",
                "1",
                "none",
                colorHexCodes[tetra[6]],
                "0.1",
                SvgHelper.getStandardAnimationGroup("6s")
            ),
            SvgHelper.circle(
                "7",
                "7",
                "2",
                "1",
                "none",
                colorHexCodes[tetra[5]],
                "0.1",
                SvgHelper.getStandardAnimationGroup("4s")
            ),
            SvgHelper.circle(
                "7",
                "7",
                "2",
                "1",
                "none",
                colorHexCodes[tetra[4]],
                "0.1",
                SvgHelper.getStandardAnimationGroup("2s")
            ),
            "</g>",
            _renderNucleus(tetra, colorHexCodes)
        );

        return
            abi.encodePacked(
                "<svg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='xMinYMin meet' viewBox='0 0 15 15' width='100%' height='auto' shape-rendering='crispEdges'><filter id='g'><feGaussianBlur stdDeviation='.14' result='cb' /><feMerge><feMergeNode in='cb'/><feMergeNode in='SourceGraphic' /></feMerge></filter>",
                "<style type='text/css'> @media screen and (-webkit-min-device-pixel-ratio: 0) and (min-resolution: 0.001dpcm) {.static { background: repeating-radial-gradient(#000 0 0.0001%,#fff 0 0.0002%) 50% 0/2500px 2500px,repeating-conic-gradient(#000 0 0.0001%,#fff 0 0.0002%) 60% 60%/2500px 2500px; background-blend-mode: difference; animation: st .2s infinite alternate; mix-blend-mode: multiply;} @keyframes st{ 100% {background-position: 20% 0, 20% 50%} } @media screen and (-webkit-min-device-pixel-ratio: 0) { _::-webkit-full-page-media, .static { background:none; }}</style>",
                SvgHelper.rectangle("0", "0", "100%", "100%", colorHexCodes[0]),
                "<g transform='translate(.5 .5)'>",
                str,
                "</g><foreignObject class='static' x='0' y='0' width='100' height='100'><div class='logoGradient' xmlns='http://www.w3.org/1999/xhtml'></div></foreignObject></svg>"
            );
    }
}

File 51 of 52 : SvgHelper.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;

import "@openzeppelin/contracts/utils/Strings.sol";

library SvgHelper {
    function circle(
        string memory x,
        string memory y,
        string memory r,
        string memory color
    ) public pure returns (string memory) {
        return circle(x, y, r, "1", color, "", "0");
    }

    function circle(
        uint256 x,
        uint256 y,
        uint256 r,
        string memory opacity,
        string memory color,
        string memory strokeColor,
        string memory strokeWidth
    ) public pure returns (string memory) {
        return
            circle(
                Strings.toString(x),
                Strings.toString(y),
                Strings.toString(r),
                opacity,
                color,
                strokeColor,
                strokeWidth,
                ""
            );
    }

    function circle(
        string memory x,
        string memory y,
        string memory r,
        string memory opacity,
        string memory color,
        string memory strokeColor,
        string memory strokeWidth
    ) public pure returns (string memory) {
        return circle(x, y, r, opacity, color, strokeColor, strokeWidth, "");
    }

    function circle(
        string memory x,
        string memory y,
        string memory r,
        string memory opacity,
        string memory color,
        string memory strokeColor,
        string memory strokeWidth,
        string memory animation
    ) public pure returns (string memory) {
        return
            circle(
                x,
                y,
                r,
                opacity,
                color,
                strokeColor,
                strokeWidth,
                animation,
                ""
            );
    }

    function circle(
        string memory x,
        string memory y,
        string memory r,
        string memory opacity,
        string memory color,
        string memory strokeColor,
        string memory strokeWidth,
        string memory animation,
        string memory filter
    ) public pure returns (string memory) {
        return
            string.concat(
                "<circle cx='",
                x,
                "' cy='",
                y,
                "' r='",
                r,
                "' fill='",
                color,
                "' opacity='",
                opacity,
                "' stroke='",
                keccak256(abi.encodePacked(strokeColor)) != ""
                    ? strokeColor
                    : "",
                "' stroke-width='",
                keccak256(abi.encodePacked(strokeColor)) != ""
                    ? strokeWidth
                    : "",
                "'",
                keccak256(abi.encodePacked(filter)) == ""
                    ? ""
                    : string.concat(" filter='", filter, "'"),
                keccak256(abi.encodePacked((animation))) == ""
                    ? "/>"
                    : string.concat(" >", animation, "</circle>")
            );
    }

    function triangle(
        string memory x0,
        string memory y0,
        string memory x1,
        string memory y1,
        string memory x2,
        string memory y2,
        string memory color
    ) public pure returns (string memory) {
        return
            string.concat(
                "<polygon points='",
                x0,
                " ",
                y0,
                ", ",
                x1,
                " ",
                y1,
                ", ",
                x2,
                " ",
                y2,
                "'  fill='",
                color,
                "'/>"
            );
    }

    function square(
        string memory x,
        string memory y,
        string memory size,
        string memory color
    ) public pure returns (string memory) {
        return square(x, y, size, color, "");
    }

    function square(
        string memory x,
        string memory y,
        string memory size,
        string memory color,
        string memory animation
    ) public pure returns (string memory) {
        return rectangle(x, y, size, size, color, "", "", animation);
    }

    function animate(
        string memory attributeName,
        string memory values,
        string memory duration
    ) public pure returns (string memory) {
        return
            string.concat(
                "<animate attributeName='",
                attributeName,
                "' values='",
                values,
                "' dur='",
                duration,
                "' repeatCount='",
                "indefinite",
                "' />"
            );
    }

    function rectangle(
        string memory x,
        string memory y,
        string memory h,
        string memory w,
        string memory color
    ) public pure returns (string memory) {
        return rectangle(x, y, h, w, color, "", "", "");
    }

    function rectangle(
        string memory x,
        string memory y,
        string memory h,
        string memory w,
        string memory color,
        string memory stroke,
        string memory strokeWidth,
        string memory animation
    ) public pure returns (string memory) {
        return
            string.concat(
                "<rect x='",
                x,
                "' y='",
                y,
                "' height='",
                h,
                "' width='",
                w,
                "' fill='",
                color,
                "' stroke='",
                stroke,
                "' stroke-width='",
                strokeWidth,
                "'",
                keccak256(abi.encodePacked((animation))) == ""
                    ? "/>"
                    : string.concat(" >", animation, "</rect>")
            );
    }

    function getStandardAnimationGroup(
        string memory s
    ) public pure returns (string memory) {
        return
            string.concat(
                animate("r", "2.5;6.9;", s),
                animate("stroke-width", "0;.1;", s),
                animate("opacity", "1;0;", s)
            );
    }
}

File 52 of 52 : TransferHelper.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.6.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

library TransferHelper {
    /// @notice Transfers tokens from the targeted address to the given destination
    /// @notice Errors with 'STF' if transfer fails
    /// @param token The contract address of the token to be transferred
    /// @param from The originating address from which the tokens will be transferred
    /// @param to The destination address of the transfer
    /// @param value The amount to be transferred
    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSelector(
                IERC20.transferFrom.selector,
                from,
                to,
                value
            )
        );
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            "STF"
        );
    }

    /// @notice Transfers tokens from msg.sender to a recipient
    /// @dev Errors with ST if transfer fails
    /// @param token The contract address of the token which will be transferred
    /// @param to The recipient of the transfer
    /// @param value The value of the transfer
    function safeTransfer(address token, address to, uint256 value) internal {
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSelector(IERC20.transfer.selector, to, value)
        );
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            "ST"
        );
    }

    /// @notice Approves the stipulated contract to spend the given allowance in the given token
    /// @dev Errors with 'SA' if transfer fails
    /// @param token The contract address of the token to be approved
    /// @param to The target of the approval
    /// @param value The amount of the given token the target will be allowed to spend
    function safeApprove(address token, address to, uint256 value) internal {
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSelector(IERC20.approve.selector, to, value)
        );
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            "SA"
        );
    }

    /// @notice Transfers ETH to the recipient address
    /// @dev Fails with `STE`
    /// @param to The destination of the transfer
    /// @param value The value to be transferred
    function safeTransferETH(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, "STE");
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 1000
  },
  "evmVersion": "paris",
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {
    "contracts/utils/RenderHelper.sol": {
      "RenderHelper": "0xccd1203f67954d8886c13da1ad1c21595ab6db31"
    }
  }
}

Contract Security Audit

Contract ABI

[{"inputs":[{"components":[{"internalType":"address","name":"randomizerAddress","type":"address"},{"internalType":"address","name":"claimsManagerAddress","type":"address"},{"internalType":"address","name":"priceOracleAddress","type":"address"},{"internalType":"address","name":"bountyVaultAddress","type":"address"},{"internalType":"address","name":"uniswapRouterAddress","type":"address"}],"internalType":"struct AddressConfig","name":"addressConfig","type":"tuple"},{"components":[{"internalType":"address","name":"wethTokenAddress","type":"address"},{"internalType":"address","name":"porkTokenAddress","type":"address"}],"internalType":"struct TokenConfig","name":"tokenConfig","type":"tuple"},{"components":[{"internalType":"address","name":"vrfCoordinatorAddress","type":"address"},{"internalType":"bytes32","name":"vrfKeyHash","type":"bytes32"},{"internalType":"address","name":"linkTokenAddress","type":"address"}],"internalType":"struct ExcavatorConfig","name":"excavatorConfig","type":"tuple"},{"components":[{"internalType":"uint256","name":"maxSupply","type":"uint256"},{"internalType":"uint256","name":"prospektPrice","type":"uint256"},{"internalType":"uint256","name":"refreshPrice","type":"uint256"},{"internalType":"uint256","name":"flashBountyValue","type":"uint256"},{"internalType":"uint8","name":"excavatorFundingPercentage","type":"uint8"},{"internalType":"uint16","name":"maxSilver2Swaps","type":"uint16"},{"internalType":"uint8","name":"silver2UnlockCadence","type":"uint8"},{"internalType":"uint8","name":"silver2UnlockAmount","type":"uint8"},{"internalType":"string[]","name":"colorHexCodes","type":"string[]"},{"internalType":"string[]","name":"colorNames","type":"string[]"}],"internalType":"struct TetraSpektraConfig","name":"tetraSpektraConfig","type":"tuple"},{"internalType":"uint256[3][3]","name":"bounties","type":"uint256[3][3]"}],"stateMutability":"payable","type":"constructor"},{"inputs":[],"name":"CannotRemoveSelf","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"owner","type":"address"}],"name":"ERC721IncorrectOwner","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721InsufficientApproval","type":"error"},{"inputs":[{"internalType":"address","name":"approver","type":"address"}],"name":"ERC721InvalidApprover","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"ERC721InvalidOperator","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"ERC721InvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"ERC721InvalidReceiver","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ERC721InvalidSender","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721NonexistentToken","type":"error"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExcavationGroupNotReady","type":"error"},{"inputs":[],"name":"ExcavationInProgress","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[],"name":"InsufficientPayment","type":"error"},{"inputs":[],"name":"InvalidToken","type":"error"},{"inputs":[],"name":"InvalidTokenCompare","type":"error"},{"inputs":[],"name":"InvalidTokenCount","type":"error"},{"inputs":[],"name":"InvalidTokenOwner","type":"error"},{"inputs":[{"internalType":"address","name":"attempted","type":"address"}],"name":"NotExecutor","type":"error"},{"inputs":[],"name":"NotMatched","type":"error"},{"inputs":[],"name":"NotSoldOut","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[],"name":"TooManySilver2Swaps","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"prospektor","type":"address"},{"indexed":false,"internalType":"uint256","name":"interactionCount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"porkAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"wethAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"canAddLiquidityAfter","type":"uint256"}],"name":"AddedLiquidityForBounty","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"prospektor","type":"address"},{"indexed":false,"internalType":"uint256","name":"claimsSessionId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"claimedTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Claimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"prospektor","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"tetraKey","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"bountyValue","type":"uint256"}],"name":"ClaimedFlashBounty","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"prospektor","type":"address"},{"indexed":false,"internalType":"uint256","name":"interactionCount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"porkAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"canCollectFeesAfter","type":"uint256"}],"name":"CollectedFeesForBounty","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"prospektor","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"enum DiscoveryType","name":"discoveryType","type":"uint8"}],"name":"Discovery","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"prospektor","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"silverTetraKey","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"goldTetraKey","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"platinumTetraKey","type":"bytes32"}],"name":"Excavated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"silverTetraKey","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"goldTetraKey","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"platinumTetraKey","type":"bytes32"}],"name":"ExcavatedFlashBounty","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"excavationId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"value","type":"uint256"}],"name":"ExcavationComplete","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"excavationId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokenId_0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokenId_1","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokenId_2","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokenId_3","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokenId_4","type":"uint256"}],"name":"ExcavationContracted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"prospektor","type":"address"},{"indexed":true,"internalType":"uint256","name":"tier","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"bountyValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokenId_0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokenId_1","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokenId_2","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokenId_3","type":"uint256"}],"name":"Swapped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"BASE_TETRA_KEY","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_POOL_FEE","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_TO_PROSPEKT","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_PORK_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_WETH_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PORK","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PORK_WETH_POOL_FEE","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SWAP_PERCENTAGE_INCREMENT","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"_getTetra","outputs":[{"internalType":"uint8[7]","name":"","type":"uint8[7]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"seed","type":"uint256"}],"name":"_modToColorIndex","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_toAdd","type":"address"}],"name":"addExecutor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"addLiquidityForBounty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bountyVault","outputs":[{"internalType":"contract IBountyVault","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"canAddLiquidity","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"canAddLiquidityAfter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"canCollectFees","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"canCollectFeesAfter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"claimTokenId","type":"uint256"}],"name":"claim","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"claimFlashBounty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"claimVaultBounty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimsManager","outputs":[{"internalType":"contract IClaimsManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"collectFeesForBounty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"colorConfig","outputs":[{"internalType":"uint8","name":"maxColors","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"enableFlashBounty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"excavationId","type":"uint256"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"excavate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"excavationGroup","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"excavationTracker","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"excavator","outputs":[{"internalType":"contract Excavator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"executors","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeCollectorBounty","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeCollectorCadence","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"flashBountyEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"flashBountyValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"excavationId","type":"uint256"}],"name":"forceExcavate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"fundingPercentage","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBounties","outputs":[{"internalType":"uint256[3][3]","name":"","type":"uint256[3][3]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getTetraExcavationId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"enum BountyTier","name":"tier","type":"uint8"}],"name":"getTetraKey","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getTetraKeys","outputs":[{"internalType":"bytes32[3]","name":"","type":"bytes32[3]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTetrasRemaining","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"prospektorFundManagerAddress","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"interactionCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"isExcavated","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"isExcavating","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_executor","type":"address"}],"name":"isExecutor","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isFlashBountyEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"isFlashBountyMatch","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isInitialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"liquidityAdderBounty","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"liquidityAdderCadence","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxSilver2Swaps","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"priceOracle","outputs":[{"internalType":"contract IPriceOracle","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"count","type":"uint256"}],"name":"prospekt","outputs":[{"internalType":"uint256[5]","name":"tokenIds","type":"uint256[5]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"prospektPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"prospektorFundManager","outputs":[{"internalType":"contract IProspektorFundManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"randomizer","outputs":[{"internalType":"contract IRandomizer","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"refine","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"reforge","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"refreshCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"refreshPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_toRemove","type":"address"}],"name":"removeExecutor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"silver2Swaps","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"silver2UnlockAmount","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"silver2UnlockCadence","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[4]","name":"tokenIds","type":"uint256[4]"},{"internalType":"enum CreatorBountyTier","name":"creatorBountyTier","type":"uint8"}],"name":"swapForBounty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"swapPercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"tetraExcavationIds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"uniswapPool","outputs":[{"internalType":"contract IUniswapV3Pool","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"uniswapRouter","outputs":[{"internalType":"contract ISwapRouter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"percentage","type":"uint8"}],"name":"updateExcavatorFundingPercentage","outputs":[],"stateMutability":"nonpayable","type":"function"}]

61010060405262000012600080620004af565b60e0908152506000601155601460125566470de4df82000060135560148055600a6015556658d15e17628000601655600c601755600160185560006019556037601a556000601d556040516200929f3803806200929f8339810160408190526200007c916200112d565b336040518060400160405280600c81526020016b54657472615370656b74726160a01b81525060405180604001604052806005815260200164544554524160d81b8152508160009081620000d19190620012b9565b506001620000e08282620012b9565b505050620000f4816200054460201b60201c565b506007805460ff191690556001600855610120820151516101008301515114620001495760405162461bcd60e51b81526020600482015260016024820152600360fc1b60448201526064015b60405180910390fd5b602085810151600a80546001600160a01b03199081166001600160a01b03938416179091558751600b80548316918416919091179055604080890151600c805484169185169190911790556060808a0151600d805485169186169190911790556080808b01516010805490951690861617909355885184169092528784015190921660a052845160c05291840151601b55830151601c5582015115620001f457816060015162000213565b60208201516200020690604662000568565b6200021390600a6200139b565b601f5560808201516010805460a085015160c086015160e087015162ffffff60a01b19909316600160a01b60ff9687160261ffff60a81b191617600160a81b61ffff909316929092029190911761ffff60b81b1916600160b81b9185169190910260ff60c01b191617600160c01b9184169190910217905560408051606081018252610100850151808252610120860180516020808501919091529051519094169282019290925281519092602f92620002d39284929091019062000ae2565b506020828101518051620002ee926001850192019062000ae2565b50604091820151600291909101805460ff191660ff90921691909117905560105484516020860151608051878501519451339530956001600160a01b0316949392916200033b9062000b3f565b6001600160a01b0397881681529587166020870152938616604086015291851660608501526080840152831660a083015290911660c082015260e001604051809103906000f08015801562000394573d6000803e3d6000fd5b50600e80546001600160a01b0319166001600160a01b03929092169182179055608051620003c5916000196200058b565b6080516001600160a01b031663d0e30db0620003e334601462000568565b6040518263ffffffff1660e01b81526004016000604051808303818588803b1580156200040f57600080fd5b505af115801562000424573d6000803e3d6000fd5b5050600e546001600160a01b03169250638129fc1c91506200044a905034605062000568565b6040518263ffffffff1660e01b81526004016000604051808303818588803b1580156200047657600080fd5b505af11580156200048b573d6000803e3d6000fd5b5050505050806026906003620004a392919062000b4d565b5050505050506200145c565b600080826002811115620004c757620004c7620013b5565b03620004e357620004db8360008062000695565b90506200053e565b6001826002811115620004fa57620004fa620013b5565b036200050f57620004db836001600062000695565b6002826002811115620005265762000526620013b5565b036200053a57620004db8360018062000695565b5060005b92915050565b6001600160a01b03166000908152600660205260409020805460ff19166001179055565b600060646200057883856200139b565b620005849190620013e1565b9392505050565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b031663095ea7b360e01b1790529151600092839290871691620005e9919062001404565b6000604051808303816000865af19150503d806000811462000628576040519150601f19603f3d011682016040523d82523d6000602084013e6200062d565b606091505b50915091508180156200065b5750805115806200065b5750808060200190518101906200065b919062001422565b6200068e5760405162461bcd60e51b8152602060048201526002602482015261534160f01b604482015260640162000140565b5050505050565b600080620006a385620007fe565b905082620006b3576000620006c8565b60c0810151620006c89060ff16600162001446565b84620006d6576000620006f8565b60a0820151620006eb9060ff16600162001446565b620006f89060646200139b565b60808301516200070d9060ff16600162001446565b6200071b906127106200139b565b6060840151620007309060ff16600162001446565b6200073f90620f42406200139b565b6040850151620007549060ff16600162001446565b62000764906305f5e1006200139b565b6020860151620007799060ff16600162001446565b6200078a906402540be4006200139b565b86516200079c9060ff16600162001446565b620007ad9064e8d4a510006200139b565b620007b9919062001446565b620007c5919062001446565b620007d1919062001446565b620007dd919062001446565b620007e9919062001446565b620007f5919062001446565b95945050505050565b6200080862000b9b565b620008138262000a69565b806200082c5750811580156200082c575060205460ff16155b806200084b575060008281526021602052604090205460101c61ffff16155b156200088f5750506040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915290565b6040805160e081018252600084815260236020908152838220548252602281528382205486835260219091529290205490918291620008d6919060101c61ffff1662000aa3565b60ff1681526000848152602360209081526040808320548352602282528083205487845260218352922054928101926200091692911c61ffff1662000aa3565b60ff168152600084815260236020908152604080832054835260228252808320548784526021835292205492019162000957919060301c61ffff1662000aa3565b60ff1681526000848152602360209081526040808320548352602282528083205487845260218352928190205491909301926200099b929161ffff911c1662000aa3565b60ff1681526000848152602360209081526040808320548352602282528083205487845260218352922054920191620009dc919060501c61ffff1662000aa3565b60ff168152600084815260236020908152604080832054835260228252808320548784526021835292205492019162000a1d919060601c61ffff1662000aa3565b60ff168152600084815260236020908152604080832054835260228252808320548784526021835292205492019162000a5e919060701c61ffff1662000aa3565b60ff16905292915050565b600081815260236020526040812054158015906200053e575050600090815260236020908152604080832054835260229091529020541590565b60315460009060001960ff918216011682848162000ac55762000ac5620013cb565b048162000ad65762000ad6620013cb565b06600101905092915050565b82805482825590600052602060002090810192821562000b2d579160200282015b8281111562000b2d578251829062000b1c9082620012b9565b509160200191906001019062000b03565b5062000b3b92915062000bb9565b5090565b611cfb80620075a483390190565b60098301918390821562000b8d579160200282015b8281111562000b8d57825162000b7c908390600362000bda565b509160200191906003019062000b62565b5062000b3b92915062000c19565b6040518060e001604052806007906020820280368337509192915050565b8082111562000b3b57600062000bd0828262000c3c565b5060010162000bb9565b826003810192821562000c0b579160200282015b8281111562000c0b57825182559160200191906001019062000bee565b5062000b3b92915062000c7e565b8082111562000b3b57600080825560018201819055600282015560030162000c19565b50805462000c4a9062001228565b6000825580601f1062000c5b575050565b601f01602090049060005260206000209081019062000c7b919062000c7e565b50565b5b8082111562000b3b576000815560010162000c7f565b634e487b7160e01b600052604160045260246000fd5b60405161014081016001600160401b038111828210171562000cd15762000cd162000c95565b60405290565b60405160a081016001600160401b038111828210171562000cd15762000cd162000c95565b604051606081016001600160401b038111828210171562000cd15762000cd162000c95565b604051601f8201601f191681016001600160401b038111828210171562000d4c5762000d4c62000c95565b604052919050565b80516001600160a01b038116811462000d6c57600080fd5b919050565b60006040828403121562000d8457600080fd5b604080519081016001600160401b038111828210171562000da95762000da962000c95565b60405290508062000dba8362000d54565b815262000dca6020840162000d54565b60208201525092915050565b60006060828403121562000de957600080fd5b604051606081016001600160401b038111828210171562000e0e5762000e0e62000c95565b60405290508062000e1f8362000d54565b81526020830151602082015262000e396040840162000d54565b60408201525092915050565b805160ff8116811462000d6c57600080fd5b805161ffff8116811462000d6c57600080fd5b60005b8381101562000e8757818101518382015260200162000e6d565b50506000910152565b6000601f83601f84011262000ea457600080fd5b825160206001600160401b038083111562000ec35762000ec362000c95565b8260051b62000ed483820162000d21565b938452868101830193838101908986111562000eef57600080fd5b84890192505b8583101562000f885782518481111562000f0f5760008081fd5b8901603f81018b1362000f225760008081fd5b8581015160408682111562000f3b5762000f3b62000c95565b62000f4e828b01601f1916890162000d21565b8281528d8284860101111562000f645760008081fd5b62000f75838a830184870162000e6a565b8552505050918401919084019062000ef5565b9998505050505050505050565b6000610140828403121562000fa957600080fd5b62000fb362000cab565b90508151815260208201516020820152604082015160408201526060820151606082015262000fe56080830162000e45565b608082015262000ff860a0830162000e57565b60a08201526200100b60c0830162000e45565b60c08201526200101e60e0830162000e45565b60e0820152610100828101516001600160401b03808211156200104057600080fd5b6200104e8683870162000e90565b838501526101209250828501519150808211156200106b57600080fd5b506200107a8582860162000e90565b82840152505092915050565b6000601f83601f8401126200109a57600080fd5b620010a462000cfc565b80610120850186811115620010b857600080fd5b855b8181101562001121578785820112620010d35760008081fd5b620010dd62000cfc565b80606083018a811115620010f15760008081fd5b835b818110156200110d578051845260209384019301620010f3565b5050855250602090930192606001620010ba565b50909695505050505050565b60008060008060008587036102808112156200114857600080fd5b60a08112156200115757600080fd5b506200116262000cd7565b6200116d8762000d54565b81526200117d6020880162000d54565b6020820152620011906040880162000d54565b6040820152620011a36060880162000d54565b6060820152620011b66080880162000d54565b60808201529450620011cc8760a0880162000d71565b9350620011dd8760e0880162000dd6565b6101408701519093506001600160401b03811115620011fb57600080fd5b620012098882890162000f95565b9250506200121c87610160880162001086565b90509295509295909350565b600181811c908216806200123d57607f821691505b6020821081036200125e57634e487b7160e01b600052602260045260246000fd5b50919050565b601f821115620012b4576000816000526020600020601f850160051c810160208610156200128f5750805b601f850160051c820191505b81811015620012b0578281556001016200129b565b5050505b505050565b81516001600160401b03811115620012d557620012d562000c95565b620012ed81620012e6845462001228565b8462001264565b602080601f8311600181146200132557600084156200130c5750858301515b600019600386901b1c1916600185901b178555620012b0565b600085815260208120601f198616915b82811015620013565788860151825594840194600190910190840162001335565b5085821015620013755787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b634e487b7160e01b600052601160045260246000fd5b80820281158282048414176200053e576200053e62001385565b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052601260045260246000fd5b600082620013ff57634e487b7160e01b600052601260045260246000fd5b500490565b600082516200141881846020870162000e6a565b9190910192915050565b6000602082840312156200143557600080fd5b815180151581146200058457600080fd5b808201808211156200053e576200053e62001385565b60805160a05160c05160e051615fef620015b5600039600081816108710152612429015260008181610fdc0152818161136a015281816121f1015281816134df015281816137230152613f1c015260008181610de2015281816115900152818161201f015281816128270152818161294101528181612bf901528181612c64015281816130e3015281816131a401528181613910015281816142040152818161432d01528181614e0201528181614e3d015261511a015260008181610dae015281816113b1015281816114eb01528181611ce601528181611d7c01528181611ff701528181612282015281816123d401528181612a3401528181612aca01528181612bcc01528181612c2a015281816130bb01528181613526015281816137d90152818161386b01528181613cf901528181613d7d015281816142340152818161435501528181614adb01526150d00152615fef6000f3fe6080604052600436106105e15760003560e01c80637e962bd711610301578063ba6427f71161019a578063cb217dfd116100ec578063e37d3bb311610095578063f10fb5841161006f578063f10fb584146110fb578063f414b3a11461111b578063fc7646b01461113c57600080fd5b8063e37d3bb314611072578063e92a4d0414611092578063e985e9c5146110b257600080fd5b8063dbb7754e116100c6578063dbb7754e14610ffe578063debfda3014611014578063e1b3c9411461105257600080fd5b8063cb217dfd14610f97578063d139830014610faa578063d5abeb0114610fca57600080fd5b8063c4a528f81161014e578063c622cd8311610128578063c622cd8314610f41578063c8261c3c14610f57578063c87b56dd14610f7757600080fd5b8063c4a528f814610ef6578063c4d66de814610f0b578063c58896e414610f2b57600080fd5b8063bd843bb21161017f578063bd843bb214610e94578063bdd3d82514610ec1578063c06aef0314610ee157600080fd5b8063ba6427f714610e5f578063bcb518dd14610e7f57600080fd5b80639ac2a01111610253578063ac6d37be11610207578063b4cce1ab116101e1578063b4cce1ab14610e04578063b514218a14610e1f578063b88d4fde14610e3f57600080fd5b8063ac6d37be14610d72578063ad5c464814610d9c578063b22604b114610dd057600080fd5b8063a22cb46511610238578063a22cb46514610d24578063a890610b14610d44578063aba4571714610d5a57600080fd5b80639ac2a01114610cde5780639fe72ab614610d0e57600080fd5b80638b0af0fa116102b557806395a668861161028f57806395a6688614610c9e57806395d89b4114610cb35780639a2a724614610cc857600080fd5b80638b0af0fa14610c34578063907c3f9914610c495780639178fcd914610c6957600080fd5b8063812251b0116102e6578063812251b014610bdd5780638410968914610c0a5780638456cb5914610c1f57600080fd5b80637e962bd714610bb15780637fd18dea14610bc757600080fd5b8063392e53cd1161047e578063541d014d116103d05780636f0b863a11610379578063735de9f711610353578063735de9f714610b665780637b27ac8714610b865780637b56cf2e14610b9b57600080fd5b80636f0b863a14610b0c57806370a0823114610b265780637177a76914610b4657600080fd5b8063604244c3116103aa578063604244c314610aac57806363204ec314610acc5780636352211e14610aec57600080fd5b8063541d014d14610a51578063595d034f14610a7e5780635c975abb14610a9457600080fd5b806342842e0e116104325780634d06ef651161040c5780634d06ef6514610a075780634febfac614610a275780635409c2f014610a3c57600080fd5b806342842e0e146109a457806348c36640146109c45780634b52a233146109f157600080fd5b80633d59ccfa116104635780633d59ccfa146109465780633f0ef9ba146109795780633f4ba83a1461098f57600080fd5b8063392e53cd146109165780633c67a5b21461093057600080fd5b80631eb08ba9116105375780632630c12f116104eb57806334120597116104c557806334120597146108b3578063346b3e9f146108c9578063379607f5146108f657600080fd5b80632630c12f1461083f5780632ab5cf661461085f5780632d887b481461089357600080fd5b8063236f3b021161051c578063236f3b02146107ca57806323b872dd146107ff578063247884291461081f57600080fd5b80631eb08ba91461078a5780631f5a0bbe146107aa57600080fd5b80630cf0ac1d116105995780631545c2e7116105735780631545c2e714610731578063170e1dcb1461075557806318160ddd1461077557600080fd5b80630cf0ac1d146106b757806313af593c146106d9578063150b7a02146106ec57600080fd5b806306fdde03116105ca57806306fdde0314610653578063081812fc14610675578063095ea7b31461069557600080fd5b806301ffc9a7146105e65780630531c7c51461061b575b600080fd5b3480156105f257600080fd5b50610606610601366004615589565b611156565b60405190151581526020015b60405180910390f35b34801561062757600080fd5b50600e5461063b906001600160a01b031681565b6040516001600160a01b039091168152602001610612565b34801561065f57600080fd5b506106686111f3565b60405161061291906155f6565b34801561068157600080fd5b5061063b610690366004615609565b611285565b3480156106a157600080fd5b506106b56106b0366004615637565b6112ae565b005b3480156106c357600080fd5b506106cc6112bd565b6040516106129190615663565b6106b56106e736600461570b565b611328565b3480156106f857600080fd5b506107186107073660046157b1565b630a85bd0160e11b95945050505050565b6040516001600160e01b03199091168152602001610612565b34801561073d57600080fd5b5061074760175481565b604051908152602001610612565b34801561076157600080fd5b50600d5461063b906001600160a01b031681565b34801561078157600080fd5b506107476115d2565b34801561079657600080fd5b50600a5461063b906001600160a01b031681565b3480156107b657600080fd5b506106b56107c5366004615850565b6115e8565b3480156107d657600080fd5b506010546107ec90600160a81b900461ffff1681565b60405161ffff9091168152602001610612565b34801561080b57600080fd5b506106b561081a36600461586d565b611651565b34801561082b57600080fd5b506106b561083a366004615850565b6116dc565b34801561084b57600080fd5b50600c5461063b906001600160a01b031681565b34801561086b57600080fd5b506107477f000000000000000000000000000000000000000000000000000000000000000081565b34801561089f57600080fd5b506106b56108ae366004615609565b611721565b3480156108bf57600080fd5b5061074760115481565b3480156108d557600080fd5b506107476108e4366004615609565b60246020526000908152604090205481565b34801561090257600080fd5b50610747610911366004615609565b6118c2565b34801561092257600080fd5b50601e546106069060ff1681565b34801561093c57600080fd5b50610747601c5481565b34801561095257600080fd5b5060105461096790600160c01b900460ff1681565b60405160ff9091168152602001610612565b34801561098557600080fd5b5061074760125481565b34801561099b57600080fd5b506106b5611a2e565b3480156109b057600080fd5b506106b56109bf36600461586d565b611a74565b3480156109d057600080fd5b506107476109df366004615609565b60236020526000908152604090205481565b3480156109fd57600080fd5b50610747601a5481565b348015610a1357600080fd5b50610606610a22366004615609565b611a94565b348015610a3357600080fd5b506106b5611acd565b348015610a4857600080fd5b506106b5611bb5565b348015610a5d57600080fd5b50610747610a6c366004615609565b60009081526023602052604090205490565b348015610a8a57600080fd5b5061074760155481565b348015610aa057600080fd5b5060075460ff16610606565b348015610ab857600080fd5b50610606610ac7366004615609565b611dff565b348015610ad857600080fd5b506106b5610ae7366004615609565b611e30565b348015610af857600080fd5b5061063b610b07366004615609565b611ee5565b348015610b1857600080fd5b506020546106069060ff1681565b348015610b3257600080fd5b50610747610b41366004615850565b611ef0565b348015610b5257600080fd5b506106b5610b61366004615609565b611f51565b348015610b7257600080fd5b5060105461063b906001600160a01b031681565b348015610b9257600080fd5b50610967600581565b348015610ba757600080fd5b5061074760145481565b348015610bbd57600080fd5b5061074760255481565b348015610bd357600080fd5b5061074760135481565b348015610be957600080fd5b50610bfd610bf8366004615609565b612198565b60405161061291906158ae565b348015610c1657600080fd5b506107476121e1565b348015610c2b57600080fd5b506106b5612215565b348015610c4057600080fd5b50610606612259565b348015610c5557600080fd5b506106b5610c643660046158df565b6122fc565b348015610c7557600080fd5b506010546109679077010000000000000000000000000000000000000000000000900460ff1681565b348015610caa57600080fd5b506106066123ab565b348015610cbf57600080fd5b5061066861240b565b348015610cd457600080fd5b50610747601f5481565b348015610cea57600080fd5b50610606610cf9366004615850565b60066020526000908152604090205460ff1681565b348015610d1a57600080fd5b50610747601b5481565b348015610d3057600080fd5b506106b5610d3f366004615910565b61241a565b348015610d5057600080fd5b5061074760195481565b348015610d6657600080fd5b506107476305f5e10081565b348015610d7e57600080fd5b50610d8861271081565b60405162ffffff9091168152602001610612565b348015610da857600080fd5b5061063b7f000000000000000000000000000000000000000000000000000000000000000081565b348015610ddc57600080fd5b5061063b7f000000000000000000000000000000000000000000000000000000000000000081565b348015610e1057600080fd5b5061074766038d7ea4c6800081565b348015610e2b57600080fd5b50610606610e3a366004615609565b612425565b348015610e4b57600080fd5b506106b5610e5a366004615971565b61245b565b348015610e6b57600080fd5b50610747610e7a366004615a20565b612472565b348015610e8b57600080fd5b506106066124f3565b348015610ea057600080fd5b50610eb4610eaf366004615609565b61250d565b6040516106129190615a49565b348015610ecd57600080fd5b50600f5461063b906001600160a01b031681565b348015610eed57600080fd5b506106b5612762565b348015610f0257600080fd5b50610967600881565b348015610f1757600080fd5b506106b5610f26366004615850565b612b01565b348015610f3757600080fd5b5061074760165481565b348015610f4d57600080fd5b50610d88610bb881565b348015610f6357600080fd5b506106b5610f72366004615a83565b612c95565b348015610f8357600080fd5b50610668610f92366004615609565b61340a565b6106b5610fa536600461570b565b61349d565b348015610fb657600080fd5b50610967610fc5366004615b10565b613644565b348015610fd657600080fd5b506107477f000000000000000000000000000000000000000000000000000000000000000081565b34801561100a57600080fd5b50610747601d5481565b34801561102057600080fd5b5061060661102f366004615850565b6001600160a01b031660009081526006602052604090205460ff16151560011490565b611065611060366004615609565b61367d565b6040516106129190615b32565b34801561107e57600080fd5b506106b561108d366004615b10565b613949565b34801561109e57600080fd5b5060095461063b906001600160a01b031681565b3480156110be57600080fd5b506106066110cd366004615b5a565b6001600160a01b03918216600090815260056020908152604080832093909416825291909152205460ff1690565b34801561110757600080fd5b50600b5461063b906001600160a01b031681565b34801561112757600080fd5b5060105461096790600160a01b900460ff1681565b34801561114857600080fd5b506031546109679060ff1681565b60006001600160e01b031982167f80ac58cd0000000000000000000000000000000000000000000000000000000014806111b957506001600160e01b031982167f5b5e139f00000000000000000000000000000000000000000000000000000000145b806111ed57507f01ffc9a7000000000000000000000000000000000000000000000000000000006001600160e01b03198316145b92915050565b60606000805461120290615b88565b80601f016020809104026020016040519081016040528092919081815260200182805461122e90615b88565b801561127b5780601f106112505761010080835404028352916020019161127b565b820191906000526020600020905b81548152906001019060200180831161125e57829003601f168201915b5050505050905090565b600061129082613a70565b506000828152600460205260409020546001600160a01b03166111ed565b6112b9828233613aa9565b5050565b6112c56154ec565b6040805160608101909152602660036000835b8282101561131f5760408051606081019182905290600384810287019182845b8154815260200190600101908083116112f8575050505050815260200190600101906112d8565b50505050905090565b611330613ab6565b611338613af3565b8051601c546113479190615bd8565b803410156113685760405163cd1c886760e01b815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000000000006113916115d2565b146113af576040516310a0445f60e21b815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b15801561140a57600080fd5b505af115801561141e573d6000803e3d6000fd5b505050505060005b82518110156114d257306001600160a01b031661145b84838151811061144e5761144e615bef565b6020026020010151611ee5565b6001600160a01b0316146114825760405163153e35b760e11b815260040160405180910390fd5b6114a6303385848151811061149957611499615bef565b6020026020010151613b36565b6114ca60028483815181106114bd576114bd615bef565b6020026020010151613be6565b600101611426565b506040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa15801561153a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061155e9190615c05565b90506016548161156e9190615c34565b600103611585576016546115829082615c48565b90505b80156115c3576115c17f00000000000000000000000000000000000000000000000000000000000000006115bb83601a54613cb8565b30613cd7565b505b50506115cf6001600855565b50565b600060016018546115e39190615c48565b905090565b3360009081526006602052604090205460ff161515600114151560011461162957604051635cf20d6360e01b81523360048201526024015b60405180910390fd5b6115cf816001600160a01b03166000908152600660205260409020805460ff19166001179055565b6001600160a01b03821661167b57604051633250574960e11b815260006004820152602401611620565b6000611688838333613daf565b9050836001600160a01b0316816001600160a01b0316146116d6576040516364283d7b60e01b81526001600160a01b0380861660048301526024820184905282166044820152606401611620565b50505050565b3360009081526006602052604090205460ff161515600114151560011461171857604051635cf20d6360e01b8152336004820152602401611620565b6115cf81613eb5565b3360009081526006602052604090205460ff161515600114151560011461175d57604051635cf20d6360e01b8152336004820152602401611620565b60005b60058110156117d0576000611776826010615bd8565b60008481526024602052604081205490911c61ffff1691508190036117c7576040517fe05cde0b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50600101611760565b50600e54600b54604080517f5ec01e4d00000000000000000000000000000000000000000000000000000000815290516001600160a01b039384169363df76bbbd938693911691635ec01e4d9160048082019260209290919082900301816000875af1158015611844573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118689190615c05565b6040516001600160e01b031960e085901b168152600481019290925260248201526044015b600060405180830381600087803b1580156118a757600080fd5b505af11580156118bb573d6000803e3d6000fd5b5050505050565b60006118cc613af3565b600a546040517faad3ec96000000000000000000000000000000000000000000000000000000008152336004820152602481018490526001600160a01b039091169063aad3ec9690604401600060405180830381600087803b15801561193157600080fd5b505af1158015611945573d6000803e3d6000fd5b50505050600061195433613f18565b600a54604080517fe46d5b06000000000000000000000000000000000000000000000000000000008152905192935033927f9cdcf2f7714cca3508c7f0110b04a90a80a3a8dd0e35de99689db74d28c5383e926001600160a01b03169163e46d5b069160048083019260209291908290030181865afa1580156119db573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119ff9190615c05565b6040805191825260208201879052810184905260600160405180910390a29050611a296001600855565b919050565b3360009081526006602052604090205460ff1615156001141515600114611a6a57604051635cf20d6360e01b8152336004820152602401611620565b611a72613fb3565b565b611a8f8383836040518060200160405280600081525061245b565b505050565b600081815260236020526040812054158015906111ed575050600090815260236020908152604080832054835260229091529020541590565b3360009081526006602052604090205460ff1615156001141515600114611b0957604051635cf20d6360e01b8152336004820152602401611620565b611b116124f3565b15611b1b57600080fd5b6020805460ff19166001179055611b326000614005565b611a726000600b60009054906101000a90046001600160a01b03166001600160a01b0316635ec01e4d6040518163ffffffff1660e01b81526004016020604051808303816000875af1158015611b8c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611bb09190615c05565b6140bd565b611bbd613af3565b6000611bc833611ef0565b11611c155760405162461bcd60e51b815260206004820152600f60248201527f4e4f545f54455452415f4f574e455200000000000000000000000000000000006044820152606401611620565b611c1d612259565b611c555760405162461bcd60e51b81526020600482015260096024820152684e4f545f524541445960b81b6044820152606401611620565b600080611c606141fa565b91509150601754601954611c749190615c5b565b601581905560195460408051918252602082018590528101839052606081019190915233907f1b63b4ee59f5f20bc7ddffc1e45d2a42feb1677a86891aac7a00b819d70c01c49060800160405180910390a260165460405163095ea7b360e01b815233600482015260248101919091527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063095ea7b3906044016020604051808303816000875af1158015611d37573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d5b9190615c6e565b5060165460405163a9059cbb60e01b815233600482015260248101919091527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb906044015b6020604051808303816000875af1158015611dce573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611df29190615c6e565b505050611a726001600855565b60205460009060ff16611e135760006111ed565b611e1e826000612472565b611e29600080612472565b1492915050565b338181611e3c82611ee5565b6001600160a01b031614611e635760405163153e35b760e11b815260040160405180910390fd5b600d546040517f2e2222f8000000000000000000000000000000000000000000000000000000008152336004820152602481018590526001600160a01b0390911690632e2222f890604401600060405180830381600087803b158015611ec857600080fd5b505af1158015611edc573d6000803e3d6000fd5b50505050505050565b60006111ed82613a70565b60006001600160a01b038216611f35576040517f89c62b6400000000000000000000000000000000000000000000000000000000815260006004820152602401611620565b506001600160a01b031660009081526003602052604090205490565b611f59613af3565b338181611f6582611ee5565b6001600160a01b031614611f8c5760405163153e35b760e11b815260040160405180910390fd5b60205460ff168015611fa25750611fa283611dff565b611fd25760405162461bcd60e51b81526020600482015260016024820152600360fc1b6044820152606401611620565b611fda614478565b600c54601f54604051630a956fa360e31b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660048301527f0000000000000000000000000000000000000000000000000000000000000000811660248301526fffffffffffffffffffffffffffffffff9092166044820152612710606482015260009291909116906354ab7d1890608401602060405180830381865afa158015612094573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120b89190615c05565b600d546040517fe2c92a5200000000000000000000000000000000000000000000000000000000815233600482015260248101839052600060448201529192506001600160a01b03169063e2c92a5290606401600060405180830381600087803b15801561212557600080fd5b505af1158015612139573d6000803e3d6000fd5b5050505083336001600160a01b03167f25d161bd539b3943e072b306a72e430a67a737fbbdc48deec66e815b2222ef9b612174876000612472565b60408051918252602082018690520160405180910390a35050506115cf6001600855565b6121a0615519565b600060405180606001604052806121b8856000612472565b81526020016121c8856001612472565b81526020016121d8856002612472565b90529392505050565b60006121eb6115d2565b6115e3907f0000000000000000000000000000000000000000000000000000000000000000615c48565b3360009081526006602052604090205460ff161515600114151560011461225157604051635cf20d6360e01b8152336004820152602401611620565b611a72614504565b6000601554601954101580156115e357506016546040516370a0823160e01b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a08231906024015b602060405180830381865afa1580156122d2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122f69190615c05565b11905090565b3360009081526006602052604090205460ff161515600114151560011461233857604051635cf20d6360e01b8152336004820152602401611620565b60648160ff1611156123705760405162461bcd60e51b81526020600482015260016024820152600360fc1b6044820152606401611620565b6010805460ff909216600160a01b027fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff909216919091179055565b6000601254601954101580156115e357506013546040516370a0823160e01b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a08231906024016122b5565b60606001805461120290615b88565b6112b9338383614541565b60007f0000000000000000000000000000000000000000000000000000000000000000612453836000612472565b141592915050565b612466848484611651565b6116d6848484846145f9565b60008082600281111561248757612487615c8b565b0361249f576124988360008061471b565b90506111ed565b60018260028111156124b3576124b3615c8b565b036124c557612498836001600061471b565b60028260028111156124d9576124d9615c8b565b036124ea576124988360018061471b565b50600092915050565b60205460009060ff1680156115e357506115e36000612425565b612515615537565b61251e82611a94565b80612535575081158015612535575060205460ff16155b80612553575060008281526021602052604090205460101c61ffff16155b156125965750506040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915290565b6040805160e0810182526000848152602360209081528382205482526022815283822054868352602190915292902054909182916125db919060101c61ffff16613644565b60ff16815260008481526023602090815260408083205483526022825280832054878452602183529220549281019261261992911c61ffff16613644565b60ff1681526000848152602360209081526040808320548352602282528083205487845260218352922054920191612658919060301c61ffff16613644565b60ff16815260008481526023602090815260408083205483526022825280832054878452602183529281902054919093019261269a929161ffff911c16613644565b60ff16815260008481526023602090815260408083205483526022825280832054878452602183529220549201916126d9919060501c61ffff16613644565b60ff1681526000848152602360209081526040808320548352602282528083205487845260218352922054920191612718919060601c61ffff16613644565b60ff1681526000848152602360209081526040808320548352602282528083205487845260218352922054920191612757919060701c61ffff16613644565b60ff16905292915050565b61276a613af3565b600061277533611ef0565b116127c25760405162461bcd60e51b815260206004820152600f60248201527f4e4f545f54455452415f4f574e455200000000000000000000000000000000006044820152606401611620565b6127ca6123ab565b6128025760405162461bcd60e51b81526020600482015260096024820152684e4f545f524541445960b81b6044820152606401611620565b600d546040516370a0823160e01b81526001600160a01b0391821660048201526000917f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa15801561286e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128929190615c05565b600954604080517f36757fea00000000000000000000000000000000000000000000000000000000815281519394506001600160a01b03909216926336757fea92600480820193929182900301816000875af11580156128f6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061291a9190615ca1565b5050600d546040516370a0823160e01b81526001600160a01b0391821660048201526000917f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa158015612988573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129ac9190615c05565b60195490915033907fe1daabe2f50262db6f2c3abaab14e5c6e55e09dfc414d9b06864533cc33652d6906129e08585615c48565b6012546040805193845260208401929092529082015260600160405180910390a2601454601954612a119190615c5b565b60125560135460405163095ea7b360e01b815233600482015260248101919091527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063095ea7b3906044016020604051808303816000875af1158015612a85573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612aa99190615c6e565b5060135460405163a9059cbb60e01b815233600482015260248101919091527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb90604401611daf565b3360009081526006602052604090205460ff1615156001141515600114612b3d57604051635cf20d6360e01b8152336004820152602401611620565b601e5460ff1615612b905760405162461bcd60e51b815260206004820152601360248201527f414c52454144595f494e495449414c495a4544000000000000000000000000006044820152606401611620565b601e805460ff19166001179055600980546001600160a01b03831673ffffffffffffffffffffffffffffffffffffffff19909116179055612bf47f00000000000000000000000000000000000000000000000000000000000000008260001961484f565b612c217f00000000000000000000000000000000000000000000000000000000000000008260001961484f565b601054612c5b907f0000000000000000000000000000000000000000000000000000000000000000906001600160a01b031660001961484f565b6010546115cf907f0000000000000000000000000000000000000000000000000000000000000000906001600160a01b031660001961484f565b612c9d613ab6565b612ca5613af3565b612cc960405180606001604052806000815260200160008152602001600081525090565b6000808252612cde84825b602002015161250d565b905060005b6004811015612f3857848160048110612cfe57612cfe615bef565b602002015115612f3857825183612d1482615cc5565b90525033612d37868360048110612d2d57612d2d615bef565b6020020151611ee5565b6001600160a01b031614612d5e5760405163153e35b760e11b815260040160405180910390fd5b6000612d75868360048110612cd457612cd4615bef565b9050612d8360016004615c48565b821015612e9b576000612d97836001615c5b565b90505b6004811015612e9957868160048110612db557612db5615bef565b602002015115612e9957868160048110612dd157612dd1615bef565b6020020151878460048110612de857612de8615bef565b602002015103612e24576040517f15f468f200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612e45878260048110612e3957612e39615bef565b60200201516000612472565b612e5a888560048110612e3957612e39615bef565b14612e91576040517f2554246c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600101612d9a565b505b8115612f2f5760a083015160ff1615801590612ebd575060a081015160ff1615155b8015612ed7575060a0808201519084015160ff9081169116145b15612ee85760208401805160010190525b60c083015160ff1615801590612f04575060c081015160ff1615155b8015612f1e575060c0808201519084015160ff9081169116145b15612f2f5760408401805160010190525b50600101612ce3565b50815160021115612f75576040517fe778681d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8151600090612f8690600190615c48565b836020015103612faf578251600191820191612fa191615c48565b836040015103612faf576001015b80158015612fbe575082516002145b1561301757601054601154600160a81b90910461ffff16900361300d576040517fb3d2043500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6011546001016011555b60005b83518110156130845761305b86826004811061303857613038615bef565b602002015160009081526021602090815260408083208390556023909152812055565b61307c333088846004811061307257613072615bef565b6020020151611a74565b60010161301a565b5060006130a1826002866000015161309c9190615c48565b614978565b600c54604051630a956fa360e31b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660048301527f0000000000000000000000000000000000000000000000000000000000000000811660248301526fffffffffffffffffffffffffffffffff841660448301526127106064830152929350600092909116906354ab7d1890608401602060405180830381865afa158015613159573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061317d9190615c05565b600d546040516370a0823160e01b81526001600160a01b03918216600482015291925082917f0000000000000000000000000000000000000000000000000000000000000000909116906370a0823190602401602060405180830381865afa1580156131ed573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906132119190615c05565b1161325e5760405162461bcd60e51b815260206004820152601260248201527f4e4f545f454e4f5547485f42414c414e434500000000000000000000000000006044820152606401611620565b6000600187600481111561327457613274615c8b565b146132e057600287600481111561328d5761328d615c8b565b146132d95760038760048111156132a6576132a6615c8b565b146132d25760048760048111156132bf576132bf615c8b565b146132cb5760006132e3565b60286132e3565b60146132e3565b600a6132e3565b60055b600d5460ff9190911691506001600160a01b031663e2c92a52336133118561330c866064615c48565b613cb8565b61331b8686613cb8565b6040516001600160e01b031960e086901b1681526001600160a01b03909316600484015260248301919091526044820152606401600060405180830381600087803b15801561336957600080fd5b505af115801561337d573d6000803e3d6000fd5b5050895160208b015160408c01518a518995503394507fca022347fe7d97a9f055d4fa842bd9862141d7c8be0797737e2560a4fc35a0d8938893909290916003106133c95760006133cf565b60608f01515b604080519586526020860194909452928401919091526060830152608082015260a00160405180910390a35050505050506112b96001600855565b606073ccd1203f67954d8886c13da1ad1c21595ab6db3163df390e96836134308561250d565b6040516001600160e01b031960e085901b168152613458929190603090602f90600401615dca565b600060405180830381865af4158015613475573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111ed9190810190615e32565b6134a5613ab6565b6134ad613af3565b8051601c546134bc9190615bd8565b803410156134dd5760405163cd1c886760e01b815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000000000006135066115d2565b14613524576040516310a0445f60e21b815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b15801561357f57600080fd5b505af1158015613593573d6000803e3d6000fd5b505050505060005b82518110156114d257336001600160a01b03166135c384838151811061144e5761144e615bef565b6001600160a01b0316146135ea5760405163153e35b760e11b815260040160405180910390fd5b6136258382815181106135ff576135ff615bef565b602002602001015160009081526021602090815260408083208390556023909152812055565b61363c60018483815181106114bd576114bd615bef565b60010161359b565b60315460009060001960ff918216011682848161366357613663615c1e565b048161367157613671615c1e565b06600101905092915050565b613685615555565b61368d613ab6565b613695613af3565b81601b546136a39190615bd8565b803410156136c45760405163cd1c886760e01b815260040160405180910390fd5b6000831180156136d5575060058311155b6137215760405162461bcd60e51b815260206004820152600160248201527f31000000000000000000000000000000000000000000000000000000000000006044820152606401611620565b7f00000000000000000000000000000000000000000000000000000000000000008361374b6115d2565b6137559190615c5b565b11156137a35760405162461bcd60e51b815260206004820152600160248201527f32000000000000000000000000000000000000000000000000000000000000006044820152606401611620565b60005b838110156137d6576137b733613f18565b8382600581106137c9576137c9615bef565b60200201526001016137a6565b507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b15801561383257600080fd5b505af1158015613846573d6000803e3d6000fd5b50505050506138536149e2565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa1580156138ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906138de9190615c05565b9050601654816138ee9190615c34565b600103613905576016546139029082615c48565b90505b801561393d5761393b7f00000000000000000000000000000000000000000000000000000000000000006115bb83601a54613cb8565b505b5050611a296001600855565b600e60009054906101000a90046001600160a01b03166001600160a01b031663a3e56fa86040518163ffffffff1660e01b8152600401602060405180830381865afa15801561399c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139c09190615ea9565b6001600160a01b0316336001600160a01b031614806139e95750600e546001600160a01b031633145b613a355760405162461bcd60e51b815260206004820152600e60248201527f494e56414c49445f53454e4445520000000000000000000000000000000000006044820152606401611620565b613a3f82826140bd565b604051819083907f14285afe2ec6fba75de22aaed813b154e820b01ca273f996b915b0d49c106c5c90600090a35050565b6000818152600260205260408120546001600160a01b0316806111ed57604051637e27328960e01b815260048101849052602401611620565b611a8f8383836001614bdd565b60075460ff1615611a72576040517fd93c066500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600260085403613b2f576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600855565b6001600160a01b038216613b6057604051633250574960e11b815260006004820152602401611620565b6000613b6e83836000613daf565b90506001600160a01b038116613b9a57604051637e27328960e01b815260048101839052602401611620565b836001600160a01b0316816001600160a01b0316146116d6576040516364283d7b60e01b81526001600160a01b0380861660048301526024820184905282166044820152606401611620565b613bef81614d28565b613bf76149e2565b601060179054906101000a900460ff1660ff16601d60008154613c1990615cc5565b9182905550613c289190615ec6565b158015613c435750601054601154600160c01b90910460ff16105b15613c7257601060189054906101000a900460ff1660ff1660116000828254613c6c9190615c48565b90915550505b80336001600160a01b03167fbad86ebd67551dab515f3deb3533553af5e9095e959be845b583dd28c9b1f2bd84604051613cac9190615eda565b60405180910390a35050565b60006064613cc68385615bd8565b613cd09190615c34565b9392505050565b6040516370a0823160e01b815230600482015260009081906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa158015613d40573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613d649190615c05565b905080841115613d72578093505b8315613da757613da47f0000000000000000000000000000000000000000000000000000000000000000868686614dfd565b91505b509392505050565b6000828152600260205260408120546001600160a01b0390811690831615613ddc57613ddc818486614f8e565b6001600160a01b03811615613e1a57613df9600085600080614bdd565b6001600160a01b038116600090815260036020526040902080546000190190555b6001600160a01b03851615613e49576001600160a01b0385166000908152600360205260409020805460010190555b600084815260026020526040808220805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0389811691821790925591518793918516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4949350505050565b336001600160a01b03821603613ef7576040517f5e03d55f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b03166000908152600660205260409020805460ff19169055565b60007f0000000000000000000000000000000000000000000000000000000000000000613f436115d2565b10613f4d57600080fd5b60185460018101601855613f61838261500b565b613f6a81614d28565b80336001600160a01b03167fbad86ebd67551dab515f3deb3533553af5e9095e959be845b583dd28c9b1f2bd6000604051613fa59190615eda565b60405180910390a392915050565b613fbb615025565b6007805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b600080600b60009054906101000a90046001600160a01b03166001600160a01b0316635ec01e4d6040518163ffffffff1660e01b81526004016020604051808303816000875af115801561405d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140819190615c05565b905060005b60078110156140a957601082901b831792506140a28160010190565b9050614086565b505060009182526021602052604090912055565b600082815260226020526040812082905582900361413b577f444dd684ad9169cbde7791d7d420dd3a30ac09e06232e426a3c0f7ff8f79f2fd614101600080612472565b61410d60006001612472565b61411960006002612472565b6040805193845260208401929092529082015260600160405180910390a15050565b60005b60058110156141e7576000614154826010615bd8565b60008581526024602052604090205461ffff911c1690508061417581611ee5565b6001600160a01b03167f826ad53ae1bd429955605b9bd4bbc359cddafdc76e81736a9950c35817515fa96141aa846000612472565b6141b5856001612472565b6141c0866002612472565b6040805193845260208401929092529082015260600160405180910390a35060010161413e565b5050600090815260246020526040812055565b60008060006142287f0000000000000000000000000000000000000000000000000000000000000000615061565b905060006016546142587f0000000000000000000000000000000000000000000000000000000000000000615061565b6142629190615c48565b6009546040517f9cd441da00000000000000000000000000000000000000000000000000000000815260048101859052602481018390529192506001600160a01b031690639cd441da906044016060604051808303816000875af11580156142ce573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906142f29190615f02565b9095509350600090506143058584615c48565b905060006143138584615c48565b600c54604051630a956fa360e31b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660048301527f0000000000000000000000000000000000000000000000000000000000000000811660248301526fffffffffffffffffffffffffffffffff861660448301526127106064830152929350600092909116906354ab7d1890608401602060405180830381865afa1580156143cb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906143ef9190615c05565b9050818111801561440f5750601a5460009061440d90600890615c48565b115b1561443057600860ff16601a600082825461442a9190615c48565b90915550505b818110801561444e5750601a54605a9061444c90600890615c5b565b105b1561446f57600860ff16601a60008282546144699190615c5b565b90915550505b50505050509091565b6144806124f3565b6144cc5760405162461bcd60e51b815260206004820152600b60248201527f4e4f545f454e41424c45440000000000000000000000000000000000000000006044820152606401611620565b6020805460ff19168155600080805260229091527fb84cf808d0d5b1ad44962c9bfddd3cfce67763c49ab557cfd0e9f6804faade9955565b61450c613ab6565b6007805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258613fe83390565b6001600160a01b03821661458c576040517f5b08ba180000000000000000000000000000000000000000000000000000000081526001600160a01b0383166004820152602401611620565b6001600160a01b03838116600081815260056020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6001600160a01b0383163b156116d657604051630a85bd0160e11b81526001600160a01b0384169063150b7a029061463b903390889087908790600401615f4e565b6020604051808303816000875af1925050508015614676575060408051601f3d908101601f1916820190925261467391810190615f80565b60015b6146df573d8080156146a4576040519150601f19603f3d011682016040523d82523d6000602084013e6146a9565b606091505b5080516000036146d757604051633250574960e11b81526001600160a01b0385166004820152602401611620565b805181602001fd5b6001600160e01b03198116630a85bd0160e11b146118bb57604051633250574960e11b81526001600160a01b0385166004820152602401611620565b6000806147278561250d565b905082614735576000614748565b60c08101516147489060ff166001615c5b565b84614754576000614772565b60a08201516147679060ff166001615c5b565b614772906064615bd8565b60808301516147859060ff166001615c5b565b61479190612710615bd8565b60608401516147a49060ff166001615c5b565b6147b190620f4240615bd8565b60408501516147c49060ff166001615c5b565b6147d2906305f5e100615bd8565b60208601516147e59060ff166001615c5b565b6147f4906402540be400615bd8565b86516148049060ff166001615c5b565b6148139064e8d4a51000615bd8565b61481d9190615c5b565b6148279190615c5b565b6148319190615c5b565b61483b9190615c5b565b6148459190615c5b565b613da49190615c5b565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1663095ea7b360e01b17905291516000928392908716916148c09190615f9d565b6000604051808303816000865af19150503d80600081146148fd576040519150601f19603f3d011682016040523d82523d6000602084013e614902565b606091505b509150915081801561492c57508051158061492c57508080602001905181019061492c9190615c6e565b6118bb5760405162461bcd60e51b815260206004820152600260248201527f53410000000000000000000000000000000000000000000000000000000000006044820152606401611620565b60006026836003811061498d5761498d615bef565b6003020182600381106149a2576149a2615bef565b01544710156149b15747613cd0565b602683600381106149c4576149c4615bef565b6003020182600381106149d9576149d9615bef565b01549392505050565b601054600160a01b900460ff16158015906149ff57506000601954115b8015614a175750601954614a1590600590615ec6565b155b15611a72576000600560ff16614aaf600e60009054906101000a90046001600160a01b03166001600160a01b0316632dddd49c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015614a79573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614a9d9190615c05565b601054600160a01b900460ff16613cb8565b614ab99190615bd8565b6040516370a0823160e01b815230600482015290915081906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa158015614b22573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614b469190615c05565b11614b935760405162461bcd60e51b815260206004820152600c60248201527f494e56414c49445f5745544800000000000000000000000000000000000000006044820152606401611620565b600e546040517f6d7bcb05000000000000000000000000000000000000000000000000000000008152600481018390526001600160a01b0390911690636d7bcb059060240161188d565b8080614bf157506001600160a01b03821615155b15614ceb576000614c0184613a70565b90506001600160a01b03831615801590614c2d5750826001600160a01b0316816001600160a01b031614155b8015614c5f57506001600160a01b0380821660009081526005602090815260408083209387168352929052205460ff16155b15614ca1576040517fa9fbf51f0000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401611620565b8115614ce95783856001600160a01b0316826001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45b505b50506000908152600460205260409020805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0392909216919091179055565b614d3181611a94565b15614d68576040517f2158886700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601954614d7790600590615ec6565b600003614d88576025819055614db0565b601954614d9790600590615ec6565b614da2906010615bd8565b602580549183901b90911790555b600081815260236020526040902060019055614dcb81614005565b600560ff16601960008154614ddf90615cc5565b9182905550614dee9190615ec6565b6000036115cf576115cf61515f565b6000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316866001600160a01b03161480614e7157507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316856001600160a01b0316145b614e7d57610bb8614e81565b6127105b6010546040805160e0810182526001600160a01b038a811682528981166020830190815262ffffff8681168486019081528a841660608601908152608086018d8152600060a0880181815260c0890191825298517f04e45aaf00000000000000000000000000000000000000000000000000000000815297518716600489015294518616602488015291519092166044860152905183166064850152516084840152925160a48301529151821660c482015292935016906304e45aaf9060e4016020604051808303816000875af1158015614f60573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614f849190615c05565b9695505050505050565b614f998383836153d1565b611a8f576001600160a01b038316614fc757604051637e27328960e01b815260048101829052602401611620565b6040517f177e802f0000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260248101829052604401611620565b6112b9828260405180602001604052806000815250615457565b60075460ff16611a72576040517f8dfc202b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040516370a0823160e01b81523060048201526000906001600160a01b038316906370a0823190602401602060405180830381865afa1580156150a8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906150cc9190615c05565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b031603615118576111ed66038d7ea4c6800082615c48565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b031603611a29576111ed6305f5e10082615c48565b600e60009054906101000a90046001600160a01b03166001600160a01b031663392e53cd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156151b2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906151d69190615c6e565b6152225760405162461bcd60e51b815260206004820152600f60248201527f4e4f5f535542534352495054494f4e00000000000000000000000000000000006044820152606401611620565b600e54604080517f8f0b707200000000000000000000000000000000000000000000000000000000815290516000926001600160a01b031691638f0b7072916004808301926020929190829003018187875af1158015615286573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906152aa9190615c05565b9050806000036152fc5760405162461bcd60e51b815260206004820152601160248201527f45584341564154494f4e5f4641494c45440000000000000000000000000000006044820152606401611620565b6025546000828152602460205260408120919091555b600581101561535857816023600061532b846010615bd8565b60008681526024602090815260408083205490931c61ffff16845283019390935201902055600101615312565b506025546040805183815261ffff808416602080840191909152601085901c82168385015284901c81166060830152603084901c8116608083015292821c90921660a0830152517f38b3bf74e600502d58a56a19e87e871fc5571f28137b720740a28ad3269bb35b9181900360c00190a1506000602555565b60006001600160a01b0383161580159061544f5750826001600160a01b0316846001600160a01b0316148061542b57506001600160a01b0380851660009081526005602090815260408083209387168352929052205460ff165b8061544f57506000828152600460205260409020546001600160a01b038481169116145b949350505050565b615461838361546e565b611a8f60008484846145f9565b6001600160a01b03821661549857604051633250574960e11b815260006004820152602401611620565b60006154a683836000613daf565b90506001600160a01b03811615611a8f576040517f73c6ac6e00000000000000000000000000000000000000000000000000000000815260006004820152602401611620565b60405180606001604052806003905b615503615519565b8152602001906001900390816154fb5790505090565b60405180606001604052806003906020820280368337509192915050565b6040518060e001604052806007906020820280368337509192915050565b6040518060a001604052806005906020820280368337509192915050565b6001600160e01b0319811681146115cf57600080fd5b60006020828403121561559b57600080fd5b8135613cd081615573565b60005b838110156155c15781810151838201526020016155a9565b50506000910152565b600081518084526155e28160208601602086016155a6565b601f01601f19169290920160200192915050565b602081526000613cd060208301846155ca565b60006020828403121561561b57600080fd5b5035919050565b6001600160a01b03811681146115cf57600080fd5b6000806040838503121561564a57600080fd5b823561565581615622565b946020939093013593505050565b610120810181836000805b600380821061567d57506156ba565b835185845b838110156156a0578251825260209283019290910190600101615682565b50505060609490940193506020929092019160010161566e565b5050505092915050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff81118282101715615703576157036156c4565b604052919050565b6000602080838503121561571e57600080fd5b823567ffffffffffffffff8082111561573657600080fd5b818501915085601f83011261574a57600080fd5b81358181111561575c5761575c6156c4565b8060051b915061576d8483016156da565b818152918301840191848101908884111561578757600080fd5b938501935b838510156157a55784358252938501939085019061578c565b98975050505050505050565b6000806000806000608086880312156157c957600080fd5b85356157d481615622565b945060208601356157e481615622565b935060408601359250606086013567ffffffffffffffff8082111561580857600080fd5b818801915088601f83011261581c57600080fd5b81358181111561582b57600080fd5b89602082850101111561583d57600080fd5b9699959850939650602001949392505050565b60006020828403121561586257600080fd5b8135613cd081615622565b60008060006060848603121561588257600080fd5b833561588d81615622565b9250602084013561589d81615622565b929592945050506040919091013590565b60608101818360005b60038110156158d65781518352602092830192909101906001016158b7565b50505092915050565b6000602082840312156158f157600080fd5b813560ff81168114613cd057600080fd5b80151581146115cf57600080fd5b6000806040838503121561592357600080fd5b823561592e81615622565b9150602083013561593e81615902565b809150509250929050565b600067ffffffffffffffff821115615963576159636156c4565b50601f01601f191660200190565b6000806000806080858703121561598757600080fd5b843561599281615622565b935060208501356159a281615622565b925060408501359150606085013567ffffffffffffffff8111156159c557600080fd5b8501601f810187136159d657600080fd5b80356159e96159e482615949565b6156da565b8181528860208385010111156159fe57600080fd5b8160208401602083013760006020838301015280935050505092959194509250565b60008060408385031215615a3357600080fd5b8235915060208301356003811061593e57600080fd5b60e08101818360005b60078110156158d657815160ff16835260209283019290910190600101615a52565b803560058110611a2957600080fd5b60008060a08385031215615a9657600080fd5b83601f840112615aa557600080fd5b6040516080810181811067ffffffffffffffff82111715615ac857615ac86156c4565b604052806080850186811115615add57600080fd5b855b81811015615af7578035835260209283019201615adf565b50829450615b0481615a74565b93505050509250929050565b60008060408385031215615b2357600080fd5b50508035926020909101359150565b60a08101818360005b60058110156158d6578151835260209283019290910190600101615b3b565b60008060408385031215615b6d57600080fd5b8235615b7881615622565b9150602083013561593e81615622565b600181811c90821680615b9c57607f821691505b602082108103615bbc57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b80820281158282048414176111ed576111ed615bc2565b634e487b7160e01b600052603260045260246000fd5b600060208284031215615c1757600080fd5b5051919050565b634e487b7160e01b600052601260045260246000fd5b600082615c4357615c43615c1e565b500490565b818103818111156111ed576111ed615bc2565b808201808211156111ed576111ed615bc2565b600060208284031215615c8057600080fd5b8151613cd081615902565b634e487b7160e01b600052602160045260246000fd5b60008060408385031215615cb457600080fd5b505080516020909101519092909150565b600060018201615cd757615cd7615bc2565b5060010190565b6000828254808552602080860195506005818360051b8501016000878152838120815b86811015615dbb57878403601f19018b5281548390600181811c9080831680615d2b57607f831692505b8a83108103615d4857634e487b7160e01b88526022600452602488fd5b82895260208901818015615d635760018114615d7857615da2565b60ff19861682528415158c1b82019650615da2565b6000898152602090208a5b86811015615d9c57815484820152908501908e01615d83565b83019750505b5050509d89019d92965050509190910190600101615d01565b50919998505050505050505050565b600061014086835260208084018760005b6007811015615dfb57815160ff1683529183019190830190600101615ddb565b5050505080610100840152615e1281840186615cde565b9050828103610120840152615e278185615cde565b979650505050505050565b600060208284031215615e4457600080fd5b815167ffffffffffffffff811115615e5b57600080fd5b8201601f81018413615e6c57600080fd5b8051615e7a6159e482615949565b818152856020838501011115615e8f57600080fd5b615ea08260208301602086016155a6565b95945050505050565b600060208284031215615ebb57600080fd5b8151613cd081615622565b600082615ed557615ed5615c1e565b500690565b6020810160038310615efc57634e487b7160e01b600052602160045260246000fd5b91905290565b600080600060608486031215615f1757600080fd5b83516fffffffffffffffffffffffffffffffff81168114615f3757600080fd5b602085015160409095015190969495509392505050565b60006001600160a01b03808716835280861660208401525083604083015260806060830152614f8460808301846155ca565b600060208284031215615f9257600080fd5b8151613cd081615573565b60008251615faf8184602087016155a6565b919091019291505056fea264697066735822122074701aedf65d4abcaf127673a6336bd5159dffea41a80df809e8cb63dfdc622864736f6c634300081700336101606040526002805463ffffffff60401b19166a0668a000000000000000001790553480156200002f57600080fd5b5060405162001cfb38038062001cfb8339810160408190526200005291620002ff565b838733806200007c57604051631e4fbdf760e01b8152600060048201526024015b60405180910390fd5b620000878162000188565b50620000b4816001600160a01b03166000908152600160208190526040909120805460ff19169091179055565b506001600160a01b039081166080528681166101405285811660a081905285821660c05283821660e081905291831661010052610120859052620000fc9190600019620001d8565b6101005160c05160405163095ea7b360e01b81526001600160a01b039182166004820152600019602482015291169063095ea7b3906044016020604051808303816000875af115801562000154573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200017a91906200038b565b5050505050505050620003e7565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b031663095ea7b360e01b1790529151600092839290871691620002369190620003b6565b6000604051808303816000865af19150503d806000811462000275576040519150601f19603f3d011682016040523d82523d6000602084013e6200027a565b606091505b5091509150818015620002a8575080511580620002a8575080806020019051810190620002a891906200038b565b620002db5760405162461bcd60e51b8152602060048201526002602482015261534160f01b604482015260640162000073565b5050505050565b80516001600160a01b0381168114620002fa57600080fd5b919050565b600080600080600080600060e0888a0312156200031b57600080fd5b6200032688620002e2565b96506200033660208901620002e2565b95506200034660408901620002e2565b94506200035660608901620002e2565b9350608088015192506200036d60a08901620002e2565b91506200037d60c08901620002e2565b905092959891949750929550565b6000602082840312156200039e57600080fd5b81518015158114620003af57600080fd5b9392505050565b6000825160005b81811015620003d95760208186018101518583015201620003bd565b506000920191825250919050565b60805160a05160c05160e051610100516101205161014051611826620004d56000396000818161117301526112320152600081816102ba0152610d980152600081816101b9015281816109a101528181610ca701528181610f0501528181610fb40152818161104c015261133d015260008181610416015281816108ab01528181610c04015281816110dc01526113180152600081816103e201528181610711015281816109d201528181610a8901528181610b9e01528181610cd001528181610df7015261107501526000818161032301526113f4015260008181610633015261068e01526118266000f3fe6080604052600436106101805760003560e01c80638129fc1c116100d6578063ad5c46481161007f578063df76bbbd11610059578063df76bbbd1461047f578063ed647d211461049f578063f2fde38b146104d957600080fd5b8063ad5c464814610404578063b60d428814610438578063debfda301461044057600080fd5b806399c5636d116100b057806399c5636d146103805780639ac2a011146103a0578063a3e56fa8146103d057600080fd5b80638129fc1c146103455780638da5cb5b1461034d5780638f0b70721461036b57600080fd5b80632dddd49c116101385780636d7bcb05116101125780636d7bcb05146102dc578063715018a6146102fc578063735de9f71461031157600080fd5b80632dddd49c14610258578063392e53cd1461027b57806351dc86a5146102a857600080fd5b80631f5a0bbe116101695780631f5a0bbe146101f85780631fe543e314610218578063247884291461023857600080fd5b80630674986c146101855780631b6b6d23146101a7575b600080fd5b34801561019157600080fd5b506101a56101a036600461150f565b6104f9565b005b3480156101b357600080fd5b506101db7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561020457600080fd5b506101a561021336600461154a565b6105c0565b34801561022457600080fd5b506101a561023336600461157d565b610628565b34801561024457600080fd5b506101a561025336600461154a565b6106c9565b34801561026457600080fd5b5061026d61070c565b6040519081526020016101ef565b34801561028757600080fd5b5060025467ffffffffffffffff1615155b60405190151581526020016101ef565b3480156102b457600080fd5b5061026d7f000000000000000000000000000000000000000000000000000000000000000081565b3480156102e857600080fd5b506101a56102f7366004611647565b610818565b34801561030857600080fd5b506101a5610a6b565b34801561031d57600080fd5b506101db7f000000000000000000000000000000000000000000000000000000000000000081565b6101a5610a7f565b34801561035957600080fd5b506000546001600160a01b03166101db565b34801561037757600080fd5b5061026d610d63565b34801561038c57600080fd5b506101a561039b366004611647565b610e72565b3480156103ac57600080fd5b506102986103bb36600461154a565b60016020526000908152604090205460ff1681565b3480156103dc57600080fd5b506101db7f000000000000000000000000000000000000000000000000000000000000000081565b34801561041057600080fd5b506101db7f000000000000000000000000000000000000000000000000000000000000000081565b6101a561109d565b34801561044c57600080fd5b5061029861045b36600461154a565b6001600160a01b031660009081526001602081905260409091205460ff1615151490565b34801561048b57600080fd5b506101a561049a366004611660565b611135565b3480156104ab57600080fd5b506002546104c09067ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016101ef565b3480156104e557600080fd5b506101a56104f436600461154a565b6111dc565b3360009081526001602081905260409091205460ff16151581141461053857604051635cf20d6360e01b81523360048201526024015b60405180910390fd5b620509108163ffffffff16116105905760405162461bcd60e51b815260206004820152600760248201527f544f4f5f4c4f5700000000000000000000000000000000000000000000000000604482015260640161052f565b6002805463ffffffff90921668010000000000000000026bffffffff000000000000000019909216919091179055565b3360009081526001602081905260409091205460ff1615158114146105fa57604051635cf20d6360e01b815233600482015260240161052f565b610625816001600160a01b03166000908152600160208190526040909120805460ff19169091179055565b50565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146106bb576040517f1cf993f40000000000000000000000000000000000000000000000000000000081523360048201526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016602482015260440161052f565b6106c58282611230565b5050565b3360009081526001602081905260409091205460ff16151581141461070357604051635cf20d6360e01b815233600482015260240161052f565b610625816112a1565b6000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663ad1783616040518163ffffffff1660e01b8152600401602060405180830381865afa15801561076d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107919190611682565b6001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa1580156107ce573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107f291906116be565b5050509150506000808213156108085781610811565b661717b72f0a40005b9392505050565b60025467ffffffffffffffff166108555760405162461bcd60e51b81526020600482015260016024820152600360fc1b604482015260640161052f565b600081116108895760405162461bcd60e51b81526020600482015260016024820152603160f81b604482015260640161052f565b6040516323b872dd60e01b8152336004820152306024820152604481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906323b872dd906064016020604051808303816000875af11580156108fc573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610920919061170e565b61092957600080fd5b60006109358230611304565b9050600081116109875760405162461bcd60e51b815260206004820152600160248201527f3200000000000000000000000000000000000000000000000000000000000000604482015260640161052f565b6002546040805167ffffffffffffffff90921660208301527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031691634000aea0917f0000000000000000000000000000000000000000000000000000000000000000918591015b6040516020818303038152906040526040518463ffffffff1660e01b8152600401610a2393929190611730565b6020604051808303816000875af1158015610a42573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a66919061170e565b505050565b610a73611461565b610a7d60006114a7565b565b610a87611461565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663a21a23e46040518163ffffffff1660e01b81526004016020604051808303816000875af1158015610ae7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b0b9190611797565b6002805467ffffffffffffffff191667ffffffffffffffff929092169182179055610b5c5760405162461bcd60e51b81526020600482015260016024820152600360fc1b604482015260640161052f565b6002546040517f7341c10c00000000000000000000000000000000000000000000000000000000815267ffffffffffffffff90911660048201523060248201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690637341c10c90604401600060405180830381600087803b158015610bea57600080fd5b505af1158015610bfe573d6000803e3d6000fd5b505050507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b158015610c5d57600080fd5b505af1158015610c71573d6000803e3d6000fd5b50505050506000610c823430611304565b6002546040805167ffffffffffffffff90921660208301529192506001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691634000aea0917f0000000000000000000000000000000000000000000000000000000000000000918591016040516020818303038152906040526040518463ffffffff1660e01b8152600401610d2093929190611730565b6020604051808303816000875af1158015610d3f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106c5919061170e565b6000610d6d611461565b6002546040517f5d3b1d300000000000000000000000000000000000000000000000000000000081527f0000000000000000000000000000000000000000000000000000000000000000600482015267ffffffffffffffff82166024820152600360448201526801000000000000000090910463ffffffff166064820152600160848201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690635d3b1d309060a4016020604051808303816000875af1158015610e48573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e6c91906117c1565b91505090565b60025467ffffffffffffffff16610eaf5760405162461bcd60e51b81526020600482015260016024820152600360fc1b604482015260640161052f565b60008111610ee35760405162461bcd60e51b81526020600482015260016024820152603160f81b604482015260640161052f565b6040516323b872dd60e01b8152336004820152306024820152604481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906323b872dd906064016020604051808303816000875af1158015610f56573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f7a919061170e565b610f8357600080fd5b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015611003573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061102791906117c1565b6002546040805167ffffffffffffffff90921660208301529192506001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691634000aea0917f0000000000000000000000000000000000000000000000000000000000000000918591016109f6565b60025467ffffffffffffffff166110da5760405162461bcd60e51b81526020600482015260016024820152600360fc1b604482015260640161052f565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b158015610c5d57600080fd5b61113d611461565b6040517fe37d3bb300000000000000000000000000000000000000000000000000000000815260048101839052602481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063e37d3bb3906044015b600060405180830381600087803b1580156111c057600080fd5b505af11580156111d4573d6000803e3d6000fd5b505050505050565b6111e4611461565b6001600160a01b038116611227576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081526000600482015260240161052f565b610625816114a7565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663e37d3bb38383600081518110611273576112736117da565b60200260200101516040518363ffffffff1660e01b81526004016111a6929190918252602082015260400190565b336001600160a01b038216036112e3576040517f5e03d55f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b03166000908152600160205260409020805460ff19169055565b6040805160e0810182526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811682527f0000000000000000000000000000000000000000000000000000000000000000811660208301908152610bb88385019081528583166060850190815260808501888152600260a08701908152600060c0880181815298517f04e45aaf000000000000000000000000000000000000000000000000000000008152975187166004890152945186166024880152925162ffffff1660448701529051841660648601525160848501525160a48401529251811660c48301527f000000000000000000000000000000000000000000000000000000000000000016906304e45aaf9060e4016020604051808303816000875af115801561143d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061081191906117c1565b6000546001600160a01b03163314610a7d576040517f118cdaa700000000000000000000000000000000000000000000000000000000815233600482015260240161052f565b600080546001600160a01b038381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006020828403121561152157600080fd5b813563ffffffff8116811461081157600080fd5b6001600160a01b038116811461062557600080fd5b60006020828403121561155c57600080fd5b813561081181611535565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561159057600080fd5b8235915060208084013567ffffffffffffffff808211156115b057600080fd5b818601915086601f8301126115c457600080fd5b8135818111156115d6576115d6611567565b8060051b604051601f19603f830116810181811085821117156115fb576115fb611567565b60405291825284820192508381018501918983111561161957600080fd5b938501935b828510156116375784358452938501939285019261161e565b8096505050505050509250929050565b60006020828403121561165957600080fd5b5035919050565b6000806040838503121561167357600080fd5b50508035926020909101359150565b60006020828403121561169457600080fd5b815161081181611535565b805169ffffffffffffffffffff811681146116b957600080fd5b919050565b600080600080600060a086880312156116d657600080fd5b6116df8661169f565b94506020860151935060408601519250606086015191506117026080870161169f565b90509295509295909350565b60006020828403121561172057600080fd5b8151801515811461081157600080fd5b6001600160a01b03841681526000602084602084015260606040840152835180606085015260005b8181101561177457858101830151858201608001528201611758565b506000608082860101526080601f19601f83011685010192505050949350505050565b6000602082840312156117a957600080fd5b815167ffffffffffffffff8116811461081157600080fd5b6000602082840312156117d357600080fd5b5051919050565b634e487b7160e01b600052603260045260246000fdfea2646970667358221220b65669414a610a07a93049459bf167b46fe4ba1ca032248c997fd46b0fa1850764736f6c63430008170033000000000000000000000000b96829b761fa8cf594144fb5b1f86c47943aa111000000000000000000000000d1cd0f7c1131ceb1f642b488405ec9e32b4026030000000000000000000000009a051784defafab0ec587113c625d19ff04e2ce4000000000000000000000000767ae0e2ff010afff8b1ad02bf8e2a8e4a682def00000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e000000000000000000000000271682deb8c4e0901d1a1550ad2e64d568e699098af398995b04c28e9951adb9721ef74c74f93e6a478f39e7e0777be13527e7ef000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca0000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000027f7d0bdb9200000000000000000000000000000000000000000000000000000429d069189e00000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000429d069189e00000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000004563918244f400000000000000000000000000000000000000000000000000004563918244f40000000000000000000000000000000000000000000000000000d02ab486cedc00000000000000000000000000000000000000000000000000022b1c8c1227a000000000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000007c585087238000000000000000000000000000000000000000000000000000007c58508723800000000000000000000000000000000000000000000000000003782dace9d90000000000000000000000000000000000000000000000000000000000000000001900000000000000000000000000000000000000000000000000000000000001a400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000004c000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000072330303030303000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000723464646464646000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007234546333230300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000072346464237303100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000723374530304536000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007236632303037300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000072330304536394100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000723303044334535000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007233030343046460000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000044f6e79780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000074469616d6f6e6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000743697472696e65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005546f70617a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008416d657468797374000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000452756279000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007456d6572616c6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a417175616d6172696e650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000085361707068697265000000000000000000000000000000000000000000000000

Deployed Bytecode

0x6080604052600436106105e15760003560e01c80637e962bd711610301578063ba6427f71161019a578063cb217dfd116100ec578063e37d3bb311610095578063f10fb5841161006f578063f10fb584146110fb578063f414b3a11461111b578063fc7646b01461113c57600080fd5b8063e37d3bb314611072578063e92a4d0414611092578063e985e9c5146110b257600080fd5b8063dbb7754e116100c6578063dbb7754e14610ffe578063debfda3014611014578063e1b3c9411461105257600080fd5b8063cb217dfd14610f97578063d139830014610faa578063d5abeb0114610fca57600080fd5b8063c4a528f81161014e578063c622cd8311610128578063c622cd8314610f41578063c8261c3c14610f57578063c87b56dd14610f7757600080fd5b8063c4a528f814610ef6578063c4d66de814610f0b578063c58896e414610f2b57600080fd5b8063bd843bb21161017f578063bd843bb214610e94578063bdd3d82514610ec1578063c06aef0314610ee157600080fd5b8063ba6427f714610e5f578063bcb518dd14610e7f57600080fd5b80639ac2a01111610253578063ac6d37be11610207578063b4cce1ab116101e1578063b4cce1ab14610e04578063b514218a14610e1f578063b88d4fde14610e3f57600080fd5b8063ac6d37be14610d72578063ad5c464814610d9c578063b22604b114610dd057600080fd5b8063a22cb46511610238578063a22cb46514610d24578063a890610b14610d44578063aba4571714610d5a57600080fd5b80639ac2a01114610cde5780639fe72ab614610d0e57600080fd5b80638b0af0fa116102b557806395a668861161028f57806395a6688614610c9e57806395d89b4114610cb35780639a2a724614610cc857600080fd5b80638b0af0fa14610c34578063907c3f9914610c495780639178fcd914610c6957600080fd5b8063812251b0116102e6578063812251b014610bdd5780638410968914610c0a5780638456cb5914610c1f57600080fd5b80637e962bd714610bb15780637fd18dea14610bc757600080fd5b8063392e53cd1161047e578063541d014d116103d05780636f0b863a11610379578063735de9f711610353578063735de9f714610b665780637b27ac8714610b865780637b56cf2e14610b9b57600080fd5b80636f0b863a14610b0c57806370a0823114610b265780637177a76914610b4657600080fd5b8063604244c3116103aa578063604244c314610aac57806363204ec314610acc5780636352211e14610aec57600080fd5b8063541d014d14610a51578063595d034f14610a7e5780635c975abb14610a9457600080fd5b806342842e0e116104325780634d06ef651161040c5780634d06ef6514610a075780634febfac614610a275780635409c2f014610a3c57600080fd5b806342842e0e146109a457806348c36640146109c45780634b52a233146109f157600080fd5b80633d59ccfa116104635780633d59ccfa146109465780633f0ef9ba146109795780633f4ba83a1461098f57600080fd5b8063392e53cd146109165780633c67a5b21461093057600080fd5b80631eb08ba9116105375780632630c12f116104eb57806334120597116104c557806334120597146108b3578063346b3e9f146108c9578063379607f5146108f657600080fd5b80632630c12f1461083f5780632ab5cf661461085f5780632d887b481461089357600080fd5b8063236f3b021161051c578063236f3b02146107ca57806323b872dd146107ff578063247884291461081f57600080fd5b80631eb08ba91461078a5780631f5a0bbe146107aa57600080fd5b80630cf0ac1d116105995780631545c2e7116105735780631545c2e714610731578063170e1dcb1461075557806318160ddd1461077557600080fd5b80630cf0ac1d146106b757806313af593c146106d9578063150b7a02146106ec57600080fd5b806306fdde03116105ca57806306fdde0314610653578063081812fc14610675578063095ea7b31461069557600080fd5b806301ffc9a7146105e65780630531c7c51461061b575b600080fd5b3480156105f257600080fd5b50610606610601366004615589565b611156565b60405190151581526020015b60405180910390f35b34801561062757600080fd5b50600e5461063b906001600160a01b031681565b6040516001600160a01b039091168152602001610612565b34801561065f57600080fd5b506106686111f3565b60405161061291906155f6565b34801561068157600080fd5b5061063b610690366004615609565b611285565b3480156106a157600080fd5b506106b56106b0366004615637565b6112ae565b005b3480156106c357600080fd5b506106cc6112bd565b6040516106129190615663565b6106b56106e736600461570b565b611328565b3480156106f857600080fd5b506107186107073660046157b1565b630a85bd0160e11b95945050505050565b6040516001600160e01b03199091168152602001610612565b34801561073d57600080fd5b5061074760175481565b604051908152602001610612565b34801561076157600080fd5b50600d5461063b906001600160a01b031681565b34801561078157600080fd5b506107476115d2565b34801561079657600080fd5b50600a5461063b906001600160a01b031681565b3480156107b657600080fd5b506106b56107c5366004615850565b6115e8565b3480156107d657600080fd5b506010546107ec90600160a81b900461ffff1681565b60405161ffff9091168152602001610612565b34801561080b57600080fd5b506106b561081a36600461586d565b611651565b34801561082b57600080fd5b506106b561083a366004615850565b6116dc565b34801561084b57600080fd5b50600c5461063b906001600160a01b031681565b34801561086b57600080fd5b506107477f000000000000000000000000000000000000000000000000000000eb2eb63e5081565b34801561089f57600080fd5b506106b56108ae366004615609565b611721565b3480156108bf57600080fd5b5061074760115481565b3480156108d557600080fd5b506107476108e4366004615609565b60246020526000908152604090205481565b34801561090257600080fd5b50610747610911366004615609565b6118c2565b34801561092257600080fd5b50601e546106069060ff1681565b34801561093c57600080fd5b50610747601c5481565b34801561095257600080fd5b5060105461096790600160c01b900460ff1681565b60405160ff9091168152602001610612565b34801561098557600080fd5b5061074760125481565b34801561099b57600080fd5b506106b5611a2e565b3480156109b057600080fd5b506106b56109bf36600461586d565b611a74565b3480156109d057600080fd5b506107476109df366004615609565b60236020526000908152604090205481565b3480156109fd57600080fd5b50610747601a5481565b348015610a1357600080fd5b50610606610a22366004615609565b611a94565b348015610a3357600080fd5b506106b5611acd565b348015610a4857600080fd5b506106b5611bb5565b348015610a5d57600080fd5b50610747610a6c366004615609565b60009081526023602052604090205490565b348015610a8a57600080fd5b5061074760155481565b348015610aa057600080fd5b5060075460ff16610606565b348015610ab857600080fd5b50610606610ac7366004615609565b611dff565b348015610ad857600080fd5b506106b5610ae7366004615609565b611e30565b348015610af857600080fd5b5061063b610b07366004615609565b611ee5565b348015610b1857600080fd5b506020546106069060ff1681565b348015610b3257600080fd5b50610747610b41366004615850565b611ef0565b348015610b5257600080fd5b506106b5610b61366004615609565b611f51565b348015610b7257600080fd5b5060105461063b906001600160a01b031681565b348015610b9257600080fd5b50610967600581565b348015610ba757600080fd5b5061074760145481565b348015610bbd57600080fd5b5061074760255481565b348015610bd357600080fd5b5061074760135481565b348015610be957600080fd5b50610bfd610bf8366004615609565b612198565b60405161061291906158ae565b348015610c1657600080fd5b506107476121e1565b348015610c2b57600080fd5b506106b5612215565b348015610c4057600080fd5b50610606612259565b348015610c5557600080fd5b506106b5610c643660046158df565b6122fc565b348015610c7557600080fd5b506010546109679077010000000000000000000000000000000000000000000000900460ff1681565b348015610caa57600080fd5b506106066123ab565b348015610cbf57600080fd5b5061066861240b565b348015610cd457600080fd5b50610747601f5481565b348015610cea57600080fd5b50610606610cf9366004615850565b60066020526000908152604090205460ff1681565b348015610d1a57600080fd5b50610747601b5481565b348015610d3057600080fd5b506106b5610d3f366004615910565b61241a565b348015610d5057600080fd5b5061074760195481565b348015610d6657600080fd5b506107476305f5e10081565b348015610d7e57600080fd5b50610d8861271081565b60405162ffffff9091168152602001610612565b348015610da857600080fd5b5061063b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b348015610ddc57600080fd5b5061063b7f000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e81565b348015610e1057600080fd5b5061074766038d7ea4c6800081565b348015610e2b57600080fd5b50610606610e3a366004615609565b612425565b348015610e4b57600080fd5b506106b5610e5a366004615971565b61245b565b348015610e6b57600080fd5b50610747610e7a366004615a20565b612472565b348015610e8b57600080fd5b506106066124f3565b348015610ea057600080fd5b50610eb4610eaf366004615609565b61250d565b6040516106129190615a49565b348015610ecd57600080fd5b50600f5461063b906001600160a01b031681565b348015610eed57600080fd5b506106b5612762565b348015610f0257600080fd5b50610967600881565b348015610f1757600080fd5b506106b5610f26366004615850565b612b01565b348015610f3757600080fd5b5061074760165481565b348015610f4d57600080fd5b50610d88610bb881565b348015610f6357600080fd5b506106b5610f72366004615a83565b612c95565b348015610f8357600080fd5b50610668610f92366004615609565b61340a565b6106b5610fa536600461570b565b61349d565b348015610fb657600080fd5b50610967610fc5366004615b10565b613644565b348015610fd657600080fd5b506107477f000000000000000000000000000000000000000000000000000000000000271081565b34801561100a57600080fd5b50610747601d5481565b34801561102057600080fd5b5061060661102f366004615850565b6001600160a01b031660009081526006602052604090205460ff16151560011490565b611065611060366004615609565b61367d565b6040516106129190615b32565b34801561107e57600080fd5b506106b561108d366004615b10565b613949565b34801561109e57600080fd5b5060095461063b906001600160a01b031681565b3480156110be57600080fd5b506106066110cd366004615b5a565b6001600160a01b03918216600090815260056020908152604080832093909416825291909152205460ff1690565b34801561110757600080fd5b50600b5461063b906001600160a01b031681565b34801561112757600080fd5b5060105461096790600160a01b900460ff1681565b34801561114857600080fd5b506031546109679060ff1681565b60006001600160e01b031982167f80ac58cd0000000000000000000000000000000000000000000000000000000014806111b957506001600160e01b031982167f5b5e139f00000000000000000000000000000000000000000000000000000000145b806111ed57507f01ffc9a7000000000000000000000000000000000000000000000000000000006001600160e01b03198316145b92915050565b60606000805461120290615b88565b80601f016020809104026020016040519081016040528092919081815260200182805461122e90615b88565b801561127b5780601f106112505761010080835404028352916020019161127b565b820191906000526020600020905b81548152906001019060200180831161125e57829003601f168201915b5050505050905090565b600061129082613a70565b506000828152600460205260409020546001600160a01b03166111ed565b6112b9828233613aa9565b5050565b6112c56154ec565b6040805160608101909152602660036000835b8282101561131f5760408051606081019182905290600384810287019182845b8154815260200190600101908083116112f8575050505050815260200190600101906112d8565b50505050905090565b611330613ab6565b611338613af3565b8051601c546113479190615bd8565b803410156113685760405163cd1c886760e01b815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000000027106113916115d2565b146113af576040516310a0445f60e21b815260040160405180910390fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b15801561140a57600080fd5b505af115801561141e573d6000803e3d6000fd5b505050505060005b82518110156114d257306001600160a01b031661145b84838151811061144e5761144e615bef565b6020026020010151611ee5565b6001600160a01b0316146114825760405163153e35b760e11b815260040160405180910390fd5b6114a6303385848151811061149957611499615bef565b6020026020010151613b36565b6114ca60028483815181106114bd576114bd615bef565b6020026020010151613be6565b600101611426565b506040516370a0823160e01b81523060048201526000907f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b0316906370a0823190602401602060405180830381865afa15801561153a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061155e9190615c05565b90506016548161156e9190615c34565b600103611585576016546115829082615c48565b90505b80156115c3576115c17f000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e6115bb83601a54613cb8565b30613cd7565b505b50506115cf6001600855565b50565b600060016018546115e39190615c48565b905090565b3360009081526006602052604090205460ff161515600114151560011461162957604051635cf20d6360e01b81523360048201526024015b60405180910390fd5b6115cf816001600160a01b03166000908152600660205260409020805460ff19166001179055565b6001600160a01b03821661167b57604051633250574960e11b815260006004820152602401611620565b6000611688838333613daf565b9050836001600160a01b0316816001600160a01b0316146116d6576040516364283d7b60e01b81526001600160a01b0380861660048301526024820184905282166044820152606401611620565b50505050565b3360009081526006602052604090205460ff161515600114151560011461171857604051635cf20d6360e01b8152336004820152602401611620565b6115cf81613eb5565b3360009081526006602052604090205460ff161515600114151560011461175d57604051635cf20d6360e01b8152336004820152602401611620565b60005b60058110156117d0576000611776826010615bd8565b60008481526024602052604081205490911c61ffff1691508190036117c7576040517fe05cde0b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50600101611760565b50600e54600b54604080517f5ec01e4d00000000000000000000000000000000000000000000000000000000815290516001600160a01b039384169363df76bbbd938693911691635ec01e4d9160048082019260209290919082900301816000875af1158015611844573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118689190615c05565b6040516001600160e01b031960e085901b168152600481019290925260248201526044015b600060405180830381600087803b1580156118a757600080fd5b505af11580156118bb573d6000803e3d6000fd5b5050505050565b60006118cc613af3565b600a546040517faad3ec96000000000000000000000000000000000000000000000000000000008152336004820152602481018490526001600160a01b039091169063aad3ec9690604401600060405180830381600087803b15801561193157600080fd5b505af1158015611945573d6000803e3d6000fd5b50505050600061195433613f18565b600a54604080517fe46d5b06000000000000000000000000000000000000000000000000000000008152905192935033927f9cdcf2f7714cca3508c7f0110b04a90a80a3a8dd0e35de99689db74d28c5383e926001600160a01b03169163e46d5b069160048083019260209291908290030181865afa1580156119db573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119ff9190615c05565b6040805191825260208201879052810184905260600160405180910390a29050611a296001600855565b919050565b3360009081526006602052604090205460ff1615156001141515600114611a6a57604051635cf20d6360e01b8152336004820152602401611620565b611a72613fb3565b565b611a8f8383836040518060200160405280600081525061245b565b505050565b600081815260236020526040812054158015906111ed575050600090815260236020908152604080832054835260229091529020541590565b3360009081526006602052604090205460ff1615156001141515600114611b0957604051635cf20d6360e01b8152336004820152602401611620565b611b116124f3565b15611b1b57600080fd5b6020805460ff19166001179055611b326000614005565b611a726000600b60009054906101000a90046001600160a01b03166001600160a01b0316635ec01e4d6040518163ffffffff1660e01b81526004016020604051808303816000875af1158015611b8c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611bb09190615c05565b6140bd565b611bbd613af3565b6000611bc833611ef0565b11611c155760405162461bcd60e51b815260206004820152600f60248201527f4e4f545f54455452415f4f574e455200000000000000000000000000000000006044820152606401611620565b611c1d612259565b611c555760405162461bcd60e51b81526020600482015260096024820152684e4f545f524541445960b81b6044820152606401611620565b600080611c606141fa565b91509150601754601954611c749190615c5b565b601581905560195460408051918252602082018590528101839052606081019190915233907f1b63b4ee59f5f20bc7ddffc1e45d2a42feb1677a86891aac7a00b819d70c01c49060800160405180910390a260165460405163095ea7b360e01b815233600482015260248101919091527f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b03169063095ea7b3906044016020604051808303816000875af1158015611d37573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d5b9190615c6e565b5060165460405163a9059cbb60e01b815233600482015260248101919091527f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b03169063a9059cbb906044015b6020604051808303816000875af1158015611dce573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611df29190615c6e565b505050611a726001600855565b60205460009060ff16611e135760006111ed565b611e1e826000612472565b611e29600080612472565b1492915050565b338181611e3c82611ee5565b6001600160a01b031614611e635760405163153e35b760e11b815260040160405180910390fd5b600d546040517f2e2222f8000000000000000000000000000000000000000000000000000000008152336004820152602481018590526001600160a01b0390911690632e2222f890604401600060405180830381600087803b158015611ec857600080fd5b505af1158015611edc573d6000803e3d6000fd5b50505050505050565b60006111ed82613a70565b60006001600160a01b038216611f35576040517f89c62b6400000000000000000000000000000000000000000000000000000000815260006004820152602401611620565b506001600160a01b031660009081526003602052604090205490565b611f59613af3565b338181611f6582611ee5565b6001600160a01b031614611f8c5760405163153e35b760e11b815260040160405180910390fd5b60205460ff168015611fa25750611fa283611dff565b611fd25760405162461bcd60e51b81526020600482015260016024820152600360fc1b6044820152606401611620565b611fda614478565b600c54601f54604051630a956fa360e31b81526001600160a01b037f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2811660048301527f000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e811660248301526fffffffffffffffffffffffffffffffff9092166044820152612710606482015260009291909116906354ab7d1890608401602060405180830381865afa158015612094573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120b89190615c05565b600d546040517fe2c92a5200000000000000000000000000000000000000000000000000000000815233600482015260248101839052600060448201529192506001600160a01b03169063e2c92a5290606401600060405180830381600087803b15801561212557600080fd5b505af1158015612139573d6000803e3d6000fd5b5050505083336001600160a01b03167f25d161bd539b3943e072b306a72e430a67a737fbbdc48deec66e815b2222ef9b612174876000612472565b60408051918252602082018690520160405180910390a35050506115cf6001600855565b6121a0615519565b600060405180606001604052806121b8856000612472565b81526020016121c8856001612472565b81526020016121d8856002612472565b90529392505050565b60006121eb6115d2565b6115e3907f0000000000000000000000000000000000000000000000000000000000002710615c48565b3360009081526006602052604090205460ff161515600114151560011461225157604051635cf20d6360e01b8152336004820152602401611620565b611a72614504565b6000601554601954101580156115e357506016546040516370a0823160e01b81523060048201527f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b0316906370a08231906024015b602060405180830381865afa1580156122d2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122f69190615c05565b11905090565b3360009081526006602052604090205460ff161515600114151560011461233857604051635cf20d6360e01b8152336004820152602401611620565b60648160ff1611156123705760405162461bcd60e51b81526020600482015260016024820152600360fc1b6044820152606401611620565b6010805460ff909216600160a01b027fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff909216919091179055565b6000601254601954101580156115e357506013546040516370a0823160e01b81523060048201527f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b0316906370a08231906024016122b5565b60606001805461120290615b88565b6112b9338383614541565b60007f000000000000000000000000000000000000000000000000000000eb2eb63e50612453836000612472565b141592915050565b612466848484611651565b6116d6848484846145f9565b60008082600281111561248757612487615c8b565b0361249f576124988360008061471b565b90506111ed565b60018260028111156124b3576124b3615c8b565b036124c557612498836001600061471b565b60028260028111156124d9576124d9615c8b565b036124ea576124988360018061471b565b50600092915050565b60205460009060ff1680156115e357506115e36000612425565b612515615537565b61251e82611a94565b80612535575081158015612535575060205460ff16155b80612553575060008281526021602052604090205460101c61ffff16155b156125965750506040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915290565b6040805160e0810182526000848152602360209081528382205482526022815283822054868352602190915292902054909182916125db919060101c61ffff16613644565b60ff16815260008481526023602090815260408083205483526022825280832054878452602183529220549281019261261992911c61ffff16613644565b60ff1681526000848152602360209081526040808320548352602282528083205487845260218352922054920191612658919060301c61ffff16613644565b60ff16815260008481526023602090815260408083205483526022825280832054878452602183529281902054919093019261269a929161ffff911c16613644565b60ff16815260008481526023602090815260408083205483526022825280832054878452602183529220549201916126d9919060501c61ffff16613644565b60ff1681526000848152602360209081526040808320548352602282528083205487845260218352922054920191612718919060601c61ffff16613644565b60ff1681526000848152602360209081526040808320548352602282528083205487845260218352922054920191612757919060701c61ffff16613644565b60ff16905292915050565b61276a613af3565b600061277533611ef0565b116127c25760405162461bcd60e51b815260206004820152600f60248201527f4e4f545f54455452415f4f574e455200000000000000000000000000000000006044820152606401611620565b6127ca6123ab565b6128025760405162461bcd60e51b81526020600482015260096024820152684e4f545f524541445960b81b6044820152606401611620565b600d546040516370a0823160e01b81526001600160a01b0391821660048201526000917f000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e16906370a0823190602401602060405180830381865afa15801561286e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128929190615c05565b600954604080517f36757fea00000000000000000000000000000000000000000000000000000000815281519394506001600160a01b03909216926336757fea92600480820193929182900301816000875af11580156128f6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061291a9190615ca1565b5050600d546040516370a0823160e01b81526001600160a01b0391821660048201526000917f000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e16906370a0823190602401602060405180830381865afa158015612988573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129ac9190615c05565b60195490915033907fe1daabe2f50262db6f2c3abaab14e5c6e55e09dfc414d9b06864533cc33652d6906129e08585615c48565b6012546040805193845260208401929092529082015260600160405180910390a2601454601954612a119190615c5b565b60125560135460405163095ea7b360e01b815233600482015260248101919091527f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b03169063095ea7b3906044016020604051808303816000875af1158015612a85573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612aa99190615c6e565b5060135460405163a9059cbb60e01b815233600482015260248101919091527f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b03169063a9059cbb90604401611daf565b3360009081526006602052604090205460ff1615156001141515600114612b3d57604051635cf20d6360e01b8152336004820152602401611620565b601e5460ff1615612b905760405162461bcd60e51b815260206004820152601360248201527f414c52454144595f494e495449414c495a4544000000000000000000000000006044820152606401611620565b601e805460ff19166001179055600980546001600160a01b03831673ffffffffffffffffffffffffffffffffffffffff19909116179055612bf47f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28260001961484f565b612c217f000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e8260001961484f565b601054612c5b907f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2906001600160a01b031660001961484f565b6010546115cf907f000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e906001600160a01b031660001961484f565b612c9d613ab6565b612ca5613af3565b612cc960405180606001604052806000815260200160008152602001600081525090565b6000808252612cde84825b602002015161250d565b905060005b6004811015612f3857848160048110612cfe57612cfe615bef565b602002015115612f3857825183612d1482615cc5565b90525033612d37868360048110612d2d57612d2d615bef565b6020020151611ee5565b6001600160a01b031614612d5e5760405163153e35b760e11b815260040160405180910390fd5b6000612d75868360048110612cd457612cd4615bef565b9050612d8360016004615c48565b821015612e9b576000612d97836001615c5b565b90505b6004811015612e9957868160048110612db557612db5615bef565b602002015115612e9957868160048110612dd157612dd1615bef565b6020020151878460048110612de857612de8615bef565b602002015103612e24576040517f15f468f200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612e45878260048110612e3957612e39615bef565b60200201516000612472565b612e5a888560048110612e3957612e39615bef565b14612e91576040517f2554246c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600101612d9a565b505b8115612f2f5760a083015160ff1615801590612ebd575060a081015160ff1615155b8015612ed7575060a0808201519084015160ff9081169116145b15612ee85760208401805160010190525b60c083015160ff1615801590612f04575060c081015160ff1615155b8015612f1e575060c0808201519084015160ff9081169116145b15612f2f5760408401805160010190525b50600101612ce3565b50815160021115612f75576040517fe778681d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8151600090612f8690600190615c48565b836020015103612faf578251600191820191612fa191615c48565b836040015103612faf576001015b80158015612fbe575082516002145b1561301757601054601154600160a81b90910461ffff16900361300d576040517fb3d2043500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6011546001016011555b60005b83518110156130845761305b86826004811061303857613038615bef565b602002015160009081526021602090815260408083208390556023909152812055565b61307c333088846004811061307257613072615bef565b6020020151611a74565b60010161301a565b5060006130a1826002866000015161309c9190615c48565b614978565b600c54604051630a956fa360e31b81526001600160a01b037f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2811660048301527f000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e811660248301526fffffffffffffffffffffffffffffffff841660448301526127106064830152929350600092909116906354ab7d1890608401602060405180830381865afa158015613159573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061317d9190615c05565b600d546040516370a0823160e01b81526001600160a01b03918216600482015291925082917f000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e909116906370a0823190602401602060405180830381865afa1580156131ed573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906132119190615c05565b1161325e5760405162461bcd60e51b815260206004820152601260248201527f4e4f545f454e4f5547485f42414c414e434500000000000000000000000000006044820152606401611620565b6000600187600481111561327457613274615c8b565b146132e057600287600481111561328d5761328d615c8b565b146132d95760038760048111156132a6576132a6615c8b565b146132d25760048760048111156132bf576132bf615c8b565b146132cb5760006132e3565b60286132e3565b60146132e3565b600a6132e3565b60055b600d5460ff9190911691506001600160a01b031663e2c92a52336133118561330c866064615c48565b613cb8565b61331b8686613cb8565b6040516001600160e01b031960e086901b1681526001600160a01b03909316600484015260248301919091526044820152606401600060405180830381600087803b15801561336957600080fd5b505af115801561337d573d6000803e3d6000fd5b5050895160208b015160408c01518a518995503394507fca022347fe7d97a9f055d4fa842bd9862141d7c8be0797737e2560a4fc35a0d8938893909290916003106133c95760006133cf565b60608f01515b604080519586526020860194909452928401919091526060830152608082015260a00160405180910390a35050505050506112b96001600855565b606073ccd1203f67954d8886c13da1ad1c21595ab6db3163df390e96836134308561250d565b6040516001600160e01b031960e085901b168152613458929190603090602f90600401615dca565b600060405180830381865af4158015613475573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111ed9190810190615e32565b6134a5613ab6565b6134ad613af3565b8051601c546134bc9190615bd8565b803410156134dd5760405163cd1c886760e01b815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000000027106135066115d2565b14613524576040516310a0445f60e21b815260040160405180910390fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b15801561357f57600080fd5b505af1158015613593573d6000803e3d6000fd5b505050505060005b82518110156114d257336001600160a01b03166135c384838151811061144e5761144e615bef565b6001600160a01b0316146135ea5760405163153e35b760e11b815260040160405180910390fd5b6136258382815181106135ff576135ff615bef565b602002602001015160009081526021602090815260408083208390556023909152812055565b61363c60018483815181106114bd576114bd615bef565b60010161359b565b60315460009060001960ff918216011682848161366357613663615c1e565b048161367157613671615c1e565b06600101905092915050565b613685615555565b61368d613ab6565b613695613af3565b81601b546136a39190615bd8565b803410156136c45760405163cd1c886760e01b815260040160405180910390fd5b6000831180156136d5575060058311155b6137215760405162461bcd60e51b815260206004820152600160248201527f31000000000000000000000000000000000000000000000000000000000000006044820152606401611620565b7f00000000000000000000000000000000000000000000000000000000000027108361374b6115d2565b6137559190615c5b565b11156137a35760405162461bcd60e51b815260206004820152600160248201527f32000000000000000000000000000000000000000000000000000000000000006044820152606401611620565b60005b838110156137d6576137b733613f18565b8382600581106137c9576137c9615bef565b60200201526001016137a6565b507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b15801561383257600080fd5b505af1158015613846573d6000803e3d6000fd5b50505050506138536149e2565b6040516370a0823160e01b81523060048201526000907f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b0316906370a0823190602401602060405180830381865afa1580156138ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906138de9190615c05565b9050601654816138ee9190615c34565b600103613905576016546139029082615c48565b90505b801561393d5761393b7f000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e6115bb83601a54613cb8565b505b5050611a296001600855565b600e60009054906101000a90046001600160a01b03166001600160a01b031663a3e56fa86040518163ffffffff1660e01b8152600401602060405180830381865afa15801561399c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139c09190615ea9565b6001600160a01b0316336001600160a01b031614806139e95750600e546001600160a01b031633145b613a355760405162461bcd60e51b815260206004820152600e60248201527f494e56414c49445f53454e4445520000000000000000000000000000000000006044820152606401611620565b613a3f82826140bd565b604051819083907f14285afe2ec6fba75de22aaed813b154e820b01ca273f996b915b0d49c106c5c90600090a35050565b6000818152600260205260408120546001600160a01b0316806111ed57604051637e27328960e01b815260048101849052602401611620565b611a8f8383836001614bdd565b60075460ff1615611a72576040517fd93c066500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600260085403613b2f576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600855565b6001600160a01b038216613b6057604051633250574960e11b815260006004820152602401611620565b6000613b6e83836000613daf565b90506001600160a01b038116613b9a57604051637e27328960e01b815260048101839052602401611620565b836001600160a01b0316816001600160a01b0316146116d6576040516364283d7b60e01b81526001600160a01b0380861660048301526024820184905282166044820152606401611620565b613bef81614d28565b613bf76149e2565b601060179054906101000a900460ff1660ff16601d60008154613c1990615cc5565b9182905550613c289190615ec6565b158015613c435750601054601154600160c01b90910460ff16105b15613c7257601060189054906101000a900460ff1660ff1660116000828254613c6c9190615c48565b90915550505b80336001600160a01b03167fbad86ebd67551dab515f3deb3533553af5e9095e959be845b583dd28c9b1f2bd84604051613cac9190615eda565b60405180910390a35050565b60006064613cc68385615bd8565b613cd09190615c34565b9392505050565b6040516370a0823160e01b815230600482015260009081906001600160a01b037f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216906370a0823190602401602060405180830381865afa158015613d40573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613d649190615c05565b905080841115613d72578093505b8315613da757613da47f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2868686614dfd565b91505b509392505050565b6000828152600260205260408120546001600160a01b0390811690831615613ddc57613ddc818486614f8e565b6001600160a01b03811615613e1a57613df9600085600080614bdd565b6001600160a01b038116600090815260036020526040902080546000190190555b6001600160a01b03851615613e49576001600160a01b0385166000908152600360205260409020805460010190555b600084815260026020526040808220805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0389811691821790925591518793918516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4949350505050565b336001600160a01b03821603613ef7576040517f5e03d55f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b03166000908152600660205260409020805460ff19169055565b60007f0000000000000000000000000000000000000000000000000000000000002710613f436115d2565b10613f4d57600080fd5b60185460018101601855613f61838261500b565b613f6a81614d28565b80336001600160a01b03167fbad86ebd67551dab515f3deb3533553af5e9095e959be845b583dd28c9b1f2bd6000604051613fa59190615eda565b60405180910390a392915050565b613fbb615025565b6007805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b600080600b60009054906101000a90046001600160a01b03166001600160a01b0316635ec01e4d6040518163ffffffff1660e01b81526004016020604051808303816000875af115801561405d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140819190615c05565b905060005b60078110156140a957601082901b831792506140a28160010190565b9050614086565b505060009182526021602052604090912055565b600082815260226020526040812082905582900361413b577f444dd684ad9169cbde7791d7d420dd3a30ac09e06232e426a3c0f7ff8f79f2fd614101600080612472565b61410d60006001612472565b61411960006002612472565b6040805193845260208401929092529082015260600160405180910390a15050565b60005b60058110156141e7576000614154826010615bd8565b60008581526024602052604090205461ffff911c1690508061417581611ee5565b6001600160a01b03167f826ad53ae1bd429955605b9bd4bbc359cddafdc76e81736a9950c35817515fa96141aa846000612472565b6141b5856001612472565b6141c0866002612472565b6040805193845260208401929092529082015260600160405180910390a35060010161413e565b5050600090815260246020526040812055565b60008060006142287f000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e615061565b905060006016546142587f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2615061565b6142629190615c48565b6009546040517f9cd441da00000000000000000000000000000000000000000000000000000000815260048101859052602481018390529192506001600160a01b031690639cd441da906044016060604051808303816000875af11580156142ce573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906142f29190615f02565b9095509350600090506143058584615c48565b905060006143138584615c48565b600c54604051630a956fa360e31b81526001600160a01b037f000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e811660048301527f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2811660248301526fffffffffffffffffffffffffffffffff861660448301526127106064830152929350600092909116906354ab7d1890608401602060405180830381865afa1580156143cb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906143ef9190615c05565b9050818111801561440f5750601a5460009061440d90600890615c48565b115b1561443057600860ff16601a600082825461442a9190615c48565b90915550505b818110801561444e5750601a54605a9061444c90600890615c5b565b105b1561446f57600860ff16601a60008282546144699190615c5b565b90915550505b50505050509091565b6144806124f3565b6144cc5760405162461bcd60e51b815260206004820152600b60248201527f4e4f545f454e41424c45440000000000000000000000000000000000000000006044820152606401611620565b6020805460ff19168155600080805260229091527fb84cf808d0d5b1ad44962c9bfddd3cfce67763c49ab557cfd0e9f6804faade9955565b61450c613ab6565b6007805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258613fe83390565b6001600160a01b03821661458c576040517f5b08ba180000000000000000000000000000000000000000000000000000000081526001600160a01b0383166004820152602401611620565b6001600160a01b03838116600081815260056020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6001600160a01b0383163b156116d657604051630a85bd0160e11b81526001600160a01b0384169063150b7a029061463b903390889087908790600401615f4e565b6020604051808303816000875af1925050508015614676575060408051601f3d908101601f1916820190925261467391810190615f80565b60015b6146df573d8080156146a4576040519150601f19603f3d011682016040523d82523d6000602084013e6146a9565b606091505b5080516000036146d757604051633250574960e11b81526001600160a01b0385166004820152602401611620565b805181602001fd5b6001600160e01b03198116630a85bd0160e11b146118bb57604051633250574960e11b81526001600160a01b0385166004820152602401611620565b6000806147278561250d565b905082614735576000614748565b60c08101516147489060ff166001615c5b565b84614754576000614772565b60a08201516147679060ff166001615c5b565b614772906064615bd8565b60808301516147859060ff166001615c5b565b61479190612710615bd8565b60608401516147a49060ff166001615c5b565b6147b190620f4240615bd8565b60408501516147c49060ff166001615c5b565b6147d2906305f5e100615bd8565b60208601516147e59060ff166001615c5b565b6147f4906402540be400615bd8565b86516148049060ff166001615c5b565b6148139064e8d4a51000615bd8565b61481d9190615c5b565b6148279190615c5b565b6148319190615c5b565b61483b9190615c5b565b6148459190615c5b565b613da49190615c5b565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1663095ea7b360e01b17905291516000928392908716916148c09190615f9d565b6000604051808303816000865af19150503d80600081146148fd576040519150601f19603f3d011682016040523d82523d6000602084013e614902565b606091505b509150915081801561492c57508051158061492c57508080602001905181019061492c9190615c6e565b6118bb5760405162461bcd60e51b815260206004820152600260248201527f53410000000000000000000000000000000000000000000000000000000000006044820152606401611620565b60006026836003811061498d5761498d615bef565b6003020182600381106149a2576149a2615bef565b01544710156149b15747613cd0565b602683600381106149c4576149c4615bef565b6003020182600381106149d9576149d9615bef565b01549392505050565b601054600160a01b900460ff16158015906149ff57506000601954115b8015614a175750601954614a1590600590615ec6565b155b15611a72576000600560ff16614aaf600e60009054906101000a90046001600160a01b03166001600160a01b0316632dddd49c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015614a79573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614a9d9190615c05565b601054600160a01b900460ff16613cb8565b614ab99190615bd8565b6040516370a0823160e01b815230600482015290915081906001600160a01b037f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216906370a0823190602401602060405180830381865afa158015614b22573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614b469190615c05565b11614b935760405162461bcd60e51b815260206004820152600c60248201527f494e56414c49445f5745544800000000000000000000000000000000000000006044820152606401611620565b600e546040517f6d7bcb05000000000000000000000000000000000000000000000000000000008152600481018390526001600160a01b0390911690636d7bcb059060240161188d565b8080614bf157506001600160a01b03821615155b15614ceb576000614c0184613a70565b90506001600160a01b03831615801590614c2d5750826001600160a01b0316816001600160a01b031614155b8015614c5f57506001600160a01b0380821660009081526005602090815260408083209387168352929052205460ff16155b15614ca1576040517fa9fbf51f0000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401611620565b8115614ce95783856001600160a01b0316826001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45b505b50506000908152600460205260409020805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0392909216919091179055565b614d3181611a94565b15614d68576040517f2158886700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601954614d7790600590615ec6565b600003614d88576025819055614db0565b601954614d9790600590615ec6565b614da2906010615bd8565b602580549183901b90911790555b600081815260236020526040902060019055614dcb81614005565b600560ff16601960008154614ddf90615cc5565b9182905550614dee9190615ec6565b6000036115cf576115cf61515f565b6000807f000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e6001600160a01b0316866001600160a01b03161480614e7157507f000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e6001600160a01b0316856001600160a01b0316145b614e7d57610bb8614e81565b6127105b6010546040805160e0810182526001600160a01b038a811682528981166020830190815262ffffff8681168486019081528a841660608601908152608086018d8152600060a0880181815260c0890191825298517f04e45aaf00000000000000000000000000000000000000000000000000000000815297518716600489015294518616602488015291519092166044860152905183166064850152516084840152925160a48301529151821660c482015292935016906304e45aaf9060e4016020604051808303816000875af1158015614f60573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614f849190615c05565b9695505050505050565b614f998383836153d1565b611a8f576001600160a01b038316614fc757604051637e27328960e01b815260048101829052602401611620565b6040517f177e802f0000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260248101829052604401611620565b6112b9828260405180602001604052806000815250615457565b60075460ff16611a72576040517f8dfc202b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040516370a0823160e01b81523060048201526000906001600160a01b038316906370a0823190602401602060405180830381865afa1580156150a8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906150cc9190615c05565b90507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b0316826001600160a01b031603615118576111ed66038d7ea4c6800082615c48565b7f000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e6001600160a01b0316826001600160a01b031603611a29576111ed6305f5e10082615c48565b600e60009054906101000a90046001600160a01b03166001600160a01b031663392e53cd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156151b2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906151d69190615c6e565b6152225760405162461bcd60e51b815260206004820152600f60248201527f4e4f5f535542534352495054494f4e00000000000000000000000000000000006044820152606401611620565b600e54604080517f8f0b707200000000000000000000000000000000000000000000000000000000815290516000926001600160a01b031691638f0b7072916004808301926020929190829003018187875af1158015615286573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906152aa9190615c05565b9050806000036152fc5760405162461bcd60e51b815260206004820152601160248201527f45584341564154494f4e5f4641494c45440000000000000000000000000000006044820152606401611620565b6025546000828152602460205260408120919091555b600581101561535857816023600061532b846010615bd8565b60008681526024602090815260408083205490931c61ffff16845283019390935201902055600101615312565b506025546040805183815261ffff808416602080840191909152601085901c82168385015284901c81166060830152603084901c8116608083015292821c90921660a0830152517f38b3bf74e600502d58a56a19e87e871fc5571f28137b720740a28ad3269bb35b9181900360c00190a1506000602555565b60006001600160a01b0383161580159061544f5750826001600160a01b0316846001600160a01b0316148061542b57506001600160a01b0380851660009081526005602090815260408083209387168352929052205460ff165b8061544f57506000828152600460205260409020546001600160a01b038481169116145b949350505050565b615461838361546e565b611a8f60008484846145f9565b6001600160a01b03821661549857604051633250574960e11b815260006004820152602401611620565b60006154a683836000613daf565b90506001600160a01b03811615611a8f576040517f73c6ac6e00000000000000000000000000000000000000000000000000000000815260006004820152602401611620565b60405180606001604052806003905b615503615519565b8152602001906001900390816154fb5790505090565b60405180606001604052806003906020820280368337509192915050565b6040518060e001604052806007906020820280368337509192915050565b6040518060a001604052806005906020820280368337509192915050565b6001600160e01b0319811681146115cf57600080fd5b60006020828403121561559b57600080fd5b8135613cd081615573565b60005b838110156155c15781810151838201526020016155a9565b50506000910152565b600081518084526155e28160208601602086016155a6565b601f01601f19169290920160200192915050565b602081526000613cd060208301846155ca565b60006020828403121561561b57600080fd5b5035919050565b6001600160a01b03811681146115cf57600080fd5b6000806040838503121561564a57600080fd5b823561565581615622565b946020939093013593505050565b610120810181836000805b600380821061567d57506156ba565b835185845b838110156156a0578251825260209283019290910190600101615682565b50505060609490940193506020929092019160010161566e565b5050505092915050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff81118282101715615703576157036156c4565b604052919050565b6000602080838503121561571e57600080fd5b823567ffffffffffffffff8082111561573657600080fd5b818501915085601f83011261574a57600080fd5b81358181111561575c5761575c6156c4565b8060051b915061576d8483016156da565b818152918301840191848101908884111561578757600080fd5b938501935b838510156157a55784358252938501939085019061578c565b98975050505050505050565b6000806000806000608086880312156157c957600080fd5b85356157d481615622565b945060208601356157e481615622565b935060408601359250606086013567ffffffffffffffff8082111561580857600080fd5b818801915088601f83011261581c57600080fd5b81358181111561582b57600080fd5b89602082850101111561583d57600080fd5b9699959850939650602001949392505050565b60006020828403121561586257600080fd5b8135613cd081615622565b60008060006060848603121561588257600080fd5b833561588d81615622565b9250602084013561589d81615622565b929592945050506040919091013590565b60608101818360005b60038110156158d65781518352602092830192909101906001016158b7565b50505092915050565b6000602082840312156158f157600080fd5b813560ff81168114613cd057600080fd5b80151581146115cf57600080fd5b6000806040838503121561592357600080fd5b823561592e81615622565b9150602083013561593e81615902565b809150509250929050565b600067ffffffffffffffff821115615963576159636156c4565b50601f01601f191660200190565b6000806000806080858703121561598757600080fd5b843561599281615622565b935060208501356159a281615622565b925060408501359150606085013567ffffffffffffffff8111156159c557600080fd5b8501601f810187136159d657600080fd5b80356159e96159e482615949565b6156da565b8181528860208385010111156159fe57600080fd5b8160208401602083013760006020838301015280935050505092959194509250565b60008060408385031215615a3357600080fd5b8235915060208301356003811061593e57600080fd5b60e08101818360005b60078110156158d657815160ff16835260209283019290910190600101615a52565b803560058110611a2957600080fd5b60008060a08385031215615a9657600080fd5b83601f840112615aa557600080fd5b6040516080810181811067ffffffffffffffff82111715615ac857615ac86156c4565b604052806080850186811115615add57600080fd5b855b81811015615af7578035835260209283019201615adf565b50829450615b0481615a74565b93505050509250929050565b60008060408385031215615b2357600080fd5b50508035926020909101359150565b60a08101818360005b60058110156158d6578151835260209283019290910190600101615b3b565b60008060408385031215615b6d57600080fd5b8235615b7881615622565b9150602083013561593e81615622565b600181811c90821680615b9c57607f821691505b602082108103615bbc57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b80820281158282048414176111ed576111ed615bc2565b634e487b7160e01b600052603260045260246000fd5b600060208284031215615c1757600080fd5b5051919050565b634e487b7160e01b600052601260045260246000fd5b600082615c4357615c43615c1e565b500490565b818103818111156111ed576111ed615bc2565b808201808211156111ed576111ed615bc2565b600060208284031215615c8057600080fd5b8151613cd081615902565b634e487b7160e01b600052602160045260246000fd5b60008060408385031215615cb457600080fd5b505080516020909101519092909150565b600060018201615cd757615cd7615bc2565b5060010190565b6000828254808552602080860195506005818360051b8501016000878152838120815b86811015615dbb57878403601f19018b5281548390600181811c9080831680615d2b57607f831692505b8a83108103615d4857634e487b7160e01b88526022600452602488fd5b82895260208901818015615d635760018114615d7857615da2565b60ff19861682528415158c1b82019650615da2565b6000898152602090208a5b86811015615d9c57815484820152908501908e01615d83565b83019750505b5050509d89019d92965050509190910190600101615d01565b50919998505050505050505050565b600061014086835260208084018760005b6007811015615dfb57815160ff1683529183019190830190600101615ddb565b5050505080610100840152615e1281840186615cde565b9050828103610120840152615e278185615cde565b979650505050505050565b600060208284031215615e4457600080fd5b815167ffffffffffffffff811115615e5b57600080fd5b8201601f81018413615e6c57600080fd5b8051615e7a6159e482615949565b818152856020838501011115615e8f57600080fd5b615ea08260208301602086016155a6565b95945050505050565b600060208284031215615ebb57600080fd5b8151613cd081615622565b600082615ed557615ed5615c1e565b500690565b6020810160038310615efc57634e487b7160e01b600052602160045260246000fd5b91905290565b600080600060608486031215615f1757600080fd5b83516fffffffffffffffffffffffffffffffff81168114615f3757600080fd5b602085015160409095015190969495509392505050565b60006001600160a01b03808716835280861660208401525083604083015260806060830152614f8460808301846155ca565b600060208284031215615f9257600080fd5b8151613cd081615573565b60008251615faf8184602087016155a6565b919091019291505056fea264697066735822122074701aedf65d4abcaf127673a6336bd5159dffea41a80df809e8cb63dfdc622864736f6c63430008170033

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

000000000000000000000000b96829b761fa8cf594144fb5b1f86c47943aa111000000000000000000000000d1cd0f7c1131ceb1f642b488405ec9e32b4026030000000000000000000000009a051784defafab0ec587113c625d19ff04e2ce4000000000000000000000000767ae0e2ff010afff8b1ad02bf8e2a8e4a682def00000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e000000000000000000000000271682deb8c4e0901d1a1550ad2e64d568e699098af398995b04c28e9951adb9721ef74c74f93e6a478f39e7e0777be13527e7ef000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca0000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000027f7d0bdb9200000000000000000000000000000000000000000000000000000429d069189e00000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000429d069189e00000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000004563918244f400000000000000000000000000000000000000000000000000004563918244f40000000000000000000000000000000000000000000000000000d02ab486cedc00000000000000000000000000000000000000000000000000022b1c8c1227a000000000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000007c585087238000000000000000000000000000000000000000000000000000007c58508723800000000000000000000000000000000000000000000000000003782dace9d90000000000000000000000000000000000000000000000000000000000000000001900000000000000000000000000000000000000000000000000000000000001a400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000004c000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000072330303030303000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000723464646464646000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007234546333230300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000072346464237303100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000723374530304536000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007236632303037300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000072330304536394100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000723303044334535000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007233030343046460000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000044f6e79780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000074469616d6f6e6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000743697472696e65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005546f70617a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008416d657468797374000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000452756279000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007456d6572616c6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a417175616d6172696e650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000085361707068697265000000000000000000000000000000000000000000000000

-----Decoded View---------------
Arg [0] : addressConfig (tuple): System.Collections.Generic.List`1[Nethereum.ABI.FunctionEncoding.ParameterOutput]
Arg [1] : tokenConfig (tuple): System.Collections.Generic.List`1[Nethereum.ABI.FunctionEncoding.ParameterOutput]
Arg [2] : excavatorConfig (tuple): System.Collections.Generic.List`1[Nethereum.ABI.FunctionEncoding.ParameterOutput]
Arg [3] : tetraSpektraConfig (tuple): System.Collections.Generic.List`1[Nethereum.ABI.FunctionEncoding.ParameterOutput]
Arg [4] : bounties (uint256[3][3]): System.Collections.Generic.List`1[System.Numerics.BigInteger],System.Collections.Generic.List`1[System.Numerics.BigInteger],System.Collections.Generic.List`1[System.Numerics.BigInteger]

-----Encoded View---------------
86 Constructor Arguments found :
Arg [0] : 000000000000000000000000b96829b761fa8cf594144fb5b1f86c47943aa111
Arg [1] : 000000000000000000000000d1cd0f7c1131ceb1f642b488405ec9e32b402603
Arg [2] : 0000000000000000000000009a051784defafab0ec587113c625d19ff04e2ce4
Arg [3] : 000000000000000000000000767ae0e2ff010afff8b1ad02bf8e2a8e4a682def
Arg [4] : 00000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45
Arg [5] : 000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
Arg [6] : 000000000000000000000000b9f599ce614feb2e1bbe58f180f370d05b39344e
Arg [7] : 000000000000000000000000271682deb8c4e0901d1a1550ad2e64d568e69909
Arg [8] : 8af398995b04c28e9951adb9721ef74c74f93e6a478f39e7e0777be13527e7ef
Arg [9] : 000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca
Arg [10] : 0000000000000000000000000000000000000000000000000000000000000280
Arg [11] : 000000000000000000000000000000000000000000000000027f7d0bdb920000
Arg [12] : 0000000000000000000000000000000000000000000000000429d069189e0000
Arg [13] : 0000000000000000000000000000000000000000000000000de0b6b3a7640000
Arg [14] : 0000000000000000000000000000000000000000000000000429d069189e0000
Arg [15] : 0000000000000000000000000000000000000000000000000de0b6b3a7640000
Arg [16] : 0000000000000000000000000000000000000000000000004563918244f40000
Arg [17] : 0000000000000000000000000000000000000000000000004563918244f40000
Arg [18] : 000000000000000000000000000000000000000000000000d02ab486cedc0000
Arg [19] : 0000000000000000000000000000000000000000000000022b1c8c1227a00000
Arg [20] : 0000000000000000000000000000000000000000000000000000000000002710
Arg [21] : 000000000000000000000000000000000000000000000000007c585087238000
Arg [22] : 000000000000000000000000000000000000000000000000007c585087238000
Arg [23] : 00000000000000000000000000000000000000000000000003782dace9d90000
Arg [24] : 0000000000000000000000000000000000000000000000000000000000000019
Arg [25] : 00000000000000000000000000000000000000000000000000000000000001a4
Arg [26] : 0000000000000000000000000000000000000000000000000000000000000005
Arg [27] : 0000000000000000000000000000000000000000000000000000000000000001
Arg [28] : 0000000000000000000000000000000000000000000000000000000000000140
Arg [29] : 00000000000000000000000000000000000000000000000000000000000004c0
Arg [30] : 0000000000000000000000000000000000000000000000000000000000000009
Arg [31] : 0000000000000000000000000000000000000000000000000000000000000120
Arg [32] : 0000000000000000000000000000000000000000000000000000000000000160
Arg [33] : 00000000000000000000000000000000000000000000000000000000000001a0
Arg [34] : 00000000000000000000000000000000000000000000000000000000000001e0
Arg [35] : 0000000000000000000000000000000000000000000000000000000000000220
Arg [36] : 0000000000000000000000000000000000000000000000000000000000000260
Arg [37] : 00000000000000000000000000000000000000000000000000000000000002a0
Arg [38] : 00000000000000000000000000000000000000000000000000000000000002e0
Arg [39] : 0000000000000000000000000000000000000000000000000000000000000320
Arg [40] : 0000000000000000000000000000000000000000000000000000000000000007
Arg [41] : 2330303030303000000000000000000000000000000000000000000000000000
Arg [42] : 0000000000000000000000000000000000000000000000000000000000000007
Arg [43] : 2346464646464600000000000000000000000000000000000000000000000000
Arg [44] : 0000000000000000000000000000000000000000000000000000000000000007
Arg [45] : 2345463332303000000000000000000000000000000000000000000000000000
Arg [46] : 0000000000000000000000000000000000000000000000000000000000000007
Arg [47] : 2346464237303100000000000000000000000000000000000000000000000000
Arg [48] : 0000000000000000000000000000000000000000000000000000000000000007
Arg [49] : 2337453030453600000000000000000000000000000000000000000000000000
Arg [50] : 0000000000000000000000000000000000000000000000000000000000000007
Arg [51] : 2366323030373000000000000000000000000000000000000000000000000000
Arg [52] : 0000000000000000000000000000000000000000000000000000000000000007
Arg [53] : 2330304536394100000000000000000000000000000000000000000000000000
Arg [54] : 0000000000000000000000000000000000000000000000000000000000000007
Arg [55] : 2330304433453500000000000000000000000000000000000000000000000000
Arg [56] : 0000000000000000000000000000000000000000000000000000000000000007
Arg [57] : 2330303430464600000000000000000000000000000000000000000000000000
Arg [58] : 0000000000000000000000000000000000000000000000000000000000000009
Arg [59] : 0000000000000000000000000000000000000000000000000000000000000120
Arg [60] : 0000000000000000000000000000000000000000000000000000000000000160
Arg [61] : 00000000000000000000000000000000000000000000000000000000000001a0
Arg [62] : 00000000000000000000000000000000000000000000000000000000000001e0
Arg [63] : 0000000000000000000000000000000000000000000000000000000000000220
Arg [64] : 0000000000000000000000000000000000000000000000000000000000000260
Arg [65] : 00000000000000000000000000000000000000000000000000000000000002a0
Arg [66] : 00000000000000000000000000000000000000000000000000000000000002e0
Arg [67] : 0000000000000000000000000000000000000000000000000000000000000320
Arg [68] : 0000000000000000000000000000000000000000000000000000000000000004
Arg [69] : 4f6e797800000000000000000000000000000000000000000000000000000000
Arg [70] : 0000000000000000000000000000000000000000000000000000000000000007
Arg [71] : 4469616d6f6e6400000000000000000000000000000000000000000000000000
Arg [72] : 0000000000000000000000000000000000000000000000000000000000000007
Arg [73] : 43697472696e6500000000000000000000000000000000000000000000000000
Arg [74] : 0000000000000000000000000000000000000000000000000000000000000005
Arg [75] : 546f70617a000000000000000000000000000000000000000000000000000000
Arg [76] : 0000000000000000000000000000000000000000000000000000000000000008
Arg [77] : 416d657468797374000000000000000000000000000000000000000000000000
Arg [78] : 0000000000000000000000000000000000000000000000000000000000000004
Arg [79] : 5275627900000000000000000000000000000000000000000000000000000000
Arg [80] : 0000000000000000000000000000000000000000000000000000000000000007
Arg [81] : 456d6572616c6400000000000000000000000000000000000000000000000000
Arg [82] : 000000000000000000000000000000000000000000000000000000000000000a
Arg [83] : 417175616d6172696e6500000000000000000000000000000000000000000000
Arg [84] : 0000000000000000000000000000000000000000000000000000000000000008
Arg [85] : 5361707068697265000000000000000000000000000000000000000000000000


Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]

A token is a representation of an on-chain or off-chain asset. The token page shows information such as price, total supply, holders, transfers and social links. Learn more about this page in our Knowledge Base.