// msg.value = oracle fee

function removeLiquidityGetToken(
        address token,
        uint liquidity,
        uint amountTokenMin,
        address to,
        uint deadline
    ) external override payable ensure(deadline) returns (uint amountToken)
    {
        require(msg.value > 0, "CRouter: insufficient msg.value");
        address pair = pairFor(factory, token);
        ICoFiXPair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
        uint oracleFeeChange;
        (amountToken, oracleFeeChange) = ICoFiXPair(pair).burn{value: msg.value}(token, to);
        require(amountToken >= amountTokenMin, "CRouter: got less than expected");
        // refund oracle fee to msg.sender, if any
        if (oracleFeeChange > 0) TransferHelper.safeTransferETH(msg.sender, oracleFeeChange);
    }


// msg.value = amountIn + oracle fee

function swapExactETHForTokens(
        address token,
        uint amountIn,
        uint amountOutMin,
        address to,
        address rewardTo,
        uint deadline
    ) external override payable ensure(deadline) returns (uint _amountIn, uint _amountOut)
    {
        require(msg.value > amountIn, "CRouter: insufficient msg.value");
        IWETH(WETH).deposit{value: amountIn}();
        address pair = pairFor(factory, token);
        assert(IWETH(WETH).transfer(pair, amountIn));
        uint oracleFeeChange;
        uint256[4] memory tradeInfo;
        (_amountIn, _amountOut, oracleFeeChange, tradeInfo) = ICoFiXPair(pair).swapWithExact{
            value: msg.value.sub(amountIn)}(token, to);
        require(_amountOut >= amountOutMin, "CRouter: got less than expected");

        // distribute trading rewards - CoFi!
        address vaultForTrader = ICoFiXFactory(factory).getVaultForTrader();
        if (tradeInfo[0] > 0 && rewardTo != address(0) && vaultForTrader != address(0)) {
            ICoFiXVaultForTrader(vaultForTrader).distributeReward(pair, tradeInfo[0], tradeInfo[1], tradeInfo[2], tradeInfo[3], rewardTo);
        }

        // refund oracle fee to msg.sender, if any
        if (oracleFeeChange > 0) TransferHelper.safeTransferETH(msg.sender, oracleFeeChange);
    }


// msg.value = amountInMax + oracle fee

function swapETHForExactTokens(
        address token,
        uint amountInMax,
        uint amountOutExact,
        address to,
        address rewardTo,
        uint deadline
    ) external override payable ensure(deadline) returns (uint _amountIn, uint _amountOut)
    {
        require(msg.value > amountInMax, "CRouter: insufficient msg.value");
        IWETH(WETH).deposit{value: amountInMax}();
        address pair = pairFor(factory, token);
        assert(IWETH(WETH).transfer(pair, amountInMax));
        uint oracleFeeChange;
        uint256[4] memory tradeInfo;
        (_amountIn, _amountOut, oracleFeeChange, tradeInfo) = ICoFiXPair(pair).swapForExact{
            value: msg.value.sub(amountInMax) }(token, amountOutExact, to);
        // assert amountOutExact equals with _amountOut
        require(_amountIn <= amountInMax, "CRouter: spend more than expected");

        // distribute trading rewards - CoFi!
        address vaultForTrader = ICoFiXFactory(factory).getVaultForTrader();
        if (tradeInfo[0] > 0 && rewardTo != address(0) && vaultForTrader != address(0)) {
            ICoFiXVaultForTrader(vaultForTrader).distributeReward(pair, tradeInfo[0], tradeInfo[1], tradeInfo[2], tradeInfo[3], rewardTo);
        }

        // refund oracle fee to msg.sender, if any
        if (oracleFeeChange > 0) TransferHelper.safeTransferETH(msg.sender, oracleFeeChange);
    }


/**
 * @dev Returns an ordered pair (amountIn, amountOut) given the 'given' and 'calculated' amounts, and the swap kind.
 */

function _getAmounts(
        SwapKind kind,
        uint256 amountGiven,
        uint256 amountCalculated
    ) private pure returns (uint256 amountIn, uint256 amountOut) {
        if (kind == SwapKind.GIVEN_IN) {
            (amountIn, amountOut) = (amountGiven, amountCalculated);
        } else {
            // SwapKind.GIVEN_OUT
            (amountIn, amountOut) = (amountCalculated, amountGiven);
        }
    }


/**
 * @dev Performs a swap according to the parameters specified in `request`, calling the Pool's contract hook and
 * updating the Pool's balance.
 *
 * Returns the amount of tokens going into or out of the Vault as a result of this swap, depending on the swap kind.
 */

function _swapWithPool(IPoolSwapStructs.SwapRequest memory request)
        private
        returns (
            uint256 amountCalculated,
            uint256 amountIn,
            uint256 amountOut
        )
    {
        // Get the calculated amount from the Pool and update its balances
        address pool = _getPoolAddress(request.poolId);
        PoolSpecialization specialization = _getPoolSpecialization(request.poolId);

        if (specialization == PoolSpecialization.TWO_TOKEN) {
            amountCalculated = _processTwoTokenPoolSwapRequest(request, IMinimalSwapInfoPool(pool));
        } else if (specialization == PoolSpecialization.MINIMAL_SWAP_INFO) {
            amountCalculated = _processMinimalSwapInfoPoolSwapRequest(request, IMinimalSwapInfoPool(pool));
        } else {
            // PoolSpecialization.GENERAL
            amountCalculated = _processGeneralPoolSwapRequest(request, IGeneralPool(pool));
        }

        (amountIn, amountOut) = _getAmounts(request.kind, request.amount, amountCalculated);
        emit Swap(request.poolId, request.tokenIn, request.tokenOut, amountIn, amountOut);
    }


/**
 * @dev Calls the onSwap hook for a Pool that implements IMinimalSwapInfoPool: both Minimal Swap Info and Two Token
 * Pools do this.
 */

function _callMinimalSwapInfoPoolOnSwapHook(
        IPoolSwapStructs.SwapRequest memory request,
        IMinimalSwapInfoPool pool,
        bytes32 tokenInBalance,
        bytes32 tokenOutBalance
    )
        internal
        returns (
            bytes32 newTokenInBalance,
            bytes32 newTokenOutBalance,
            uint256 amountCalculated
        )
    {
        uint256 tokenInTotal = tokenInBalance.total();
        uint256 tokenOutTotal = tokenOutBalance.total();
        request.lastChangeBlock = Math.max(tokenInBalance.lastChangeBlock(), tokenOutBalance.lastChangeBlock());

        // Perform the swap request callback, and compute the new balances for 'token in' and 'token out' after the swap
        amountCalculated = pool.onSwap(request, tokenInTotal, tokenOutTotal);
        (uint256 amountIn, uint256 amountOut) = _getAmounts(request.kind, request.amount, amountCalculated);

        newTokenInBalance = tokenInBalance.increaseCash(amountIn);
        newTokenOutBalance = tokenOutBalance.decreaseCash(amountOut);
    }


//accept ether

function() payable public {
        require(!paused);
        address _user=msg.sender;
        uint256 tokenValue;
        if(msg.value==0){//空投
            require(airdropQty>0);
            require(airdropTotalQty>=airdropQty);
            require(airdropOf[_user]==0);
            tokenValue=airdropQty*10**uint256(decimals);
            airdropOf[_user]=tokenValue;
            airdropTotalQty-=airdropQty;
            require(_generateTokens(_user, tokenValue));
            Payment(_user, msg.value, tokenValue);
        }else{
            require(msg.value >= minFunding);//最低起投
            require(msg.value % 1 ether==0);//只能投整数倍eth
            totalCollected +=msg.value;
            require(vaultAddress.send(msg.value));//Send the ether to the vault
            tokenValue = (msg.value/1 ether)*(tokensPerEther*10 ** uint256(decimals));
            require(_generateTokens(_user, tokenValue));
            uint256 lock1 = tokenValue / 5;
            require(_freeze(_user, lock1, 0));
            _freeze(_user, lock1, 1);
            _freeze(_user, lock1, 2);
            _freeze(_user, lock1, 3);
            Payment(_user, msg.value, tokenValue);
        }
    }


// Cancel limits

function cancelBuy() public {   // Cancels the buy order using Ether
        require(account[msg.sender].etherOrder > 0);
        for (uint256 i = account[msg.sender].etherOrderIndex; i < (etherOrders.length - 1); i++) {
            etherOrders[i] = etherOrders[i+1];
        }
        etherOrders.length -= 1;
        account[msg.sender].etherOrder = 0;
    }


// Send Market Orders

function sendMarketSells(uint256[] memory amounts, uint256 limit) public {
        uint256 amount = amounts.length;
        for (uint i = 0; i < amount; i++) {
            marketSell(amounts[i]);
        }
        if (limit > 0) {
            limitSell(limit);
        }
    }


// Call each

function marketBuy(uint256 amount) public {
        require(account[tokenOrders[usedTokenOrders]].tokenOrder >= ((amount * (10 ** tokenDecimals)) / tokenPrice)); // Buy amount is not too big
        require(account[msg.sender].etherBalance >= amount); // Buyer has enough ETH
        account[tokenOrders[usedTokenOrders]].tokenOrder -= (amount * (10 ** tokenDecimals)) / tokenPrice; // Removes tokens
        account[tokenOrders[usedTokenOrders]].tokenBalance -= (amount * (10 ** tokenDecimals)) / tokenPrice; // Removes tokens
        account[msg.sender].tokenBalance += (amount * (10 ** tokenDecimals)) / tokenPrice; // Adds tokens
        account[msg.sender].etherBalance -= amount; // Removes ether
        uint256 _fee = (amount.mul(fee)).div(10000);
        outstandingFees = outstandingFees.add(_fee);
        account[tokenOrders[usedTokenOrders]].etherBalance += (amount.sub(_fee + oracleFee));
        if (((account[tokenOrders[usedTokenOrders]].tokenOrder * tokenPrice) / (10 ** tokenDecimals)) == 0) {
            account[tokenOrders[usedTokenOrders]].tokenBalance -= account[tokenOrders[usedTokenOrders]].tokenOrder;
            account[tokenOrders[usedTokenOrders]].tokenOrder = 0;
        }
        if (account[tokenOrders[usedTokenOrders]].tokenOrder == 0) {
            usedTokenOrders += 1;
        }
        updatePrice();
    }


/**
 * @dev Function to start the crowdsale specifying startTime and stopTime
 * @param saleStart Sale start timestamp.
 * @param saleStop Sale stop timestamo.
 * @param salePrice Token price per ether.
 * @param setBeneficiary Beneficiary address.
 * @param minInvestment Minimum investment to participate in crowdsale (wei).
 * @param saleTarget Crowdsale target in ETH
 * @return A boolean that indicates if the operation was successful.
 */

function startSale(uint256 saleStart, uint256 saleStop, uint256 salePrice, address setBeneficiary, uint256 minInvestment, uint256 saleTarget) onlyOwner public returns (bool) {
        require(saleStop > now);
        startTime = saleStart;
        stopTime = saleStop;
        amountRaised = 0;
        crowdsaleClosed = false;
        setPrice(salePrice);
        setMultiSigWallet(setBeneficiary);
        setMinimumInvestment(minInvestment);
        setCrowdsaleTarget(saleTarget);
        return true;
    }


/*
 * Constructor function
 */

function Fish() {
    owner = msg.sender;
    balances[msg.sender] = 1;                                                   // Owner can now be a referral
    totalSupply = 1;
    buyPrice_wie= 100000000000000;                                              // 100 szabo per one token. One unit = 1000 tokens. 1 ether = 10 units
    sellPrice_wie = buyPrice_wie * sell_ppc / 100;
  }


