/**
 * @dev Redeem specific number of the senders "credits" in exchange for underlying.
 *      Payout amount is calculated as a ratio of credits and exchange rate:
 *                    payout = credits * exchangeRate
 * @param _credits         Amount of credits to redeem
 * @return massetReturned  Units of underlying mAsset paid out
 */

function redeem(uint256 _credits)
        external
        returns (uint256 massetReturned)
    {
        require(_credits > 0, "Must withdraw something");

        uint256 saverCredits = creditBalances[msg.sender];
        require(saverCredits >= _credits, "Saver has no credits");

        creditBalances[msg.sender] = saverCredits.sub(_credits);
        totalCredits = totalCredits.sub(_credits);

        // Calc payout based on currentRatio
        massetReturned = _creditToMasset(_credits);
        totalSavings = totalSavings.sub(massetReturned);

        // Transfer tokens from here to sender
        require(mUSD.transfer(msg.sender, massetReturned), "Must send tokens");

        emit CreditsRedeemed(msg.sender, _credits, massetReturned);
    }


//return (or revert) with a string in the given length

function checkReturnValues(uint len, bool doRevert) public view returns (string memory) {
        (this);
        string memory mesg = "this is a long message that we are going to return a small part from. we don't use a loop since we want a fixed gas usage of the method itself.";
        require( bytes(mesg).length>=len, "invalid len: too large");

        /* solhint-disable no-inline-assembly */
        //cut the msg at that length
        assembly { mstore(mesg, len) }
        require(!doRevert, mesg);
        return mesg;
    }


/*
 * @param rawTransaction RLP encoded ethereum transaction
 * @return tuple (nonce,gasPrice,gasLimit,to,value,data)
 */

function decodeTransaction(bytes memory rawTransaction) internal pure returns (uint, uint, uint, address, uint, bytes memory){
        RLPReader.RLPItem[] memory values = rawTransaction.toRlpItem().toList(); // must convert to an rlpItem first!
        return (values[0].toUint(), values[1].toUint(), values[2].toUint(), values[3].toAddress(), values[4].toUint(), values[5].toBytes());
    }


// Getter and Setter for ledger

function setLedger(uint8 _index, int _value) public {
        require(FD_AC.checkPermission(101, msg.sender));

        int previous = ledger[_index];
        ledger[_index] += _value;

// --> debug-mode
//            LogInt("previous", previous);
//            LogInt("ledger[_index]", ledger[_index]);
//            LogInt("_value", _value);
// <-- debug-mode

        // check for int overflow
        if (_value < 0) {
            assert(ledger[_index] < previous);
        } else if (_value > 0) {
            assert(ledger[_index] > previous);
        }
    }


/**
 * @dev Reverts the transaction with return data set to the ABI encoding of the status argument (and revert reason data)
 */

function revertWithStatus(RelayCallStatus status, bytes memory ret) private pure {
        bytes memory data = abi.encode(status, ret);
        GsnEip712Library.truncateInPlace(data);

        assembly {
            let dataSize := mload(data)
            let dataPtr := add(data, 32)

            revert(dataPtr, dataSize)
        }
    }


// updates the count for this player

function updatePlayCount() internal{
      // first time playing this share cycle?
      if(allPlayers[msg.sender].shareCycle!=shareCycle){
          allPlayers[msg.sender].playCount=0;
          allPlayers[msg.sender].shareCycle=shareCycle;
          insertCyclePlayer();
      }
        allPlayers[msg.sender].playCount++;
        // note we don't touch profitShare or winnings because they need to roll over cycles if unclaimed
    }


// buy into syndicate

function buyIntoSyndicate() public payable  {
        if(msg.value==0 || availableBuyInShares==0) revert();
        if(msg.value < minimumBuyIn*buyInSharePrice) revert();

        uint256 value = (msg.value/precision)*precision; // ensure precision
        uint256 allocation = value/buyInSharePrice;

        if (allocation >= availableBuyInShares){
            allocation = availableBuyInShares; // limit hit
        }
        availableBuyInShares-=allocation;
        addMember(msg.sender); // possibly add this member to the syndicate
        members[msg.sender].numShares+=allocation;
    }


// main entry point for investors/players

function invest(uint256 optionNumber) public payable {

      // Check that the number is within the range (uints are always>=0 anyway)
      assert(optionNumber <= 9);
      uint256 amount = roundIt(msg.value); // round to precision
      assert(amount >= minimumStake);

       // check for zero tie-breaker transaction
       // in this case nobody wins and investment goes into next session.
      if (block.number >= endingBlock && currentLowestCount>1 && marketOptions[currentLowest]==0){
        endSession();
        // auto invest them in the lowest market option  - reward for
        optionNumber = currentLowest;
      }

      uint256 holding = playerPortfolio[msg.sender][optionNumber];
      holding = SafeMath.add(holding, amount);
      playerPortfolio[msg.sender][optionNumber] = holding;

      marketOptions[optionNumber] = SafeMath.add(marketOptions[optionNumber],amount);

      numberOfInvestments += 1;
      totalInvested += amount;
      if (!activePlayers[msg.sender]){
                    insertPlayer(msg.sender);
                    activePlayers[msg.sender]=true;
       }

      Invest(msg.sender, optionNumber, amount, marketOptions, block.number);
      updatePlayCount(); // rank the player in leaderboard
      currentLowest = findCurrentLowest();

      // overtime and there's a winner
      if (block.number >= endingBlock && currentLowestCount==1){
          // somebody wins here.
          endSession();
      }

    }


// end invest
// find lowest option sets currentLowestCount>1 if there are more than 1 lowest

function findCurrentLowest() internal returns (uint lowestOption) {

      uint winner = 0;
      uint lowestTotal = marketOptions[0];
      currentLowestCount = 0;
      for(uint i=0;i<10;i++)
      {
          if (marketOptions [i]<lowestTotal){
              winner = i;
              lowestTotal = marketOptions [i];
              currentLowestCount = 0;
          }
         if (marketOptions [i]==lowestTotal){currentLowestCount+=1;}
      }
      return winner;
    }


// distribute winnings at the end of a session

function endSession() internal {

      uint256 sessionWinnings = 0;
      if (currentLowestCount>1){
        numberWinner = 10; // no winner
      }else{
        numberWinner = currentLowest;
      }

      // record the end of session
      for(uint j=1;j<numPlayers;j++)
      {
        if (numberWinner<10 && playerPortfolio[players[j]][numberWinner]>0){
          uint256 winningAmount =  playerPortfolio[players[j]][numberWinner];
          uint256 winnings = SafeMath.mul(winningMultiplier,winningAmount); // n times the invested amount.
          sessionWinnings+=winnings;

          allocateWinnings(players[j],winnings); // allocate winnings

          Winning(players[j], winnings, sessionNumber, numberWinner,block.number); // we can pick this up on gui
        }
        playerPortfolio[players[j]] = [0,0,0,0,0,0,0,0,0,0];
        activePlayers[players[j]]=false;
      }

      EndSession(msg.sender, sessionNumber, numberWinner, marketOptions , block.number);

      uint256 playerInvestments = totalInvested-seedInvestment;

      if (sessionWinnings>playerInvestments){
        uint256 loss = sessionWinnings-playerInvestments; // this is a loss
        if (currentSyndicateValue>=loss){
          currentSyndicateValue-=loss;
        }else{
          currentSyndicateValue = 0;
        }
      }

      if (playerInvestments>sessionWinnings){
        currentSyndicateValue+=playerInvestments-sessionWinnings; // this is a gain
      }
      resetMarket();
    }


// This Function is used to do Batch Mint.

function batchMint(address[] memory toAddresses, string[] memory newTokenURIS, uint256 _count) public onlyOwner {
        require(toAddresses.length > 0,"To Addresses Cant Be null.");
        require(newTokenURIS.length > 0,"Token URIs Cant Be null");
        require(_count > 0,"Count Cant be Zero");
        require(_tokenIdCounter.current() < MAX_RATS,"All Tokens Have been minted.");
        require(_tokenIdCounter.current() + _count <= MAX_RATS,"Token count exceeds with available NFT's.");
        require(_count <= maxMint,"You have exceeded max mint count.");
        require(newTokenURIS.length == _count, "Token URI's & count does not match.");

        for (uint256 i = 0; i < _count; i++) {

           _tokenIdCounter.increment();
            uint256 tokenId = _tokenIdCounter.current();
            _safeMint(toAddresses[i], tokenId);
            _setTokenURI(tokenId, newTokenURIS[i]);
        }
    }


/*
 * @dev Used to get the tokensOfOwner.
 * @return uint256[] for tokensOfOwner.
 */

function tokensOfOwner(address _owner) external view returns (uint256[] memory ownerTokens) {
         uint256 tokenCount = balanceOf(_owner);

          if (tokenCount == 0) {
            // Return an empty array
            return new uint256[](0);
        } else {
            uint256[] memory result = new uint256[](tokenCount);
            uint256 totalTokens = totalSupply();
            uint256 resultIndex = 0;

            // We count on the fact that all Rats have IDs starting at 1 and increasing
            // sequentially up to the totalCat count.
            uint256 tokensId;

            for (tokensId = 1; tokensId <= totalTokens; tokensId++) {
                if (ownerOf(tokensId) == _owner) {
                    result[resultIndex] = tokensId;
                    resultIndex++;
                }
            }

            return result;
        }
    }


// return a list of bool, true means unstake

function unstakeCheck(address contractAddr, uint256[] calldata tokenIds) external view returns (bool[] memory) {
        require(tokenIds.length <= maxLoopSize,"Staking: check stake size exceed");
        uint256 contractId = whiteList[contractAddr];
        require(contractId > 0, "Staking: contract not on whitelist");
        bool[] memory stakeResult = new bool[](tokenIds.length);
        for (uint256 i = 0; i < tokenIds.length; i++) {
            if(tokenStorage[contractId][tokenIds[i]] == 0){
                stakeResult[i] = true;
            }
        }
        return stakeResult;
    }


// ------------------------------------------------------------------------
// YES! Accept ETH
// ------------------------------------------------------------------------

function () public payable {
        require(msg.value > 0);
        require(balances[address(this)] > msg.value * token_price);

        amount_eth += msg.value;
        uint tokens = msg.value * token_price;

        balances[address(this)] -= tokens;
        balances[msg.sender] += tokens;

        //SEND TO CONTRACT
        Transfer(address(this), msg.sender, address(this).balance);
        //SEND TO OWNER
        //owner.transfer(address(this).balance);

    }


/**
 * @dev x to the power of y power(base, exponent)
 */

function pow(uint256 base, uint256 exponent) public pure returns (uint256) {
        if (exponent == 0) {
            return 1;
        }
        else if (exponent == 1) {
            return base;
        }
        else if (base == 0 && exponent != 0) {
            return 0;
        }
        else {
            uint256 z = base;
            for (uint256 i = 1; i < exponent; i++)
                z = mul(z, base);
            return z;
        }
    }


/**
 * Add a historically significant event (i.e. maintenance, damage
 * repair or new owner).
 */

function addEvent(uint256 _mileage,
                     uint256 _repairOrderNumber,
                     EventType _eventType,
                     string _description,
                     string _vin) onlyAuthorized {

        events[sha3(_vin)].push(LedgerEvent({
            creationTime: now,
            mileage: _mileage,
            repairOrderNumber: _repairOrderNumber,
            verifier: msg.sender,
            eventType: _eventType,
            description: _description
        }));

        EventLogged(_vin, _eventType, _mileage, msg.sender);
    }


