/**
 * @dev See {IERC165-supportsInterface}.
 */

function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return interfaceId == type(ICreatorCore).interfaceId || super.supportsInterface(interfaceId)
            || interfaceId == _INTERFACE_ID_ROYALTIES_CREATORCORE || interfaceId == _INTERFACE_ID_ROYALTIES_EIP2981
            || interfaceId == _INTERFACE_ID_ROYALTIES_FOUNDATION || interfaceId == _INTERFACE_ID_ROYALTIES_EIP2981;
    }


/**
 * @dev Retrieve a token's URI
 */

function _tokenURI(uint256 tokenId) internal view returns (string memory) {
        if (bytes(_tokenURIs[tokenId]).length != 0) {
            return _tokenURIs[tokenId];
        }

        uint256 creatorId;
        if(tokenId > MAX_TOKEN_ID) {
            creatorId = _creatorTokens[tokenId];
        } else {
            creatorId = tokenId / CREATOR_SCALE;
        }
        if (bytes(_creatorURIs[creatorId]).length != 0) {
            return string(abi.encodePacked(_creatorURIs[creatorId], tokenId.toString()));
        }

        return string(abi.encodePacked(_creatorURIs[0], tokenId.toString()));
    }


/**
 * Helper to get royalty receivers for a token
 */

function _getRoyaltyReceivers(uint256 tokenId) view internal returns (address payable[] storage) {
        uint256 creatorId;
        if(tokenId > MAX_TOKEN_ID) {
            creatorId = _creatorTokens[tokenId];
        } else {
            creatorId = tokenId / CREATOR_SCALE;
        }

        if (_tokenRoyaltyReceivers[tokenId].length > 0) {
            return _tokenRoyaltyReceivers[tokenId];
        } else if (_creatorRoyaltyReceivers[creatorId].length > 0) {
            return _creatorRoyaltyReceivers[creatorId];
        }
        return _creatorRoyaltyReceivers[0];
    }


/**
 * Helper to get royalty basis points for a token
 */

function _getRoyaltyBPS(uint256 tokenId) view internal returns (uint256[] storage) {
        uint256 creatorId;
        if(tokenId > MAX_TOKEN_ID) {
            creatorId = _creatorTokens[tokenId];
        } else {
            creatorId = tokenId / CREATOR_SCALE;
        }

        if (_tokenRoyaltyBPS[tokenId].length > 0) {
            return _tokenRoyaltyBPS[tokenId];
        } else if (_creatorRoyaltyBPS[creatorId].length > 0) {
            return _creatorRoyaltyBPS[creatorId];
        }
        return _creatorRoyaltyBPS[0];
    }


/**
 * Helper to shorten royalties arrays if it is too long
 */

function _shortenRoyalties(address payable[] storage receivers, uint256[] storage basisPoints, uint256 targetLength) internal {
        require(receivers.length == basisPoints.length, "CreatorCore: Invalid input");
        if (targetLength < receivers.length) {
            for (uint i = receivers.length; i > targetLength; i--) {
                receivers.pop();
                basisPoints.pop();
            }
        }
    }


/**
 * Helper to update royalites
 */

function _updateRoyalties(address payable[] storage receivers, uint256[] storage basisPoints, address payable[] calldata newReceivers, uint256[] calldata newBPS) internal {
        require(receivers.length == basisPoints.length, "CreatorCore: Invalid input");
        require(newReceivers.length == newBPS.length, "CreatorCore: Invalid input");
        uint256 totalRoyalties;
        for (uint i = 0; i < newReceivers.length; i++) {
            if (i < receivers.length) {
                receivers[i] = newReceivers[i];
                basisPoints[i] = newBPS[i];
            } else {
                receivers.push(newReceivers[i]);
                basisPoints.push(newBPS[i]);
            }
            totalRoyalties += newBPS[i];
        }
        require(totalRoyalties < 10000, "CreatorCore: Invalid total royalties");
    }


/**
 * Set royalties for all tokens of an extension
 */

function _setRoyaltiesCreator(uint256 creatorId, address payable[] calldata receivers, uint256[] calldata basisPoints) internal {
        require(receivers.length == basisPoints.length, "CreatorCore: Invalid input");
        _shortenRoyalties(_creatorRoyaltyReceivers[creatorId], _creatorRoyaltyBPS[creatorId], receivers.length);
        _updateRoyalties(_creatorRoyaltyReceivers[creatorId], _creatorRoyaltyBPS[creatorId], receivers, basisPoints);
        if (creatorId == 0) {
            emit DefaultRoyaltiesUpdated(receivers, basisPoints);
        } else {
            emit CreatorRoyaltiesUpdated(creatorId, receivers, basisPoints);
        }
    }


// Quick swap low gas method for pool swaps

function deposit(uint256 _amount)
      external
      nonReentrant
  {
      require(_amount > 0, "deposit must be greater than 0");
      pool = _calcPoolValueInToken();

      IERC20(token).safeTransferFrom(msg.sender, address(this), _amount);

      // Calculate pool shares
      uint256 shares = 0;
      if (pool == 0) {
        shares = _amount;
        pool = _amount;
      } else {
        shares = (_amount.mul(_totalSupply)).div(pool);
      }
      pool = _calcPoolValueInToken();
      _mint(msg.sender, shares);
  }


// 1999999614570950845

function _withdrawSomeFulcrum(uint256 _amount) internal {
    // Balance of fulcrum tokens, 1 iDAI = 1.00x DAI
    uint256 b = balanceFulcrum(); // 1970469086655766652
    // Balance of token in fulcrum
    uint256 bT = balanceFulcrumInToken(); // 2000000803224344406
    require(bT >= _amount, "insufficient funds");
    // can have unintentional rounding errors
    uint256 amount = (b.mul(_amount)).div(bT).add(1);
    _withdrawFulcrum(amount);
  }


// Internal only rebalance for better gas in redeem

function _rebalance(Lender newProvider) internal {
    if (_balance() > 0) {
      if (newProvider == Lender.DYDX) {
        supplyDydx(_balance());
      } else if (newProvider == Lender.FULCRUM) {
        supplyFulcrum(_balance());
      } else if (newProvider == Lender.COMPOUND) {
        supplyCompound(_balance());
      } else if (newProvider == Lender.AAVE) {
        supplyAave(_balance());
      }
    }
    provider = newProvider;
  }


/**
 * Set some Moai aside
 */