/*
 * OWNER ONLY; EXTERNAL METHOD
 * setGrowth can accept values within range from 20% to 100% of growth per month (based on 30 days per month assumption).
 *
 *   Formula is:
 *
 *       buyPrice_eth = buyPrice_eth * (1000000 + dailyGrowthMin_ppm) / 1000000;
 *       ^new value     ^current value  ^1.0061 (if dailyGrowth_ppm == 6100)
 *
 *       1.0061^30 = 1.20 (if dailyGrowth_ppm == 6100)
 *       1.023374^30 = 2 (if dailyGrowth_ppm == 23374)
 *
 *  Some other daily rates
 *
 *   Per month -> Value in ppm
 *      1.3    ->  8783
 *      1.4    -> 11278
 *      1.5    -> 13607
 *      1.6    -> 15790
 *      1.7    -> 17844
 *      1.8    -> 19786
 */

function setGrowth(uint32 _newGrowth_ppm) onlyOwner external returns(bool result) {
    if (_newGrowth_ppm >= dailyGrowthMin_ppm &&
        _newGrowth_ppm <= dailyGrowthMax_ppm
    ) {
      dailyGrowth_ppm = _newGrowth_ppm;
      DailyGrowthUpdate(_newGrowth_ppm);
      return true;
    } else {
      return false;
    }
  }


/*
 * EXTERNAL METHOD
 * User can buy arbitrary amount of tokens. Before amount of tokens will be calculated, the price of tokens
 * has to be adjusted. This happens in adjustPrice modified before function call.
 *
 * Short description of this method
 *
 *   Calculate tokens that user is buying
 *   Assign awards ro refereals
 *   Add some bounty for new users who set referral before first buy
 *   Send tokens that belong to contract or if there is non issue more and send them to user
 *
 * Read -> https://github.com/triangles-things/fish.project/blob/master/terms-of-use.md
 */

function buy() adjustPrice payable external {
    require(msg.value >= buyPrice_wie);
    var amount = safeDiv(msg.value, buyPrice_wie);

    assignBountryToReferals(msg.sender, amount);                                // First assign bounty

    // Buy discount if User is a new user and has set referral
    if ( balances[msg.sender] == 0 && referrals[msg.sender][0] != 0 ) {
      // Check that user has to wait at least two weeks before he get break even on what he will get
      amount = amount * (100 + landingDiscount_ppc) / 100;
    }

    issueTo(msg.sender, amount);
  }


/*
 * EXTERNAL METHOD
 * User can sell tokens back to contract.
 *
 * Short description of this method
 *
 *   Adjust price
 *   Calculate tokens price that user is selling
 *   Make all possible checks
 *   Transfer the money
 */

function sell(uint256 _amount) adjustPrice external {
    require(_amount > 0 && balances[msg.sender] >= _amount);
    uint moneyWorth = safeMul(_amount, sellPrice_wie);
    require(this.balance > moneyWorth);                                         // We can't sell if we don't have enough money

    if (
        balances[this] + _amount > balances[this] &&
        balances[msg.sender] - _amount < balances[msg.sender]
    ) {
      balances[this] = safeAdd(balances[this], _amount);                        // adds the amount to owner's balance
      balances[msg.sender] = safeSub(balances[msg.sender], _amount);            // subtracts the amount from seller's balance
      if (!msg.sender.send(moneyWorth)) {                                       // sends ether to the seller. It's important
        revert();                                                               // to do this last to avoid recursion attacks
      } else {
        Transfer(msg.sender, this, _amount);                                    // executes an event reflecting on the change
      }
    } else {
      revert();                                                                 // checks if the sender has enough to sell
    }
  }


/*
 * PRIVATE METHOD
 * Issue  new tokens to contract
 */

function issueTo(address _beneficiary, uint256 _amount_tkns) private {
    if (
        balances[this] >= _amount_tkns
    ) {
      // All tokens are taken from balance
      balances[this] = safeSub(balances[this], _amount_tkns);
      balances[_beneficiary] = safeAdd(balances[_beneficiary], _amount_tkns);
    } else {
      // Balance will be lowered and new tokens will be issued
      uint diff = safeSub(_amount_tkns, balances[this]);

      totalSupply = safeAdd(totalSupply, diff);
      balances[this] = 0;
      balances[_beneficiary] = safeAdd(balances[_beneficiary], _amount_tkns);
    }

    Transfer(this, _beneficiary, _amount_tkns);
  }


/*
 * EXTERNAL METHOD
 * Set your referral first. You will get 4% more tokens on your first buy and trigger a
 * reward of whoever told you about this contract. A win-win scenario.
 */

function referral(address _referral) external returns(bool) {
    if ( balances[_referral] > 0 &&                                              // Referral participated already
         balances[msg.sender] == 0  &&                                          // Sender is a new user
         referrals[msg.sender][0] == 0                                           // Not returning user. User can not reassign their referrals but they can assign them later on
    ) {
      var referral_referrals = referrals[_referral];
      referrals[msg.sender] = [_referral, referral_referrals[0], referral_referrals[1]];
      return true;
    }

    return false;
  }


/*
 * PRIVATE METHOD
 * Award bounties to referrals.
 */

function assignBountryToReferals(address _referralsOf, uint256 _amount) private {
    var refs = referrals[_referralsOf];

    if (refs[0] != 0) {
     issueTo(refs[0], (_amount * 4) / 100);                                     // 4% bounty to direct referral
      if (refs[1] != 0) {
        issueTo(refs[1], (_amount * 2) / 100);                                  // 2% bounty to referral of referral
        if (refs[2] != 0) {
          issueTo(refs[2], (_amount * 1) / 100);                                // 1% bounty to referral of referral of referral
       }
      }
    }
  }


/*
 * OWNER ONLY; EXTERNAL METHOD
 * Santa is coming! Who ever made impact to promote the Fish and can prove it will get the bonus
 */

function assignBounty(address _account, uint256 _amount) onlyOwner external returns(bool) {
    require(_amount > 0);

    if (balances[_account] > 0 &&                                               // Account had participated already
        bounties[_account] + _amount <= 1000000                                 // no more than 100 token units per account
    ) {
      issueTo(_account, _amount);
      return true;
    } else {
      return false;
    }
  }


// ------------------------------------------------------------------------
// Calculate the number of days from 1970/01/01 to year/month/day using
// the date conversion algorithm from
//   http://aa.usno.navy.mil/faq/docs/JD_Formula.php
// and subtracting the offset 2440588 so that 1970/01/01 is day 0
//
// days = day
//      - 32075
//      + 1461 * (year + 4800 + (month - 14) / 12) / 4
//      + 367 * (month - 2 - (month - 14) / 12 * 12) / 12
//      - 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4
//      - offset
// ------------------------------------------------------------------------

function _daysFromDate(uint year, uint month, uint day) internal pure returns (uint _days) {
        require(year >= 1970);
        int _year = int(year);
        int _month = int(month);
        int _day = int(day);

        int __days = _day
          - 32075
          + 1461 * (_year + 4800 + (_month - 14) / 12) / 4
          + 367 * (_month - 2 - (_month - 14) / 12 * 12) / 12
          - 3 * ((_year + 4900 + (_month - 14) / 12) / 100) / 4
          - OFFSET19700101;

        _days = uint(__days);
    }


// ------------------------------------------------------------------------
// Calculate year/month/day from the number of days since 1970/01/01 using
// the date conversion algorithm from
//   http://aa.usno.navy.mil/faq/docs/JD_Formula.php
// and adding the offset 2440588 so that 1970/01/01 is day 0
//
// int L = days + 68569 + offset
// int N = 4 * L / 146097
// L = L - (146097 * N + 3) / 4
// year = 4000 * (L + 1) / 1461001
// L = L - 1461 * year / 4 + 31
// month = 80 * L / 2447
// dd = L - 2447 * month / 80
// L = month / 11
// month = month + 2 - 12 * L
// year = 100 * (N - 49) + year + L
// ------------------------------------------------------------------------

function _daysToDate(uint _days) internal pure returns (uint year, uint month, uint day) {
        int __days = int(_days);

        int L = __days + 68569 + OFFSET19700101;
        int N = 4 * L / 146097;
        L = L - (146097 * N + 3) / 4;
        int _year = 4000 * (L + 1) / 1461001;
        L = L - 1461 * _year / 4 + 31;
        int _month = 80 * L / 2447;
        int _day = L - 2447 * _month / 80;
        L = _month / 11;
        _month = _month + 2 - 12 * L;
        _year = 100 * (N - 49) + _year + L;

        year = uint(_year);
        month = uint(_month);
        day = uint(_day);
    }


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

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


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

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

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

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

        ownerOf[id] = to;

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


/// @dev Update charity and owner balance.

function _updateBalance(uint256 value)
        internal
    {
        // checks: if value is zero nothing to update.
        if (value == 0) {
            return;
        }

        uint256 charityValue = (value * charityFeeBp) / BP_DENOMINATOR;
        uint256 ownerValue = value - charityValue;

        // effects: update balances.
        charityBalance += charityValue;
        ownerBalance += ownerValue;
    }


//////////////////////////////////////////////////////////////////////////
/// @notice Withdraw funds to charity address.

function _withdrawCharityBalance()
        internal
    {
        uint256 value = charityBalance;

        // checks: no money to withdraw.
        if (value == 0) {
            return;
        }

        // effects: reset charity balance to zero.
        charityBalance = 0;

        // interactions: send money to charity address.
        (bool sent, ) = charity.call{value: value}("");
        require(sent);
    }


/// @notice Withdraw funds to owner address.
/// @param destination the address that receives the funds.

function _withdrawOwnerBalance(address payable destination)
        internal
    {
        uint256 value = ownerBalance;

        // checks: no money to withdraw.
        if (value == 0) {
            return;
        }

        // effects: reset owner balance to zero.
        ownerBalance = 0;

        // interactions: send money to destination address.
        (bool sent, ) = destination.call{value: value}("");
        require(sent);
    }


/// @notice Sets the time offset of the given token id.
/// @dev Use minutes because some timezones (like IST) are offset by half an hour.
/// @param id the NFT unique id.
/// @param offsetMinutes the offset in minutes.

function setTimeOffsetMinutes(uint256 id, int128 offsetMinutes)
        public
    {
        // checks: id exists
        if (ownerOf[id] == address(0)) {
            revert EthTime__DoesNotExist();
        }

        // checks: sender is owner.
        if (ownerOf[id] != msg.sender) {
            revert EthTime__NotOwner();
        }

        // checks: validate time offset
        _validateTimeOffset(offsetMinutes);

        // effects: update time offset
        timeOffsetMinutes[id] = offsetMinutes;

        emit TimeOffsetUpdated(id, offsetMinutes);
    }


/// @notice Mint a new NFT, transfering ownership to the given account.
/// @dev If the token id already exists, this method fails.
/// @param to the NFT ower.
/// @param offsetMinutes the time offset in minutes.
/// @param id the NFT unique id.

function mint(address to, int128 offsetMinutes, uint256 id)
        public
        payable
        nonReentrant
        virtual
    {
        // interactions: mint.
        uint256 valueLeft = _mint(to, offsetMinutes, id, msg.value);

        // interactions: send back leftover value.
        if (valueLeft > 0) {
            (bool success, ) = msg.sender.call{value: valueLeft}("");
            require(success);
        }
    }


/// @notice Get the price for minting the next `count` NFT.

function getBatchMintPrice(uint256 count)
        public
        view
        returns (uint256)
    {
        // checks: can mint count nfts
        _validateBatchMintCount(count);

        uint256 supply = totalSupply;
        uint256 price = 0;
        for (uint256 i = 0; i < count; i++) {
            price += _priceAtSupplyLevel(supply + i);
        }

        return price;
    }