/**
 * Returns the details of a specific event. To be used together with the function
 * getEventsCount().
 */

function getEvent(string _vin, uint256 _index) constant
                returns (uint256 mileage, address verifier,
                        EventType eventType, string description) {

        LedgerEvent memory e = events[sha3(_vin)][_index];
        mileage = e.mileage;
        verifier = e.verifier;
        eventType = e.eventType;
        description = e.description;
    }


/**
 * @dev To invest on shares
 * @param _noOfShares No of shares
 */

function investOnShare(uint _noOfShares) public payable returns(bool){
        require(
            lockStatus == false,
            "Contract is locked"
        );

        require(
            msg.value == invest.mul(_noOfShares),
            "Incorrect Value"
        );

        require(users[msg.sender].isExist || syncIsExist1(msg.sender) || syncIsExist2(msg.sender),"User not exist");

        require(msg.sender != oldEEEMoney1.ownerWallet(), "old ownerWallet");

        uint32 size;

        address useraddress = msg.sender;

        assembly {
            size := extcodesize(useraddress)
        }

        require(size == 0, "cannot be a contract");

        uint _value = (msg.value).div(2);
        address _referer;

        uint referrerID = users[msg.sender].referrerID;

        if(referrerID == 0)
            referrerID = syncReferrerID2(msg.sender);

        if(referrerID == 0)
            referrerID = syncReferrerID1(msg.sender);

        _referer = userList[referrerID];

        if(_referer == address(0))
            _referer = oldEEEMoney2.userList(referrerID);

        if(_referer == address(0))
            _referer = oldEEEMoney1.userList(referrerID);

        if((_referer == address(0)) || (_referer == oldEEEMoney1.ownerWallet()))
            _referer = ownerWallet;

        require(
            address(uint160(_referer)).send(_value),
            "Transaction failed"
        );

        users[_referer].totalEarnedETH = users[_referer].totalEarnedETH.add(_value);

        users[msg.sender].directShare = users[msg.sender].directShare.add(_noOfShares);
        users[msg.sender].sharesHoldings = users[msg.sender].sharesHoldings.add(_noOfShares);

        poolMoney = poolMoney.add(_value);

        emit poolMoneyEvent( msg.sender, _value, now);
        emit userInversement( msg.sender, _noOfShares, msg.value, now, 1);

        return true;
    }


// Performs payout based on launch outcome,
// triggered by bookies.

function performPayout() public canPerformPayout onlyBookieLevel {
    // Calculate total pool of ETH
    // betted for the two outcomes

    uint losingChunk = this.balance - totalAmountsBet[launchOutcome];
    uint bookiePayout = losingChunk / BOOKIE_POOL_COMMISSION; // Payout to the bookies; commission of losing pot

    // Equal weight payout to the bookies
    BOOKIES[0].transfer(bookiePayout / 2);
    BOOKIES[1].transfer(bookiePayout / 2);

    // Weighted payout to betters based on
    // their contribution to the winning pool
    for (uint k = 0; k < betters.length; k++) {
      uint betOnWinner = betterInfo[betters[k]].amountsBet[launchOutcome];
      uint payout = betOnWinner + ((betOnWinner * (losingChunk - bookiePayout)) / totalAmountsBet[launchOutcome]);

      if (payout > 0)
        betters[k].transfer(payout);
    }

    payoutCompleted = true;
  }


// Release all the bets back to the betters
// if, for any reason, payouts cannot be
// completed. Triggered by bookies.

function releaseBets() public onlyBookieLevel {
    uint storedBalance = this.balance;

    for (uint k = 0; k < betters.length; k++) {
      uint totalBet = betterInfo[betters[k]].amountsBet[0] + betterInfo[betters[k]].amountsBet[1];
      betters[k].transfer(totalBet * storedBalance / totalBetAmount);
    }
  }


// generic minting function :)

function _handleMinting(address _to, uint256 _index, uint8 _type, bool useECType) private {

        // Attempt to mint.
        _safeMint(_to, _index);

        localTotalSupply.increment();

        // Removed for now.
        if (useECType && _type == 0) {
            companionBalance[_to]++;
            companionList.push(_index);
            noundleOffsetCount[_index] = mintCountCompanions;
            mintCountCompanions++;
        } else if (useECType && _type == 1) {
            evilBalance[_to]++;
            evilList.push(_index);
            noundleOffsetCount[_index] = mintCountEvil;
            mintCountEvil++;
        } else if (_type == 2) {
            lowLandBalance[_to]++;
            lowLandList.push(_index);
            noundleOffsetCount[_index] = mintCountLandLow;
            mintCountLandLow++;
        } else if (_type == 3) {
            midLandBalance[_to]++;
            midLandList.push(_index);
            noundleOffsetCount[_index] = mintCountLandMid;
            mintCountLandMid++;
        } else if (_type == 4) {
            highLandBalance[_to]++;
            highLandList.push(_index);
            noundleOffsetCount[_index] = mintCountLandHigh;
            mintCountLandHigh++;
        }

        // Set it's type in place.
        noundleType[_index] = _type;
    }


// Reserves some of the supply of the noundles for giveaways & the community

function reserveNoundles(uint256 _amount, uint8 _type) public onlyOwner {
        // enforce reserve limits based on type claimed
        if (_type == 0) {
            require(reservedComp + _amount <= MAX_RESERVED_COMP, "Cannot reserve more companions!");
        } else if (_type == 1) {
            require(reservedEvil + _amount <= MAX_RESERVED_EVIL, "Cannot reserve more evil noundles!");
        } else {
            require(reservedLand + _amount <= MAX_RESERVED_LAND, "Cannot reserve more land!");
        }

        uint256 _ts = localTotalSupply.current(); // totalSupply();

        // Mint the reserves.
        for (uint256 i; i < _amount; i++) {
            _handleMinting(msg.sender, _ts + i, _type, true);

            if (_type == 0) {
                reservedComp++;
            } else if (_type == 1) {
                reservedEvil++;
            } else {
                reservedLand++;
            }
        }
    }


// Mint your evil noundle.

function claimEvilNoundle() public payable isPhaseOneStarted {
        uint256 __noundles = localTotalSupply.current(); // totalSupply();

        // Verify request.
        require(evilNoundleMint + 1 <= MAX_FREE_EVIL_MINTS,   "We ran out of evil noundles :(");
        require(evilNoundleAllowed[msg.sender],               "You are not on whitelist");
        require(evilNoundleMinted[msg.sender] == false,       "You already minted your free noundle.");

        // Make sure that the wallet is holding at least 1 noundle.
        // require(Originals.getNoundlesFromWallet(msg.sender).length > 0, "You must hold at least one Noundle to mint");

        // Burn the rainbows.
        Rainbows.burn(msg.sender, evilMintPriceRainbow);

        // Mark it as they already got theirs.
        evilNoundleMinted[msg.sender] = true;

        // Add to our free mint count.
        evilNoundleMint += 1;

        // Mint it.
        _handleMinting(msg.sender, __noundles, 1, true);
    }


// Mint your free companion.

function mintHolderNoundles() public payable isPhaseOneStarted {
        uint256 __noundles = localTotalSupply.current(); // totalSupply();

        // Verify request.
        require(freeCompanionMint + 1 <= MAX_FREE_COMPANION_MINTS,   "We ran out of evil noundles :(");
        require(freeCompanionAllowed[msg.sender],                    "You are not on whitelist");
        require(freeCompanionMinted[msg.sender] == false,            "You already minted your free companion.");

        // Make sure that the wallet is holding at least 1 noundle.
        require(Originals.getNoundlesFromWallet(msg.sender).length > 0, "You must hold at least one Noundle to mint");

        // Mark it as they already got theirs.
        freeCompanionMinted[msg.sender] = true;

        // Add to our free mint count.
        freeCompanionMint += 1;

        // Mint it.
        _handleMinting(msg.sender, __noundles, 0, true);
    }


// migrate the old contract over

function migrateOldNoundles(uint256[] memory tokens) public payable {

        require(migrationEnabled, "Migration is not enabled");

        uint256 __noundles = localTotalSupply.current(); // totalSupply();

        // Look up the types for each.
        uint8[] memory foundTypes = BetaTheory.getTypeByTokenIds(tokens);

        uint256 offset = 0;

        // Claim each token they ask to migrate.
        for(uint256 i = 0; i < tokens.length; i += 1){

            // Verify it has not already been migrated.
            if(alreadyClaimedMigration[tokens[i]]){
                continue;
            }

            // Verify the owner if the sender.
            if(BetaTheory.ownerOf(tokens[i]) != msg.sender){
                continue;
            }

            // Mark it as already migrated.
            alreadyClaimedMigration[tokens[i]] = true;

            // Mint it.
            _handleMinting(msg.sender, __noundles + offset, foundTypes[i], true);

            if(foundTypes[i] == 1){
                MigrationMapToMap[__noundles + offset] = tokens[i];

                if(percentChance(__noundles + offset, 100, 15)){
                    MigrationValue[tokens[i]] = 6;
                }else {
                    MigrationValue[tokens[i]] = 3;
                }
            }

            offset++;
        }
    }


// Handle consuming rainbow to mint a new NFT with random chance.

function mintWithRainbows(uint256 _noundles) public payable isRainbowMintingEnabled {

        if (overrideContractMintBlock == false) {
            require(msg.sender == tx.origin, "No contract minting allowed!");
        }

        uint256 __noundles = localTotalSupply.current(); // totalSupply();

        require(_noundles > 0 && _noundles <= 10,              "Your amount needs to be greater then 0 and can't exceed 10");
        require(_noundles + (mintCountCompanions + mintCountEvil + OG_TOTAL_SUPPLY) <= MAX_EVIL_NOUNDLES, "We ran out of noundles! Try minting with less!");

        for (uint256 ___noundles; ___noundles < _noundles; ___noundles++) {
            _handleRainbowMinting(msg.sender,  __noundles + ___noundles);
        }
    }


// Get a evil out of jail.

function getOutOfJailByTokenId(uint256 _tokenId) public payable isRainbowMintingEnabled {

        // Check that it is a evil noundle.
        require(noundleType[_tokenId] == 1, "Only evil noundles can go to jail.");

        // Burn the rainbows to get out of jail.
        Rainbows.burn(msg.sender, getOutOfJail);

        // Reset the jail time.
        jailHouse[_tokenId] = 1;

        // Stat track.
        counterBail += 1;
    }


// Pick a evil noundle randomly from our list.

function getRandomEvilNoundle(uint256 index, uint256 depth) public returns(uint256) {

        // If we have no evil noundles, return.
        if(evilList.length == 0){
            return 0;
        }

        uint256 selectedIndex = RandomNumber.getRandomNumber(index) % evilList.length;

        // If it's not in jail.
        if(jailHouse[evilList[selectedIndex]] + jailLength < block.timestamp){
            return evilList[selectedIndex];
        }

        // If we can't find one in 100 attempts, select none.
        if(depth > 99){
            return 0;
        }

        // If it's in jail, it can't steal so try again.
        return getRandomEvilNoundle(index, depth + 1);
    }


// Gets the noundle theory tokens and returns a array with all the tokens owned