function reserveMoai() public onlyOwner {
        uint supply = totalSupply();
        require(supply.add(50) <= MAX_MOAI , "would exceed max supply of Moai");
        uint i;
        for (i = 0; i < 50; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }


/**
 * Mints Moai
 */

function mintMoai(uint numberOfTokens) public payable {
        require(saleIsActive, "Sale must be active to mint Moai");
        require(numberOfTokens <= maxMoaiPurchase, "Can only mint 8 tokens at a time");
        require(totalSupply().add(numberOfTokens) <= MAX_MOAI, "Purchase would exceed max supply of Moai");
        require(moaiPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

        for(uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = totalSupply();
            if (totalSupply() < MAX_MOAI) {
                _safeMint(msg.sender, mintIndex);
            }
        }
    }


/**
 * @dev See {ICreatorCore-setTokenURI}.
 */

function setTokenURI(uint256[] memory tokenIds, string[] calldata uris) external override adminRequired {
        require(tokenIds.length == uris.length, "LGND: Invalid input");
        for (uint i = 0; i < tokenIds.length; i++) {
            _setTokenURI(tokenIds[i], uris[i]);
        }
    }


/**
 * @dev Mint token
 */

function _mintCreator(address to, uint256 creatorId, uint256 templateId, string memory uri) internal virtual returns(uint256 tokenId) {
        require(templateId <= MAX_TEMPLATE_ID, "LGND: templateId exceeds maximum");

        uint256 mintId = _creatorTokenCount[creatorId][templateId] + 1;
        require(mintId <= MAX_MINT_ID, "LGND: no remaining mints available");
        _creatorTokenCount[creatorId][templateId] = mintId;

        tokenId = (creatorId * CREATOR_SCALE) + (templateId * TEMPLATE_SCALE) + mintId;

        _supply++;
        _mint(_creatorAddresses[creatorId], tokenId);
        if(_creatorAddresses[creatorId] != to) {
            _safeTransfer(_creatorAddresses[creatorId], to, tokenId, "");
        }

        if (bytes(uri).length > 0) {
            _tokenURIs[tokenId] = uri;
        }

        return tokenId;
    }


/**
 * @dev See {IERC721CreatorCore-burn}.
 */

function burn(uint256 tokenId) external override {
        require(_isApprovedOrOwner(msg.sender, tokenId), "LGND: caller is not owner nor approved");
        address owner = ERC721.ownerOf(tokenId);
        require(bytes(_linkedAccounts[owner]).length != 0, "LGND: Must link account with setLinkedAccount");

        if(tokenId > MAX_TOKEN_ID) {
            require(_bridgeEnabled, "LGND: Bridge has not been enabled for this token");
            _supply--;
            // Delete token origin extension tracking
            delete _creatorTokens[tokenId];
        } else {
            require(_creatorBridgeEnabled[tokenId / CREATOR_SCALE], "LGND: Bridge has not been enabled for this token");
        }

        if (bytes(_tokenURIs[tokenId]).length != 0) {
            delete _tokenURIs[tokenId];
        }

        emit ExportedToken(owner, exports++, tokenId, _linkedAccounts[owner]);

        _burn(tokenId);
    }


// Allows the developer or anyone with the password to claim the bounty and shut down everything except withdrawals in emergencies.

function activate_kill_switch(string password) {
    // Only activate the kill switch if the sender is the developer or the password is correct.
    if (msg.sender != developer && sha3(password) != password_hash) throw;
    // Store the claimed bounty in a temporary variable.
    uint256 claimed_bounty = bounty;
    // Update bounty prior to sending to prevent recursive call.
    bounty = 0;
    // Irreversibly activate the kill switch.
    kill_switch = true;
    // Send the caller their bounty for activating the kill switch.
    msg.sender.transfer(claimed_bounty);
  }


// Automatically withdraws on users' behalves (less a 1% fee on tokens).

function auto_withdraw(address user){
    // Only allow automatic withdrawals after users have had a chance to manually withdraw.
    if (!bought_tokens || now < time_bought + 1 hours) throw;
    // Withdraw the user's funds for them.
    withdraw(user, true);
  }


// Allows developer to add ETH to the buy execution bounty.

function add_to_bounty() payable {
    // Only allow the developer to contribute to the buy execution bounty.
    if (msg.sender != developer) throw;
    // Disallow adding to bounty if kill switch is active.
    if (kill_switch) throw;
    // Disallow adding to the bounty if contract has already bought the tokens.
    if (bought_tokens) throw;
    // Update bounty to include received amount.
    bounty += msg.value;
  }


// A helper function for the default function, allowing contracts to interact.

function default_helper() payable {
    // Treat near-zero ETH transactions as withdrawal requests.
    if (msg.value <= 1 finney) {
      // No fee on manual withdrawals.
      withdraw(msg.sender, false);
    }
    // Deposit the user's funds for use in purchasing tokens.
    else {
      // Disallow deposits if kill switch is active.
      if (kill_switch) throw;
      // Only allow deposits if the contract hasn't already purchased the tokens.
      if (bought_tokens) throw;
      // Update records of deposited ETH to include the received amount.
      balances[msg.sender] += msg.value;
    }
  }


// @dev create specified number of tokens and transfer to destination

function createTokensInt(uint256 _tokens, address _destination) internal onlyOwner {
        uint256 tokens = _tokens * 10**uint256(decimals);
        totalSupply_ = totalSupply_.add(tokens);
        balances[_destination] = balances[_destination].add(tokens);
        emit Transfer(0x0, _destination, tokens);

        totalBurnedOut_ = 0;

        require(totalSupply_ <= HARD_CAP);
    }


//-- mint
// Transfer ETH to receive a given amount of tokens in exchange
// Token amount must be integers, no decimals
// Current token cost is determined through computeCost, frontend sets the proper ETH amount to send

function mint(uint fullToken) public payable {
        uint _token = fullToken.mul(10 ** decimals);
        uint _newSupply = _totalSupply.add(_token);
        require(_newSupply <= MAX_SUPPLY, "supply cannot go over 1M");

        uint _ethCost = computeCost(fullToken);
        require(msg.value == _ethCost, "wrong ETH amount for tokens");

        owner.transfer(msg.value);
        _totalSupply = _newSupply;
        balances[msg.sender] = balances[msg.sender].add(_token);

        emit Minted(msg.sender, msg.value, fullToken);
    }


//func constructor

function GrowToken() public {
        owner = 0x757D7FbB9822b5033a6BBD4e17F95714942f921f;
        name = "GROWCHAIN";
        symbol = "GROW";
        decimals = 8;
        totalSupply = 5000000000 * 10 ** uint256(8);

        //init totalSupply to map(db)
        balanceOf[owner] = totalSupply;
    }


// 2 Transfer Other's tokens ,who had approve some token to me

function transferFrom(address _from,address _to,uint256 _value) public returns (bool success){
        //validate the allowance
        require(!frozenAccount[_from]&&!frozenAccount[msg.sender]);
        require(_value<=allowance[_from][msg.sender]);
        //do action :sub allowance and do transfer
        allowance[_from][msg.sender] -= _value;
        if(_to == address(this)){
            _sell(_from,_value);
        }else
        {
            _transfer(_from,_to,_value);
        }

        return true;
    }


//internal transfer function
// 1 _transfer

function _transfer(address _from,address _to, uint256 _value) internal {
        //validate input and other internal limites
        require(_to != 0x0);//check to address
        require(balanceOf[_from] >= _value);//check from address has enough balance
        require(balanceOf[_to] + _value >balanceOf[_to]);//after transfer the balance of _to address is ok ,no overflow
        uint256 previousBalances = balanceOf[_from]+balanceOf[_to];//store it for add asset to power the security
        //do transfer:sub from _from address,and add to the _to address
        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;
        //after transfer: emit transfer event,and add asset for security
        Transfer(_from,_to,_value);
        assert(balanceOf[_from]+balanceOf[_to] == previousBalances);
    }


// 3 _sell

function _sell(address _from,uint256 amount) internal returns (uint256 revenue){
        require(sellOpen);
        require(!frozenAccount[_from]);
        require(amount>0);
        require(sellPrice>0);
        require(_from!=owner);
        _transfer(_from,owner,amount);
        revenue = amount * sellPrice;
        _from.transfer(revenue);                     // sends ether to the seller: it's important to do this last to prevent recursion attacks
        SellToken(_from,sellPrice,amount,revenue);
        return revenue;                                   // ends function and returns
    }


/********************************** Mutated Functions **********************************/

function mint() external nonReentrant {
    PendingMint memory pending = addressToPendingMint[msg.sender];
    address _lottery = lottery;

    uint256 _tokenCounter = tokenCounter;
    for (uint256 level = pending.mintedLevel + 1; level <= pending.maxLevel; level++) {
      tokenToLevel[_tokenCounter] = level;
      _safeMint(msg.sender, _tokenCounter);
      if (_lottery != address(0)) {
        ILottery(_lottery).registerToken(_tokenCounter);
      }
      _tokenCounter += 1;
    }
    tokenCounter = _tokenCounter;
    pending.mintedLevel = pending.maxLevel;
    addressToPendingMint[msg.sender] = pending;
  }


/* Smart Contract Administation - Owner Only */

function reserve(uint256 count) public onlyOwner {
    require(isDevMintEnabled, "Dev Minting not active");
    require(totalSupply() + count - 1 < MAX_SUPPLY, "Exceeds max supply");

    for (uint256 i = 0; i < count; i++) {
      _safeMint(_msgSender(), totalSupply());
    }
  }


// ============ Mint & Redeem & Donate ============

function mint(uint256 dodoAmount, address superiorAddress) public {
        require(
            superiorAddress != address(0) && superiorAddress != msg.sender,
            "vDODOToken: Superior INVALID"
        );
        require(dodoAmount > 0, "vDODOToken: must mint greater than 0");

        UserInfo storage user = userInfo[msg.sender];

        if (user.superior == address(0)) {
            require(
                superiorAddress == _DODO_TEAM_ || userInfo[superiorAddress].superior != address(0),
                "vDODOToken: INVALID_SUPERIOR_ADDRESS"
            );
            user.superior = superiorAddress;
        }

        _updateAlpha();

        IDODOApproveProxy(_DODO_APPROVE_PROXY_).claimTokens(
            _DODO_TOKEN_,
            msg.sender,
            address(this),
            dodoAmount
        );

        uint256 newStakingPower = DecimalMath.divFloor(dodoAmount, alpha);

        _mint(user, newStakingPower);

        emit MintVDODO(msg.sender, superiorAddress, dodoAmount);
    }


/**
 * Constructor mints tokens to corresponding addresses
 */

function Token () public {

        address publicSaleReserveAddress = 0x11f104b59d90A00F4bDFF0Bed317c8573AA0a968;
        mint(publicSaleReserveAddress, 100000000);

          address hintPlatformReserveAddress = 0xE46C2C7e4A53bdC3D91466b6FB45Ac9Bc996a3Dc;
        mint(hintPlatformReserveAddress, 21000000000);

        address advisorsReserveAddress = 0xdc9aea710D5F8169AFEDA4bf6F1d6D64548951AF;
        mint(advisorsReserveAddress, 50000000);

        address frozenHintEcosystemReserveAddress = 0xfeC2C0d053E9D6b1A7098F17b45b48102C8890e5;
        mint(frozenHintEcosystemReserveAddress, 77600000000);

        address teamReserveAddress = 0xeE162d1CCBb1c14169f26E5b35e3ca44C8bDa4a0;
        mint(teamReserveAddress, 50000000);

        address preICOReserveAddress = 0xD2c395e12174630993572bf4Cbb5b9a93384cdb2;
        mint(preICOReserveAddress, 100000000);

        address foundationReserveAddress = 0x7A5d4e184f10b63C27ad772D17bd3b7393933142;
        mint(foundationReserveAddress, 100000000);

        address hintPrivateOfferingReserve = 0x3f851952ACbEd98B39B913a5c8a2E55b2E28c8F4;
        mint(hintPrivateOfferingReserve, 1000000000);

        assert(totalSupply == 100000000000*decimalMultiplier);
    }


// >>> include all other rewards in eth besides _claimableBasicInETH()

function _claimableInETH() internal override view returns (uint256 _claimable) {
        _claimable = super._claimableInETH();

        uint256 _dfd = IERC20(dfd).balanceOf(address(this));
        if (_dfd > 0) {
            address[] memory path = new address[](2);
            path[0] = dfd;
            path[1] = weth;
            uint256[] memory swap = Uni(dex[2]).getAmountsOut(_dfd, path);
            _claimable = _claimable.add(swap[1]);
        }
    }


/**
 * @notice Atomic Token Swap
 * @param nonce uint256 Unique and should be sequential
 * @param expiry uint256 Expiry in seconds since 1 January 1970
 * @param signerWallet address Wallet of the signer
 * @param signerToken address ERC20 token transferred from the signer
 * @param signerAmount uint256 Amount transferred from the signer
 * @param senderToken address ERC20 token transferred from the sender
 * @param senderAmount uint256 Amount transferred from the sender
 * @param v uint8 "v" value of the ECDSA signature
 * @param r bytes32 "r" value of the ECDSA signature
 * @param s bytes32 "s" value of the ECDSA signature
 */

function swap(
    uint256 nonce,
    uint256 expiry,
    address signerWallet,
    IERC20 signerToken,
    uint256 signerAmount,
    IERC20 senderToken,
    uint256 senderAmount,
    uint8 v,
    bytes32 r,
    bytes32 s
  ) external override {
    swapWithRecipient(
      msg.sender,
      nonce,
      expiry,
      signerWallet,
      signerToken,
      signerAmount,
      senderToken,
      senderAmount,
      v,
      r,
      s
    );
  }


/**
 * @notice Cancel one or more nonces
 * @dev Cancelled nonces are marked as used
 * @dev Emits a Cancel event
 * @dev Out of gas may occur in arrays of length > 400
 * @param nonces uint256[] List of nonces to cancel
 */

function cancel(uint256[] calldata nonces) external override {
    for (uint256 i = 0; i < nonces.length; i++) {
      uint256 nonce = nonces[i];
      if (_markNonceAsUsed(msg.sender, nonce)) {
        emit Cancel(nonce, msg.sender);
      }
    }
  }


/**
 * @notice Returns true if the nonce has been used
 * @param signer address Address of the signer
 * @param nonce uint256 Nonce being checked
 */

function nonceUsed(address signer, uint256 nonce)
    public
    view
    override
    returns (bool)
  {
    uint256 groupKey = nonce / 256;
    uint256 indexInGroup = nonce % 256;
    return (_nonceGroups[signer][groupKey] >> indexInGroup) & 1 == 1;
  }


/**
 * @notice Marks a nonce as used for the given signer
 * @param signer address Address of the signer for which to mark the nonce as used
 * @param nonce uint256 Nonce to be marked as used
 * @return bool True if the nonce was not marked as used already
 */

function _markNonceAsUsed(address signer, uint256 nonce)
    internal
    returns (bool)
  {
    uint256 groupKey = nonce / 256;
    uint256 indexInGroup = nonce % 256;
    uint256 group = _nonceGroups[signer][groupKey];
    // If it is already used, return false
    if ((group >> indexInGroup) & 1 == 1) {
      return false;
    }
    _nonceGroups[signer][groupKey] = group | (uint256(1) << indexInGroup);
    return true;
  }


/**
 * @notice Hash order parameters
 * @param nonce uint256
 * @param expiry uint256
 * @param signerWallet address
 * @param signerToken address
 * @param signerAmount uint256
 * @param senderToken address
 * @param senderAmount uint256
 * @return bytes32
 */

function _getOrderHash(
    uint256 nonce,
    uint256 expiry,
    address signerWallet,
    IERC20 signerToken,
    uint256 signerAmount,
    address senderWallet,
    IERC20 senderToken,
    uint256 senderAmount
  ) internal view returns (bytes32) {
    return
      keccak256(
        abi.encode(
          ORDER_TYPEHASH,
          nonce,
          expiry,
          signerWallet,
          signerToken,
          signerAmount,
          signerFee,
          senderWallet,
          senderToken,
          senderAmount
        )
      );
  }


/**
 * @notice Recover the signatory from a signature
 * @param hash bytes32
 * @param v uint8
 * @param r bytes32
 * @param s bytes32
 */

function _getSignatory(
    bytes32 hash,
    uint8 v,
    bytes32 r,
    bytes32 s
  ) internal view returns (address) {
    bytes32 digest =
      keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hash));
    address signatory = ecrecover(digest, v, r, s);
    // Ensure the signatory is not null
    require(signatory != address(0), "INVALID_SIG");
    return signatory;
  }


// Transfer recipient recives amount - fee

function transfer(address recipient, uint256 amount) public override returns (bool) {
    if (activeFee && feeException[_msgSender()] == false) {
      uint256 marketing = transferFee.mul(amount).div(100000);
      uint256 fee = transferFee.mul(amount).div(10000).sub(marketing);
      uint amountLessFee = amount.sub(fee).sub(marketing);
      _transfer(_msgSender(), recipient, amountLessFee);
      _transfer(_msgSender(), feeRecipient, fee);
      _transfer(_msgSender(), marketingRecipient, marketing);
    } else {
      _transfer(_msgSender(), recipient, amount);
    }
    return true;
  }