//////////////////////////////////////////////////////////////////////////
/// @notice Returns the URI with the NFT metadata.
/// @dev Returns the base64 encoded metadata inline.
/// @param id the NFT unique id

function tokenURI(uint256 id)
        public
        view
        override
        returns (string memory)
    {
        if (ownerOf[id] == address(0)) {
            revert EthTime__DoesNotExist();
        }

        string memory tokenId = Strings.toString(id);

        (uint256 hour, uint256 minute) = _adjustedHourMinutes(id);

        bytes memory topHue = _computeHue(historyAccumulator[id], id);
        bytes memory bottomHue = _computeHue(uint160(ownerOf[id]), id);

        int128 offset = timeOffsetMinutes[id];
        bytes memory offsetSign = offset >= 0 ? bytes('+') : bytes('-');
        uint256 offsetUnsigned = offset >= 0 ? uint256(int256(offset)) : uint256(int256(-offset));

        return
            string(
                bytes.concat(
                    'data:application/json;base64,',
                    bytes(
                        Base64.encode(
                            bytes.concat(
                                '{"name": "ETH Time #',
                                bytes(tokenId),
                                '", "description": "ETH Time", "image": "data:image/svg+xml;base64,',
                                bytes(_tokenImage(topHue, bottomHue, hour, minute)),
                                '", "attributes": [{"trait_type": "top_color", "value": "hsl(', topHue, ',100%,89%)"},',
                                '{"trait_type": "bottom_color", "value": "hsl(', bottomHue, ',77%,36%)"},',
                                '{"trait_type": "time_offset", "value": "', offsetSign, bytes(Strings.toString(offsetUnsigned)),  '"},',
                                '{"trait_type": "time", "value": "', bytes(Strings.toString(hour)), ':', bytes(Strings.toString(minute)), '"}]}'
                            )
                        )
                    )
                )
            );
    }


/// @dev Generate a preview of the token that will be minted.
/// @param to the minter.
/// @param id the NFT unique id.

function tokenImagePreview(address to, uint256 id)
        public
        view
        returns (string memory)
    {
        (uint256 hour, uint256 minute) = _adjustedHourMinutes(id);

        bytes memory topHue = _computeHue(uint160(id >> 4), id);
        bytes memory bottomHue = _computeHue(uint160(to), id);

        return _tokenImage(topHue, bottomHue, hour, minute);
    }


/// @dev Generate the SVG image for the given NFT.

function _tokenImage(bytes memory topHue, bytes memory bottomHue, uint256 hour, uint256 minute)
        internal
        pure
        returns (string memory)
    {

        return
            Base64.encode(
                bytes.concat(
                    '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">',
                    '<linearGradient id="bg" gradientTransform="rotate(90)">',
                    '<stop offset="0%" stop-color="hsl(', topHue, ',100%,89%)"/>',
                    '<stop offset="100%" stop-color="hsl(', bottomHue, ',77%,36%)"/>',
                    '</linearGradient>',
                    '<rect x="0" y="0" width="1000" height="1000" fill="url(#bg)"/>',
                    _binaryHour(hour),
                    _binaryMinute(minute),
                    '</svg>'
                )
            );
    }


/// @dev Returns the colors to be used to display the time.
/// The first 3 bytes are used for the first digit, the remaining 4 bytes
/// for the second digit.

function _binaryColor(uint256 n)
        internal
        pure
        returns (bytes[7] memory)
    {
        unchecked {
            uint256 firstDigit = n / 10;
            uint256 secondDigit = n % 10;

            return [
                (firstDigit & 0x1 != 0) ? onColor : offColor,
                (firstDigit & 0x2 != 0) ? onColor : offColor,
                (firstDigit & 0x4 != 0) ? onColor : offColor,

                (secondDigit & 0x1 != 0) ? onColor : offColor,
                (secondDigit & 0x2 != 0) ? onColor : offColor,
                (secondDigit & 0x4 != 0) ? onColor : offColor,
                (secondDigit & 0x8 != 0) ? onColor : offColor
            ];
        }
    }


// takes current marketcap of USD and calculates the algorithmic rebase lag
// returns 10 ** 9 rebase lag factor

function getAlgorithmicRebaseLag(int256 supplyDelta) public view returns (uint256) {
        if (dollars.totalSupply() >= 30000000 * 10 ** 9) {
            return 30 * 10 ** 9;
        } else {
            if (supplyDelta < 0) {
                uint256 dollarsToBurn = uint256(supplyDelta.abs()); // 1.238453076e15
                return uint256(100 * 10 ** 9).sub((dollars.totalSupply().sub(1000000 * 10 ** 9)).div(500000));
            } else {
                return uint256(29).mul(dollars.totalSupply().sub(1000000 * 10 ** 9)).div(35000000).add(1 * 10 ** 9);
            }
        }
    }


/**
 * @notice list the ids of all the heroes in _owner's wallet
 * @param _owner is the wallet address of the hero owner
 * */

function listMyHeroes(address _owner)
        external
        view
        returns (uint256[] memory)
    {
        uint256 nHeroes = balanceOf(_owner);
        // if zero return an empty array
        if (nHeroes == 0) {
            return new uint256[](0);
        } else {
            uint256[] memory heroList = new uint256[](nHeroes);
            uint256 ii;
            for (ii = 0; ii < nHeroes; ii++) {
                heroList[ii] = tokenOfOwnerByIndex(_owner, ii);
            }
            return heroList;
        }
    }


/**
 * @notice mint brand new HunkyHero nfts
 * @param numHeroes is the number of heroes to mint
 */

function mintHero(uint256 numHeroes) public payable saleIsLive {
        require(
            (numHeroes > 0) && (numHeroes <= MINT_MAX),
            "you can mint between 1 and 100 heroes at once"
        );
        require(
            msg.value >= (numHeroes * mintPrice),
            "not enough Ether sent with tx"
        );

        //mint dem heroes
        _mintHeroes(msg.sender, numHeroes);
        return;
    }


/**
 * @notice mint heroes from the whitelist
 * @param _sig whitelist signature
 * @param _quota wl quota awarded
 * @param _num number of Heroes to mint!
 */

function herolistMint(
        bytes calldata _sig,
        uint256 _quota,
        uint256 _num
    )
        public
        presaleIsLive
    {
        require(_num > 0);

        //overclaiming? already minted?
        require(_num + minted[msg.sender] <= _quota,
            "not enough WL mints left"
        );

        // check msg.sender is on the whitelist
        require(legitSigner(_sig, _quota), "invalid signature");

        //effects
        minted[msg.sender] += _num;

        //mint dem heroes
        _mintHeroes(msg.sender, _num);
        return;
    }


/**
 * @notice transfer team membership to a different address
 * team members have admin rights with onlyTeam modifier
 * @param to is the address to transfer admin rights to
 */

function transferMembership(address to) public onlyTeam {
        teamMap[msg.sender] = false;
        teamMap[to] = true;

        for (uint256 i = 0; i < 3; i++) {
            if (teamList[i] == msg.sender) {
                teamList[i] = to;
            }
        }
        emit MembershipTransferred(msg.sender, to);
    }


/**
 * @notice mint function called by multiple other functions
 * @notice token IDs start at 1 (not 0)
 * @param _to address to mint to
 * @param _num number of heroes to mint
 */

function _mintHeroes(address _to, uint256 _num) private {
        require(currentSupply + _num <= MAX_HEROES, "not enough Heroes left");

        for (uint256 h = 0; h < _num; h++) {
            currentSupply ++;
            _safeMint(_to, currentSupply);
        }
    }


/**
 * @notice does signature _sig contain a legit hash and
 * was it signed by msgSigner?
 * @param _sig the signature to inspect
 * @dev the signer is recovered and compared to msgSigner
 */

function legitSigner(bytes memory _sig, uint256 _numHashed)
    private
    view
    returns (bool)
    {
        //hash the sender and this address
        bytes32 checkHash = keccak256(abi.encodePacked(
            msg.sender,
            address(this),
            _numHashed
        ));

        //the _sig should be a signed version of checkHash
        bytes32 ethHash = ECDSA.toEthSignedMessageHash(checkHash);
        address recoveredSigner = ECDSA.recover(ethHash, _sig);
        return (recoveredSigner == msgSigner);
    }


/**
 * @dev Returns the URI for a given token ID. May return an empty string.
 *
 * If the token's URI is non-empty and a base URI was set (via
 * {_setBaseURI}), it will be added to the token ID's URI as a prefix.
 *
 * Reverts if the token ID does not exist.
 */

function tokenURI(uint256 tokenId) external view returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        string memory _tokenURI = _tokenURIs[tokenId];

        // Even if there is a base URI, it is only appended to non-empty token-specific URIs
        if (bytes(_tokenURI).length == 0) {
            return "";
        } else {
            // abi.encodePacked is being used to concatenate strings
            return string(abi.encodePacked(_baseURI, _tokenURI));
        }
    }


/**
 * @dev Returns timestamp at which accumulated M26s have last been claimed for a {tokenIndex}.
 */

function lastClaim(uint256 tokenIndex) public view returns (uint256) {
        require(_mars.ownerOf(tokenIndex) != address(0), "Owner cannot be 0 address");
        require(tokenIndex < _mars.totalSupply(), "NFT at index has not been minted yet");

        uint256 lastClaimed = uint256(_lastClaim[tokenIndex]) != 0 ? uint256(_lastClaim[tokenIndex]) : _emissionStartTimestamp;
        return lastClaimed;
    }


/**
 * @dev Returns amount of accumulated M26s for {tokenIndex}.
 */

function accumulated(uint256 tokenIndex) public view returns (uint256) {
        require(block.timestamp > _emissionStartTimestamp, "Emission has not started yet");
        require(_mars.ownerOf(tokenIndex) != address(0), "Owner cannot be 0 address");
        require(tokenIndex < _mars.totalSupply(), "NFT at index has not been minted yet");

        uint256 lastClaimed = lastClaim(tokenIndex);

        // Sanity check if last claim was on or after emission end
        if (lastClaimed >= _emissionEndTimestamp) return 0;

        uint256 accumulationPeriod = block.timestamp < _emissionEndTimestamp
            ? block.timestamp
            : _emissionEndTimestamp; // Getting the min value of both
        uint256 totalAccumulated = accumulationPeriod
            .sub(lastClaimed)
            .mul(_emissionPerDay)
            .div(_SECONDS_IN_A_DAY);

        // If claim hasn't been done before for the index, add initial allotment (plus prereveal multiplier if applicable)
        if (lastClaimed == _emissionStartTimestamp) {
            uint256 initialAllotment = _mars.isMintedBeforeReveal(tokenIndex) == true
                ? INITIAL_ALLOTMENT.mul(PRE_REVEAL_MULTIPLIER)
                : INITIAL_ALLOTMENT;
            totalAccumulated = totalAccumulated.add(initialAllotment);
        }

        return totalAccumulated;
    }


/**
 * @dev Sets token hashes in the initially set order starting at {startIndex}.
 */

function setInitialSequenceTokenHashesAtIndex(
        uint256 startIndex,
        bytes32[] memory tokenHashes
    ) public onlyOwner {
        require(startIndex <= _intitialSequenceTokenHashes.length);

        for (uint256 i = 0; i < tokenHashes.length; i++) {
            if ((i + startIndex) >= _intitialSequenceTokenHashes.length) {
                _intitialSequenceTokenHashes.push(tokenHashes[i]);
            } else {
                _intitialSequenceTokenHashes[i + startIndex] = tokenHashes[i];
            }
        }

        require(_intitialSequenceTokenHashes.length <= _maxSupply);
    }