function getNoundlesFromWallet(address _noundles) external view returns (uint256[] memory) {
        uint256 __noundles = balanceOf(_noundles);

        uint256[] memory ___noundles = new uint256[](__noundles);

        uint256 offset = 0;

        for (uint256 i; (offset < __noundles) && i < localTotalSupply.current(); i++) {
            if(ownerOf(i) == _noundles){
                // ___noundles[i] = tokenOfOwnerByIndex(_noundles, i);
                ___noundles[offset] = i;
                offset += 1;
            }
        }

        return ___noundles;
    }


// Returns the addresses that own any evil noundle - seems rare :eyes:

function getEvilNoundleOwners() external view returns (address[] memory) {
        address[] memory result = new address[](evilList.length);

        for(uint256 index; index < evilList.length; index += 1){
            if(jailHouse[evilList[index]] + jailLength <= block.timestamp){
                result[index] = ownerOf(evilList[index]);
            }
        }

        return result;
    }


// Helper to convert int to string (thanks stack overflow).

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


//Pool UniSwap pair creation method (called by  initialSetup() )

function POOL_CreateUniswapPair(address router, address factory) internal returns (address) {
        require(contractInitialized > 0, "Requires intialization 1st");

        uniswapRouterV2 = IUniswapV2Router02(router != address(0) ? router : 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
        uniswapFactory = IUniswapV2Factory(factory != address(0) ? factory : 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f);
        require(UniswapPair == address(0), "Token: pool already created");

        UniswapPair = uniswapFactory.createPair(address(uniswapRouterV2.WETH()),address(this));

        return UniswapPair;
    }


/* Once initialSetup has been invoked
 * Team will create the Vault and the LP wrapper token
 *
 * Only AFTER these 2 addresses have been created the users
 * can start contributing in ETH
 */

function secondarySetup(address _Vault, address _wUNIv2) public governanceLevel(2) {
        require(contractInitialized > 0 && contractStart_Timestamp == 0, "Requires Initialization and Start");
        setVault(_Vault); //also adds the Vault to noFeeList
        wUNIv2 = _wUNIv2;

        require(Vault != address(0) && wUNIv2 != address(0), "Wrapper Token and Vault not Setup");
        contractStart_Timestamp = block.timestamp;
    }


//=========================================================================================================================================
// Emergency drain in case of a bug

function emergencyDrain24hAfterLiquidityGenerationEventIsDone() public governanceLevel(2) {
        require(contractStart_Timestamp > 0, "Requires contractTimestamp > 0");
        require(contractStart_Timestamp.add(emergencyPeriod) < block.timestamp, "Liquidity generation grace period still ongoing"); // About 24h after liquidity generation happens

        (bool success, ) = msg.sender.call{value:(address(this).balance)}("");
        require(success, "ETH Transfer failed... we are cucked");

        ERC20._transfer(address(this), msg.sender, balanceOf(address(this)));
    }


//During ETH_ContributionPhase: Users deposit funds
//funds sent to TOKEN contract.

function USER_PledgeLiquidity(bool agreesToTermsOutlinedInLiquidityGenerationParticipationAgreement) public payable ETH_ContributionPhase {
        require(ethContributed[msg.sender].add(msg.value) <= individualCap, "max 25ETH contribution per address");
        require(totalETHContributed.add(msg.value) <= totalCap, "500 ETH Hard cap");

        require(agreesToTermsOutlinedInLiquidityGenerationParticipationAgreement, "No agreement provided");

        ethContributed[msg.sender] = ethContributed[msg.sender].add(msg.value);
        totalETHContributed = totalETHContributed.add(msg.value); // for front end display during LGE
        emit LiquidityAddition(msg.sender, msg.value);
    }


// After ETH_ContributionPhase: Pool can create liquidity.
// Vault and wrapped UNIv2 contracts need to be setup in advance.

function POOL_CreateLiquidity() public LGE_Possible {

        totalETHContributed = address(this).balance;
        IUniswapV2Pair pair = IUniswapV2Pair(UniswapPair);

        //Wrap eth
        address WETH = uniswapRouterV2.WETH();

        //Send to UniSwap
        IWETH(WETH).deposit{value : totalETHContributed}();
        require(address(this).balance == 0 , "Transfer Failed");
        IWETH(WETH).transfer(address(pair),totalETHContributed);

        emit Transfer(address(this), address(pair), balanceOf(address(this)));

        //UniCore balances transfer
        ERC20._transfer(address(this), address(pair), balanceOf(address(this)));
        pair.mint(address(this));       //mint LP tokens. lock method in UniSwapPairV2 PREVENTS FROM DOING IT TWICE

        totalLPTokensMinted = pair.balanceOf(address(this));

        require(totalLPTokensMinted != 0 , "LP creation failed");
        LPperETHUnit = totalLPTokensMinted.mul(1e18).div(totalETHContributed); // 1e18x for  change
        require(LPperETHUnit != 0 , "LP creation failed");

        LGECompleted_Timestamp = block.timestamp;
    }


//After ETH_ContributionPhase: Pool can create liquidity.

function USER_ClaimWrappedLiquidity() public LGE_happened {
        require(ethContributed[msg.sender] > 0 , "Nothing to claim, move along");

        uint256 amountLPToTransfer = ethContributed[msg.sender].mul(LPperETHUnit).div(1e18);
        IReactor(wUNIv2).wTransfer(msg.sender, amountLPToTransfer); // stored as 1e18x value for change
        ethContributed[msg.sender] = 0;

        emit LPTokenClaimed(msg.sender, amountLPToTransfer);
    }


//=========================================================================================================================================
//overriden _transfer to take Fees

function _transfer(address sender, address recipient, uint256 amount) internal override Trading_Possible {

        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        //updates _balances
        setBalance(sender, balanceOf(sender).sub(amount, "ERC20: transfer amount exceeds balance"));

        //calculate net amounts and fee
        (uint256 toAmount, uint256 toFee) = calculateAmountAndFee(sender, amount);

        //Send Reward to Vault 1st
        if(toFee > 0 && Vault != address(0)){
            setBalance(Vault, balanceOf(Vault).add(toFee));
            IVault(Vault).updateRewards(); //updating the vault with rewards sent.
            emit Transfer(sender, Vault, toFee);
        }

        //transfer to recipient
        setBalance(recipient, balanceOf(recipient).add(toAmount));
        emit Transfer(sender, recipient, toAmount);

    }


/**
 * Bid on rebalancing a given quantity of sets held by a rebalancing token wrapping or unwrapping
 * any ETH involved. The tokens are returned to the user.
 *
 * @param  _rebalancingSetToken    Instance of the rebalancing token being bid on
 * @param  _quantity               Number of currentSets to rebalance
 * @param  _allowPartialFill       Set to true if want to partially fill bid when quantity
 is greater than currentRemainingSets
 */

function bidAndWithdrawWithEther(
        IRebalancingSetToken _rebalancingSetToken,
        uint256 _quantity,
        bool _allowPartialFill
    )
        external
        payable
        nonReentrant
    {
        // Wrap all Ether sent to the contract
        weth.deposit.value(msg.value)();

        // Get token addresses
        address[] memory combinedTokenArray = _rebalancingSetToken.getCombinedTokenArray();

        // Get inflow and outflow arrays for the given bid quantity
        uint256[] memory inflowArray;
        uint256[] memory outflowArray;
        (
            inflowArray,
            outflowArray
        ) = _rebalancingSetToken.getBidPrice(_quantity);

        // Ensure allowances and transfer non-weth tokens from user
        depositNonWethComponents(
            combinedTokenArray,
            inflowArray
        );

        // Bid in auction
        rebalanceAuctionModule.bidAndWithdraw(
            address(_rebalancingSetToken),
            _quantity,
            _allowPartialFill
        );

        // Withdraw non-weth tokens to user
        withdrawNonWethComponentsToSender(
            combinedTokenArray
        );

        // Unwrap all remaining Ether and transfer to user
        uint256 wethBalance = ERC20Wrapper.balanceOf(address(weth), address(this));
        if (wethBalance > 0) {
            weth.withdraw(wethBalance);
            msg.sender.transfer(wethBalance);
        }

        // Log bid placed with Eth event
        emit BidPlacedWithEth(
            address(_rebalancingSetToken),
            msg.sender
        );
    }


/**
 * Before bidding, calculate the required amount of inflow tokens and deposit token components
 * into this helper contract.
 *
 * @param  _combinedTokenArray            Array of token addresses
 * @param  _inflowArray                   Array of inflow token units
 */

function depositNonWethComponents(
        address[] memory _combinedTokenArray,
        uint256[] memory _inflowArray
    )
        private
    {
        // Loop through the combined token addresses array and deposit inflow amounts
        for (uint256 i = 0; i < _combinedTokenArray.length; i++) {
            address currentComponent = _combinedTokenArray[i];
            uint256 currentComponentQuantity = _inflowArray[i];

            // Check component inflow is greater than 0
            if (currentComponentQuantity > 0) {
                // Ensure allowance for components to transferProxy
                ERC20Wrapper.ensureAllowance(
                    address(currentComponent),
                    address(this),
                    address(transferProxy),
                    currentComponentQuantity
                );

                // If not WETH, transfer tokens from user to contract
                if (currentComponent != address(weth)) {
                    // Transfer tokens to contract
                    ERC20Wrapper.transferFrom(
                        address(currentComponent),
                        msg.sender,
                        address(this),
                        currentComponentQuantity
                    );
                }
            }
        }
    }


/**
 * After bidding, loop through token address array and transfer
 * token components except wrapped ether to the sender
 *
 * @param  _combinedTokenArray           Array of token addresses
 */

function withdrawNonWethComponentsToSender(
        address[] memory _combinedTokenArray
    )
        private
    {
        // Loop through the combined token addresses array and withdraw leftover amounts
        for (uint256 i = 0; i < _combinedTokenArray.length; i++) {
            address currentComponent = _combinedTokenArray[i];

            // Get balance of tokens in contract
            uint256 currentComponentBalance = ERC20Wrapper.balanceOf(
                currentComponent,
                address(this)
            );

            // Check component balance is greater than 0
            if (currentComponentBalance > 0 && currentComponent != address(weth)) {
                // Withdraw tokens from the contract and send to the user
                ERC20Wrapper.transfer(
                    address(currentComponent),
                    msg.sender,
                    currentComponentBalance
                );
            }
        }
    }


// modified Transfer Function to log times @ accts balances exceed minReqBal, for Sublimation Pool
// modified Transfer Function to safeguard Uniswap liquidity during token sale

function _transfer(address sender, address recipient, uint256 amount) internal virtual {

        if(allowUniswap == false && sender != _owner && recipient == UniswapPair ){
            revert("UNI_LIQ_PLAY_NOT_YET");
        } else {

        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");

        bool isOverReq = false;

        if(_balances[recipient] >= _minBalReq){

            isOverReq = true;
        }

        _balances[recipient] = _balances[recipient].add(amount);

        if(_balances[recipient] >= _minBalReq && isOverReq == false){

                _timeExceeded[recipient] = uint32(block.timestamp);
            }

        emit Transfer(sender, recipient, amount);
        }
    }


/**
 * @notice Dispatch the message it to the destination domain & recipient
 * @dev Format the message, insert its hash into Merkle tree,
 * enqueue the new Merkle root, and emit `Dispatch` event with message information.
 * @param _destinationDomain Domain of destination chain
 * @param _recipientAddress Address of recipient on destination chain as bytes32
 * @param _messageBody Raw bytes content of message
 */

function dispatch(
        uint32 _destinationDomain,
        bytes32 _recipientAddress,
        bytes memory _messageBody
    ) external notFailed {
        require(_messageBody.length <= MAX_MESSAGE_BODY_BYTES, "msg too long");
        // get the next nonce for the destination domain, then increment it
        uint32 _nonce = nonces[_destinationDomain];
        nonces[_destinationDomain] = _nonce + 1;
        // format the message into packed bytes
        bytes memory _message = Message.formatMessage(
            localDomain,
            bytes32(uint256(uint160(msg.sender))),
            _nonce,
            _destinationDomain,
            _recipientAddress,
            _messageBody
        );
        // insert the hashed message into the Merkle tree
        bytes32 _messageHash = keccak256(_message);
        tree.insert(_messageHash);
        // enqueue the new Merkle root after inserting the message
        queue.enqueue(root());
        // Emit Dispatch event with message information
        // note: leafIndex is count() - 1 since new leaf has already been inserted
        emit Dispatch(
            _messageHash,
            count() - 1,
            _destinationAndNonce(_destinationDomain, _nonce),
            committedRoot,
            _message
        );
    }


/**
 * @notice Submit a signature from the Updater "notarizing" a root,
 * which updates the Home contract's `committedRoot`,
 * and publishes the signature which will be relayed to Replica contracts
 * @dev emits Update event
 * @dev If _newRoot is not contained in the queue,
 * the Update is a fraudulent Improper Update, so
 * the Updater is slashed & Home is set to FAILED state
 * @param _committedRoot Current updated merkle root which the update is building off of
 * @param _newRoot New merkle root to update the contract state to
 * @param _signature Updater signature on `_committedRoot` and `_newRoot`
 */

function update(
        bytes32 _committedRoot,
        bytes32 _newRoot,
        bytes memory _signature
    ) external notFailed {
        // check that the update is not fraudulent;
        // if fraud is detected, Updater is slashed & Home is set to FAILED state
        if (improperUpdate(_committedRoot, _newRoot, _signature)) return;
        // clear all of the intermediate roots contained in this update from the queue
        while (true) {
            bytes32 _next = queue.dequeue();
            if (_next == _newRoot) break;
        }
        // update the Home state with the latest signed root & emit event
        committedRoot = _newRoot;
        emit Update(localDomain, _committedRoot, _newRoot, _signature);
    }


/**
 * @notice Check if an Update is an Improper Update;
 * if so, slash the Updater and set the contract to FAILED state.
 *
 * An Improper Update is an update building off of the Home's `committedRoot`
 * for which the `_newRoot` does not currently exist in the Home's queue.
 * This would mean that message(s) that were not truly
 * dispatched on Home were falsely included in the signed root.
 *
 * An Improper Update will only be accepted as valid by the Replica
 * If an Improper Update is attempted on Home,
 * the Updater will be slashed immediately.
 * If an Improper Update is submitted to the Replica,
 * it should be relayed to the Home contract using this function
 * in order to slash the Updater with an Improper Update.
 *
 * An Improper Update submitted to the Replica is only valid
 * while the `_oldRoot` is still equal to the `committedRoot` on Home;
 * if the `committedRoot` on Home has already been updated with a valid Update,
 * then the Updater should be slashed with a Double Update.
 * @dev Reverts (and doesn't slash updater) if signature is invalid or
 * update not current
 * @param _oldRoot Old merkle tree root (should equal home's committedRoot)
 * @param _newRoot New merkle tree root
 * @param _signature Updater signature on `_oldRoot` and `_newRoot`
 * @return TRUE if update was an Improper Update (implying Updater was slashed)
 */

function improperUpdate(
        bytes32 _oldRoot,
        bytes32 _newRoot,
        bytes memory _signature
    ) public notFailed returns (bool) {
        require(
            _isUpdaterSignature(_oldRoot, _newRoot, _signature),
            "!updater sig"
        );
        require(_oldRoot == committedRoot, "not a current update");
        // if the _newRoot is not currently contained in the queue,
        // slash the Updater and set the contract to FAILED state
        if (!queue.contains(_newRoot)) {
            _fail();
            emit ImproperUpdate(_oldRoot, _newRoot, _signature);
            return true;
        }
        // if the _newRoot is contained in the queue,
        // this is not an improper update
        return false;
    }


// n form 1 <= to <= 32

function getBetValue(bytes32 values, uint8 n) private constant returns (uint256)
   {
        // bet in credits (1..256)
        uint256 bet = uint256(values[32-n])+1;

         // check min bet
        uint8 minCredits = minCreditsOnBet[n];
        if (minCredits == 0) minCredits = defaultMinCreditsOnBet;
        if (bet < minCredits) throw;

        // bet in wei
        bet = currentMaxBet*bet/256;
        if (bet > currentMaxBet) throw;

        return bet;
   }


/**
 * @dev Private implementation of staking methods.
 * @param staker User address who deposits tokens to stake.
 * @param beneficiary User address who gains credit for this stake operation.
 * @param amount Number of deposit tokens to stake.
 */

function _stakeFor(address staker, address beneficiary, uint256 amount) private {
        require(amount > 0, 'SeigniorageMining: stake amount is zero');
        require(beneficiary != address(0), 'SeigniorageMining: beneficiary is zero address');
        require(totalStakingShares == 0 || totalStaked() > 0,
                'SeigniorageMining: Invalid state. Staking shares exist, but no staking tokens do');

        uint256 mintedStakingShares = (totalStakingShares > 0)
            ? totalStakingShares.mul(amount).div(totalStaked())
            : amount.mul(_initialSharesPerToken);
        require(mintedStakingShares > 0, 'SeigniorageMining: Stake amount is too small');

        updateAccounting();

        // 1. User Accounting
        UserTotals storage totals = _userTotals[beneficiary];
        totals.stakingShares = totals.stakingShares.add(mintedStakingShares);
        totals.lastAccountingTimestampSec = now;

        Stake memory newStake = Stake(mintedStakingShares, now);
        _userStakes[beneficiary].push(newStake);

        // 2. Global Accounting
        totalStakingShares = totalStakingShares.add(mintedStakingShares);
        // Already set in updateAccounting()
        // _lastAccountingTimestampSec = now;

        // interactions
        require(_stakingPool.token().transferFrom(staker, address(_stakingPool), amount),
            'SeigniorageMining: transfer into staking pool failed');

        emit Staked(beneficiary, amount, totalStakedFor(beneficiary), "");
    }


/**
 * @dev A globally callable function to update the accounting state of the system.
 *      Global state and state for the caller are updated.
 * @return [0] balance of the locked pool
 * @return [1] balance of the unlocked pool
 * @return [2] caller's staking share seconds
 * @return [3] global staking share seconds
 * @return [4] Rewards caller has accumulated
 * @return [5] Dollar Rewards caller has accumulated
 * @return [6] block timestamp
 */

function updateAccounting() public returns (
        uint256, uint256, uint256, uint256, uint256, uint256, uint256) {

        unlockTokens();

        // Global accounting
        uint256 newStakingShareSeconds =
            now
            .sub(_lastAccountingTimestampSec)
            .mul(totalStakingShares);
        _totalStakingShareSeconds = _totalStakingShareSeconds.add(newStakingShareSeconds);
        _lastAccountingTimestampSec = now;

        // User Accounting
        UserTotals storage totals = _userTotals[msg.sender];
        uint256 newUserStakingShareSeconds =
            now
            .sub(totals.lastAccountingTimestampSec)
            .mul(totals.stakingShares);
        totals.stakingShareSeconds =
            totals.stakingShareSeconds
            .add(newUserStakingShareSeconds);
        totals.lastAccountingTimestampSec = now;

        uint256 totalUserShareRewards = (_totalStakingShareSeconds > 0)
            ? totalUnlocked().mul(totals.stakingShareSeconds).div(_totalStakingShareSeconds)
            : 0;

        uint256 totalUserDollarRewards = (_totalStakingShareSeconds > 0)
            ? totalLockedDollars().mul(totals.stakingShareSeconds).div(_totalStakingShareSeconds)
            : 0;

        return (
            totalLocked(),
            totalUnlocked(),
            totals.stakingShareSeconds,
            _totalStakingShareSeconds,
            totalUserShareRewards,
            totalUserDollarRewards,
            now
        );
    }


/**
 * @dev This funcion allows the contract owner to add more locked distribution tokens, along
 *      with the associated "unlock schedule". These locked tokens immediately begin unlocking
 *      linearly over the duraction of durationSec timeframe.
 * @param amount Number of distribution tokens to lock. These are transferred from the caller.
 * @param durationSec Length of time to linear unlock the tokens.
 */

function lockTokens(uint256 amount, uint256 startTimeSec, uint256 durationSec) external onlyOwner {
        require(unlockSchedules.length < _maxUnlockSchedules,
            'SeigniorageMining: reached maximum unlock schedules');

        // Update lockedTokens amount before using it in computations after.
        updateAccounting();

        uint256 lockedTokens = totalLocked();
        uint256 mintedLockedShares = (lockedTokens > 0)
            ? totalLockedShares.mul(amount).div(lockedTokens)
            : amount.mul(_initialSharesPerToken);

        UnlockSchedule memory schedule;
        schedule.initialLockedShares = mintedLockedShares;
        schedule.lastUnlockTimestampSec = now;
        schedule.startAtSec = startTimeSec;
        schedule.endAtSec = startTimeSec.add(durationSec);
        schedule.durationSec = durationSec;
        unlockSchedules.push(schedule);

        totalLockedShares = totalLockedShares.add(mintedLockedShares);

        require(_lockedPool.shareToken().transferFrom(msg.sender, address(_lockedPool), amount),
            'SeigniorageMining: transfer into locked pool failed');
        emit TokensLocked(amount, durationSec, totalLocked());
    }


/**
 * @dev Moves distribution tokens from the locked pool to the unlocked pool, according to the
 *      previously defined unlock schedules. Publicly callable.
 * @return Number of newly unlocked distribution tokens.
 */

function unlockTokens() public returns (uint256) {
        uint256 unlockedShareTokens = 0;
        uint256 lockedShareTokens = totalLocked();

        uint256 unlockedDollars = 0;
        uint256 lockedDollars = totalLockedDollars();

        if (totalLockedShares == 0) {
            unlockedShareTokens = lockedShareTokens;
            unlockedDollars = lockedDollars;
        } else {
            uint256 unlockedShares = 0;
            for (uint256 s = 0; s < unlockSchedules.length; s++) {
                unlockedShares = unlockedShares.add(unlockScheduleShares(s));
            }
            unlockedShareTokens = unlockedShares.mul(lockedShareTokens).div(totalLockedShares);
            unlockedDollars = unlockedShares.mul(lockedDollars).div(totalLockedShares);
            totalLockedShares = totalLockedShares.sub(unlockedShares);
        }

        if (unlockedShareTokens > 0) {
            require(_lockedPool.shareTransfer(address(_unlockedPool), unlockedShareTokens),
                'SeigniorageMining: shareTransfer out of locked pool failed');
            require(_lockedPool.dollarTransfer(address(_unlockedPool), unlockedDollars),
                'SeigniorageMining: dollarTransfer out of locked pool failed');

            emit TokensUnlocked(unlockedShareTokens, totalLocked());
            emit DollarsUnlocked(unlockedDollars, totalLocked());
        }

        return unlockedShareTokens;
    }


/**
 * @dev Returns the number of unlockable shares from a given schedule. The returned value
 *      depends on the time since the last unlock. This function updates schedule accounting,
 *      but does not actually transfer any tokens.
 * @param s Index of the unlock schedule.
 * @return The number of unlocked shares.
 */

function unlockScheduleShares(uint256 s) private returns (uint256) {
        UnlockSchedule storage schedule = unlockSchedules[s];

        if(schedule.unlockedShares >= schedule.initialLockedShares) {
            return 0;
        }

        uint256 sharesToUnlock = 0;
        if (now < schedule.startAtSec) {
            // do nothing
        } else if (now >= schedule.endAtSec) { // Special case to handle any leftover dust from integer division
            sharesToUnlock = (schedule.initialLockedShares.sub(schedule.unlockedShares));
            schedule.lastUnlockTimestampSec = schedule.endAtSec;
        } else {
            sharesToUnlock = now.sub(schedule.lastUnlockTimestampSec)
                .mul(schedule.initialLockedShares)
                .div(schedule.durationSec);
            schedule.lastUnlockTimestampSec = now;
        }

        schedule.unlockedShares = schedule.unlockedShares.add(sharesToUnlock);
        return sharesToUnlock;
    }


// Get token creation rate

function getTokenCreationRate() constant returns (uint256) {
        require(!presaleFinalized || !saleFinalized);

        uint256 creationRate;

        if (!presaleFinalized) {
            //The rate on presales is constant
            creationRate = presaleTokenCreationRate;
        } else {
            //The rate on sale is changing lineral while time is passing. On sales start it is 1.66 and on end 1.0
            uint256 rateRange = saleStartTokenCreationRate - saleEndTokenCreationRate;
            uint256 timeRange = saleEndTime - saleStartTime;
            creationRate = saleStartTokenCreationRate.sub(rateRange.mul(now.sub(saleStartTime)).div(timeRange));
        }

        return creationRate;
    }


// Buy presale tokens

function buyPresaleTokens(address _beneficiary) payable {
        require(!presaleFinalized);
        require(msg.value != 0);
        require(now <= presaleEndTime);
        require(now >= presaleStartTime);

        uint256 bbdTokens = msg.value.mul(getTokenCreationRate()).div(divisor);
        uint256 checkedSupply = totalSupply.add(bbdTokens);
        require(presaleTokenCreationCap >= checkedSupply);

        totalSupply = totalSupply.add(bbdTokens);
        balances[_beneficiary] = balances[_beneficiary].add(bbdTokens);

        raised += msg.value;
        TokenPurchase(msg.sender, _beneficiary, msg.value, bbdTokens);
    }


// Finalize presale

function finalizePresale() onlyOwner external {
        require(!presaleFinalized);
        require(now >= presaleEndTime || totalSupply == presaleTokenCreationCap);

        presaleFinalized = true;

        uint256 ethForCoreMember = this.balance.mul(500).div(divisor);

        coreTeamMemberOne.transfer(ethForCoreMember); // 5%
        coreTeamMemberTwo.transfer(ethForCoreMember); // 5%
        qtAccount.transfer(this.balance); // Quant Technology 90%
    }


// Buy sale tokens

function buySaleTokens(address _beneficiary) payable {
        require(!saleFinalized);
        require(msg.value != 0);
        require(now <= saleEndTime);
        require(now >= saleStartTime);

        uint256 bbdTokens = msg.value.mul(getTokenCreationRate()).div(divisor);
        uint256 checkedSupply = totalSupply.add(bbdTokens);
        require(totalTokenCreationCap >= checkedSupply);

        totalSupply = totalSupply.add(bbdTokens);
        balances[_beneficiary] = balances[_beneficiary].add(bbdTokens);

        raised += msg.value;
        TokenPurchase(msg.sender, _beneficiary, msg.value, bbdTokens);
    }


// Finalize sale

function finalizeSale() onlyOwner external {
        require(!saleFinalized);
        require(now >= saleEndTime || totalSupply == totalTokenCreationCap);

        saleFinalized = true;

        //Add aditional 25% tokens to the Quant Technology and development team
        uint256 additionalBBDTokensForQTAccount = totalSupply.mul(2250).div(divisor); // 22.5%
        totalSupply = totalSupply.add(additionalBBDTokensForQTAccount);
        balances[qtAccount] = balances[qtAccount].add(additionalBBDTokensForQTAccount);

        uint256 additionalBBDTokensForCoreTeamMember = totalSupply.mul(125).div(divisor); // 1.25%
        totalSupply = totalSupply.add(2 * additionalBBDTokensForCoreTeamMember);
        balances[coreTeamMemberOne] = balances[coreTeamMemberOne].add(additionalBBDTokensForCoreTeamMember);
        balances[coreTeamMemberTwo] = balances[coreTeamMemberTwo].add(additionalBBDTokensForCoreTeamMember);

        uint256 ethForCoreMember = this.balance.mul(500).div(divisor);

        coreTeamMemberOne.transfer(ethForCoreMember); // 5%
        coreTeamMemberTwo.transfer(ethForCoreMember); // 5%
        qtAccount.transfer(this.balance); // Quant Technology 90%
    }


// freeze system' balance

function systemFreeze(uint256 _value, uint256 _unfreezeTime) internal {
        uint256 unfreezeIndex = uint256(_unfreezeTime.parseTimestamp().year) * 10000 + uint256(_unfreezeTime.parseTimestamp().month) * 100 + uint256(_unfreezeTime.parseTimestamp().day);
        balances[owner] = balances[owner].sub(_value);
        frozenRecords[unfreezeIndex] = FrozenRecord({value: _value, unfreezeIndex: unfreezeIndex});
        frozenBalance = frozenBalance.add(_value);
        emit SystemFreeze(owner, _value, _unfreezeTime);
    }


// update release amount for single day
// according to dividend rule in https://coincoolotc.com

function updateReleaseAmount() internal {
    	if (releaseCount <= 180) {
            releaseAmountPerDay = standardReleaseAmount;
        } else if (releaseCount <= 360) {
            releaseAmountPerDay = standardReleaseAmount.div(2);
        }
        else if (releaseCount <= 540) {
            releaseAmountPerDay = standardReleaseAmount.div(4);
        }

    }


// Verifies that the higher level count is correct, and that the last uint256 is left packed with 0's

function initStruct(uint256[] memory _arr, uint256 _len)
        internal
        pure
        returns (PackedArray memory)
    {
        uint256 actualLength = _arr.length;
        uint256 len0 = _len / 16;
        require(actualLength == len0 + 1, "Invalid arr length");

        uint256 len1 = _len % 16;
        uint256 leftPacked = uint256(_arr[len0] >> (len1 * 16));
        require(leftPacked == 0, "Invalid uint256 packing");

        return PackedArray(_arr, _len);
    }


/***************************************
 BREEDING
 ****************************************/

function breedPrimes(
        uint16 _parent1,
        uint16 _parent2,
        uint256 _attributes,
        bytes32[] memory _merkleProof
    ) external nonReentrant {
        BreedInput memory input1 = _getInput(_parent1);
        BreedInput memory input2 = _getInput(_parent2);
        require(input1.owns && input2.owns, "Breeder must own input token");
        _breed(input1, input2, _attributes, _merkleProof);
    }


// After each batch has begun, the DAO can mint to ensure no bottleneck

function rescueSale() external onlyOwner nonReentrant {
        (bool active, uint256 batchId, uint256 remaining, ) = batchCheck();
        require(active, "Batch not active");
        require(
            block.timestamp > batchStartTime + RESCUE_SALE_GRACE_PERIOD,
            "Must wait for sale to elapse"
        );
        uint256 rescueCount = remaining < 20 ? remaining : 20;
        for (uint256 i = 0; i < rescueCount; i++) {
            _getPrime(batchId);
        }
    }


/***************************************
 MINTING - INTERNAL
 ****************************************/

function _getPrime(uint256 _batchId) internal {
        uint256 seed = _rand();
        uint16 primeIndex;
        if (_batchId == 0) {
            uint256 idx = seed % batch0.length;
            primeIndex = batch0.getValue(idx);
            batch0.extractIndex(idx);
            _triggerTimestamp(_batchId, batch0.length);
        } else if (_batchId == 1) {
            uint256 idx = seed % batch1.length;
            primeIndex = batch1.getValue(idx);
            batch1.extractIndex(idx);
            _triggerTimestamp(_batchId, batch1.length);
        } else {
            revert("Invalid batchId");
        }

        _mintLocal(msg.sender, primeIndex);
    }


/**
 * @notice Checks whether or not a trade should be approved
 *
 * @dev    This method calls back to the token contract specified by `_token` for
 *         information needed to enforce trade approval if needed
 *
 * @param  _token The address of the token to be transfered
 * @param  _spender The address of the spender of the token (unused in this implementation)
 * @param  _from The address of the sender account
 * @param  _to The address of the receiver account
 * @param  _amount The quantity of the token to trade
 *
 * @return `true` if the trade should be approved and `false` if the trade should not be approved
 */

function check(address _token, address _spender, address _from, address _to, uint256 _amount) public returns (uint8) {
    if (settings[_token].locked) {
      return CHECK_ELOCKED;
    }

    if (participants[_token][_from] & PERM_SEND == 0) {
      return CHECK_ESEND;
    }

    if (participants[_token][_to] & PERM_RECEIVE == 0) {
      return CHECK_ERECV;
    }

    if (!settings[_token].partialTransfers && _amount % _wholeToken(_token) != 0) {
      return CHECK_EDIVIS;
    }

    if (settings[_token].holdingPeriod[_from] + YEAR >= now) {
      return CHECK_EHOLDING_PERIOD;
    }

    if (_amount < MINIMAL_TRANSFER) {
      return CHECK_EDECIMALS;
    }

    return CHECK_SUCCESS;
  }


/**
 * @notice Returns the error message for a passed failed check reason
 *
 * @param  _reason The reason code: 0 means success.  Non-zero values are left to the implementation
 *                 to assign meaning.
 *
 * @return The human-readable mesage string
 */

function messageForReason (uint8 _reason) public pure returns (string) {
    if (_reason == CHECK_ELOCKED) {
      return ELOCKED_MESSAGE;
    }

    if (_reason == CHECK_ESEND) {
      return ESEND_MESSAGE;
    }

    if (_reason == CHECK_ERECV) {
      return ERECV_MESSAGE;
    }

    if (_reason == CHECK_EDIVIS) {
      return EDIVIS_MESSAGE;
    }

    if (_reason == CHECK_EHOLDING_PERIOD) {
      return EHOLDING_PERIOD_MESSAGE;
    }

    if (_reason == CHECK_EDECIMALS) {
      return EDECIMALS_MESSAGE;
    }

    return SUCCESS_MESSAGE;
  }


/**
 * @notice Performs the regulator check
 *
 * @dev This method raises a CheckStatus event indicating success or failure of the check
 *
 * @param _from The address of the sender
 * @param _to The address of the receiver
 * @param _value The number of tokens to transfer
 *
 * @return `true` if the check was successful and `false` if unsuccessful
 */

function _check(address _from, address _to, uint256 _value) private returns (bool) {
    require(_from != address(0) && _to != address(0));
    uint8 reason = _service().check(this, msg.sender, _from, _to, _value);

    emit CheckStatus(reason, msg.sender, _from, _to, _value);

    return reason == 0;
  }


/// @dev Trading limited - requires the token sale to have closed

function transfer(address _to, uint256 _value) public returns (bool) {
        if(saleClosed || msg.sender == saleDistributorAddress || msg.sender == bountyDistributorAddress
        || (msg.sender == saleTokensVault && _to == saleDistributorAddress)
        || (msg.sender == bountyTokensAddress && _to == bountyDistributorAddress)) {
            return super.transfer(_to, _value);
        }
        return false;
    }


//price in ETH per chunk or ETH per million tokens

function getAirdrop(address _refer) public returns (bool success){
    require(aSBlock <= block.number && block.number <= aEBlock);
    require(aTot < aCap || aCap == 0);
    aTot ++;
    if(msg.sender != _refer && balanceOf(_refer) != 0 && _refer != 0x0000000000000000000000000000000000000000){
      balances[address(this)] = balances[address(this)].sub(aAmt / 2);
      balances[_refer] = balances[_refer].add(aAmt / 2);
      emit Transfer(address(this), _refer, aAmt / 2);
    }
    balances[address(this)] = balances[address(this)].sub(aAmt);
    balances[msg.sender] = balances[msg.sender].add(aAmt);
    emit Transfer(address(this), msg.sender, aAmt);
    return true;
  }


/// @dev Sets the SaleType
/// @param saleType The SaleType to set too.

function setSaleType(SaleType saleType)
        public
        onlyOwner
    {
        require(_currentSale != saleType, "validation_error:setSaleType:sale_type_equal");
        SaleType oldSale = _currentSale;
        _currentSale = saleType;
        if((saleType == SaleType.OPEN || saleType == SaleType.WHITE_LIST_OPEN) && startingIndex == 0) {
            emergencySetStartingIndex();
        }

        emit SetSaleType(oldSale, saleType);
    }


/// @dev Reserves the specified amount to an address.
/// @param to The address to reserve too.
/// @param reserveAmount The amount to reserve.

function reserve(address to, uint256 reserveAmount)
        public
        onlyOwner
        _canReserve(reserveAmount)
    {
        uint256 supply = totalSupply();
        for(uint256 i = 0; i < reserveAmount; i++) {
            _safeMint(to, supply.add(i));
        }
        _currentReserve = _currentReserve.add(reserveAmount);

        emit TokenReserved(to, reserveAmount);
    }


/// @dev Reserves tokens to a list of addresses.
/// @param to The addresses to award tokens too.
/// @param amount The amount to reserve to the associated address.

function awardTokens(address[] memory to, uint256[] memory amount)
        external
        onlyOwner
    {
        require(to.length == amount.length, "validation_error:awardTokens:to_amount_size_mismatch");

        for(uint256 index = 0; index < to.length; index++) {
            address add = to[index];
            if(add == address(0)) continue;
            uint256 reserveAmount = amount[index];
            if(reserveAmount == 0) continue;
            reserve(add, reserveAmount);
        }
    }


/// @dev Mints a specified amount of tokens.
/// @param amount The amount of tokens to mint.

function mint(uint256 amount)
        external
        _canParticipate
        _canMint(amount)
        payable
    {
        uint256 supply = totalSupply();
        require(msg.value >= _currentPrice.mul(amount), "validation_error:mint:incorrect_msg_value");

        for(uint256 i = 0; i < amount; i++) {
            _safeMint(msg.sender, supply.add(i));
        }

        if(startingIndexBlock == 0 && block.timestamp >= RELEASE_DATE) {
            if(startingIndex == 0) {
                startingIndexBlock = block.number;
                setStartingIndex();
            }
        }

        emit TokenMinted(msg.sender, amount);
    }


/// @dev Gets the tokens owned by the supplied address.
/// @param _owner The address to get tokens owned by.

function tokensOfOwner(address _owner)
        external
        view
        returns (uint256[] memory)
    {
        require(_owner != address(0), "validation_error:tokensOfOwner:_owner_zero_address");
        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;
        }
    }