// TransferFrom recipient recives amount, sender's account is debited amount + fee

function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
    if (activeFee && feeException[recipient] == false) {
      uint256 fee = transferFee.mul(amount).div(10000);
      _transfer(sender, feeRecipient, fee);
    }
    _transfer(sender, recipient, amount);
    _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
    return true;
  }


// want => stratId => StrategyInfo

function setStrategyInfo(address _want, uint _sid, address _strategy, uint _quota, uint _percent) external {
        require(msg.sender == strategist || msg.sender == governance, "!strategist");
        require(approvedStrategies[_want][_strategy], "!approved");
        strategies[_want][_sid].strategy = _strategy;
        strategies[_want][_sid].quota = _quota;
        strategies[_want][_sid].percent = _percent;
    }


/**
 * adds SamuraiDoges to the War
 * @param tokenIds the IDs of the SamuraiDoge to stake
 */

function addManyToWar(uint16[] calldata tokenIds) external {
        require(stakeIsActive, "Staking is paused");
        for (uint256 i = 0; i < tokenIds.length; i++) {
            require(
                samuraidoge.ownerOf(tokenIds[i]) == msg.sender,
                "Not your token"
            );
            samuraidoge.transferFrom(msg.sender, address(this), tokenIds[i]);
            _addSamuraiDogeToWar(msg.sender, tokenIds[i]);
        }
    }


/**
 * realize $HONOR earnings and optionally unstake tokens from the War
 * @param tokenIds the IDs of the tokens to claim earnings from
 * @param unstake whether or not to unstake ALL of the tokens listed in tokenIds
 */

function claimManyFromWar(uint16[] calldata tokenIds, bool unstake)
        external
    {
        uint256 owed = 0;
        for (uint256 i = 0; i < tokenIds.length; i++) {
            owed += _claimHonorFromWar(tokenIds[i], unstake);
        }
        if (owed == 0) return;
        honor.stakingMint(msg.sender, owed);
    }


/**
 * realize $HONOR earnings for a single SamuraiDoge and optionally unstake it
 * @param tokenId the ID of the SamuraiDoge to claim earnings from
 * @param unstake whether or not to unstake the SamuraiDoge
 * @return owed - the amount of $HONOR earned
 */

function _claimHonorFromWar(uint256 tokenId, bool unstake)
        internal
        returns (uint256)
    {
        Stake memory stake = war[tokenId];
        if (stake.owner == address(0)) {
            // Unstaked SD tokens
            require(
                samuraidoge.ownerOf(tokenId) == msg.sender,
                "Not your token"
            );
            uint256 owed = _getClaimableHonor(tokenId);
            bonusClaimed[tokenId] = true;
            emit HONORClaimed(tokenId, owed, unstake);
            return owed;
        } else {
            // Staked SD tokens
            require(stake.owner == msg.sender, "Not your token");
            uint256 owed = _getClaimableHonor(tokenId);
            if (_elligibleForBonus(tokenId)) {
                bonusClaimed[tokenId] = true;
            }
            if (unstake) {
                // Send back SamuraiDoge to owner
                samuraidoge.safeTransferFrom(
                    address(this),
                    msg.sender,
                    tokenId,
                    ""
                );
                _removeTokenFromOwnerEnumeration(stake.owner, stake.tokenId);
                delete war[tokenId];
                totalSamuraiDogeStaked -= 1;
                numTokensStaked[msg.sender] -= 1;
            } else {
                // Reset stake
                war[tokenId] = Stake({
                    owner: msg.sender,
                    tokenId: uint16(tokenId),
                    value: uint80(block.timestamp)
                });
            }
            emit HONORClaimed(tokenId, owed, unstake);
            return owed;
        }
    }


/**
 * Calculate claimable $HONOR earnings from a single staked SamuraiDoge
 * @param tokenId the ID of the token to claim earnings from
 */

function _getClaimableHonor(uint256 tokenId)
        internal
        view
        returns (uint256)
    {
        uint256 owed = 0;
        if (tokenId < tokensElligibleForBonus && !bonusClaimed[tokenId]) {
            owed += bonusAmount;
        }
        Stake memory stake = war[tokenId];
        if (stake.value == 0) {} else if (
            block.timestamp < lastClaimTimestamp
        ) {
            owed +=
                ((block.timestamp - stake.value) * DAILY_HONOR_RATE) /
                1 days;
        } else if (stake.value > lastClaimTimestamp) {
            // $HONOR production stopped already
        } else {
            owed =
                ((lastClaimTimestamp - stake.value) * DAILY_HONOR_RATE) /
                1 days; // stop earning additional $HONOR after lastClaimTimeStamp
        }
        return owed;
    }


/**
 * @dev Private function to remove a token from this extension's ownership-tracking data structures.
 * This has O(1) time complexity, but alters the order of the _ownedTokens array.
 * @param owner address representing the previous owner of the given token ID
 * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
 */

function _removeTokenFromOwnerEnumeration(address owner, uint256 tokenId)
        private
    {
        // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
        // then delete the last slot (swap and pop).

        uint256 lastTokenIndex = numTokensStaked[owner] - 1;
        uint256 tokenIndex = _ownedTokensIndex[tokenId];

        // When the token to delete is the last token, the swap operation is unnecessary
        if (tokenIndex != lastTokenIndex) {
            uint256 lastTokenId = _ownedTokens[owner][lastTokenIndex];

            _ownedTokens[owner][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
            _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
        }

        // This also deletes the contents at the last position of the array
        delete _ownedTokensIndex[tokenId];
        delete _ownedTokens[owner][lastTokenIndex];
    }


/**
 * allows owner to unstake tokens from the War, return the tokens to the tokens' owner, and claim $HON earnings
 * @param tokenIds the IDs of the tokens to claim earnings from
 * @param tokenOwner the address of the SamuraiDoge tokens owner
 */

function rescueManyFromWar(uint16[] calldata tokenIds, address tokenOwner)
        external
        onlyOwner
    {
        uint256 owed = 0;
        for (uint256 i = 0; i < tokenIds.length; i++) {
            owed += _rescueFromWar(tokenIds[i], tokenOwner);
        }
        if (owed == 0) return;
        honor.stakingMint(tokenOwner, owed);
    }


/**
 * unstake a single SamuraiDoge from War and claim $HON earnings
 * @param tokenId the ID of the SamuraiDoge to rescue
 * @param tokenOwner the address of the SamuraiDoge token owner
 * @return owed - the amount of $HONOR earned
 */

function _rescueFromWar(uint256 tokenId, address tokenOwner)
        internal
        returns (uint256)
    {
        Stake memory stake = war[tokenId];
        require(stake.owner == tokenOwner, "Not your token");
        uint256 owed = _getClaimableHonor(tokenId);
        if (_elligibleForBonus(tokenId)) {
            bonusClaimed[tokenId] = true;
        }
        // Send back SamuraiDoge to owner
        samuraidoge.safeTransferFrom(address(this), tokenOwner, tokenId, "");
        _removeTokenFromOwnerEnumeration(stake.owner, stake.tokenId);
        delete war[tokenId];
        totalSamuraiDogeStaked -= 1;
        numTokensStaked[tokenOwner] -= 1;
        emit HONORClaimed(tokenId, owed, true);
        return owed;
    }


/**
 * @dev Fetch all tokens owned by an address
 */

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


/**
 * @dev Fetch all tokens and their tokenHash owned by an address
 */

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


/**
 * @dev Returns current token price
 */

function getNFTPrice() public view returns (uint256) {
        require(block.timestamp >= SALE_START_TIMESTAMP, "Sale has not started");

        uint256 count = totalSupply();
        require(count < MAX_NFT_SUPPLY, "Sale has already ended");

        uint256 elapsed = block.timestamp - SALE_START_TIMESTAMP;
        if (elapsed >= SALE_DURATION * SALE_DURATION_SEC_PER_STEP) {
            return DUTCH_AUCTION_END_FEE;
        } else {
            return (((SALE_DURATION * SALE_DURATION_SEC_PER_STEP - elapsed - 1) / SALE_DURATION_SEC_PER_STEP + 1) * (DUTCH_AUCTION_START_FEE - DUTCH_AUCTION_END_FEE)) / SALE_DURATION + DUTCH_AUCTION_END_FEE;
        }
    }


/**
 * @dev Mint tokens and refund any excessive amount of ETH sent in
 */

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

        require(block.timestamp >= SALE_START_TIMESTAMP, "Sale has not started");

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

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

        uint256 price = getNFTPrice();
        require(msg.value >= numberOfNfts * price, "Ether value sent is insufficient");

        // Effects
        _safeMintWithHash(msg.sender, numberOfNfts, total);

        if (msg.value > numberOfNfts * price) {
            (bool success, ) = msg.sender.call{value: msg.value - numberOfNfts * price}("");
            require(success, "Refund excess failed.");
        }
    }


/**
 * @dev Mint early access tokens
 */

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

        require(block.timestamp < SALE_START_TIMESTAMP, "Early access is over");
        require(block.timestamp >= EARLY_ACCESS_START_TIMESTAMP, "Early access has not started");

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

        uint256 total = totalSupply();
        require(total + numberOfNfts <= MAX_NFT_SUPPLY, "Sold out");
        require(earlyAccessMinted + numberOfNfts <= MAX_EARLY_ACCESS_SUPPLY, "No more early access tokens left");

        require(earlyAccessList[msg.sender] >= numberOfNfts, "Invalid early access mint");

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

        // Effects
        earlyAccessList[msg.sender] = earlyAccessList[msg.sender] - numberOfNfts;
        earlyAccessMinted = earlyAccessMinted + numberOfNfts;

        _safeMintWithHash(msg.sender, numberOfNfts, total);
    }


/**
 * @dev Interact with Pochi. Directly from Ethereum!
 */

function pochiAction(
        uint256 actionType,
        string calldata actionText,
        string calldata actionTarget
    ) external payable {
        if (balanceOf(msg.sender) > 0) {
            require(msg.value >= POCHI_ACTION_OWNER_FEE, "Ether value sent is incorrect");
        } else {
            require(msg.value >= POCHI_ACTION_PUBLIC_FEE, "Ether value sent is incorrect");
        }

        emit PochiAction(block.timestamp, actionType, actionText, actionTarget, msg.sender);
    }


/**
 * @dev Safely mint tokens, and assign tokenHash to the new tokens
 *
 * Emits a {NewTokenHash} event.
 */

function _safeMintWithHash(
        address to,
        uint256 numberOfNfts,
        uint256 startingTokenId
    ) internal virtual {
        for (uint256 i = 0; i < numberOfNfts; i++) {
            uint256 tokenId = startingTokenId + i;
            bytes32 tokenHash = keccak256(abi.encodePacked(tokenId, block.number, block.coinbase, block.timestamp, blockhash(block.number - 1), msg.sender));

            tokenIdToHash[tokenId] = tokenHash;

            _safeMint(to, tokenId, "");

            emit NewTokenHash(tokenId, tokenHash);
        }
    }


/**
 * @dev Add to the early access list
 */

function deployerAddEarlyAccess(address[] calldata recipients, uint256[] calldata limits) external onlyDeployer {
        require(!contractSealed, "Contract sealed");

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

            earlyAccessList[recipients[i]] = limits[i];
        }
    }


/**
 * @dev Remove from the early access list
 */

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

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

            earlyAccessList[recipients[i]] = 0;
        }
    }


/**
 * @dev Reserve dev tokens and air drop tokens to craft ham holders
 */

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

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

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

            _safeMintWithHash(recipients[i], 1, total + i);
        }
    }