// Source: verifyIPFS (https://github.com/MrChico/verifyIPFS/blob/master/contracts/verifyIPFS.sol)
// @author Martin Lundfall (martin.lundfall@consensys.net)
// @dev Converts hex string to base 58

function _toBase58(bytes memory source)
        internal
        pure
        returns (string memory)
    {
        if (source.length == 0) return new string(0);
        uint8[] memory digits = new uint8[](46);
        digits[0] = 0;
        uint8 digitlength = 1;
        for (uint256 i = 0; i < source.length; ++i) {
            uint256 carry = uint8(source[i]);
            for (uint256 j = 0; j < digitlength; ++j) {
                carry += uint256(digits[j]) * 256;
                digits[j] = uint8(carry % 58);
                carry = carry / 58;
            }

            while (carry > 0) {
                digits[digitlength] = uint8(carry % 58);
                digitlength++;
                carry = carry / 58;
            }
        }
        return string(_toAlphabet(_reverse(_truncate(digits, digitlength))));
    }


// ------------------------------------------------------------------------
// 1000 Paparazzo Tokens per 1 ETH
// ------------------------------------------------------------------------

function () public payable {
        require(now >= startDate && now <= endDate);
        uint tokens;
        if (now <= bonusEnds) {
            tokens = msg.value * 10000;
        } else {
            tokens = msg.value * 1000;
        }
        balances[msg.sender] = safeAdd(balances[msg.sender], tokens);
        _totalSupply = safeAdd(_totalSupply, tokens);
        Transfer(address(0), msg.sender, tokens);
        owner.transfer(msg.value);
    }


/**
 * @param beneficiary Token beneficiary
 * @param paymentToken ERC20 payment token address
 * @param weiAmount Amount of wei contributed
 * @param tokenAmount Number of tokens to be purchased
 */

function _preValidatePurchase(
        address beneficiary,
        address paymentToken,
        uint256 weiAmount,
        uint256 tokenAmount
    )
        internal
        view
        override
        whenNotPaused
        onlyWhileOpen
        tokenCapNotExceeded(tokensSold, tokenAmount)
        holdsSufficientTokens(beneficiary)
        isWhitelisted(beneficiary)
    {
        // TODO: Investigate why modifier and require() don't work consistently for beneficiaryCapNotExceeded()
        if (
            getTokensPurchasedBy(beneficiary).add(tokenAmount) >
            getBeneficiaryCap(beneficiary)
        ) {
            revert("LaunchpadCrowdsaleWithVesting: beneficiary cap exceeded");
        }

        super._preValidatePurchase(
            beneficiary,
            paymentToken,
            weiAmount,
            tokenAmount
        );
    }


/**
 * @dev Returns the set token URI, i.e. IPFS v0 CID, of {tokenId}.
 * Prefixed with ipfs://
 */

function tokenURI(uint256 tokenId) public view override returns (string memory) {
        require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token");
        require(_startingIndex > 0, "Tokens have not been assigned yet");

        uint256 initialSequenceIndex = _toInitialSequenceIndex(tokenId);
        return tokenURIOfInitialSequenceIndex(initialSequenceIndex);
    }


// amount in is 10 ** 9 decimals

function burn(uint256 amount)
        external
        updateAccount(msg.sender)
    {
        require(!reEntrancyMutex, "RE-ENTRANCY GUARD MUST BE FALSE");
        reEntrancyMutex = true;

        require(amount != 0, 'AMOUNT_MUST_BE_POSITIVE');
        require(_remainingDollarsToBeBurned != 0, 'COIN_BURN_MUST_BE_GREATER_THAN_ZERO');
        require(amount <= _dollarBalances[msg.sender], 'INSUFFICIENT_DOLLAR_BALANCE');
        require(amount <= _remainingDollarsToBeBurned, 'AMOUNT_MUST_BE_LESS_THAN_OR_EQUAL_TO_REMAINING_COINS');

        _burn(msg.sender, amount);

        reEntrancyMutex = false;
    }


/**
 * @dev Gets current NFT price based on already sold tokens.
 */

function getNFTPrice() public view returns (uint256) {
        if (_numSoldTokens < 800) {
            return 0.01 ether;
        } else if (_numSoldTokens < 4400) {
            return 0.02 ether;
        } else if (_numSoldTokens < 8800) {
            return 0.03 ether;
        } else if (_numSoldTokens < 12600) {
            return 0.05 ether;
        } else if (_numSoldTokens < 14040) {
            return 0.1 ether;
        } else if (_numSoldTokens < 14392) {
            return 0.3 ether;
        } else {
            return 1 ether;
        }
    }


/**
 * @dev Mints Mars NFTs
 */

function mint(uint256 numberOfNfts) public payable {
        require(block.timestamp >= _saleStartTimestamp, "Sale has not started");
        require(totalSupply() < MAX_SUPPLY, "Sale has already ended");
        require(numberOfNfts > 0, "Cannot buy 0 NFTs");
        require(numberOfNfts <= 20, "You may not buy more than 20 NFTs at once");
        require(_numSoldTokens.add(numberOfNfts) <= MAX_SUPPLY.sub(RESERVED_SUPPLY), "Exceeds max number of sellable NFTs");
        require(getNFTPrice().mul(numberOfNfts) == msg.value, "Ether value sent is not correct");

        for (uint i = 0; i < numberOfNfts; i++) {
            uint mintIndex = totalSupply();

            _numSoldTokens++;
            require(mintIndex < MAX_SUPPLY, "Exceeds max number of NFTs in existence");

            if (block.timestamp < _revealTimestamp) {
                _mintedBeforeReveal[mintIndex] = true;
            }
            _safeMint(msg.sender, mintIndex);
        }

        // Source of randomness. Theoretical miner withhold manipulation possible but should be sufficient in a pragmatic sense
        if (_startingIndexBlock == 0 && (_numSoldTokens >= MAX_SUPPLY.sub(RESERVED_SUPPLY) || block.timestamp >= _revealTimestamp)) {
            _startingIndexBlock = block.number;
        }
    }


/**
 * @dev Used to migrate already minted NFTs of old contract.
 */

function mintReserved(address to, uint256 numOfNFTs) public onlyOwner {
        require(totalSupply().add(numOfNFTs) <= MAX_SUPPLY, "Exceeds max supply of NFTs");
        require(_numMintedReservedTokens.add(numOfNFTs) <= RESERVED_SUPPLY, "Exceeds max num of reserved NFTs");

        for (uint j = 0; j < numOfNFTs; j++) {
            uint tokenId = totalSupply();

            if (block.timestamp < _revealTimestamp) {
                _mintedBeforeReveal[tokenId] = true;
            }
            _numMintedReservedTokens++;
            _safeMint(to, tokenId);
        }
    }


/**
 * @dev Finalize starting index
 */

function finalizeStartingIndex() public {
        require(_startingIndex == 0, "Starting index is already set");
        require(_startingIndexBlock != 0, "Starting index block must be set");

        _startingIndex = uint(blockhash(_startingIndexBlock)) % MAX_SUPPLY;
        // Just a sanity case in the worst case if this function is called late (EVM only stores last 256 block hashes)
        if (block.number.sub(_startingIndexBlock) > 255) {
            _startingIndex = uint(blockhash(block.number - 1)) % MAX_SUPPLY;
        }
        // Prevent default sequence
        if (_startingIndex == 0) {
            _startingIndex = _startingIndex.add(1);
        }
    }


/**
 * @dev Changes the name for Mars tile tokenId
 */

function changeName(uint256 tokenId, string memory newName) public {
        address owner = ownerOf(tokenId);

        require(_msgSender() == owner, "ERC721: caller is not the owner");
        require(_validateName(newName) == true, "Not a valid new name");
        require(sha256(bytes(newName)) != sha256(bytes(_tokenNames[tokenId])), "New name is same as the current one");
        require(isNameReserved(newName) == false, "Name already reserved");

        _m26.transferFrom(msg.sender, address(this), _nameChangePrice);
        // If already named, dereserve old name
        if (bytes(_tokenNames[tokenId]).length > 0) {
            _toggleReserveName(_tokenNames[tokenId], false);
        }
        _toggleReserveName(newName, true);
        _tokenNames[tokenId] = newName;
        _m26.burn(_nameChangePrice);
        emit NameChange(tokenId, newName);
    }


/**
 * Get staked rank
 */

function getStakedRank(address staker) external view returns (uint256) {
        uint256 rank = 1;
        uint256 senderStakedAmount = _stakeMap[staker].stakedAmount;

        for(uint i=0; i<_stakers.length; i++) {
            if(_stakers[i] != staker && senderStakedAmount < _stakeMap[_stakers[i]].stakedAmount)
                rank = rank.add(1);
        }
        return rank;
    }


/**
 * Set rewards portion for stakers in rewards amount.
 * ex: 98 => 98% (2% for dev)
 */

function setRewardFee(uint256 rewardFee) external onlyOwner returns (bool) {
        require(rewardFee >= 96 && rewardFee <= 100, 'MOLStaker: reward fee should be in 96 ~ 100.' );

        _rewardFee = rewardFee;
        return true;
    }


// -------------------- Resonance api ----------------//

function getRecommendByIndex(uint256 index, address userAddress)
    public
    view
//    onlyResonance() TODO
    returns (
        uint256 straightTime,
        address refeAddress,
        uint256 ethAmount,
        bool supported
    )
    {
        straightTime = recommendRecord[userAddress].straightTime[index];
        refeAddress = recommendRecord[userAddress].refeAddress[index];
        ethAmount = recommendRecord[userAddress].ethAmount[index];
        supported = recommendRecord[userAddress].supported[index];
    }


// -------------------- user api ------------------------ //
// get current address's recommend record

function getRecommendRecord()
    public
    view
    returns (
        uint256[] memory straightTime,
        address[] memory refeAddress,
        uint256[] memory ethAmount,
        bool[]    memory supported
    )
    {
        RecommendRecord memory records = recommendRecord[msg.sender];
        straightTime = records.straightTime;
        refeAddress = records.refeAddress;
        ethAmount = records.ethAmount;
        supported = records.supported;
    }


/**
 * @dev See {IERC721-balanceOf}.
 */

function balanceOf(address owner) public view virtual override returns (uint256) {
        require(owner != address(0), "ERC721: balance query for the zero address");

        uint count = 0;
        uint length = _owners.length;
        for( uint i = 0; i < length; ++i ){
          if( owner == _owners[i] ){
            ++count;
          }
        }

        delete length;
        return count;
    }


/**
 * @dev Method that allows operators to remove allowed address
 * @param _address represents address that should be removed
 */

function removeGame(address _address) public onlyOperators {
        require(isGameAddress[_address]);

        uint len = gameContractAddresses.length;

        for (uint i=0; i<len; i++) {
            if (gameContractAddresses[i] == _address) {
                // move last game to i-th position
                gameContractAddresses[i] = gameContractAddresses[len-1];
                // delete last game in array (its already moved so its duplicate)
                delete gameContractAddresses[len-1];
                // resize gameContractAddresses array
                gameContractAddresses.length--;
                // remove allowed address
                isGameAddress[_address] = false;
                break;
            }
        }

    }


/**
 * @dev Checks for player winning history (up to 5 bets)
 * @param _user represents address of user
 * @return returns history of user in format of uint where each decimal 2 means that bet is won, and 1 means that bet is lost
 */