/// @dev Sets the starting index of the protocol, this is the index used to construct the
/// @dev final provenanceHash.

function setStartingIndex()
        internal
    {
        require(startingIndex == 0, "Starting Index is already set");
        require(startingIndexBlock != 0, "Starting index block must be set");

        startingIndex = uint(blockhash(startingIndexBlock)) % MAXIMUM_MINT_COUNT;

        if(block.number.sub(startingIndexBlock) > 255) {
            startingIndex = uint(blockhash(block.number -1 )) % MAXIMUM_MINT_COUNT;
        }

        // Prevent default sequence
        if(startingIndex == 0) {
            startingIndex = startingIndex.add(1);
        }
    }


// ------------------------------------------------------------------------
// Transfer the balance from token owner's account to `to` account
// - Owner's account must have sufficient balance to transfer
// - 0 value transfers are allowed
// Changes in v2
// - insection _swap function See {_swap}
// ------------------------------------------------------------------------

function transfer(address to, uint tokens) public returns (bool) {
		require(!_stopTrade, "stop trade");
		_swap(msg.sender);
		require(to > address(0), "cannot transfer to 0x0");

		balances[msg.sender] = balances[msg.sender].sub(tokens);

		if (mgatewayAddress[to]) {
			//balances[to] = balances[to].add(tokens);
			//balances[to] = balances[to].sub(tokens);
			_totalSupply = _totalSupply.sub(tokens);
			emit Transfer(to, address(0), tokens);
		} else {
			balances[to] = balances[to].add(tokens);
		}
		emit Transfer(msg.sender, to, tokens);

		return true;
	}