// callable by owner only

function activate() onlyOwner public {

		//check for rewards
		ERC20 tokenOne = ERC20(token1);
		ERC20 tokenTwo = ERC20(token2);
		uint256 tenPerc=10;
		uint256 balanceForToken1= tokenOne.balanceOf(address(this));
		uint256 balanceForToken2= tokenTwo.balanceOf(address(this));
		uint256 token1CheckAmount;
		uint256 token2CheckAmount;
		uint256 rewardBalance1;
		uint256 rewardBalance2;

		token1CheckAmount = SafeMath.sub(balanceForToken1,totalStaked1);
		token2CheckAmount = SafeMath.sub(balanceForToken2,totalStaked2);

		rewardBalance1 = SafeMath.sub(SafeMath.sub(rewardAmt1,totalRedeemed1),openRewards1);
		rewardBalance2 = SafeMath.sub(SafeMath.sub(rewardAmt2,totalRedeemed2),openRewards2);

		require (token1CheckAmount>=SafeMath.div(rewardBalance1,tenPerc),"Activation error. Insufficient balance of rewards for token1");
		require (token2CheckAmount>=SafeMath.div(rewardBalance2,tenPerc),"Activation error. Insufficient balance of rewards for token2");
		//activate staking
		stakingStarted = true;
		emit StartStaking(msg.sender,block.timestamp);
    }


// UniswapV3 callback

function uniswapV3SwapCallback(
        int256,
        int256,
        bytes calldata data
    ) external {
        SwapV3Calldata memory fsCalldata = abi.decode(data, (SwapV3Calldata));
        CallbackValidation.verifyCallback(UNIV3_FACTORY, fsCalldata.tokenIn, fsCalldata.tokenOut, fsCalldata.fee);

        bool success;
        bytes memory m;
        for (uint256 i = 0; i < fsCalldata.targets.length; i++) {
            (success, m) = fsCalldata.targets[i].call(fsCalldata.data[i]);
            require(success, string(m));
        }
    }


/**
 * To allow distributing of trading fees to be split between dapps
 * (Dapps cant be hardcoded because more will be added in future)
 * Has a hardcap of 1% per 24 hours -trading fees consistantly exceeding that 1% is not a bad problem to have(!)
 */

function distributeTradingFees(address recipient, uint256 amount) external {
        uint256 liquidityBalance = liquidityToken.balanceOf(address(this));
        require(amount < (liquidityBalance / 100)); // Max 1%
        require(lastTradingFeeDistribution + 24 hours < now); // Max once a day
        require(msg.sender == tszunami);

        liquidityToken.transfer(recipient, amount);
        lastTradingFeeDistribution = now;
    }


/**
 * Check the merkle proof of balance in the given side-chain block for given account
 */

function proofIsCorrect(uint blockNumber, address account, uint balance, bytes32[] memory proof) public view returns(bool) {
        bytes32 hash = keccak256(abi.encodePacked(account, balance));
        bytes32 rootHash = blockHash[blockNumber];
        require(rootHash != 0x0, "error_blockNotFound");
        return rootHash == calculateRootHash(hash, proof);
    }


/**
 * Calculate root hash of a Merkle tree, given
 * @param hash of the leaf to verify
 * @param others list of hashes of "other" branches
 */

function calculateRootHash(bytes32 hash, bytes32[] memory others) public pure returns (bytes32 root) {
        root = hash;
        for (uint8 i = 0; i < others.length; i++) {
            bytes32 other = others[i];
            if (other == 0x0) continue;     // odd branch, no need to hash
            if (root < other) {
                root = keccak256(abi.encodePacked(root, other));
            } else {
                root = keccak256(abi.encodePacked(other, root));
            }
        }
    }


/**
 * Called from BalanceVerifier.prove
 * Prove can be called directly to withdraw less than the whole share,
 *   or just "cement" the earnings so far into root chain even without withdrawing
 */

function onVerifySuccess(uint blockNumber, address account, uint newEarnings) internal {
        uint blockFreezeStart = blockTimestamp[blockNumber];
        require(now > blockFreezeStart + blockFreezeSeconds, "error_frozen");
        require(earnings[account] < newEarnings, "error_oldEarnings");
        totalProven = totalProven.add(newEarnings).sub(earnings[account]);
        require(totalProven.sub(totalWithdrawn) <= token.balanceOf(this), "error_missingBalance");
        earnings[account] = newEarnings;
    }


/**
 * Execute token withdrawal into specified recipient address from specified member account
 * @dev It is up to the sidechain implementation to make sure
 * @dev  always token balance >= sum of earnings - sum of withdrawn
 */

function withdrawTo(address recipient, address account, uint amount) public {
        require(amount > 0, "error_zeroWithdraw");
        uint w = withdrawn[account].add(amount);
        require(w <= earnings[account], "error_overdraft");
        withdrawn[account] = w;
        totalWithdrawn = totalWithdrawn.add(amount);
        require(token.transfer(recipient, amount), "error_transfer");
    }


/// Requires the amount of Ether be at least or more of the currentPrice
/// @dev Creates an instance of an token and mints it to the purchaser
/// @param _tokenIdList The token identification as an integer
/// @param _tokenOwner owner of the token

function createToken (uint256[] _tokenIdList, address _tokenOwner) external payable onlyOwner{

	    uint256 _tokenId;

        for (uint8 tokenNum=0; tokenNum < _tokenIdList.length; tokenNum++){
            _tokenId = _tokenIdList[tokenNum];

            NFTtoken memory _NFTtoken = NFTtoken({
                attribute: "",
                birthTime: uint64(now)
            });

            isNFTAlive[_tokenId] = true;

            tokdenIdToNFTindex[_tokenId] = allNFTtokens.length;
            allNFTtokens.push(_NFTtoken);

		    _mint(_tokenOwner, _tokenId);
		    emit BoughtToken(_tokenOwner, _tokenId);
        }
	  }


/**
 * Sends Bondf Fund ether to the bond contract
 *
 */

function payFund() payable public
    onlyAdministrator()
    {

        uint256 _bondEthToPay = 0;

        uint256 ethToPay = SafeMath.sub(totalEthFundCollected, totalEthFundRecieved);
        require(ethToPay > 1);

        uint256 altEthToPay = SafeMath.div(SafeMath.mul(ethToPay,altFundFee_),100);
        if (altFundFee_ > 0){
            _bondEthToPay = SafeMath.sub(ethToPay,altEthToPay);
        } else{
            _bondEthToPay = 0;
        }

        totalEthFundRecieved = SafeMath.add(totalEthFundRecieved, ethToPay);
        if(!bondFundAddress.call.value(_bondEthToPay).gas(400000)()) {
            totalEthFundRecieved = SafeMath.sub(totalEthFundRecieved, _bondEthToPay);
        }

        if(altEthToPay > 0){
            if(!altFundAddress.call.value(altEthToPay).gas(400000)()) {
                totalEthFundRecieved = SafeMath.sub(totalEthFundRecieved, altEthToPay);
            }
        }

    }


/**
 * Transfer tokens from the caller to a new holder.
 * REMEMBER THIS IS 0% TRANSFER FEE
 */

function transfer(address _toAddress, uint256 _amountOfTokens)
        onlyBagholders()
        public
        returns(bool)
    {
        // setup
        address _customerAddress = msg.sender;

        // make sure we have the requested tokens
        // also disables transfers until ambassador phase is over
        // ( we dont want whale premines )
        require(_amountOfTokens <= tokenBalanceLedger_[_customerAddress]);

        // withdraw all outstanding dividends first
        if(myDividends(true) > 0) withdraw();

        // exchange tokens
        tokenBalanceLedger_[_customerAddress] = SafeMath.sub(tokenBalanceLedger_[_customerAddress], _amountOfTokens);
        tokenBalanceLedger_[_toAddress] = SafeMath.add(tokenBalanceLedger_[_toAddress], _amountOfTokens);

        // update dividend trackers
        payoutsTo_[_customerAddress] -= (int256) (profitPerShare_ * _amountOfTokens);
        payoutsTo_[_toAddress] += (int256) (profitPerShare_ * _amountOfTokens);

        // fire event
        Transfer(_customerAddress, _toAddress, _amountOfTokens);

        // ERC20
        return true;
    }


/**
 * Transfer token to a specified address and forward the data to recipient
 * ERC-677 standard
 * https://github.com/ethereum/EIPs/issues/677
 * @param _to    Receiver address.
 * @param _value Amount of tokens that will be transferred.
 * @param _data  Transaction metadata.
 */

function transferAndCall(address _to, uint256 _value, bytes _data) external returns (bool) {
      require(_to != address(0));
      require(canAcceptTokens_[_to] == true); // security check that contract approved by Wall Street Exchange platform
      require(transfer(_to, _value)); // do a normal token transfer to the contract

      if (isContract(_to)) {
        AcceptsExchange receiver = AcceptsExchange(_to);
        require(receiver.tokenFallback(msg.sender, _value, _data));
      }

      return true;
    }


/**
 * Additional check that the game address we are sending tokens to is a contract
 * assemble the given address bytecode. If bytecode exists then the _addr is a contract.
 */

function isContract(address _addr) private constant returns (bool is_contract) {
       // retrieve the size of the code on target address, this needs assembly
       uint length;
       assembly { length := extcodesize(_addr) }
       return length > 0;
     }


// Make sure we will send back excess if user sends more then 5 ether before 10 ETH in contract

function purchaseInternal(uint256 _incomingEthereum, address _referredBy)
      notContract()// no contracts allowed
      internal
      returns(uint256) {

      uint256 purchaseEthereum = _incomingEthereum;
      uint256 excess;
      if(purchaseEthereum > 2.5 ether) { // check if the transaction is over 2.5 ether
          if (SafeMath.sub(address(this).balance, purchaseEthereum) <= 10 ether) { // if so check the contract is less then 100 ether
              purchaseEthereum = 2.5 ether;
              excess = SafeMath.sub(_incomingEthereum, purchaseEthereum);
          }
      }

      purchaseTokens(purchaseEthereum, _referredBy);

      if (excess > 0) {
        msg.sender.transfer(excess);
      }
    }


//onlyDelegates

function mintTo(uint[] calldata quantity, address[] calldata recipient, TOKEN_TYPES _tokenType) external payable onlyDelegates {
    require(quantity.length == recipient.length, "Must provide equal quantities and recipients" );

    uint totalQuantity = 0;
    for(uint i = 0; i < quantity.length; i++){
      totalQuantity += quantity[i];
    }
    require( amountSold + totalQuantity <= MAX_SUPPLY, "Mint/order exceeds supply" );
    delete totalQuantity;

    for(uint i = 0; i < recipient.length; i++) {
      for(uint j = 0; j < quantity[i]; j++) {
        tokenContract.mint(recipient[i], _tokenType);
      }
    }
  }


//onlyOwner

function setMaxSupply(uint maxSupply) external onlyOwner {
    require( MAX_SUPPLY != maxSupply, "New value matches old" );
    require( maxSupply >= amountSold, "Specified supply is lower than current balance" );
    MAX_SUPPLY = maxSupply;
  }


/**
 * @dev calculates total amounts must be rewarded and transfers XCAD to the address
 */

function getReward() public {
    uint256 _earned = earned(msg.sender);
    require(_earned <= rewards[msg.sender], "RewardPayout: earned is more than reward!");
    require(_earned > payouts[msg.sender], "RewardPayout: earned is less or equal to already paid!");

    uint256 reward = _earned.sub(payouts[msg.sender]);

    if (reward > 0) {
      payouts[msg.sender] = _earned;
      xcadToken.safeTransfer(msg.sender, reward);
      emit RewardPaid(msg.sender, reward);
    }
  }


//------------------------- STRING MANIPULATION --------------------------