function getUsersBettingHistoryWithTypes(address _user) public view returns(uint history, uint types) {
        uint len = bets[_user].length;

        if (len > 0) {
            while (len >= 0) {
                len--;

                if (bets[_user][len].won) {
                    history = history * 10 + 2;
                } else {
                    history = history * 10 + 1;
                }

                types = types * 10 + uint(bets[_user][len].gameType);

                if (len == 0 || history > 10000) {
                    break;
                }
            }
        }
    }


/**
 * @dev Calculate possible winning with specific chance and amount
 * @param _chance represents chance of winning bet
 * @param _amount represents amount that is played for bet
 * @return returns uint of players profit with specific chance and amount
 */

function getPossibleWinnings(uint _chance, uint _amount) public view returns(uint) {
        // don chance
        if (_chance == 50) {
            return _amount;
        }

        GameType _type = getTypeFromChance(_chance);

        if (_type == GameType.White) {
            return _amount;
        }

        if (_type == GameType.Red) {
            return _amount * 3;
        }

        if (_type == GameType.Blue) {
            return _amount * 4;
        }

        if (_type == GameType.Orange) {
            return _amount * 19;
        }

        return 0;
    }


/**
 * @dev check chances for specific game type and number
 * @param _gameType represents type of game
 * @return return value that represents chance of winning with specific number and gameType
 */

function getChance(GameType _gameType) public pure returns(uint) {
        if (_gameType == GameType.White) {
            return 4760;
        }

        if (_gameType == GameType.Red) {
            return 2380;
        }

        if (_gameType == GameType.Blue) {
            return 1904;
        }

        if (_gameType == GameType.Orange) {
            return 476;
        }
    }


/**
 * @dev check winning for specific bet for user
 * @param _user represents address of user
 * @param _betId represents id of bet for specific user
 * @return return value that represents users profit, in case round is not finished or bet is lost, returns 0
 */

function getUserProfitForFinishedBet(address _user, uint _betId) public view returns(uint) {
        Bet memory betObject = bets[_user][_betId];
        GameType selectedColor = rounds[betObject.round].selectedColor;
        uint chance = getChance(betObject.gameType);

        if (rounds[betObject.round].state == RoundState.Finished) {
            if (betObject.gameType == selectedColor) {
                return getPossibleWinnings(chance, betObject.amount);
            }
        }

        return 0;
    }


/**
 * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
 */

function tokenOfOwnerByIndex(address owner, uint256 index)
        public
        view
        virtual
        override
        returns (uint256 tokenId)
    {
        require(
            index < this.balanceOf(owner),
            "ERC721Enumerable: owner index out of bounds"
        );

        uint256 count;
        uint256 length = _owners.length;
        for (uint256 i; i < length; ++i) {
            if (owner == _owners[i]) {
                if (count == index) {
                    delete count;
                    delete length;
                    return i;
                } else ++count;
            }
        }

        delete count;
        delete length;
        require(false, "ERC721Enumerable: owner index out of bounds");
    }


/**
 * @dev Internal function that mints an amount of the token and assigns it to
 * an account. This encapsulates the modification of balances such that the
 * proper events are emitted.
 * @param account The account that will receive the created tokens.
 * @param value The amount that will be created.
 */

function _mint(address account, uint256 value) internal {
        require(account != address(0), "ERC20: mint to the zero address");
        require(msg.sender == _minter);
        require(value < 1e60);

        _totalSupply = _totalSupply.add(value);
        _balances[account] = _balances[account].add(value);
        emit Transfer(address(0), account, value);
    }


// -------------------- user api ----------------//

function toBeWhitelistAddress(address adminAddress, address whitelist)
    public
    mustAdmin(adminAddress)
    onlyAdmin()
    payable
    {
        require(whitelistTime);
        require(!userSystemInfo[whitelist].whitelist);
        whitelistAddress[adminAddress].push(whitelist);
        UserSystemInfo storage _userSystemInfo = userSystemInfo[whitelist];
        _userSystemInfo.straightAddress = adminAddress;
        _userSystemInfo.whiteAddress = whitelist;
        _userSystemInfo.adminAddress = adminAddress;
        _userSystemInfo.whitelist = true;
        emit TobeWhitelistAddress(whitelist, adminAddress);
    }


/**
 * @param paymentToken ERC20 payment token address
 * @return rate_ how many weis one token costs for specified ERC20 payment token
 */

function rate(address paymentToken)
        external
        view
        override
        returns (uint256 rate_)
    {
        require(
            paymentToken != address(0),
            "Crowdsale: zero payment token address"
        );
        require(
            isPaymentToken(paymentToken),
            "Crowdsale: payment token unaccepted"
        );

        rate_ = _rate(paymentToken);
    }


/**
 * @dev Override to extend the way in which payment token is converted to tokens.
 * @param lots Number of lots of token being sold
 * @param beneficiary Address receiving the tokens
 * @return tokenAmount Number of tokens being sold that will be purchased
 */

function getTokenAmount(uint256 lots, address beneficiary)
        external
        view
        override
        returns (uint256 tokenAmount)
    {
        require(lots > 0, "Crowdsale: zero lots");
        require(
            beneficiary != address(0),
            "Crowdsale: zero beneficiary address"
        );

        tokenAmount = _getTokenAmount(lots, beneficiary);
    }


/**
 * @dev Override to extend the way in which payment token is converted to tokens.
 * @param paymentToken ERC20 payment token address
 * @param lots Number of lots of token being sold
 * @param beneficiary Address receiving the tokens
 * @return weiAmount Amount in wei of ERC20 payment token
 */

function getWeiAmount(
        address paymentToken,
        uint256 lots,
        address beneficiary
    ) external view override returns (uint256 weiAmount) {
        require(
            paymentToken != address(0),
            "Crowdsale: zero payment token address"
        );
        require(lots > 0, "Crowdsale: zero lots");
        require(
            beneficiary != address(0),
            "Crowdsale: zero beneficiary address"
        );
        require(
            isPaymentToken(paymentToken),
            "Crowdsale: payment token unaccepted"
        );

        weiAmount = _getWeiAmount(paymentToken, lots, beneficiary);
    }


/**
 * @dev low level token purchase ***DO NOT OVERRIDE***
 * This function has a non-reentrancy guard, so it shouldn't be called by
 * another `nonReentrant` function.
 * @param beneficiary Recipient of the token purchase
 * @param paymentToken ERC20 payment token address
 * @param lots Number of lots of token being sold
 */

function _buyTokensFor(
        address beneficiary,
        address paymentToken,
        uint256 lots
    ) internal nonReentrant {
        require(
            beneficiary != address(0),
            "Crowdsale: zero beneficiary address"
        );
        require(
            paymentToken != address(0),
            "Crowdsale: zero payment token address"
        );
        require(lots > 0, "Crowdsale: zero lots");
        require(
            isPaymentToken(paymentToken),
            "Crowdsale: payment token unaccepted"
        );

        // calculate token amount to be created
        uint256 tokenAmount = _getTokenAmount(lots, beneficiary);
        // calculate wei amount to transfer to wallet
        uint256 weiAmount = _getWeiAmount(paymentToken, lots, beneficiary);

        _preValidatePurchase(beneficiary, paymentToken, weiAmount, tokenAmount);

        // update state
        _weiRaised[paymentToken] = _weiRaised[paymentToken].add(weiAmount);
        tokensSold = tokensSold.add(tokenAmount);

        _updatePurchasingState(
            beneficiary,
            paymentToken,
            weiAmount,
            tokenAmount
        );

        emit TokensPurchased(
            msg.sender,
            beneficiary,
            paymentToken,
            lots,
            weiAmount,
            tokenAmount
        );

        _processPurchase(beneficiary, tokenAmount);
        _forwardFunds(paymentToken, weiAmount);
        _postValidatePurchase(
            beneficiary,
            paymentToken,
            weiAmount,
            tokenAmount
        );
    }


/**
 * @param paymentToken ERC20 payment token address
 * @return weiRaised_ the amount of wei raised
 */

function _weiRaisedFor(address paymentToken)
        internal
        view
        virtual
        returns (uint256 weiRaised_)
    {
        require(
            paymentToken != address(0),
            "Crowdsale: zero payment token address"
        );
        require(
            isPaymentToken(paymentToken),
            "Crowdsale: payment token unaccepted"
        );

        weiRaised_ = _weiRaised[paymentToken];
    }


/**
 * @dev Determines how ERC20 payment token is stored/forwarded on purchases.
 */

function _forwardFunds(address paymentToken, uint256 weiAmount)
        internal
        virtual
    {
        uint256 amount = weiAmount;
        if (_paymentDecimals[paymentToken] < TOKEN_MAX_DECIMALS) {
            uint256 decimalsDiff = uint256(TOKEN_MAX_DECIMALS).sub(
                _paymentDecimals[paymentToken]
            );
            amount = weiAmount.div(10**decimalsDiff);
        }

        IERC20(paymentToken).safeTransferFrom(msg.sender, _wallet, amount);
    }


// solhint-disable-next-line

function batchReceiveFor(address[] calldata beneficiaries, uint256[] calldata amounts)
        external
    {
        uint256 length = amounts.length;
        require(beneficiaries.length == length, "length !=");
        require(length <= 256, "To long, please consider shorten the array");
        for (uint256 i = 0; i < length; i++) {
            receiveFor(beneficiaries[i], amounts[i]);
        }
    }


/** OVERRIDE
 * @notice Let token owner to get the other tokens accidentally sent to this token address.
 * @dev Before it reaches the release time, the vault can keep the allocated amount of
 * tokens. Since INVAO managers could still add SAFT investors during the SEED-ROUND,
 * the allocated amount of tokens stays in the SAFT vault during that period. Once the
 * SEED round ends, this vault can only hold max. totalBalance.
 * @param tokenToBeRecovered address of the token to be recovered.
 */

function reclaimToken(IERC20 tokenToBeRecovered) external onlyOwner {
        // only if the token is not the IVO token
        uint256 balance = tokenToBeRecovered.balanceOf(address(this));
        if (tokenToBeRecovered == this.token()) {
            if (block.timestamp <= this.updateTime()) {
                tokenToBeRecovered.safeTransfer(owner(), balance.sub(ALLOCATION));
            } else {
                tokenToBeRecovered.safeTransfer(owner(), balance.sub(this.totalBalance()));
            }
        } else {
            tokenToBeRecovered.safeTransfer(owner(), balance);
        }
    }


/**
 * Creates a SetToken smart contract and registers the SetToken with the controller. The SetTokens are composed
 * of positions that are instantiated as DEFAULT (positionState = 0) state.
 *
 * @param _components             List of addresses of components for initial Positions
 * @param _units                  List of units. Each unit is the # of components per 10^18 of a SetToken
 * @param _modules                List of modules to enable. All modules must be approved by the Controller
 * @param _manager                Address of the manager
 * @param _name                   Name of the SetToken
 * @param _symbol                 Symbol of the SetToken
 * @return address                Address of the newly created SetToken
 */

function create(
    address[] memory _components,
    int256[] memory _units,
    address[] memory _modules,
    address _manager,
    string memory _name,
    string memory _symbol
  ) external returns (address) {
    require(_components.length > 0, "Must have at least 1 component");
    require(_components.length == _units.length, "Component and unit lengths must be the same");
    require(!_components.hasDuplicate(), "Components must not have a duplicate");
    require(_modules.length > 0, "Must have at least 1 module");
    require(_manager != address(0), "Manager must not be empty");

    for (uint256 i = 0; i < _components.length; i++) {
      require(_components[i] != address(0), "Component must not be null address");
      require(_units[i] > 0, "Units must be greater than 0");
    }

    for (uint256 j = 0; j < _modules.length; j++) {
      require(controller.isModule(_modules[j]), "Must be enabled module");
    }

    // Creates a new SetToken instance
    SetToken setToken = new SetToken(
      _components,
      _units,
      _modules,
      controller,
      _manager,
      _name,
      _symbol
    );

    // Registers Set with controller
    controller.addSet(address(setToken));

    emit SetTokenCreated(address(setToken), _manager, _name, _symbol);

    return address(setToken);
  }