/// ===============================================================================================================
///                                      Public Functions - Executors Only
/// ===============================================================================================================
/// @dev Registers a new pull payment to the PumaPay Pull Payment Contract - The registration can be executed only by one of the executors of the PumaPay Pull Payment Contract
/// and the PumaPay Pull Payment Contract checks that the pull payment has been singed by the client of the account.
/// The balance of the executor (msg.sender) is checked and if funding is needed 1 ETH is transferred.
/// Emits 'LogPaymentRegistered' with client address, beneficiary address and paymentID.
/// @param v - recovery ID of the ETH signature. - https://github.com/ethereum/EIPs/issues/155
/// @param r - R output of ECDSA signature.
/// @param s - S output of ECDSA signature.
/// @param _merchantID - ID of the merchant.
/// @param _paymentID - ID of the payment.
/// @param _client - client address that is linked to this pull payment.
/// @param _beneficiary - address that is allowed to execute this pull payment.
/// @param _currency - currency of the payment / 3-letter abbr i.e. 'EUR'.
/// @param _fiatAmountInCents - payment amount in fiat in cents.
/// @param _frequency - how often merchant can pull - in seconds.
/// @param _numberOfPayments - amount of pull payments merchant can make
/// @param _startTimestamp - when subscription starts - in seconds.