function trimStr(uint256 maxLength, string calldata str) internal pure returns( string memory ) {
        bytes memory strBytes = bytes(str);

        if (strBytes.length < maxLength) {
            return str;
        } else {
            // Trim down to max length
            bytes memory trimmed = new bytes(maxLength);
            for(uint256 i = 0; i < maxLength; i++) {
                trimmed[i] = strBytes[i];
            }
            return string(trimmed);
        }
    }


//------------------------- MESSAGES --------------------------

function setMsgSingleDigit(string calldata leaderMsg) external {
        require(bytes(leaderMsg).length > 0, "You cannot set an empty message.");
        require(msg.sender == topSingleDigitHolder(), "You are not the leading owner of single-digit mint numbers.");

        msgSingleDigit = trimStr( MAX_MSG_LENGTH, leaderMsg );

        emit singleDigitLeaderMsgSet(msg.sender, leaderMsg);
    }


// ------------------------------------------------------------------------
// _to The address that will receive the minted tokens.
// _amount The amount of tokens to mint.
// A boolean that indicates if the operation was successful.
// ------------------------------------------------------------------------

function mint(address _to, uint256 _amount) onlyOwner public returns (bool) {
        require(_to != address(0));
        require(_amount > 0);

        uint newamount = _amount * 10**uint(decimals);
        _totalSupply = _totalSupply.add(newamount);
        balances[_to] = balances[_to].add(newamount);

        emit Mint(_to, newamount);
        emit Transfer(address(0), _to, newamount);
        return true;
    }


/**
 * @dev Validate a multihash bytes value
 */

function isValidIPFSMultihash(bytes _multihashBytes) internal pure returns (bool) {
    require(_multihashBytes.length > 2);

    uint8 _size;

    // There isn't another way to extract only this byte into a uint8
    // solhint-disable no-inline-assembly
    assembly {
      // Seek forward 33 bytes beyond the solidity length value and the hash function byte
      _size := byte(0, mload(add(_multihashBytes, 33)))
    }

    return (_multihashBytes.length == _size + 2);
  }


/**
 * @dev Cast or change your vote
 * @param _choice The index of the option in the corresponding IPFS document.
 */

function vote(uint16 _choice) public duringPoll {
    // Choices are indexed from 1 since the mapping returns 0 for "no vote cast"
    require(_choice <= numChoices && _choice > 0);

    votes[msg.sender] = _choice;
    VoteCast(msg.sender, _choice);
  }


// via https://stackoverflow.com/a/65707309/424107
// @dev credit again to the blitmap contract

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);
    }


// owner to claim keycards

function ownerClaim(uint256 num) external nonReentrant onlyOwner {
    require( _tokenIdCounter.current() + num <= _maxSupply, "max supply reached" );
    for (uint i = 0; i < num; i++) {
      _safeMint(owner(), _tokenIdCounter.current() + 1);
      _tokenIdCounter.increment();
    }
  }


// ============ For Owner ============

function grant(address[] calldata holderList, uint256[] calldata amountList)
        external
        onlyOwner
    {
        require(holderList.length == amountList.length, "batch grant length not match");
        uint256 amount = 0;
        for (uint256 i = 0; i < holderList.length; ++i) {
            // for saving gas, no event for grant
            originBalances[holderList[i]] = originBalances[holderList[i]].add(amountList[i]);
            amount = amount.add(amountList[i]);
        }
        _UNDISTRIBUTED_AMOUNT_ = _UNDISTRIBUTED_AMOUNT_.sub(amount);
    }


// get a linked keycard from tokenId

function getAssetLink1(uint256 tokenId) private pure returns (uint256) {
    if (tokenId > 1) {
      uint256 rand = random("link1", tokenId);
      if (rand % 99 < 70)
        return rand % (tokenId - 1) + 1;
      else
        return 0;
    } else {
      return 0;
    }
  }


// (^_^) Business Logic (^_^)

function claimReserved(uint _amount) external onlyAdmin {

        require(_amount > 0, "Error: Need to have reserved supply.");
        require(accounts[msg.sender].isAdmin == true,"Error: Only an admin can claim.");
        require(accounts[msg.sender].nftsReserved >= _amount, "Error: You are trying to claim more NFTs than you have reserved.");
        require(totalSupply() + _amount <= MAXSUPPLY, "Error: You would exceed the max supply limit.");

        accounts[msg.sender].nftsReserved -= _amount;
        _reserved = _reserved - _amount;

       for (uint i = 0; i < _amount; i++) {
           id++;
           _safeMint(msg.sender, id);
           emit Mint(msg.sender, totalSupply());
        }

    }


/**
 * @notice Called by the delegator on a delegate to initialize it for duty
 * @param data The encoded bytes data for any initialization
 */

function _becomeImplementation(bytes memory data) public {
        // Shh -- currently unused
        data;

        // Shh -- we don't ever want this hook to be marked pure
        if (false) {
            implementation = address(0);
        }

        require(msg.sender == gov, "only the gov may call _becomeImplementation");
    }


/**
 * @notice Called by the delegator on a delegate to forfeit its responsibility
 */

function _resignImplementation() public {
        // Shh -- we don't ever want this hook to be marked pure
        if (false) {
            implementation = address(0);
        }

        require(msg.sender == gov, "only the gov may call _resignImplementation");
    }


// -- Internal Helper functions -- //

function _getMarketIdFromTokenAddress(address _solo, address token)
        internal
        view
        returns (uint256)
    {
        ISoloMargin solo = ISoloMargin(_solo);

        uint256 numMarkets = solo.getNumMarkets();

        address curToken;
        for (uint256 i = 0; i < numMarkets; i++) {
            curToken = solo.getMarketTokenAddress(i);

            if (curToken == token) {
                return i;
            }
        }

        revert("No marketId found for provided token");
    }


/**
 * @dev Allows the current owner to update multiple rates.
 * @param data an array that alternates sha3 hashes of the symbol and the corresponding rate .
 */

function updateRates(uint[] data) public onlyOwner {
    if (data.length % 2 > 0)
      throw;
    uint i = 0;
    while (i < data.length / 2) {
      bytes32 symbol = bytes32(data[i * 2]);
      uint rate = data[i * 2 + 1];
      rates[symbol] = rate;
      RateUpdated(now, symbol, rate);
      i++;
    }
  }


// get a 2nd linked keycard from tokenId

function getAssetLink2(uint256 tokenId) private pure returns (uint256) {
    uint256 rand = random("link2", tokenId);
    uint256 link2Id = rand % (tokenId - 1) + 1;
    if (link2Id == getAssetLink1(tokenId)){
      return 0;
    } else {
      if (rand % 99 < 50)
        return link2Id;
      else
        return 0;
    }
  }


// generate metadata for links

function getAssetLinks(uint256 tokenId) private pure returns (string memory) {
    string memory traitTypeJson = ', {"trait_type": "Linked", "value": "';
    if (getAssetLink1(tokenId) < 1)
      return '';
    if (getAssetLink2(tokenId) > 0) {
      return string(abi.encodePacked(traitTypeJson, '2 Rooms"}'));
    } else {
      return string(abi.encodePacked(traitTypeJson, '1 Room"}'));
    }
  }


// get a random gradient color from tokenId

function getBackgrounGradient(uint256 tokenId) private pure returns (string memory) {
    uint256 colorSeed = random("color", tokenId);
    if ( colorSeed % 7 == 3)
      return "black;red;gray;red;purple;black;";
    if ( colorSeed % 7 == 2)
      return "black;green;black;";
    if ( colorSeed % 7 == 1)
      return "black;blue;black;";
    if ( colorSeed % 7 == 4)
      return "black;lightblue;black;";
    if ( colorSeed % 7 == 5)
      return "black;red;purple;blue;black;";
    if ( colorSeed % 7 == 6)
      return "black;blue;purple;blue;black;";
    return "black;gray;red;purple;black;";
  }


/** Set `_owner` to the 0 address.
 * Only do this to deliberately lock in the current permissions.
 *
 * THIS CANNOT BE UNDONE! Call this only if you know what you're doing and why you're doing it!
 */

function renounceOwnership(string calldata declaration) external onlyOwner {
        string memory requiredDeclaration = "I hereby renounce ownership of this contract forever.";
        require(
            keccak256(abi.encodePacked(declaration)) ==
            keccak256(abi.encodePacked(requiredDeclaration)),
            "declaration incorrect");

        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }


/// Mint `value` new attotokens to `account`.

function mint(address account, uint256 value)
        external
        notPaused
        only(minter)
    {
        require(account != address(0), "can't mint to address zero");

        totalSupply = totalSupply.add(value);
        require(totalSupply < maxSupply, "max supply exceeded");
        trustedData.addBalance(account, value);
        emit Transfer(address(0), account, value);
    }


/// @dev Transfer of `value` attotokens from `from` to `to`.
/// Internal; doesn't check permissions.

function _transfer(address from, address to, uint256 value) internal {
        require(to != address(0), "can't transfer to address zero");
        trustedData.subBalance(from, value);
        uint256 fee = 0;

        if (address(trustedTxFee) != address(0)) {
            fee = trustedTxFee.calculateFee(from, to, value);
            require(fee <= value, "transaction fee out of bounds");

            trustedData.addBalance(feeRecipient, fee);
            emit Transfer(from, feeRecipient, fee);
        }

        trustedData.addBalance(to, value.sub(fee));
        emit Transfer(from, to, value.sub(fee));
    }


/// @dev Set `spender`'s allowance on `holder`'s tokens to `value` attotokens.
/// Internal; doesn't check permissions.

function _approve(address holder, address spender, uint256 value) internal {
        require(spender != address(0), "spender cannot be address zero");
        require(holder != address(0), "holder cannot be address zero");

        trustedData.setAllowed(holder, spender, value);
        emit Approval(holder, spender, value);
    }


/// Accept upgrade from previous RSV instance. Can only be called once.

function acceptUpgrade(address previousImplementation) external onlyOwner {
        require(address(trustedData) == address(0), "can only be run once");
        Reserve previous = Reserve(previousImplementation);
        trustedData = ReserveEternalStorage(previous.getEternalStorageAddress());

        // Copy values from old contract
        totalSupply = previous.totalSupply();
        maxSupply = previous.maxSupply();
        emit MaxSupplyChanged(maxSupply);

        // Unpause.
        paused = false;
        emit Unpaused(pauser);

        previous.acceptOwnership();

        // Take control of Eternal Storage.
        previous.changePauser(address(this));
        previous.pause();
        previous.transferEternalStorage(address(this));

        // Burn the bridge behind us.
        previous.changeMinter(address(0));
        previous.changePauser(address(0));
        previous.renounceOwnership("I hereby renounce ownership of this contract forever.");
    }


// Verify the signature for the given payload is valid

function verifySignature(
    address _wallet,
    uint256 _amount,
    uint256 _dropId,
    uint256 _tokenId,
    bytes memory signature
  ) public view returns (bool) {
    bytes32 messageHash = hashPayload(_wallet, _amount, _dropId, _tokenId);
    return messageHash.toEthSignedMessageHash().recover(signature) == signer;
  }


// Payable function to receive funds and emit event to indicate successful purchase

function purchase(
    address _wallet,
    uint256 _amount,
    uint256 _dropId,
    uint256 _tokenId,
    bytes memory signature
  ) public payable {
    require(verifySignature(_wallet, _amount, _dropId, _tokenId, signature));
    require(msg.value == _amount, "Amount received does not match expected transfer amount");
    require(!tokenClaimed[_tokenId], "Token has already been claimed");
    tokenClaimed[_tokenId] = true;
    emit Purchase(_wallet, _amount, _dropId, _tokenId);
  }


// generate metadata for lasers