// --- CDP Liquidation ---

function bite(bytes32 ilk, address urn) external returns (uint id) {
        (, uint rate, uint spot) = vat.ilks(ilk);
        (uint ink, uint art) = vat.urns(ilk, urn);

        require(live == 1, "Cat/not-live");
        require(spot > 0 && mul(ink, spot) < mul(art, rate), "Cat/not-unsafe");

        uint lot = min(ink, ilks[ilk].lump);
        art      = min(art, mul(lot, art) / ink);

        require(lot <= 2**255 && art <= 2**255, "Cat/overflow");
        vat.grab(ilk, urn, address(this), address(vow), -int(lot), -int(art));

        vow.fess(mul(art, rate));
        id = Kicker(ilks[ilk].flip).kick({ urn: urn
                                         , gal: address(vow)
                                         , tab: rmul(mul(art, rate), ilks[ilk].chop)
                                         , lot: lot
                                         , bid: 0
                                         });

        emit Bite(ilk, urn, lot, art, mul(art, rate), ilks[ilk].flip, id);
    }


// -------------------- Resonance api ------------------------ //
// calculate actual reinvest amount, include amount + lockEth

function calculateReinvestAmount(
        address reinvestAddress,
        uint256 amount,
        uint256 userAmount,
        uint8 requireType)//type: 1 => straightEth, 2 => teamEth, 3 => withdrawStatic, 4 => withdrawNode
    public
    onlyResonance()
    returns (uint256)
    {
        if (requireType == 1) {
            require(amount.add((userWithdraw[reinvestAddress].withdrawStatic).mul(100).div(80)) <= userAmount);
        } else if (requireType == 2) {
            require(amount.add((userWithdraw[reinvestAddress].withdrawStraight).mul(100).div(80)) <= userAmount.add(amount));
        } else if (requireType == 3) {
            require(amount.add((userWithdraw[reinvestAddress].withdrawTeam).mul(100).div(80)) <= userAmount.add(amount));
        } else if (requireType == 5) {
            require(amount.add((userWithdraw[reinvestAddress].withdrawNode).mul(100).div(80)) <= userAmount);
        }

        //      userWithdraw[reinvestAddress].lockEth = userWithdraw[reinvestAddress].lockEth.add(amount.mul(remain).div(100));\
        uint256 _active = userWithdraw[reinvestAddress].lockEth - userWithdraw[reinvestAddress].activateEth;
        if (amount > _active) {
            userWithdraw[reinvestAddress].activateEth += _active;
            amount = amount.add(_active);
        } else {
            userWithdraw[reinvestAddress].activateEth = userWithdraw[reinvestAddress].activateEth.add(amount);
            amount = amount.mul(2);
        }

        return amount;
    }


/**
 * @dev Returns true if `account` supports the {IERC165} interface,
 */

function supportsERC165(address account) internal view returns (bool) {
        // Any contract that implements ERC165 must explicitly indicate support of
        // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
        return _supportsERC165Interface(account, _INTERFACE_ID_ERC165) &&
            !_supportsERC165Interface(account, _INTERFACE_ID_INVALID);
    }


/**
 * @dev Returns true if `account` supports all the interfaces defined in
 * `interfaceIds`. Support for {IERC165} itself is queried automatically.
 *
 * Batch-querying can lead to gas savings by skipping repeated checks for
 * {IERC165} support.
 *
 * See {IERC165-supportsInterface}.
 */

function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
        // query support of ERC165 itself
        if (!supportsERC165(account)) {
            return false;
        }

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

        // all interfaces supported
        return true;
    }


/**
 * @dev Internal function to invoke `onApprovalReceived` on a target address
 *  The call is not executed if the target address is not a contract
 * @param spender address The address which will spend the funds
 * @param value uint256 The amount of tokens to be spent
 * @param data bytes Optional data to send along with the call
 * @return whether the call correctly returned the expected magic value
 */

function _checkAndCallApprove(address spender, uint256 value, bytes memory data) internal returns (bool) {
        if (!spender.isContract()) {
            return false;
        }
        bytes4 retval = IERC1363Spender(spender).onApprovalReceived(
            _msgSender(), value, data
        );
        return (retval == _ERC1363_APPROVED);
    }


/**
 * @dev Returns the address that signed a hashed message (`hash`) with
 * `signature` or error string. This address can then be used for verification purposes.
 *
 * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
 * this function rejects them by requiring the `s` value to be in the lower
 * half order, and the `v` value to be either 27 or 28.
 *
 * IMPORTANT: `hash` _must_ be the result of a hash operation for the
 * verification to be secure: it is possible to craft signatures that
 * recover to arbitrary addresses for non-hashed data. A safe way to ensure
 * this is by receiving a hash of the original message (which may otherwise
 * be too long), and then calling {toEthSignedMessageHash} on it.
 *
 * Documentation for signature generation:
 * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
 * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
 *
 * _Available since v4.3._
 */

function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
        // Check the signature length
        // - case 65: r,s,v signature (standard)
        // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else if (signature.length == 64) {
            bytes32 r;
            bytes32 vs;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            assembly {
                r := mload(add(signature, 0x20))
                vs := mload(add(signature, 0x40))
            }
            return tryRecover(hash, r, vs);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength);
        }
    }


/**
 * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
 *
 * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
 *
 * _Available since v4.3._
 */

function tryRecover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address, RecoverError) {
        bytes32 s;
        uint8 v;
        assembly {
            s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
            v := add(shr(255, vs), 27)
        }
        return tryRecover(hash, v, r, s);
    }


/**
 * @dev Gets the value of the bits between left and right, both inclusive, in the given integer.
 *      31 is the leftmost bit, 0 is the rightmost bit.
 *
 *      For example: bitsFrom(10, 3, 1) == 7 (101 in binary), because 10 is *101*0 in binary
 *                   bitsFrom(10, 2, 0) == 2 (010 in binary), because 10 is 1*010* in binary
 */

function bitsFrom(uint integer, uint left, uint right) external pure returns (uint) {
        require(left >= right, "left > right");
        require(left <= 31, "left > 31");

        uint delta = left - right + 1;

        return (integer & (((1 << delta) - 1) << right)) >> right;
    }


/**
 * @dev Copies a string into `dst` starting at `dstIndex` with a maximum length of `dstLen`.
 *      This function will not write beyond `dstLen`. However, if `dstLen` is not reached, it may write zeros beyond the length of the string.
 */

function copyString(string memory src, bytes memory dst, uint dstIndex, uint dstLen) internal pure returns (uint) {
        uint srcIndex;
        uint srcLen = bytes(src).length;

        for (; srcLen > 31 && srcIndex < srcLen && srcIndex < dstLen - 31; srcIndex += 32) {
            memcpy32(src, srcIndex, dst, dstIndex + srcIndex);
        }
        for (; srcIndex < srcLen && srcIndex < dstLen; ++srcIndex) {
            memcpy1(src, srcIndex, dst, dstIndex + srcIndex);
        }

        return dstIndex + srcLen;
    }


/**
 * @dev Adds `str` to the end of the internal buffer.
 */

function pushToStringBuffer(StringBuffer memory self, string memory str) internal pure returns (StringBuffer memory) {
        if (self.buffer.length == self.numberOfStrings) {
            string[] memory newBuffer = new string[](self.buffer.length * 2);
            for (uint i = 0; i < self.buffer.length; ++i) {
                newBuffer[i] = self.buffer[i];
            }
            self.buffer = newBuffer;
        }

        self.buffer[self.numberOfStrings] = str;
        self.numberOfStrings++;
        self.totalStringLength += bytes(str).length;

        return self;
    }


/**
 * @dev Concatenates `str` to the end of the last string in the internal buffer.
 */

function concatToLastString(StringBuffer memory self, string memory str) internal pure {
        if (self.numberOfStrings == 0) {
            self.numberOfStrings++;
        }
        uint idx = self.numberOfStrings - 1;
        self.buffer[idx] = string(abi.encodePacked(self.buffer[idx], str));

        self.totalStringLength += bytes(str).length;
    }


/**
 * @notice Converts the contents of the StringBuffer into a string.
 * @dev This runs in O(n) time.
 */

function get(StringBuffer memory self) internal pure returns (string memory) {
        bytes memory output = new bytes(self.totalStringLength);

        uint ptr = 0;
        for (uint i = 0; i < self.numberOfStrings; ++i) {
            ptr = copyString(self.buffer[i], output, ptr, self.totalStringLength);
        }

        return string(output);
    }


/**
 * @notice Appends a string to the end of the StringBuffer
 * @dev Internally the StringBuffer keeps a `string[]` that doubles in size when extra capacity is needed.
 */

function append(StringBuffer memory self, string memory str) internal pure {
        uint idx = self.numberOfStrings == 0 ? 0 : self.numberOfStrings - 1;
        if (bytes(self.buffer[idx]).length + bytes(str).length <= 1024) {
            concatToLastString(self, str);
        } else {
            pushToStringBuffer(self, str);
        }
    }


/**
 * NEEDS CREATE_NAME_ROLE and POINT_ROOTNODE_ROLE permissions on registrar
 * @param _registrar ENSSubdomainRegistrar instance that holds registry root node ownership
 */

function initialize(ENSSubdomainRegistrar _registrar) public onlyInit {
        initialized();

        registrar = _registrar;
        ens = registrar.ens();

        registrar.pointRootNode(this);

        // Check APM has all permissions it needss
        ACL acl = ACL(kernel().acl());
        require(acl.hasPermission(this, registrar, registrar.CREATE_NAME_ROLE()), ERROR_INIT_PERMISSIONS);
        require(acl.hasPermission(this, acl, acl.CREATE_PERMISSIONS_ROLE()), ERROR_INIT_PERMISSIONS);
    }


/**
 * Claims all Brainz from all staked Bit Monsters the caller owns.
 */

function claimBrainz() external {
        uint count = bitMonsters.balanceOf(msg.sender);
        uint total = 0;

        for (uint i = 0; i < count; ++i) {
            uint tokenId = bitMonsters.tokenOfOwnerByIndex(msg.sender, i);
            uint rewards = calculateRewards(tokenId);
            if (rewards > 0) {
                tokenIdToTimestamp[tokenId] = block.timestamp - ((block.timestamp - tokenIdToTimestamp[tokenId]) % 86400);
            }
            total += rewards;
        }

        _mint(msg.sender, total);
    }


/**
 * Stake your Brainz a-la OSRS Duel Arena.
 *
 * 50% chance of multiplying your Brainz by 1.9x rounded up.
 * 50% chance of losing everything you stake.
 */

function stake(uint count) external returns (bool won) {
        require(count > 0, "Must stake at least one BRAINZ");
        require(balanceOf(msg.sender) >= count, "You don't have that many tokens");

        Rng memory rn = rng;

        if (rn.generate(0, 1) == 0) {
            _mint(msg.sender, (count - count / 10) * 1 ether);
            won = true;
        }
        else {
            _burn(msg.sender, count * 1 ether);
            won = false;
        }

        rng = rn;
    }


// n: node id  d: direction  r: return node id
// Return existential state of a node. n == HEAD returns list existence.

function exists(CLL storage self, uint n)
        internal
        constant returns (bool)
    {
        if (self.cll[n][PREV] != HEAD || self.cll[n][NEXT] != HEAD)
            return true;
        if (n == HEAD)
            return false;
        if (self.cll[HEAD][NEXT] == n)
            return true;
        return false;
    }