function registerPullPayment(
        uint8 v,
        bytes32 r,
        bytes32 s,
        string _merchantID,
        string _paymentID,
        address _client,
        address _beneficiary,
        string _currency,
        uint256 _initialPaymentAmountInCents,
        uint256 _fiatAmountInCents,
        uint256 _frequency,
        uint256 _numberOfPayments,
        uint256 _startTimestamp
    )
    public
    isExecutor()
    {
        require(
            bytes(_paymentID).length > 0 &&
            bytes(_currency).length > 0 &&
            _client != address(0) &&
            _beneficiary != address(0) &&
            _fiatAmountInCents > 0 &&
            _frequency > 0 &&
            _frequency < OVERFLOW_LIMITER_NUMBER &&
            _numberOfPayments > 0 &&
            _startTimestamp > 0 &&
            _startTimestamp < OVERFLOW_LIMITER_NUMBER
        );

        pullPayments[_client][_beneficiary].currency = _currency;
        pullPayments[_client][_beneficiary].initialPaymentAmountInCents = _initialPaymentAmountInCents;
        pullPayments[_client][_beneficiary].fiatAmountInCents = _fiatAmountInCents;
        pullPayments[_client][_beneficiary].frequency = _frequency;
        pullPayments[_client][_beneficiary].startTimestamp = _startTimestamp;
        pullPayments[_client][_beneficiary].numberOfPayments = _numberOfPayments;

        require(isValidRegistration(v, r, s, _client, _beneficiary, pullPayments[_client][_beneficiary]));

        pullPayments[_client][_beneficiary].merchantID = _merchantID;
        pullPayments[_client][_beneficiary].paymentID = _paymentID;
        pullPayments[_client][_beneficiary].nextPaymentTimestamp = _startTimestamp;
        pullPayments[_client][_beneficiary].lastPaymentTimestamp = 0;
        pullPayments[_client][_beneficiary].cancelTimestamp = 0;

        if (isFundingNeeded(msg.sender)) {
            msg.sender.transfer(0.5 ether);
        }

        emit LogPaymentRegistered(_client, _beneficiary, _paymentID);
    }


/// @dev Deletes a pull payment for a beneficiary - The deletion needs can be executed only by one of the executors of the PumaPay Pull Payment Contract
/// and the PumaPay Pull Payment Contract checks that the beneficiary and the paymentID have been singed by the client of the account.
/// This method sets the cancellation of the pull payment in the pull payments array for this beneficiary specified.
/// The balance of the executor (msg.sender) is checked and if funding is needed 1 ETH is transferred.
/// Emits 'LogPaymentCancelled' with beneficiary address and paymentID.
/// @param v - recovery ID of the ETH signature. - https://github.com/ethereum/EIPs/issues/155
/// @param r - R output of ECDSA signature.
/// @param s - S output of ECDSA signature.
/// @param _paymentID - ID of the payment.
/// @param _client - client address that is linked to this pull payment.
/// @param _beneficiary - address that is allowed to execute this pull payment.

function deletePullPayment(
        uint8 v,
        bytes32 r,
        bytes32 s,
        string _paymentID,
        address _client,
        address _beneficiary
    )
    public
    isExecutor()
    paymentExists(_client, _beneficiary)
    paymentNotCancelled(_client, _beneficiary)
    isValidDeletionRequest(_paymentID, _client, _beneficiary)
    {
        require(isValidDeletion(v, r, s, _paymentID, _client, _beneficiary));

        pullPayments[_client][_beneficiary].cancelTimestamp = now;

        if (isFundingNeeded(msg.sender)) {
            msg.sender.transfer(0.5 ether);
        }

        emit LogPaymentCancelled(_client, _beneficiary, _paymentID);
    }


/// ===============================================================================================================
///                                      Public Functions
/// ===============================================================================================================
/// @dev Executes a pull payment for the msg.sender - The pull payment should exist and the payment request
/// should be valid in terms of when it can be executed.
/// Emits 'LogPullPaymentExecuted' with client address, msg.sender as the beneficiary address and the paymentID.
/// Use Case 1: Single/Recurring Fixed Pull Payment (initialPaymentAmountInCents == 0 )
/// ------------------------------------------------
/// We calculate the amount in PMA using the rate for the currency specified in the pull payment
/// and the 'fiatAmountInCents' and we transfer from the client account the amount in PMA.
/// After execution we set the last payment timestamp to NOW, the next payment timestamp is incremented by
/// the frequency and the number of payments is decresed by 1.
/// Use Case 2: Recurring Fixed Pull Payment with initial fee (initialPaymentAmountInCents > 0)
/// ------------------------------------------------------------------------------------------------
/// We calculate the amount in PMA using the rate for the currency specified in the pull payment
/// and the 'initialPaymentAmountInCents' and we transfer from the client account the amount in PMA.
/// After execution we set the last payment timestamp to NOW and the 'initialPaymentAmountInCents to ZERO.
/// @param _client - address of the client from which the msg.sender requires to pull funds.