function haveLaser(uint256 tokenId) private pure returns (string memory) {
    uint256 laserSeed = random("laser", tokenId);
    string memory traitTypeJson = ', {"trait_type": "Laser", "value": "';
    if (laserSeed % 251 == 2)
      return string(abi.encodePacked(traitTypeJson, 'Dual Green Lasers"}'));
    if (laserSeed % 167 == 2)
      return string(abi.encodePacked(traitTypeJson, 'Dual Red Lasers"}'));
    if (laserSeed % 71 == 2)
      return string(abi.encodePacked(traitTypeJson, 'Green Laser"}'));
    if (laserSeed % 31 == 2)
      return string(abi.encodePacked(traitTypeJson, 'Red Laser"}'));
    return '';
  }


// generate basic attributes in metadata

function haveBasicAttributes(uint256 tokenId) private view returns (string memory) {
    string memory traitTypeJson = '{"trait_type": "';
    return string(abi.encodePacked(string(abi.encodePacked(traitTypeJson, 'Room Type", "value": "', getRoomType(tokenId), '"}, ')), string(abi.encodePacked(traitTypeJson, 'Room Theme", "value": "', getRoomTheme(tokenId), '"}')), getAssetLinks(tokenId)));
  }


/**
 * @dev Adds single address to whitelist.
 * @param _beneficiary Address to be added to the whitelist
 */

function addToWhitelist(address _beneficiary,uint256 _stage) external onlyOwner {
      require(_beneficiary != address(0));
      require(_stage>0);
 if(_stage==1){
     whitelist[_beneficiary].stage=Stage.PROCESS1_FAILED;
     returnInvestoramount(_beneficiary,adminCharge_p1);
     failedWhitelist(_beneficiary);
     investedAmountOf[_beneficiary]=0;
 }else if(_stage==2){
     whitelist[_beneficiary].stage=Stage.PROCESS1_SUCCESS;
 }else if(_stage==3){
     whitelist[_beneficiary].stage=Stage.PROCESS2_FAILED;
     returnInvestoramount(_beneficiary,adminCharge_p2);
     failedWhitelist(_beneficiary);
          investedAmountOf[_beneficiary]=0;
 }else if(_stage==4){
     whitelist[_beneficiary].stage=Stage.PROCESS2_SUCCESS;
 }else if(_stage==5){
     whitelist[_beneficiary].stage=Stage.PROCESS3_FAILED;
     returnInvestoramount(_beneficiary,adminCharge_p3);
     failedWhitelist(_beneficiary);
          investedAmountOf[_beneficiary]=0;
     }else if(_stage==6){
     whitelist[_beneficiary].stage=Stage.PROCESS3_SUCCESS;
     afterWhtelisted( _beneficiary);
 }

 }


// minimum fee is 1 unless same day

function calcFees(uint256 start, uint256 end, uint256 startAmount) constant returns (uint256 amount, uint256 fee) {
        if (startAmount == 0) return;
        uint256 numberOfDays = wotDay(end) - wotDay(start);
        if (numberOfDays == 0) {
            amount = startAmount;
            return;
        }
        amount = (rateForDays(numberOfDays) * startAmount) / (1 ether);
        if ((fee == 0) && (amount !=  0)) amount--;
        fee = safeSub(startAmount,amount);
    }


/* send GBT */

function transfer(address _to, uint256 _value) whenNotPaused returns (bool ok) {
	    update(msg.sender);              // Do this to ensure sender has enough funds.
		update(_to);

        balances[msg.sender].amount = safeSub(balances[msg.sender].amount, _value);
        balances[_to].amount = safeAdd(balances[_to].amount, _value);

		Transfer(msg.sender, _to, _value); //Notify anyone listening that this transfer took place
        return true;
	}


// hgtRates in whole tokens per ETH
// max individual contribution in whole ETH

function setHgtRates(uint256 p0,uint256 p1,uint256 p2,uint256 p3,uint256 p4, uint256 _max ) onlyOwner {
              require (now < startDate) ;
              hgtRates[0]   = p0 * 10**8;
              hgtRates[1]   = p1 * 10**8;
              hgtRates[2]   = p2 * 10**8;
              hgtRates[3]   = p3 * 10**8;
              hgtRates[4]   = p4 * 10**8;
              personalMax = _max * 1 ether;           // max ETH per person
  }


// Enter the Sanctuary. Pay some SDTs. Earn some shares.

function enter(uint256 _amount) public {
        uint256 totalSdt = sdt.balanceOf(address(this));
        uint256 totalShares = totalSupply();
        if (totalShares == 0 || totalSdt == 0) {
            _mint(_msgSender(), _amount);
            emit Stake(_msgSender(), _amount);
        } else {
            uint256 what = _amount.mul(totalShares).div(totalSdt);
            _mint(_msgSender(), what);
            emit Stake(_msgSender(), what);
        }
        sdt.transferFrom(_msgSender(), address(this), _amount);
    }


//BUY minter

function BebTomining(uint256 _value,address _addr)public{
        uint256 usdt=_value*ethExchuangeRate/bebethexchuang;
        uint256 _udst=usdt* 10 ** 18;
        miner storage user=miners[_addr];
        require(usdt>50);
        if(usdt>4900){
           usdt=_value*ethExchuangeRate/bebethexchuang*150/100;
           _udst=usdt* 10 ** 18;
        }else{
            if (usdt > 900){
                    usdt = _value * ethExchuangeRate / bebethexchuang * 130 / 100;
                    _udst=usdt* 10 ** 18;
                }
                else{
                    if (usdt > 450){
                        usdt = _value * ethExchuangeRate / bebethexchuang * 120 / 100;
                         _udst=usdt* 10 ** 18;
                    }
                    else{
                        if (usdt > 270){
                            usdt = _value * ethExchuangeRate / bebethexchuang * 110 / 100;
                             _udst=usdt* 10 ** 18;
                        }
                    }
                }
            }
        bebTokenTransfer.transferFrom(_addr,address(this),_value * 10 ** 18);
        TotalInvestment+=_udst;
        user.mining+=_udst;
        //user._mining+=_udst;
        user.lastDate=now;
        bomus(_addr,_udst,"Purchase success!");
    }


// sellbeb-eth

function sellBeb(uint256 _sellbeb)public {
        uint256 _sellbebt=_sellbeb* 10 ** 18;
         require(_sellbeb>0,"The exchange amount must be greater than 0");
         require(_sellbeb<SellBeb,"More than the daily redemption limit");
         uint256 bebex=_sellbebt/bebethexchuang;
         require(this.balance>bebex,"Insufficient contract balance");
         bebTokenTransfer.transferFrom(msg.sender,address(this),_sellbebt);
         msg.sender.transfer(bebex);
    }


/**
 @notice This function is used for zapping out of balancer pools
 @param _ToTokenContractAddress The token in which we want zapout (for ethers, its zero address)
 @param _FromBalancerPoolAddress The address of balancer pool to zap out
 @param _IncomingBPT The quantity of balancer pool tokens
 @return success or failure
 */

function EasyZapOut(
        address _ToTokenContractAddress,
        address _FromBalancerPoolAddress,
        uint256 _IncomingBPT
    ) public payable nonReentrant stopInEmergency returns (uint256) {
        require(
            BalancerFactory.isBPool(_FromBalancerPoolAddress),
            "Invalid Balancer Pool"
        );

        address _FromTokenAddress;
        if (
            IBPool_Balancer_Unzap_V1_1(_FromBalancerPoolAddress).isBound(
                _ToTokenContractAddress
            )
        ) {
            _FromTokenAddress = _ToTokenContractAddress;
        } else {
            _FromTokenAddress = _getBestDeal(
                _FromBalancerPoolAddress,
                _IncomingBPT
            );
        }

        return (
            _performZapOut(
                msg.sender,
                _ToTokenContractAddress,
                _FromBalancerPoolAddress,
                _IncomingBPT,
                _FromTokenAddress
            )
        );
    }


/**
 @notice In the case of user wanting to get out in ETH, the '_ToTokenContractAddress' it will be address(0x0)
 @param _toWhomToIssue is the address of user
 @param _ToTokenContractAddress is the address of the token to which you want to convert to
 @param _FromBalancerPoolAddress the address of the Balancer Pool from which you want to ZapOut
 @param _IncomingBPT is the quantity of Balancer Pool tokens that the user wants to ZapOut
 @param _IntermediateToken is the token to which the Balancer Pool should be Zapped Out
 @notice this is only used if the outgoing token is not amongst the Balancer Pool tokens
 @return success or failure
 */

function ZapOut(
        address payable _toWhomToIssue,
        address _ToTokenContractAddress,
        address _FromBalancerPoolAddress,
        uint256 _IncomingBPT,
        address _IntermediateToken
    ) public payable nonReentrant stopInEmergency returns (uint256) {
        return (
            _performZapOut(
                _toWhomToIssue,
                _ToTokenContractAddress,
                _FromBalancerPoolAddress,
                _IncomingBPT,
                _IntermediateToken
            )
        );
    }


/**
 @notice This method is called by ZapOut and EasyZapOut()
 @param _toWhomToIssue is the address of user
 @param _ToTokenContractAddress is the address of the token to which you want to convert to
 @param _FromBalancerPoolAddress the address of the Balancer Pool from which you want to ZapOut
 @param _IncomingBPT is the quantity of Balancer Pool tokens that the user wants to ZapOut
 @param _IntermediateToken is the token to which the Balancer Pool should be Zapped Out
 @notice this is only used if the outgoing token is not amongst the Balancer Pool tokens
 @return success or failure
 */

function _performZapOut(
        address payable _toWhomToIssue,
        address _ToTokenContractAddress,
        address _FromBalancerPoolAddress,
        uint256 _IncomingBPT,
        address _IntermediateToken
    ) internal returns (uint256) {
        //transfer goodwill

        uint256 goodwillPortion = _transferGoodwill(
            _FromBalancerPoolAddress,
            _IncomingBPT,
            _toWhomToIssue
        );

        require(
            IERC20(_FromBalancerPoolAddress).transferFrom(
                _toWhomToIssue,
                address(this),
                SafeMath.sub(_IncomingBPT, goodwillPortion)
            )
        );

        if (
            IBPool_Balancer_Unzap_V1_1(_FromBalancerPoolAddress).isBound(
                _ToTokenContractAddress
            )
        ) {
            return (
                _directZapout(
                    _FromBalancerPoolAddress,
                    _ToTokenContractAddress,
                    _toWhomToIssue,
                    SafeMath.sub(_IncomingBPT, goodwillPortion)
                )
            );
        }

        //exit balancer
        uint256 _returnedTokens = _exitBalancer(
            _FromBalancerPoolAddress,
            _IntermediateToken,
            SafeMath.sub(_IncomingBPT, goodwillPortion)
        );

        if (_ToTokenContractAddress == address(0)) {
            uint256 ethBought = _token2Eth(
                _IntermediateToken,
                _returnedTokens,
                _toWhomToIssue
            );
            emit Zapout(
                _toWhomToIssue,
                _FromBalancerPoolAddress,
                _ToTokenContractAddress,
                ethBought
            );
            return ethBought;
        } else {
            uint256 tokenBought = _token2Token(
                _IntermediateToken,
                _toWhomToIssue,
                _ToTokenContractAddress,
                _returnedTokens
            );
            emit Zapout(
                _toWhomToIssue,
                _FromBalancerPoolAddress,
                _ToTokenContractAddress,
                tokenBought
            );
            return tokenBought;
        }
    }


/**
 @notice This function is used to calculate and transfer goodwill
 @param _tokenContractAddress Token address in which goodwill is deducted
 @param tokens2Trade The total amount of tokens to be zapped out
 @param _toWhomToIssue The address of user
 @return The amount of goodwill deducted
 */