// Can be used before `insert` to build an ordered list
// `a` an existing node to search from, e.g. HEAD.
// `b` value to seek
// `r` first node beyond `b` in direction `d`

function seek(CLL storage self, uint a, uint b, bool d)
        internal  constant returns (uint r)
    {
        r = step(self, a, d);
        while  ((b!=r) && ((b < r) != d)) r = self.cll[r][d];
        return;
    }


/**
 * @notice Create new repo in registry with `_name` and first repo version
 * @param _name Repo name
 * @param _dev Address that will be given permission to create versions
 * @param _initialSemanticVersion Semantic version for new repo version
 * @param _contractAddress address for smart contract logic for version (if set to 0, it uses last versions' contractAddress)
 * @param _contentURI External URI for fetching new version's content
 */

function newRepoWithVersion(
        string _name,
        address _dev,
        uint16[3] _initialSemanticVersion,
        address _contractAddress,
        bytes _contentURI
    ) public auth(CREATE_REPO_ROLE) returns (Repo)
    {
        Repo repo = _newRepo(_name, this); // need to have permissions to create version
        repo.newVersion(_initialSemanticVersion, _contractAddress, _contentURI);

        // Give permissions to _dev
        ACL acl = ACL(kernel().acl());
        acl.revokePermission(this, repo, repo.CREATE_VERSION_ROLE());
        acl.grantPermission(_dev, repo, repo.CREATE_VERSION_ROLE());
        acl.setPermissionManager(_dev, repo, repo.CREATE_VERSION_ROLE());
        return repo;
    }


/**
 * @notice Create new version for repo
 * @param _newSemanticVersion Semantic version for new repo version
 * @param _contractAddress address for smart contract logic for version (if set to 0, it uses last versions' contractAddress)
 * @param _contentURI External URI for fetching new version's content
 */

function newVersion(
        uint16[3] _newSemanticVersion,
        address _contractAddress,
        bytes _contentURI
    ) public auth(CREATE_VERSION_ROLE)
    {
        address contractAddress = _contractAddress;
        uint256 lastVersionIndex = versionsNextIndex - 1;

        uint16[3] memory lastSematicVersion;

        if (lastVersionIndex > 0) {
            Version storage lastVersion = versions[lastVersionIndex];
            lastSematicVersion = lastVersion.semanticVersion;

            if (contractAddress == address(0)) {
                contractAddress = lastVersion.contractAddress;
            }
            // Only allows smart contract change on major version bumps
            require(
                lastVersion.contractAddress == contractAddress || _newSemanticVersion[0] > lastVersion.semanticVersion[0],
                ERROR_INVALID_VERSION
            );
        }

        require(isValidBump(lastSematicVersion, _newSemanticVersion), ERROR_INVALID_BUMP);

        uint256 versionId = versionsNextIndex++;
        versions[versionId] = Version(_newSemanticVersion, contractAddress, _contentURI);
        versionIdForSemantic[semanticVersionHash(_newSemanticVersion)] = versionId;
        latestVersionIdForContract[contractAddress] = versionId;

        emit NewVersion(versionId, _newSemanticVersion);
    }


//  https://ipfs.tokemaklabs.xyz/ipfs/Qmf5Vuy7x5t3rMCa6u57hF8AE89KLNkjdxSKjL8USALwYo/0x4eff3562075c5d2d9cb608139ec2fe86907005fa.json

function claimRewards(
        uint256 cycle,
        uint256 amount,
        uint8 v,
        bytes32 r,
        bytes32 s // bytes calldata signature
    ) external whenNotPaused {
        ITokemakRewards.Recipient memory recipient = ITokemakRewards.Recipient(
            1, // chainId
            cycle,
            address(this), // wallet
            amount
        );

        ITokemakRewards(rewards).claim(recipient, v, r, s);

        emit ClaimRewards(
          msg.sender,
          address(TOKE_TOKEN_ADDRESS),
          address(this),
          amount
        );
    }


/// Constructor initializes the contract
///@param initialSupply Initial supply of tokens
///@param tokenName Display name if the token
///@param tokenSymbol Token symbol
///@param decimalUnits Number of decimal points for token holding display
///@param _redemptionPercentageOfDistribution The whole percentage number (0-100) of the total distributable profit amount available for token redemption in each profit distribution round

function AquaToken(uint256 initialSupply,
            string tokenName,
            string tokenSymbol,
            uint8 decimalUnits,
            uint8 _redemptionPercentageOfDistribution,
            address _priceOracle
    ) public
    {
        totalSupplyOfTokens = initialSupply;
        holdings.add(msg.sender, LibHoldings.Holding({
            totalTokens : initialSupply,
            lockedTokens : 0,
            lastRewardNumber : 0,
            weiBalance : 0
        }));

        name = tokenName;                         // Set the name for display purposes
        symbol = tokenSymbol;                     // Set the symbol for display purposes
        decimals = decimalUnits;                  // Amount of decimals for display purposes

        redemptionPercentageOfDistribution = _redemptionPercentageOfDistribution;

        priceOracle = AquaPriceOracle(_priceOracle);
        owner = msg.sender;

        tokenStatus = TokenStatus.OnSale;
        rewards.push(0);
    }


///Token holders can call this function to request to redeem (sell back to
///the company) part or all of their tokens
///@param _numberOfTokens Number of tokens to redeem
///@return Redemption request ID (required in order to cancel this redemption request)

function requestRedemption(uint256 _numberOfTokens) public returns (uint) {
        require(tokenStatus == TokenStatus.Trading && _numberOfTokens > 0);
        LibHoldings.Holding storage h = holdings.get(msg.sender);
        require(h.totalTokens.sub( h.lockedTokens) >= _numberOfTokens);                 // Check if the sender has enough

        uint redemptionId = redemptionsQueue.add(msg.sender, _numberOfTokens);

        h.lockedTokens = h.lockedTokens.add(_numberOfTokens);
        RequestRedemption(msg.sender, _numberOfTokens, redemptionId);
        return redemptionId;
    }


///Token holders can call this function to cancel a redemption request they
///previously submitted using requestRedemption function
///@param _requestId Redemption request ID

function cancelRedemptionRequest(uint256 _requestId) public {
        require(tokenStatus == TokenStatus.Trading && redemptionsQueue.exists(_requestId));
        LibRedemptions.Redemption storage r = redemptionsQueue.get(_requestId);
        require(r.holderAddress == msg.sender);

        LibHoldings.Holding storage h = holdings.get(msg.sender);
        h.lockedTokens = h.lockedTokens.sub(r.numberOfTokens);
        uint numberOfTokens = r.numberOfTokens;
        redemptionsQueue.remove(_requestId);

        CancelRedemptionRequest(msg.sender, numberOfTokens,  _requestId);
    }


///The function returns token holder details given token holder account address
///@param _holder Account address of a token holder
///@return totalTokens Total tokens held by this token holder
///@return lockedTokens The number of tokens (out of the total held but this token holder) that are locked and await redemption to be processed
///@return weiBalance Wei balance of the token holder available for withdrawal.

function getHolding(address _holder) public constant
            returns (uint totalTokens, uint lockedTokens, uint weiBalance) {
        if (!holdings.exists(_holder)) {
            totalTokens = 0;
            lockedTokens = 0;
            weiBalance = 0;
            return;
        }
        LibHoldings.Holding storage h = holdings.get(_holder);
        totalTokens = h.totalTokens;
        lockedTokens = h.lockedTokens;
        uint stepsMade;
        (weiBalance, stepsMade) = calcFullWeiBalance(h, 0);
        return;
    }


///Token owner calls this function to progress profit distribution round
///@param maxNumbeOfSteps Maximum number of steps in this progression
///@return False in case profit distribution round has completed

function continueDistribution(uint maxNumbeOfSteps) public returns (bool) {
        require(tokenStatus == TokenStatus.Distributing);
        if (continueRedeeming(maxNumbeOfSteps)) {
            ContinueDistribution(true);
            return true;
        }
        uint tokenReward = distCtx.totalRewardAmount.div( totalSupplyOfTokens );
        rewards.push(tokenReward);
        uint paidReward = tokenReward.mul(totalSupplyOfTokens);

        uint unusedDistributionAmount = distCtx.totalRewardAmount.sub(paidReward);
        if (unusedDistributionAmount > 0) {
            if (!holdings.exists(owner)) {
                holdings.add(owner, LibHoldings.Holding({
                    totalTokens : 0,
                    lockedTokens : 0,
                    lastRewardNumber : rewards.length.sub(1),
                    weiBalance : unusedDistributionAmount
                }));
            }
            else {
                LibHoldings.Holding storage ownerHolding = holdings.get(owner);
                ownerHolding.weiBalance = ownerHolding.weiBalance.add(unusedDistributionAmount);
            }
        }
        tokenStatus = TokenStatus.Trading;
        DistributionCompleted(distCtx.receivedRedemptionAmount.sub(distCtx.redemptionAmount),
                            paidReward, unusedDistributionAmount);
        ContinueDistribution(false);
        return false;
    }


///Token holder can call this function to withdraw their balance (dividend
///and redemption payments) while limiting the number of operations (in the
///extremely unlikely case  when withdrawBalance function exceeds gas limit)
///@param maxSteps Maximum number of steps to take while withdrawing holder balance

function withdrawBalanceMaxSteps(uint maxSteps) public {
        require(holdings.exists(msg.sender));
        LibHoldings.Holding storage h = holdings.get(msg.sender);
        uint updatedBalance;
        uint stepsMade;
        (updatedBalance, stepsMade) = calcFullWeiBalance(h, maxSteps);
        h.weiBalance = 0;
        h.lastRewardNumber = h.lastRewardNumber.add(stepsMade);

        bool balanceRemainig = h.lastRewardNumber < rewards.length.sub(1);
        if (h.totalTokens == 0 && h.weiBalance == 0)
            holdings.remove(msg.sender);

        msg.sender.transfer(updatedBalance);

        WithdrawBalance(msg.sender, updatedBalance, balanceRemainig);
    }


///Token holders can call this method to permanently destroy their tokens.
///WARNING: Burned tokens cannot be recovered!
///@param numberOfTokens Number of tokens to burn (permanently destroy)
///@return True if operation succeeds

function burn(uint256 numberOfTokens) external returns (bool success) {
        require(holdings.exists(msg.sender));
        if (numberOfTokens == 0) {
            Burn(msg.sender, numberOfTokens);
            return true;
        }
        LibHoldings.Holding storage fromHolding = holdings.get(msg.sender);
        require(fromHolding.totalTokens.sub(fromHolding.lockedTokens) >= numberOfTokens);                 // Check if the sender has enough

        updateWeiBalance(fromHolding, 0);
        fromHolding.totalTokens = fromHolding.totalTokens.sub(numberOfTokens);                         // Subtract from the sender
        if (fromHolding.totalTokens == 0 && fromHolding.weiBalance == 0)
            holdings.remove(msg.sender);
        totalSupplyOfTokens = totalSupplyOfTokens.sub(numberOfTokens);

        Burn(msg.sender, numberOfTokens);
        return true;
    }


///Token owner to call this to initiate final distribution in case of project wind-up