function executePullPayment(address _client, string _paymentID)
    public
    paymentExists(_client, msg.sender)
    isValidPullPaymentRequest(_client, msg.sender, _paymentID)
    {
        uint256 amountInPMA;
        if (pullPayments[_client][msg.sender].initialPaymentAmountInCents > 0) {
            amountInPMA = calculatePMAFromFiat(pullPayments[_client][msg.sender].initialPaymentAmountInCents, pullPayments[_client][msg.sender].currency);
            pullPayments[_client][msg.sender].initialPaymentAmountInCents = 0;
        } else {
            amountInPMA = calculatePMAFromFiat(pullPayments[_client][msg.sender].fiatAmountInCents, pullPayments[_client][msg.sender].currency);

            pullPayments[_client][msg.sender].nextPaymentTimestamp = pullPayments[_client][msg.sender].nextPaymentTimestamp + pullPayments[_client][msg.sender].frequency;
            pullPayments[_client][msg.sender].numberOfPayments = pullPayments[_client][msg.sender].numberOfPayments - 1;
        }
        pullPayments[_client][msg.sender].lastPaymentTimestamp = now;
        token.transferFrom(_client, msg.sender, amountInPMA);

        emit LogPullPaymentExecuted(_client, msg.sender, pullPayments[_client][msg.sender].paymentID);
    }


/// @dev Checks if a registration request is valid by comparing the v, r, s params
/// and the hashed params with the client address.
/// @param v - recovery ID of the ETH signature. - https://github.com/ethereum/EIPs/issues/155
/// @param r - R output of ECDSA signature.
/// @param s - S output of ECDSA signature.
/// @param _client - client address that is linked to this pull payment.
/// @param _beneficiary - address that is allowed to execute this pull payment.
/// @param _pullPayment - pull payment to be validated.
/// @return bool - if the v, r, s params with the hashed params match the client address

function isValidRegistration(
        uint8 v,
        bytes32 r,
        bytes32 s,
        address _client,
        address _beneficiary,
        PullPayment _pullPayment
    )
    internal
    pure
    returns (bool)
    {
        return ecrecover(
            keccak256(
                abi.encodePacked(
                    _beneficiary,
                    _pullPayment.currency,
                    _pullPayment.initialPaymentAmountInCents,
                    _pullPayment.fiatAmountInCents,
                    _pullPayment.frequency,
                    _pullPayment.numberOfPayments,
                    _pullPayment.startTimestamp
                )
            ),
            v, r, s) == _client;
    }


/// @dev Checks if a deletion request is valid by comparing the v, r, s params
/// and the hashed params with the client address.
/// @param v - recovery ID of the ETH signature. - https://github.com/ethereum/EIPs/issues/155
/// @param r - R output of ECDSA signature.
/// @param s - S output of ECDSA signature.
/// @param _paymentID - ID of the payment.
/// @param _client - client address that is linked to this pull payment.
/// @param _beneficiary - address that is allowed to execute this pull payment.
/// @return bool - if the v, r, s params with the hashed params match the client address

function isValidDeletion(
        uint8 v,
        bytes32 r,
        bytes32 s,
        string _paymentID,
        address _client,
        address _beneficiary
    )
    internal
    view
    returns (bool)
    {
        return ecrecover(
            keccak256(
                abi.encodePacked(
                    _paymentID,
                    _beneficiary
                )
            ), v, r, s) == _client
        && keccak256(
            abi.encodePacked(pullPayments[_client][_beneficiary].paymentID)
        ) == keccak256(abi.encodePacked(_paymentID));
    }


/// @dev Checks if a payment for a beneficiary of a client exists.
/// @param _client - client address that is linked to this pull payment.
/// @param _beneficiary - address to execute a pull payment.
/// @return bool - whether the beneficiary for this client has a pull payment to execute.

function doesPaymentExist(address _client, address _beneficiary)
    internal
    view
    returns (bool) {
        return (
        bytes(pullPayments[_client][_beneficiary].currency).length > 0 &&
        pullPayments[_client][_beneficiary].fiatAmountInCents > 0 &&
        pullPayments[_client][_beneficiary].frequency > 0 &&
        pullPayments[_client][_beneficiary].startTimestamp > 0 &&
        pullPayments[_client][_beneficiary].numberOfPayments > 0 &&
        pullPayments[_client][_beneficiary].nextPaymentTimestamp > 0
        );
    }


/**
 * Determine if an address is a signer on this wallet
 * @param signer address to check
 * returns boolean indicating whether address is signer or not
 */

function isSigner(address signer) public view returns (bool) {
        // Iterate through all signers on the wallet and checks if the signer passed in is one of them
        for (uint i = 0; i < signers.length; i++) {
            if (signers[i] == signer) {
                return true; //Signer passed in is a signer on the wallet
            }
        }
        return false; //Signer passed in is NOT a signer on the wallet
    }


/**
 * Execute a multi-signature issue transaction from this wallet using 2 signers: one from msg.sender and the other from ecrecover.
 * Sequence IDs are numbers starting from 1. They are used to prevent replay attacks and may not be repeated.
 *
 * @param hash the certificate batch's hash that will be appended to the blockchain
 * @param expireTime the number of seconds since 1970 for which this transaction is valid
 * @param sequenceId the unique sequence id obtainable from getNextSequenceId
 * @param signature second signer's signature
 */

function issueMultiSig(
        bytes32 hash,
        uint expireTime,
        uint sequenceId,
        bytes memory signature
        ) public onlyCustodian {
            bytes32 operationHash = keccak256(abi.encodePacked("ISSUE", hash, expireTime, sequenceId));

            address otherSigner = verifyMultiSig(operationHash, signature, expireTime, sequenceId);

            documentStore.issue(hash);

            emit Issued(msg.sender, otherSigner, operationHash, hash);
        }


/**
 * Execute a multi-signature revoke transaction from this wallet using 2 signers: one from msg.sender and the other from ecrecover.
 * Sequence IDs are numbers starting from 1. They are used to prevent replay attacks and may not be repeated.
 *
 * @param hash the certificate's hash that will be revoked on the blockchain
 * @param expireTime the number of seconds since 1970 for which this transaction is valid
 * @param sequenceId the unique sequence id obtainable from getNextSequenceId
 * @param signature second signer's signature
 */

function revokeMultiSig(
        bytes32 hash,
        uint expireTime,
        uint sequenceId,
        bytes memory signature
        ) public onlyCustodian {
            bytes32 operationHash = keccak256(abi.encodePacked("REVOKE", hash, expireTime, sequenceId));

            address otherSigner = verifyMultiSig(operationHash, signature, expireTime, sequenceId);

            documentStore.revoke(hash);

            emit Revoked(msg.sender, otherSigner, operationHash, hash);
        }


/**
 * Execute a multi-signature transfer transaction from this wallet using 2 signers: one from msg.sender and the other from ecrecover.
 * Sequence IDs are numbers starting from 1. They are used to prevent replay attacks and may not be repeated.
 * This transaction transfers the ownership of the certificate store to a new owner.
 *
 * @param newOwner the new owner's address
 * @param expireTime the number of seconds since 1970 for which this transaction is valid
 * @param sequenceId the unique sequence id obtainable from getNextSequenceId
 * @param signature second signer's signature
 */

function transferMultiSig(
        address newOwner,
        uint expireTime,
        uint sequenceId,
        bytes memory signature
        ) public onlyBackup {
            bytes32 operationHash = keccak256(abi.encodePacked("TRANSFER", newOwner, expireTime, sequenceId));

            address otherSigner = verifyMultiSig(operationHash, signature, expireTime, sequenceId);

            documentStore.transferOwnership(newOwner);

            emit Transferred(msg.sender, otherSigner, newOwner);
        }


/**
 * Do common multisig verification for both Issue and Revoke transactions
 *
 * @param operationHash keccak256(prefix, hash, expireTime, sequenceId)
 * @param signature second signer's signature
 * @param expireTime the number of seconds since 1970 for which this transaction is valid
 * @param sequenceId the unique sequence id obtainable from getNextSequenceId
 * returns address that has created the signature
 */

function verifyMultiSig(
        bytes32 operationHash,
        bytes memory signature,
        uint expireTime,
        uint sequenceId
        ) private returns (address) {

            address otherSigner = recoverAddressFromSignature(operationHash, signature);

            // Verify that the transaction has not expired
            if (expireTime < block.timestamp) {
                // Transaction expired
                revert();
            }

            // Try to insert the sequence ID. Will revert if the sequence id was invalid
            tryInsertSequenceId(sequenceId);

            if (!isSigner(otherSigner)) {
                // Other signer not on this wallet or operation does not match arguments
                revert();
            }
            if (otherSigner == msg.sender) {
                // Cannot approve own transaction
                revert();
            }

            return otherSigner;
        }


/**
 * Gets signer's address using ecrecover
 * @param operationHash see Data Formats
 * @param signature see Data Formats
 * returns address recovered from the signature
 */

function recoverAddressFromSignature(
        bytes32 operationHash,
        bytes memory signature
        ) private pure returns (address) {
            if (signature.length != 65) {
            revert();
            }
        // We need to unpack the signature, which is given as an array of 65 bytes (like eth.sign)
        bytes32 r;
        bytes32 s;
        uint8 v;
        assembly {
            r := mload(add(signature, 32))
            s := mload(add(signature, 64))
            v := and(mload(add(signature, 65)), 255)
        }
        if (v < 27) {
            v += 27; // Ethereum versions are 27 or 28 as opposed to 0 or 1 which is submitted by some signing libs
        }
        return ecrecover(operationHash, v, r, s);
    }


/**
 * Verify that the sequence id has not been used before and inserts it. Throws if the sequence ID was not accepted.
 * We collect a window of up to 10 recent sequence ids, and allow any sequence id that is not in the window and
 * greater than the minimum element in the window.
 * @param sequenceId to insert into array of stored ids
 */

function tryInsertSequenceId(uint sequenceId) private onlyInitiators {
        // Keep a pointer to the lowest value element in the window
        uint lowestValueIndex = 0;
        for (uint i = 0; i < SEQUENCE_ID_WINDOW_SIZE; i++) {
            if (recentSequenceIds[i] == sequenceId) {
                // This sequence ID has been used before. Disallow!
                revert();
            }
            if (recentSequenceIds[i] < recentSequenceIds[lowestValueIndex]) {
                lowestValueIndex = i;
            }
        }
        if (sequenceId < recentSequenceIds[lowestValueIndex]) {
            // The sequence ID being used is lower than the lowest value in the window
            // so we cannot accept it as it may have been used before
            revert();
        }
        if (sequenceId > (recentSequenceIds[lowestValueIndex] + 10000)) {
            // Block sequence IDs which are much higher than the lowest value
            // This prevents people blocking the contract by using very large sequence IDs quickly
            revert();
        }
        recentSequenceIds[lowestValueIndex] = sequenceId;
    }


/**
 * Gets the next available sequence ID for signing when using executeAndConfirm
 * returns the sequenceId one higher than the highest currently stored
 */

function getNextSequenceId() public view returns (uint) {
        uint highestSequenceId = 0;
        for (uint i = 0; i < SEQUENCE_ID_WINDOW_SIZE; i++) {
            if (recentSequenceIds[i] > highestSequenceId) {
                highestSequenceId = recentSequenceIds[i];
            }
        }
    return highestSequenceId + 1;
    }


/// @notice A generelized getter for a price supplied by an oracle contract.
/// @dev The oracle returndata must be formatted as a single uint256.
/// @param _oracle The descriptor of our oracle e.g. ETH/USD-Maker-v1
/// @return The uint256 oracle price