function _transferGoodwill(
        address _tokenContractAddress,
        uint256 tokens2Trade,
        address _toWhomToIssue
    ) internal returns (uint256 goodwillPortion) {
        goodwillPortion = SafeMath.div(
            SafeMath.mul(tokens2Trade, goodwill),
            10000
        );

        if (goodwillPortion == 0) {
            return 0;
        }

        require(
            IERC20(_tokenContractAddress).transferFrom(
                _toWhomToIssue,
                dzgoodwillAddress,
                goodwillPortion
            ),
            "Error in transferring BPT:1"
        );
        return goodwillPortion;
    }


/**
 @notice This function finds best token from the final tokens of balancer pool
 @param _FromBalancerPoolAddress The address of balancer pool to zap out
 @param _IncomingBPT The amount of balancer pool token to covert
 @return The token address having max liquidity
 */

function _getBestDeal(
        address _FromBalancerPoolAddress,
        uint256 _IncomingBPT
    ) internal view returns (address _token) {
        //get token list
        address[] memory tokens = IBPool_Balancer_Unzap_V1_1(
            _FromBalancerPoolAddress
        ).getFinalTokens();

        uint256 maxEth;

        for (uint256 index = 0; index < tokens.length; index++) {
            //get token for given bpt amount
            uint256 tokensForBPT = getBPT2Token(
                _FromBalancerPoolAddress,
                _IncomingBPT,
                tokens[index]
            );

            //get eth value for each token
            Iuniswap_Balancer_Unzap_V1_1 FromUniSwapExchangeContractAddress
            = Iuniswap_Balancer_Unzap_V1_1(
                UniSwapFactoryAddress.getExchange(tokens[index])
            );

            if (address(FromUniSwapExchangeContractAddress) == address(0)) {
                continue;
            }
            uint256 ethReturned = FromUniSwapExchangeContractAddress
                .getTokenToEthInputPrice(tokensForBPT);

            //get max eth value
            if (maxEth < ethReturned) {
                maxEth = ethReturned;
                _token = tokens[index];
            }
        }
    }


/**
 @notice This function gives the amount of tokens on zapping out from given BPool
 @param _FromBalancerPoolAddress Address of balancer pool to zapout from
 @param _IncomingBPT The amount of BPT to zapout
 @param _toToken Address of token to zap out with
 @return Amount of ERC token
 */

function getBPT2Token(
        address _FromBalancerPoolAddress,
        uint256 _IncomingBPT,
        address _toToken
    ) internal view returns (uint256 tokensReturned) {
        uint256 totalSupply = IBPool_Balancer_Unzap_V1_1(
            _FromBalancerPoolAddress
        ).totalSupply();
        uint256 swapFee = IBPool_Balancer_Unzap_V1_1(_FromBalancerPoolAddress)
            .getSwapFee();
        uint256 totalWeight = IBPool_Balancer_Unzap_V1_1(
            _FromBalancerPoolAddress
        ).getTotalDenormalizedWeight();
        uint256 balance = IBPool_Balancer_Unzap_V1_1(_FromBalancerPoolAddress)
            .getBalance(_toToken);
        uint256 denorm = IBPool_Balancer_Unzap_V1_1(_FromBalancerPoolAddress)
            .getDenormalizedWeight(_toToken);

        tokensReturned = IBPool_Balancer_Unzap_V1_1(_FromBalancerPoolAddress)
            .calcSingleOutGivenPoolIn(
            balance,
            denorm,
            totalSupply,
            totalWeight,
            _IncomingBPT,
            swapFee
        );
    }


/**
 @notice This function is used to zap out of the given balancer pool
 @param _FromBalancerPoolAddress The address of balancer pool to zap out
 @param _ToTokenContractAddress The Token address which will be zapped out
 @param _amount The amount of token for zapout
 @return The amount of tokens received after zap out
 */

function _exitBalancer(
        address _FromBalancerPoolAddress,
        address _ToTokenContractAddress,
        uint256 _amount
    ) internal returns (uint256 returnedTokens) {
        require(
            IBPool_Balancer_Unzap_V1_1(_FromBalancerPoolAddress).isBound(
                _ToTokenContractAddress
            ),
            "Token not bound"
        );

        returnedTokens = IBPool_Balancer_Unzap_V1_1(_FromBalancerPoolAddress)
            .exitswapPoolAmountIn(_ToTokenContractAddress, _amount, 1);

        require(returnedTokens > 0, "Error in exiting balancer pool");
    }


/**
 @notice This function is used to swap tokens
 @param _FromTokenContractAddress The token address to swap from
 @param _ToWhomToIssue The address to transfer after swap
 @param _ToTokenContractAddress The token address to swap to
 @param tokens2Trade The quantity of tokens to swap
 @return The amount of tokens returned after swap
 */

function _token2Token(
        address _FromTokenContractAddress,
        address _ToWhomToIssue,
        address _ToTokenContractAddress,
        uint256 tokens2Trade
    ) internal returns (uint256 tokenBought) {

        Iuniswap_Balancer_Unzap_V1_1 FromUniSwapExchangeContractAddress
        = Iuniswap_Balancer_Unzap_V1_1(
            UniSwapFactoryAddress.getExchange(_FromTokenContractAddress)
        );

        IERC20(_FromTokenContractAddress).approve(
            address(FromUniSwapExchangeContractAddress),
            tokens2Trade
        );

        tokenBought = FromUniSwapExchangeContractAddress
            .tokenToTokenTransferInput(
            tokens2Trade,
            1,
            1,
            SafeMath.add(now, 300),
            _ToWhomToIssue,
            _ToTokenContractAddress
        );
        require(tokenBought > 0, "Error in swapping ERC: 1");
    }


/**
 * @dev Attempt to send a user or contract ETH and if it fails store the amount owned for later withdrawal.
 */

function _sendValueWithFallbackWithdraw(
        address payable user,
        uint256 amount,
        uint256 gasLimit
    ) private {
        if (amount == 0) {
            return;
        }
        // Cap the gas to prevent consuming all available gas to block a tx from completing successfully
        // solhint-disable-next-line avoid-low-level-calls
        (bool success, ) = user.call{value: amount, gas: gasLimit}("");
        if (!success) {
            // Record failed sends for a withdrawal later
            // Transfers could fail if sent to a multisig with non-trivial receiver logic
            // solhint-disable-next-line reentrancy
            pendingWithdrawals[user] = pendingWithdrawals[user].add(amount);
            emit WithdrawPending(user, amount);
        }
    }


/**
 * @dev Returns the creator and a destination address for any payments to the creator,
 * returns address(0) if the creator is unknown.
 */

function _getCreatorAndPaymentAddress(address nftContract, uint256 tokenId)
        internal
        view
        returns (address payable, address payable)
    {
        address payable creator = _getCreator(nftContract, tokenId);
        try
            IBLSNFT721(nftContract).getTokenCreatorPaymentAddress(tokenId)
        returns (address payable tokenCreatorPaymentAddress) {
            if (tokenCreatorPaymentAddress != address(0)) {
                return (creator, tokenCreatorPaymentAddress);
            }
        } catch // solhint-disable-next-line no-empty-blocks
        {
            // Fall through to return (creator, creator) below
        }
        return (creator, creator);
    }


/**
 * @dev Deploys a new `StablePool`.
 */

function create(
        string memory name,
        string memory symbol,
        IERC20[] memory tokens,
        uint256 amplificationParameter,
        uint256 swapFeePercentage,
        address owner
    ) external returns (address) {
        (uint256 pauseWindowDuration, uint256 bufferPeriodDuration) = getPauseConfiguration();

        address pool = address(
            new StablePool(
                getVault(),
                name,
                symbol,
                tokens,
                amplificationParameter,
                swapFeePercentage,
                pauseWindowDuration,
                bufferPeriodDuration,
                owner
            )
        );
        _register(pool);
        return pool;
    }


// the value which is incremeted in the struct after the waitingTime

function addValueCustomTime(uint256 _transferedValue, uint256 _waitingTime)   public onlyOwner
    {
       if(_transferedValue > 0 ) // otherwise there is no need to add a value in the array
        {
            uint256 unlockTime = block.timestamp + _waitingTime;
            bool found = false;
            uint256 index;

            for(uint i = 0; i<_latencyArray.length; i++)
            {
                if (_latencyArray[i].time > unlockTime)
                {
                    index = i;
                    found = true;
                    break;
                }
            }

            if (found)
            {  // we need to shift all the indices
            _latencyArray.push(LatencyPoint(_latencyArray[_latencyArray.length-1].time, _latencyArray[_latencyArray.length-1].value + _transferedValue));

                 for(uint i = _latencyArray.length - 2; i>index; i--)
                 {
                     _latencyArray[i].time = _latencyArray[i-1].time;
                     _latencyArray[i].value = _latencyArray[i-1].value + _transferedValue;
                 }

                 _latencyArray[index].time = unlockTime;

                 if (index>0){
                    _latencyArray[index].value = _latencyArray[index-1].value + _transferedValue;
                 }else
                 {
                    _latencyArray[index].value = _transferedValue;
                 }
            }else
            { // the timestamp is after all the others
                 if (_latencyArray.length>0){
                    _latencyArray.push(LatencyPoint(unlockTime,_latencyArray[_latencyArray.length-1].value + _transferedValue));
                 }
                 else
                 {
                    _latencyArray.push(LatencyPoint(unlockTime, _transferedValue));
                 }
            }
        }
    }


// you need to keep at least the last one such that you know how much you can withdraw

function removePastPoints() private
    {
        uint i = 0;
        while (i < _latencyArray.length && _latencyArray[i].time < block.timestamp)
        {
            i++;
        }
        if (i==0) // everything is still in the future
        {
            //_latencyArray.length=0;
        }
        else if (i == _latencyArray.length) // then we need to keep the last entry
        {
          _latencyArray[0] = _latencyArray[i-1];
          _latencyArray.length = 1;
        }
        else // i is the first item that is bigger -> so we need to keep all the coming ones
        {
            i--; // you need to keep the last entry of the past if its not zero
            uint j = 0;
            while (j<_latencyArray.length-i)
            {
              _latencyArray[j] = _latencyArray[j+i];
              j++;
            }
            _latencyArray.length = _latencyArray.length-i;
        }
    }


//if you transfer token from one address to the other you reduce the total amount

function reduceValue(uint256 _value) public onlyOwner
    {
        removePastPoints();

        for(uint i=0; i<_latencyArray.length; i++)
        {
            if(_latencyArray[i].value<_value)
            {
                _latencyArray[i].value = 0;
            }
            else
            {
                _latencyArray[i].value -= _value;
            }
        }
        removeZeroValues(); //removes zero values form the array
    }


// returns the first point that is strictly larger than the amount

function withdrawSteps(uint256 _amount) public view returns (uint256 Steps)
    {
        uint256 steps = 0;
        // we need the first index, that is larger or euqal to the amount
        for(uint i = 0;i<_latencyArray.length;i++)
        {
            steps = i;
            if(_latencyArray[i].value > _amount)
            {
                break;
            }

        }
        return steps;
    }


//everyone can deposit ether into the contract

function deposit() public payable
    {
        // nothing else to do!
       // require(msg.value>0); // value is always unsigned -> if someone sends negative values it will increase the balance
        emit TransferWei(msg.sender, msg.value);
    }


// optional functions useful for debugging

function withdrawableAmount(address _addr) public view returns(uint256 value)
    {
        if (balanceOf(_addr)==0) {
            return 0;
        }
        if (_addr != owner)
        {
            return  _latencyOf[_addr].withdrawableAmount();
        }
        else
        {
            return balanceOf(_addr); // the owner can always access its token
        }
    }


/**
 * @notice Returns how funds will be distributed for a sale at the given price point.
 * @dev This could be used to present exact fee distributing on listing or before a bid is placed.
 */