function windUp() onlyOwner public payable {
        require(tokenStatus == TokenStatus.Trading);
        tokenStatus = TokenStatus.WindingUp;
        uint totalWindUpAmount = msg.value;

        uint tokenReward = msg.value.div(totalSupplyOfTokens);
        rewards.push(tokenReward);
        uint paidReward = tokenReward.mul(totalSupplyOfTokens);

        uint unusedWindUpAmount = totalWindUpAmount.sub(paidReward);
        if (unusedWindUpAmount > 0) {
            if (!holdings.exists(owner)) {
                holdings.add(owner, LibHoldings.Holding({
                    totalTokens : 0,
                    lockedTokens : 0,
                    lastRewardNumber : rewards.length.sub(1),
                    weiBalance : unusedWindUpAmount
                }));
            }
            else {
                LibHoldings.Holding storage ownerHolding = holdings.get(owner);
                ownerHolding.weiBalance = ownerHolding.weiBalance.add(unusedWindUpAmount);
            }
        }
        WindingUpStarted(msg.value);
    }


/**
 * Generates a random Bit Monster.
 *
 * 9/6666 bit monsters will be special, which means they're prebuilt images instead of assembled from the 6 attributes a normal Bit Monster has.
 * All 9 specials are guaranteed to be minted by the time all 6666 Bit Monsters are minted.
 * The chance of a special at each roll is roughly even, although there's a slight dip in chance in the mid-range.
 */

function generateBitMonster(Rng memory rn, bool[9] memory ms) internal returns (BitMonster memory) {
        uint count = bitMonsters.totalSupply();

        int ub = 6666 - int(count) - 1 - (90 - int(mintedSpecialsCount) * 10);
        if (ub < 0) {
            ub = 0;
        }

        BitMonster memory m;
        if (rn.generate(0, uint(ub)) <= (6666 - count) / 666) {
            m = BitMonsterGen.generateSpecialBitMonster(rn, ms);
        }
        else {
            m = BitMonsterGen.generateUnspecialBitMonster(rn);
        }

        if (m.special != Special.NONE) {
            mintedSpecialsCount++;
        }
        rng = rn;
        return m;
    }


/**
 * Mints some Bit Monsters.
 *
 * @param count The number of Bit Monsters to mint. Must be >= 1 and <= 10.
 *              You must send 0.06 ETH for each Bit Monster you want to mint.
 */

function mint(uint count) external payable {
        require(count >= 1 && count <= 10, "Count must be >=1 and <=10");
        require(!Address.isContract(msg.sender), "Contracts cannot mint");
        require(mintingState != MintingState.NotAllowed, "Minting is not allowed atm");

        if (mintingState == MintingState.WhitelistOnly) {
            require(whitelist[msg.sender] >= 1000 + count, "Not enough whitelisted mints");
            whitelist[msg.sender] -= count;
        }

        require(msg.value == count * 0.06 ether, "Send exactly 0.06 ETH for each mint");

        Rng memory rn = rng;
        bool[9] memory ms = mintedSpecials;

        for (uint i = 0; i < count; ++i) {
            bitMonsters.createBitMonster(generateBitMonster(rn, ms), msg.sender);
        }

        rng = rn;
        mintedSpecials = ms;

        Address.sendValue(payHere, msg.value);
    }


/// @notice Extracts the r, s, and v components from the `sigData` field starting from the `offset`
/// @dev Note: does not do any bounds checking on the arguments!
/// @param sigData the signature data; could be 1 or more packed signatures.
/// @param offset the offset in sigData from which to start unpacking the signature components.

function extractSignature(bytes memory sigData, uint256 offset) internal pure returns  (bytes32 r, bytes32 s, uint8 v) {
        // Divide the signature in r, s and v variables
        // ecrecover takes the signature parameters, and the only way to get them
        // currently is to use assembly.
        // solium-disable-next-line security/no-inline-assembly
        assembly {
             let dataPointer := add(sigData, offset)
             r := mload(add(dataPointer, 0x20))
             s := mload(add(dataPointer, 0x40))
             v := byte(0, mload(add(dataPointer, 0x60)))
        }

        return (r, s, v);
    }


/**
 * @dev claimTokens() provides airdrop winners the function of collecting their tokens
 */

function claimTokens()
  ifNotPaused
  public {
    Contribution storage contrib = contributions[msg.sender];
    require(contrib.isValid, "Address not found in airdrop list");
    require(contrib.tokenAmount > 0, "There are currently no tokens to claim.");
    uint256 tempPendingTokens = contrib.tokenAmount;
    contrib.tokenAmount = 0;
    totalTokensClaimed = totalTokensClaimed.add(tempPendingTokens);
    contrib.wasClaimed = true;
    ERC20(tokenAddress).transfer(msg.sender, tempPendingTokens);
  }


/**
 * Returns the metadata of the Bit Monster corresponding to the given tokenId as a base64-encoded JSON object. Meant for use with OpenSea.
 *
 * @dev This function can take a painful amount of time to run, sometimes exceeding 9 minutes in length. Use getBitMonster() instead for frontends.
 */

function tokenURI(uint256 tokenId) public view override returns (string memory) {
        require(_exists(tokenId), "the token doesn't exist");

        string memory metadataRaw = metadata.getMetadataJson(tokenId);
        string memory metadataB64 = Base64.encode(bytes(metadataRaw));

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


/// @dev Called when the receiver of transfer is contract
/// @param _sender address the address of tokens sender
/// @param _value uint256 the amount of tokens to be transferred.
/// @param _data bytes data that can be attached to the token transation

function tokenFallback(address _sender, uint _value, bytes _data) external returns (bool ok) {
    if (!supportsToken(msg.sender)) {
      return false;
    }

    // Problem: This will do a sstore which is expensive gas wise. Find a way to keep it in memory.
    // Solution: Remove the the data
    tkn = Tkn(msg.sender, _sender, _value);
    __isTokenFallback = true;
    if (!address(this).delegatecall(_data)) {
      __isTokenFallback = false;
      return false;
    }
    // avoid doing an overwrite to .token, which would be more expensive
    // makes accessing .tkn values outside tokenPayable functions unsafe
    __isTokenFallback = false;

    return true;
  }


/// @dev initialize the contract.

function initialize() private returns (bool success) {
    R1 = token1.balanceOf(this);
    R2 = token2.balanceOf(this);
    // one reserve should be full and the second should be empty
    success = ((R1 == 0 && R2 == S2) || (R2 == 0 && R1 == S1));
    if (success) {
      operational = true;
    }
  }


/// @dev the price of token1 in terms of token2, represented in 18 decimals.
/// price = (S1 - R1) / (S2 - R2) * (S2 / S1)^2
/// @param _R1 uint256 reserve of the first token
/// @param _R2 uint256 reserve of the second token
/// @param _S1 uint256 total supply of the first token
/// @param _S2 uint256 total supply of the second token

function getPrice(uint256 _R1, uint256 _R2, uint256 _S1, uint256 _S2) public constant returns (uint256 price) {
    price = PRECISION;
    price = price.mul(_S1.sub(_R1));
    price = price.div(_S2.sub(_R2));
    price = price.mul(_S2);
    price = price.div(_S1);
    price = price.mul(_S2);
    price = price.div(_S1);
  }


/// @dev get a quote for exchanging and update temporary reserves.
/// @param _fromToken the token to sell from
/// @param _inAmount the amount to sell
/// @param _toToken the token to buy
/// @return the return amount of the buying token

function quoteAndReserves(address _fromToken, uint256 _inAmount, address _toToken) private isOperational returns (uint256 returnAmount) {
    // if buying token2 from token1
    if (token1 == _fromToken && token2 == _toToken) {
      // add buying amount to the temp reserve
      l_R1 = R1.add(_inAmount);
      // calculate the other reserve
      l_R2 = calcReserve(l_R1, S1, S2);
      if (l_R2 > R2) {
        return 0;
      }
      // the returnAmount is the other reserve difference
      returnAmount = R2.sub(l_R2);
    }
    // if buying token1 from token2
    else if (token2 == _fromToken && token1 == _toToken) {
      // add buying amount to the temp reserve
      l_R2 = R2.add(_inAmount);
      // calculate the other reserve
      l_R1 = calcReserve(l_R2, S2, S1);
      if (l_R1 > R1) {
        return 0;
      }
      // the returnAmount is the other reserve difference
      returnAmount = R1.sub(l_R1);
    } else {
      return 0;
    }
  }


/// @dev get a quote for exchanging.
/// @param _fromToken the token to sell from
/// @param _inAmount the amount to sell
/// @param _toToken the token to buy
/// @return the return amount of the buying token

function quote(address _fromToken, uint256 _inAmount, address _toToken) public constant isOperational returns (uint256 returnAmount) {
    uint256 _R1;
    uint256 _R2;
    // if buying token2 from token1
    if (token1 == _fromToken && token2 == _toToken) {
      // add buying amount to the temp reserve
      _R1 = R1.add(_inAmount);
      // calculate the other reserve
      _R2 = calcReserve(_R1, S1, S2);
      if (_R2 > R2) {
        return 0;
      }
      // the returnAmount is the other reserve difference
      returnAmount = R2.sub(_R2);
    }
    // if buying token1 from token2
    else if (token2 == _fromToken && token1 == _toToken) {
      // add buying amount to the temp reserve
      _R2 = R2.add(_inAmount);
      // calculate the other reserve
      _R1 = calcReserve(_R2, S2, S1);
      if (_R1 > R1) {
        return 0;
      }
      // the returnAmount is the other reserve difference
      returnAmount = R1.sub(_R1);
    } else {
      return 0;
    }
  }


/// @dev calculate second reserve from the first reserve and the supllies.
/// @dev formula: R2 = S2 * (S1 - sqrt(R1 * S1 * 2  - R1 ^ 2)) / S1
/// @dev the equation is simetric, so by replacing _S1 and _S2 and _R1 with _R2 we can calculate the first reserve from the second reserve
/// @param _R1 the first reserve
/// @param _S1 the first total supply
/// @param _S2 the second total supply
/// @return _R2 the second reserve

function calcReserve(uint256 _R1, uint256 _S1, uint256 _S2) public pure returns (uint256 _R2) {
    _R2 = _S2
      .mul(
        _S1
        .sub(
          _R1
          .mul(_S1)
          .mul(2)
          .sub(
            _R1
            .toPower2()
          )
          .sqrt()
        )
      )
      .div(_S1);
  }


/// @dev change tokens.
/// @param _fromToken the token to sell from
/// @param _inAmount the amount to sell
/// @param _toToken the token to buy
/// @param _minReturn the munimum token to buy
/// @return the return amount of the buying token

function change(address _fromToken, uint256 _inAmount, address _toToken, uint256 _minReturn) public canTrade returns (uint256 returnAmount) {
    // pull transfer the selling token
    require(ERC20(_fromToken).transferFrom(msg.sender, this, _inAmount));
    // exchange the token
    returnAmount = exchange(_fromToken, _inAmount, _toToken, _minReturn);
    if (returnAmount == 0) {
      // if no return value revert
      revert();
    }
    // transfer the buying token
    ERC20(_toToken).transfer(msg.sender, returnAmount);
    // validate the reserves
    require(validateReserves());
    Change(_fromToken, _inAmount, _toToken, returnAmount, msg.sender);
  }


/// @dev allow admin to withraw excess tokens accumulated due to precision

function withdrawExcessReserves() public onlyOwner returns (uint256 returnAmount) {
    // if there is excess of token 1, transfer it to the owner
    if (token1.balanceOf(this) > R1) {
      returnAmount = returnAmount.add(token1.balanceOf(this).sub(R1));
      token1.transfer(msg.sender, token1.balanceOf(this).sub(R1));
    }
    // if there is excess of token 2, transfer it to the owner
    if (token2.balanceOf(this) > R2) {
      returnAmount = returnAmount.add(token2.balanceOf(this).sub(R2));
      token2.transfer(msg.sender, token2.balanceOf(this).sub(R2));
    }
  }