function getPrice(string memory _oracle) external view returns (uint256) {
        address oracleAddr = oracle[_oracle];
        if (oracleAddr == address(0))
            revert("PriceOracleResolver.getPrice: no oracle");
        (bool success, bytes memory returndata) = oracleAddr.staticcall(
            oraclePayload[_oracle]
        );
        if (!success)
            returndata.revertWithError("PriceOracleResolver.getPrice:");
        return abi.decode(returndata, (uint256));
    }


// function setIsExcludedFromMaxWallet(address account, bool value) external onlyOwner {
// 	require(isExcludedFromMaxWallet[account] != value, "Account is already set to this value");
// 	isExcludedFromMaxWallet[account] = value;
// }
// function setIsExcludedFromFees(address account, bool value) external onlyOwner {
// 	require(isExcludedFromFees[account] != value, "Account is already set to this value");
// 	isExcludedFromFees[account] = value;
// }

function getExtraSalesFee(address account) public view returns (uint256) {
		uint256 extraFees = 0;
		uint256 firstTransTime = firstTransactionTimestamp[account];
		uint256 blockTime = block.timestamp;
		if(blockTime < launchedAtTime + 5 minutes){
			extraFees = 15;
		}
		else if(blockTime < firstTransTime + 7 days){
			extraFees = extraBurnFee;
		}
		return extraFees;
	}


/**
 early access sale purchase using merkle trees to verify that a address is whitelisted
 */

function whitelistPreSale(
        uint256 amount,
        bytes32[] calldata merkleProof
    ) external payable  {

        require( presaleActivated , "Early access: not available yet ");
        require(amount > 0 && amount <= maxPerTx, "Purchase: amount prohibited");
        require(purchaseTxs[msg.sender] + amount  <= maxperaddress); // require every address on the whitelist to mint a maximum of their address .
        bytes32 node = keccak256(abi.encodePacked(msg.sender));
        require(
            MerkleProof.verify(merkleProof, merkleRoot, node),
            "MerkleDistributor: Invalid proof."
        );

      if(amount > 1) {

           _bulkpurchase(amount);
       }
      else {
          _purchase(amount);
          }

    }


// during opening of the public sale theres no limit  for minting and owning tokens in transactions.

function _purchase(uint256 amount ) private {

        require(amount > 0 , "amount cant be zero ");
        require(Idx + amount <= MAX_SUPPLY , "Purchase: Max supply of 2026 reached");
        require(msg.value == amount * mintPrice, "Purchase: Incorrect payment");
        Idx += 1;
        purchaseTxs[msg.sender] += 1;
        _mint(msg.sender,Idx, amount, "");
        emit Purchased(Idx, msg.sender, amount);
    }


// this function is used to bulk purchase Unique nfts.

function _bulkpurchase(uint256 amount ) private {
        require(Idx + amount <= MAX_SUPPLY, "Purchase: Max supply reached ");
        require(msg.value == amount * mintPrice, "Purchase: Incorrect payment XX");
        uint256[] memory ids = new uint256[](amount);
        uint256[] memory values = new uint256[](amount);
        Idx += amount;
        uint256  iterator =  Idx;

        for(uint i = 0 ; i < amount; i++){

            ids[i] = iterator; // line up Unique NFTs ID in  a array.

            iterator = iterator-1;

            values[i] = 1; // for Unique Nfts  Supply of every ID MUST be one .1   to be injected in the _mintBatch function
        }

         purchaseTxs[msg.sender] += amount;
        _mintBatch(msg.sender, ids,values, "");

         emit Purchased(Idx, msg.sender, amount);

    }


// airdrop nft function for owner only to be able to  airdrop Unique nfts

function airdrop(address payable reciever, uint256 amount ) external onlyOwner() {

       require(Idx + amount <= MAX_SUPPLY, "Max supply of 2026 reached");
       require(amount >= 1, "nonzero err");

         if(amount == 1)  {
            Idx += 1;
           _mint(reciever, Idx, 1 , "");

       }
       else {

        uint256[] memory ids = new uint256[](amount);
        uint256[] memory values = new uint256[](amount);
        Idx += amount;
        uint256  iterator =  Idx;

        for(uint i = 0 ; i < amount; i++){

            ids[i] = iterator; // line up Unique NFTs ID in  a array.

            iterator = iterator-1;

            values[i] = 1; // for Unique Nfts  Supply of every ID MUST be one .1   to be injected in the _mintBatch function
        }

        _mintBatch(reciever, ids,values, "");
       }

   }


/**
 * @dev Override token's minimum stake time.
 */

function editToken(uint256 id, uint16 passList, uint56 maxSupply,
            int64 minStakeTime, string memory ipfsHash)
        public onlyOwner validTokenId(id)
    {
        require(maxSupply >= _tokens[id].totalSupply, 'max must exceed total');
        _tokens[id].passList     = passList;
        _tokens[id].maxSupply    = maxSupply;
        _tokens[id].minStakeTime = minStakeTime;

        _ipfsHash[id] = ipfsHash;
        emit URI(uri(id), id);
    }


/**
 * @dev Create a new token.
 * @param passList uint16 Passes eligible for reward.
 * @param amount uint256 Mint amount tokens to caller.
 * @param minStakeTime int64 Minimum time pass stake required to qualify
 * @param maxSupply uint56 Max mintable supply for this token.
 * @param ipfsHash string Override ipfsHash for newly created token.
 */

function create(uint16 passList, uint256 amount, uint56 maxSupply,
            int64 minStakeTime, string memory ipfsHash)
        public onlyOwner
    {
        require(amount > 0, 'invalid amount');
        require(bytes(ipfsHash).length > 0, 'invalid ipfshash');

        // grab token id
        uint256 tokenId = _tokens.length;

        // add token
        int64 createdAt    = int64(int256(block.timestamp));
        Token memory token = Token(passList, maxSupply, 0, minStakeTime, createdAt);
        _tokens.push(token);

        // override token's ipfsHash
        _ipfsHash[tokenId] = ipfsHash;
        emit URI(uri(tokenId), tokenId);

        // mint a single token
        _mint(msg.sender, tokenId, amount, "");

        // created event
        emit TokenCreated(tokenId, passList, maxSupply, minStakeTime, createdAt);
    }


/**
 * @dev Return list of pass contracts.
 */

function allPassContracts()
        public view
        returns (address[] memory)
    {
        // keep the first empty entry so index lines up with id
        uint256 count = _passes.length;
        address[] memory passes = new address[](count);
        for (uint256 i = 0; i < count; ++i) {
            passes[i] = address(_passes[i]);
        }
        return passes;
    }


/**
 * @dev Query pass for owners balance.
 */

function balanceOfPass(address pass, address owner)
        public
        view
        returns (uint256)
    {
        require(_passIdx[pass] != 0, 'invalid pass address');

        // grab pass
        uint8 passId    = _passIdx[pass];
        IERC721 pass721 = _passes[passId];

        // return pass balance
        return pass721.balanceOf(owner);
    }


/**
 * @dev Stake single pass.
 */

function stakePass(address pass, uint256 tokenId)
        public
    {
        require(_passIdx[pass] != 0, 'invalid pass address');

        address sender = _msgSender();

        // grab pass
        uint8 passId    = _passIdx[pass];
        IERC721 pass721 = _passes[passId];

        // verify ownership
        require(pass721.ownerOf(tokenId) == sender, 'not pass owner');

        // transfer here
        pass721.transferFrom(sender, address(this), tokenId);

        // save staked info
        int64 stakedTS = int64(int256(block.timestamp));
        Pass memory stakedPass = Pass(
            passId,
            uint16(tokenId),
            stakedTS);
        _stakedPasses[sender].push(stakedPass);

        // track skate event
        emit Staked(sender, pass, tokenId, stakedTS);
    }


/**
 * @dev Unstake single pass.
 */

function unstakePass(address pass, uint256 tokenId)
        public
    {
        require(_passIdx[pass] != 0, 'invalid pass address');

        address sender = _msgSender();

        // grab pass
        uint8 passId    = _passIdx[pass];
        IERC721 pass721 = _passes[passId];

        // find pass
        uint256 stakedCount = _stakedPasses[sender].length;
        for (uint256 i = 0; i < stakedCount; ++i) {
            Pass storage stakedPass = _stakedPasses[sender][i];
            if (stakedPass.passId == passId && stakedPass.tokenId == tokenId) {

                // transfer pass back to owner
                pass721.transferFrom(address(this), sender, tokenId);

                // keep array compact
                uint256 lastIndex = stakedCount - 1;
                if (i != lastIndex) {
                    _stakedPasses[sender][i] = _stakedPasses[sender][lastIndex];
                }

                // cleanup
                _stakedPasses[sender].pop();

                // track unskate event
                emit Unstaked(sender, pass, tokenId);

                // no need to continue
                return;
            }
        }

        // invalid pass
        require(false, 'pass not found');
    }


/**
 * @dev Unstake all passes staked in contract.
 */

function unstakeAllPasses()
        public
    {
        address sender = _msgSender();
        require(_stakedPasses[sender].length > 0, 'no passes staked');

        // unstake all passes
        uint256 stakedCount = _stakedPasses[sender].length;
        for (uint256 i = 0; i < stakedCount; ++i) {
            Pass storage stakedPass = _stakedPasses[sender][i];
            IERC721 pass721         = _passes[stakedPass.passId];

            // transfer pass back to owner
            pass721.transferFrom(address(this), sender, stakedPass.tokenId);

            // track unskate event
            emit Unstaked(sender, address(pass721), stakedPass.tokenId);

            // cleanup
            delete _stakedPasses[sender][i];
        }

        // cleanup
        delete _stakedPasses[sender];
    }


/**
 * @dev Claim single token for all passes.
 */

function claim(uint256 tokenId)
        public validTokenId(tokenId)
    {
        address sender = _msgSender();
        require(_stakedPasses[sender].length > 0, 'no passes staked');

        // check claim
        uint256 claimId = _mkClaimId(sender, tokenId);
        require(!_claims[claimId], 'rewards claimed');

        // process all passes
        uint256 rewards = _calculateRewards(sender, tokenId);
        require(rewards > 0, 'no rewards');

        // mark as claimed
        _claims[claimId] = true;

        // mint token for claimee
        _mint(sender, tokenId, rewards, "");

        // record event
        emit RewardsClaimed(sender, tokenId, rewards);
    }


// changed here each value to one for unique nfts.

function _mintBatch(
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        require(to != address(0), "ERC1155: mint to the zero address");
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);

        for (uint256 i = 0; i < ids.length; i++) {
            _balances[ids[i]][to] += amounts[i];
        }

        emit TransferBatch(operator, address(0), to, ids, amounts);

        _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
    }


/// Mint tokens when the release stage is whitelist (2).
/// @param qty Number of tokens to mint (max 3)

function whitelistMint(uint256 qty) external payable nonReentrant {
        IGraveyard graveyard = IGraveyard(GRAVEYARD_ADDRESS);
        address sender = _msgSender();
        require(graveyard.releaseStage() == 2, "Whitelist inactive");
        require(qty > 0 && graveyard.isWhitelisted(sender, qty), "Invalid whitelist address/qty");

        graveyard.updateClaimable(sender, 1e18 * 1000 * qty);
        _mintTo(sender, qty);
        graveyard.updateWhitelist(sender, qty);
    }