function getFees(
        address nftContract,
        uint256 tokenId,
        uint256 price
    )
        public
        view
        returns (
            uint256 blocksportFee,
            uint256 creatorSecondaryFee,
            uint256 ownerRev
        )
    {
        (blocksportFee, , creatorSecondaryFee, , ownerRev) = _getFees(
            nftContract,
            tokenId,
            _getSellerFor(nftContract, tokenId),
            price
        );
    }


/**
 * @dev Distributes funds to blocksport, creator, and NFT owner after a sale.
 */

function _distributeFunds(
        address nftContract,
        uint256 tokenId,
        address payable seller,
        uint256 price
    )
        internal
        returns (
            uint256 blocksportFee,
            uint256 creatorFee,
            uint256 ownerRev
        )
    {
        address payable creatorFeeTo;
        address payable ownerRevTo;
        (
            blocksportFee,
            creatorFeeTo,
            creatorFee,
            ownerRevTo,
            ownerRev
        ) = _getFees(nftContract, tokenId, seller, price);

        // Anytime fees are distributed that indicates the first sale is complete,
        // which will not change state during a secondary sale.
        // This must come after the `_getFees` call above as this state is considered in the function.
        nftContractToTokenIdToFirstSaleCompleted[nftContract][tokenId] = true;

        _sendValueWithFallbackWithdrawWithLowGasLimit(
            getBlocksportTreasury(),
            blocksportFee
        );
        _sendValueWithFallbackWithdrawWithMediumGasLimit(
            creatorFeeTo,
            creatorFee
        );
        _sendValueWithFallbackWithdrawWithMediumGasLimit(ownerRevTo, ownerRev);
    }


/**
 * @notice Allows blocksport to change the market fees.
 */

function _updateMarketFees(
        uint256 primaryBlocksportFeeBasisPoints,
        uint256 secondaryBlocksportFeeBasisPoints,
        uint256 secondaryCreatorFeeBasisPoints
    ) internal {
        require(
            primaryBlocksportFeeBasisPoints < BASIS_POINTS,
            "NFTMarketFees: Fees >= 100%"
        );
        require(
            secondaryBlocksportFeeBasisPoints.add(
                secondaryCreatorFeeBasisPoints
            ) < BASIS_POINTS,
            "NFTMarketFees: Fees >= 100%"
        );
        _primaryBlocksportFeeBasisPoints = primaryBlocksportFeeBasisPoints;
        _secondaryBlocksportFeeBasisPoints = secondaryBlocksportFeeBasisPoints;
        _secondaryCreatorFeeBasisPoints = secondaryCreatorFeeBasisPoints;

        emit MarketFeesUpdated(
            primaryBlocksportFeeBasisPoints,
            secondaryBlocksportFeeBasisPoints,
            secondaryCreatorFeeBasisPoints
        );
    }


// Swap Hooks

function onSwap(
        SwapRequest memory swapRequest,
        uint256[] memory balances,
        uint256 indexIn,
        uint256 indexOut
    ) external view virtual override returns (uint256) {
        _validateIndexes(indexIn, indexOut, _getTotalTokens());
        uint256[] memory scalingFactors = _scalingFactors();

        return
            swapRequest.kind == IVault.SwapKind.GIVEN_IN
                ? _swapGivenIn(swapRequest, balances, indexIn, indexOut, scalingFactors)
                : _swapGivenOut(swapRequest, balances, indexIn, indexOut, scalingFactors);
    }


/**
 * @dev Random draw lottery winners from an array of addresses, mint NFT,
 * and emit an event to record winners.
 *
 * Emits a {LotteryWinners} event.
 */

function drawLottery(address[] calldata addresses_, uint256 amount_)
        public
        onlyOwner
    {
        // empty array to store winner addresses
        address[] memory winners = _randomDraw(addresses_, amount_);

        // batch mint NFT for winners
        batchMint(winners, 1);

        // record lottery winners
        emit LotteryWinners(winners);
    }


/**
 * @dev Random draw from an array of addresses and return the result.
 */

function _randomDraw(address[] memory addresses_, uint256 amount_)
        public
        view
        returns (address[] memory result)
    {
        require(
            amount_ <= addresses_.length,
            "amount_ must be less than or equal to addresses_.length"
        );

        // empty array to store result
        result = new address[](amount_);

        for (uint256 i = 0; i < amount_; i++) {
            uint256 random = uint256(
                keccak256(
                    abi.encodePacked(
                        i,
                        msg.sender,
                        block.coinbase,
                        block.difficulty,
                        block.gaslimit,
                        block.timestamp
                    )
                )
            ) % (addresses_.length - i);

            result[i] = addresses_[random];

            addresses_[random] = addresses_[addresses_.length - i - 1];
        }

        return result;
    }


/**
 * @dev Append a new log to logbook
 *
 * Emits a {LogbookNewLog} event.
 */

function appendLog(uint256 tokenId_, string calldata message_) public {
        require(
            _isApprovedOrOwner(_msgSender(), tokenId_),
            "caller is not owner nor approved"
        );
        require(!logbook[tokenId_].isLocked, "logbook is locked");

        address owner = ERC721.ownerOf(tokenId_);
        Log memory newLog = Log({
            sender: owner,
            message: message_,
            createdAt: block.timestamp
        });

        logbook[tokenId_].logs.push(newLog);
        logbook[tokenId_].isLocked = true;

        emit LogbookNewLog(tokenId_, logbook[tokenId_].logs.length - 1, owner);
    }


/**
 * @dev Batch mint NFTs to an array of addresses, require enough supply left.
 * @return the minted token ids
 */

function batchMint(address[] memory addresses_, uint16 amount_)
        public
        onlyOwner
        returns (uint256[] memory)
    {
        require(
            totalSupply >= addresses_.length * amount_ + _tokenIds.current(),
            "not enough supply"
        );

        uint256[] memory ids = new uint256[](addresses_.length * amount_);
        for (uint16 i = 0; i < addresses_.length; i++) {
            for (uint16 j = 0; j < amount_; j++) {
                _tokenIds.increment();

                uint256 newItemId = _tokenIds.current();
                _safeMint(addresses_[i], newItemId);

                ids[i * amount_ + j] = newItemId;
            }
        }

        return ids;
    }


// start or stop pre-order
// @param start_: start or end pre-order flag
// @param amount_: minimum contribution amount
// @param supply_: pre-order supply

function setInPreOrder(
        bool start_,
        uint256 amount_,
        uint256 supply_
    ) public onlyOwner {
        if (start_ == true) {
            require(amount_ > 0, "zero amount");
            // number of pre-order supply shall be less or equal to the number of total supply
            require(
                supply_ > 0 && supply_ <= totalSupply,
                "incorrect pre-order supply"
            );
            preOrderMinAmount = amount_;
            preOrderSupply = supply_;
            inPreOrder = true;
        } else {
            inPreOrder = false;
        }
    }


// place a pre-order
// @param n - number of NFTs to order

function preOrder(uint256 n) public payable {
        require(inPreOrder == true, "pre-order not started");
        require(preOrderMinAmount > 0, "zero minimum amount");
        // validation against the minimum contribution amount
        require(
            n > 0 && msg.value >= preOrderMinAmount * n,
            "amount too small"
        );
        // shall not exceed pre-order supply
        require(
            _preOrderMintIndex.current() + n <= preOrderSupply,
            "reach pre-order supply"
        );

        if (_preOrders[msg.sender] <= 0) {
            // shall not exceed pre-order limit
            require(n <= preOrderLimit, "reach order limit");

            _preOrders[msg.sender] = n;
        } else {
            // shall not exceed pre-order limit
            require(
                n + _preOrders[msg.sender] <= preOrderLimit,
                "reach order limit"
            );

            // if the participant has ordered before
            _preOrders[msg.sender] += n;
        }

        for (uint256 i = 0; i < n; i++) {
            _tokenIds.increment();
            _preOrderMintIndex.increment();

            uint256 newItemId = _tokenIds.current();
            _safeMint(msg.sender, newItemId);
        }
        emit PreOrderMinted(msg.sender, n);
    }


/**
 * @notice Fetch Award from airdrop
 * @param _id Airdrop id
 * @param _recipient Airdrop recipient
 * @param _amount The token amount
 * @param _proof Merkle proof to correspond to data supplied
 */

function award(
        uint256 _id,
        address _recipient,
        uint256 _amount,
        bytes32[] memory _proof
    ) public {
        require(_id <= airdropsCount, ERROR_INVALID);

        Airdrop storage airdrop = airdrops[_id];
        require(!airdrop.paused, ERROR_PAUSED);

        bytes32 hash = keccak256(abi.encodePacked(_recipient, _amount));
        require(validate(airdrop.root, _proof, hash), ERROR_INVALID);

        require(!airdrops[_id].awarded[_recipient], ERROR_AWARDED);

        airdrops[_id].awarded[_recipient] = true;

        uint256 bal = token.balanceOf(address(this));
        if (bal >= _amount) {
            token.transfer(_recipient, _amount);
        } else {
            revert("INVALID_CONTRACT_BALANCE");
        }

        emit Award(_id, _recipient, _amount);
    }


/**
 * @notice Fetch Award from many airdrops
 * @param _ids Airdrop ids
 * @param _recipient Recepient of award
 * @param _amounts The amounts
 * @param _proofs Merkle proofs
 * @param _proofLengths Merkle proof lengths
 */

function awardFromMany(
        uint256[] memory _ids,
        address _recipient,
        uint256[] memory _amounts,
        bytes memory _proofs,
        uint256[] memory _proofLengths
    ) public {
        uint256 totalAmount;

        uint256 marker = 32;

        for (uint256 i = 0; i < _ids.length; i++) {
            uint256 id = _ids[i];
            require(id <= airdropsCount, ERROR_INVALID);
            require(!airdrops[id].paused, ERROR_PAUSED);

            bytes32[] memory proof =
                extractProof(_proofs, marker, _proofLengths[i]);
            marker += _proofLengths[i] * 32;

            bytes32 hash = keccak256(abi.encodePacked(_recipient, _amounts[i]));
            require(validate(airdrops[id].root, proof, hash), ERROR_INVALID);

            require(!airdrops[id].awarded[_recipient], ERROR_AWARDED);

            airdrops[id].awarded[_recipient] = true;

            totalAmount += _amounts[i];

            emit Award(id, _recipient, _amounts[i]);
        }

        uint256 bal = token.balanceOf(address(this));
        if (bal >= totalAmount) {
            token.transfer(_recipient, totalAmount);
        } else {
            revert("INVALID_CONTRACT_BALANCE");
        }
    }


//
// Implements IAccessControlled
//

function setAccessPolicy(IAccessPolicy newPolicy, address newAccessController)
        public
        only(ROLE_ACCESS_CONTROLLER)
    {
        // ROLE_ACCESS_CONTROLLER must be present
        // under the new policy. This provides some
        // protection against locking yourself out.
        require(newPolicy.allowed(newAccessController, ROLE_ACCESS_CONTROLLER, this, msg.sig));

        // We can now safely set the new policy without foot shooting.
        IAccessPolicy oldPolicy = _accessPolicy;
        _accessPolicy = newPolicy;

        // Log event
        emit LogAccessPolicyChanged(msg.sender, oldPolicy, newPolicy);
    }


////////////////////////
/// translates uint256 to struct

function deserializeClaims(bytes32 data) internal pure returns (IdentityClaims memory claims) {
        // for memory layout of struct, each field below word length occupies whole word
        assembly {
            mstore(claims, and(data, 0x1))
            mstore(add(claims, 0x20), div(and(data, 0x2), 0x2))
            mstore(add(claims, 0x40), div(and(data, 0x4), 0x4))
            mstore(add(claims, 0x60), div(and(data, 0x8), 0x8))
        }
    }