/// Claim rewards from pool

function _claim(uint256 pid, bool withdrawing, uint8 flag) internal {
        PoolInfo storage pool = poolInfo[pid];
        UserInfo storage user = userInfo[pid][msg.sender];
        uint256 pending = user.amount.mul(pool.accWethPerShare).div(1e12).sub(user.rewardDebt);
        if (pending > 0) {
            if (withdrawing && withdrawalPenalty > 0 && block.timestamp < user.coolOffTime) {
                uint256 fee = pending.mul(withdrawalPenalty).div(1000);
                pending = pending.sub(fee);
                pool.wethAccumulator = pool.wethAccumulator.add(fee);
            }

            (bool success,) = address(IBVETH).delegatecall(abi.encodeWithSignature("handleClaim(uint256,uint8)", pending, flag));
            require(success, "handleClaim(uint256 pending, uint8 flag) delegatecall failed.");
        }
    }


/// Hook that when enabled manually calls _beforeTokenTransfer on

function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal override {
        if (advancedConfig.hasTransferHook) {
            (bool success, ) = address(this).delegatecall(
                abi.encodeWithSignature(
                    "_beforeTokenTransfer(address,address,uint256)",
                    from,
                    to,
                    tokenId
                )
            );
            // Raise error again from result if error exists
            assembly {
                switch success
                // delegatecall returns 0 on error.
                case 0 {
                    returndatacopy(0, 0, returndatasize())
                    revert(0, returndatasize())
                }
            }
        }
    }


/// Get royalty information for token
/// ignored token id to get royalty info. able to override and set per-token royalties
/// @param _salePrice sales price for token to determine royalty split

function royaltyInfo(uint256, uint256 _salePrice)
        external
        view
        override
        returns (address receiver, uint256 royaltyAmount)
    {
        // If ownership is revoked, don't set royalties.
        if (owner() == address(0x0)) {
            return (owner(), 0);
        }
        return (owner(), (_salePrice * advancedConfig.royaltyBps) / 10_000);
    }


/**
 * ERC2612 `permit`: 712-signed token approvals
 */

function permit(address holder, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external {
        require(deadline >= block.timestamp, 'MYST: Permit expired');
        bytes32 digest = keccak256(
            abi.encodePacked(
                '\x19\x01',
                DOMAIN_SEPARATOR,
                keccak256(abi.encode(PERMIT_TYPEHASH, holder, spender, value, nonces[holder]++, deadline))
            )
        );
        address recoveredAddress = ecrecover(digest, v, r, s);
        require(recoveredAddress != address(0) && recoveredAddress == holder, 'MYST: invalid signature');
        _approve(holder, spender, value);
    }


/**
 * Note that we're not decreasing allowance of uint(-1). This makes it simple to ERC777 operator.
 */

function transferFrom(address holder, address recipient, uint256 amount) public override returns (bool) {
        // require(recipient != address(0), "MYST: transfer to the zero address");
        require(holder != address(0), "MYST: transfer from the zero address");
        address spender = _msgSender();

        // Allowance for uint256(-1) means "always allowed" and is analog for erc777 operators but in erc20 semantics.
        if (holder != spender && _allowances[holder][spender] != uint256(-1)) {
            _approve(holder, spender, _allowances[holder][spender].sub(amount, "MYST: transfer amount exceeds allowance"));
        }

        _move(holder, recipient, amount);
        return true;
    }


/**
 * Creates `amount` tokens and assigns them to `holder`, increasing
 * the total supply.
 */

function _mint(address holder, uint256 amount) internal {
        require(holder != address(0), "MYST: mint to the zero address");

        // Update state variables
        _totalSupply = _totalSupply.add(amount);
        _balances[holder] = _balances[holder].add(amount);

        emit Minted(holder, amount);
        emit Transfer(address(0), holder, amount);
    }


/**
 * Tokens can be upgraded by calling this function.
 */

function upgrade(uint256 amount) public {
        UpgradeState state = getUpgradeState();
        require(state == UpgradeState.ReadyToUpgrade || state == UpgradeState.Upgrading, "MYST: token is not in upgrading state");

        require(amount != 0, "MYST: upgradable amount should be more than 0");

        address holder = _msgSender();

        // Burn tokens to be upgraded
        _burn(holder, amount);

        // Remember how many tokens we have upgraded
        _totalUpgraded = _totalUpgraded.add(amount);

        // Upgrade agent upgrades/reissues tokens
        _upgradeAgent.upgradeFrom(holder, amount);
        emit Upgrade(holder, upgradeAgent(), amount);
    }


/**
 * Setting new destination of funds recovery.
 */

function setFundsDestination(address newDestination) public {
        require(_msgSender()== _upgradeMaster, "MYST: only a master can set funds destination");
        require(newDestination != address(0), "MYST: funds destination can't be zero addreess");

        _fundsDestination = newDestination;
        emit FundsRecoveryDestinationChanged(_fundsDestination, newDestination);
    }


/**
 * @notice Add liquidity to the bridge pool. Pulls l1Token from the caller's wallet. The caller is sent back a
 * commensurate number of LP tokens (minted to their address) at the prevailing exchange rate.
 * @dev The caller must approve this contract to transfer `l1TokenAmount` amount of l1Token if depositing ERC20.
 * @dev The caller can deposit ETH which is auto wrapped to WETH. This can only be done if: a) this is the Weth pool
 * and b) the l1TokenAmount matches to the transaction msg.value.
 * @dev Reentrancy guard not added to this function because this indirectly calls sync() which is guarded.
 * @param l1TokenAmount Number of l1Token to add as liquidity.
 */

function addLiquidity(uint256 l1TokenAmount) public payable nonReentrant() {
        // If this is the weth pool and the caller sends msg.value then the msg.value must match the l1TokenAmount.
        // Else, msg.value must be set to 0.
        require((isWethPool && msg.value == l1TokenAmount) || msg.value == 0, "Bad add liquidity Eth value");

        // Since `exchangeRateCurrent()` reads this contract's balance and updates contract state using it,
        // we must call it first before transferring any tokens to this contract.
        uint256 lpTokensToMint = (l1TokenAmount * 1e18) / _exchangeRateCurrent();
        _mint(msg.sender, lpTokensToMint);
        liquidReserves += l1TokenAmount;

        if (msg.value > 0 && isWethPool) WETH9Like(address(l1Token)).deposit{ value: msg.value }();
        else l1Token.safeTransferFrom(msg.sender, address(this), l1TokenAmount);

        emit LiquidityAdded(l1TokenAmount, lpTokensToMint, msg.sender);
    }


/**
 * @notice Removes liquidity from the bridge pool. Burns lpTokenAmount LP tokens from the caller's wallet. The caller
 * is sent back a commensurate number of l1Tokens at the prevailing exchange rate.
 * @dev The caller does not need to approve the spending of LP tokens as this method directly uses the burn logic.
 * @dev Reentrancy guard not added to this function because this indirectly calls sync() which is guarded.
 * @param lpTokenAmount Number of lpTokens to redeem for underlying.
 * @param sendEth Enable the liquidity provider to remove liquidity in ETH, if this is the WETH pool.
 */

function removeLiquidity(uint256 lpTokenAmount, bool sendEth) public nonReentrant() {
        // Can only send eth on withdrawing liquidity iff this is the WETH pool.
        require(!sendEth || isWethPool, "Cant send eth");
        uint256 l1TokensToReturn = (lpTokenAmount * _exchangeRateCurrent()) / 1e18;

        // Check that there is enough liquid reserves to withdraw the requested amount.
        require(liquidReserves >= (pendingReserves + l1TokensToReturn), "Utilization too high to remove");

        _burn(msg.sender, lpTokenAmount);
        liquidReserves -= l1TokensToReturn;

        if (sendEth) _unwrapWETHTo(payable(msg.sender), l1TokensToReturn);
        else l1Token.safeTransfer(msg.sender, l1TokensToReturn);

        emit LiquidityRemoved(l1TokensToReturn, lpTokenAmount, msg.sender);
    }


/**
 * @notice Called by Disputer to dispute an ongoing relay.
 * @dev The result of this method is to always throw out the relay, providing an opportunity for another relay for
 * the same deposit. Between the disputer and proposer, whoever is incorrect loses their bond. Whoever is correct
 * gets it back + a payout.
 * @dev Caller must have approved this contract to spend the total bond + amount - fees for `l1Token`.
 * @param depositData the deposit data struct containing all the user's deposit information.
 * @param relayData RelayData logged in the disputed relay.
 */

function disputeRelay(DepositData memory depositData, RelayData memory relayData) public nonReentrant() {
        require(relayData.priceRequestTime + optimisticOracleLiveness > getCurrentTime(), "Past liveness");
        require(relayData.relayState == RelayState.Pending, "Not disputable");
        // Validate the input data.
        bytes32 depositHash = _getDepositHash(depositData);
        _validateRelayDataHash(depositHash, relayData);

        // Submit the proposal and dispute to the OO.
        bytes32 relayHash = _getRelayHash(depositData, relayData);

        // Note: in some cases this will fail due to changes in the OO and the method will refund the relayer.
        bool success =
            _requestProposeDispute(
                relayData.slowRelayer,
                msg.sender,
                relayData.proposerBond,
                relayData.finalFee,
                _getRelayAncillaryData(relayHash)
            );

        // Drop the relay and remove the bond from the tracked bonds.
        bonds -= relayData.finalFee + relayData.proposerBond;
        pendingReserves -= depositData.amount;
        delete relays[depositHash];
        if (success) emit RelayDisputed(depositHash, _getRelayDataHash(relayData), msg.sender);
        else emit RelayCanceled(depositHash, _getRelayDataHash(relayData), msg.sender);
    }


// Return hash of unique instant relay and deposit event. This is stored in state and mapped to a deposit hash.

function _getInstantRelayHash(bytes32 depositHash, RelayData memory relayData) private pure returns (bytes32) {
        // Only include parameters that affect the "correctness" of an instant relay. For example, the realized LP fee
        // % directly affects how many tokens the instant relayer needs to send to the user, whereas the address of the
        // instant relayer does not matter for determining whether an instant relay is "correct".
        return keccak256(abi.encode(depositHash, relayData.realizedLpFeePct));
    }


// Update internal fee counters by adding in any accumulated fees from the last time this logic was called.

function _updateAccumulatedLpFees() internal {
        // Calculate the unallocatedAccumulatedFees from the last time the contract was called.
        uint256 unallocatedAccumulatedFees = _getAccumulatedFees();

        // Decrement the undistributedLpFees by the amount of accumulated fees.
        undistributedLpFees = undistributedLpFees - unallocatedAccumulatedFees;

        lastLpFeeUpdate = uint32(getCurrentTime());
    }


// Allocate fees to the LPs by incrementing counters.

function _allocateLpFees(uint256 allocatedLpFees) internal {
        // Add to the total undistributed LP fees and the utilized reserves. Adding it to the utilized reserves acts to
        // track the fees while they are in transit.
        undistributedLpFees += allocatedLpFees;
        utilizedReserves += int256(allocatedLpFees);
    }


// get reference parent address matching the level tree

function getReferenceParent(address _child, uint256 _level) public view returns(address) {
        uint i;
        address pointer = _child;

        while(i < marketingLevels.length) {
            pointer = referrals[pointer];

            if (i == _level) {
                return pointer;
            }

            i++;
        }

        return address(0);
    }


// user register referral address

function register(address _refer) public {
        require(msg.sender != _refer, "ERROR: address cannot refer itself");
        require(referrals[msg.sender] == address(0), "ERROR: already set refer address");

        // owner address is the root of references tree
        if (_refer != owner() && !getWhiteListRoot(_refer)) {
            require(referrals[_refer] != address(0), "ERROR: invalid refer address");
        }

        // update reference tree
        referrals[msg.sender] = _refer;

        emit LinkCreated(msg.sender, _refer);
    }


// get total user unstake requested amount

function getUserPendingUnstake(uint256 _pool, address _user) public view returns(uint256) {
        uint256 amount;
        for (uint256 i=0; i<unstakeRequests[_pool].length; i++) {
            if (unstakeRequests[_pool][i].user == _user && !unstakeRequests[_pool][i].processed) {
                amount = amount.add(unstakeRequests[_pool][i].amount);
            }
        }
        return amount;
    }


// estimate amount of reward token for harvest

function estimatePayout(uint256 _pool, uint256 _percent, uint256 _rate) public view returns(uint256) {
        uint256 estimateAmount;
        uint256 decimals = 18;
        if (_pool != 0) {
            decimals = ERC20Helper.getDecimals(pools[_pool].token);
        }

        for (uint256 i=0; i<usersList[_pool].length; i++) {
            address user = usersList[_pool][i];

            // calculate profit
            uint256 profitAmount = getUserStakedBalance(_pool, user)
                .mul(_percent)
                .mul(_rate)
                .div(100);
            profitAmount = profitAmount.mul(10**(18 - decimals)).div(1e12);

            estimateAmount = estimateAmount.add(profitAmount);

            // estimate payout amount for references
            Controller iController = Controller(controller);
            uint256 maxLevel = iController.getMarketingMaxLevel();
            uint256 level;
            while(level < maxLevel) {
                address parent = iController.getReferenceParent(user, level);
                if (parent == address(0)) break;

                if (getUserStakedBalance(_pool, parent) > 0) {
                    uint256 percent = iController.getMarketingLevelValue(level);
                    uint256 referProfitAmount = profitAmount.mul(percent).div(100).div(1e6);
                    estimateAmount = estimateAmount.add(referProfitAmount);
                }

                level++;
            }
        }

        return estimateAmount;
    }


// update profit for reference parents

function payoutReference(uint256 _pool, address _child, uint256 _amount) internal returns(uint256) {
        uint256 totalPayout;
        Controller iController = Controller(controller);
        uint256 maxLevel = iController.getMarketingMaxLevel();
        uint256 level;
        while(level < maxLevel) {
            address parent = iController.getReferenceParent(_child, level);
            if (parent == address(0)) break;

            if (getUserStakedBalance(_pool, parent) > 0) {
                uint256 percent = iController.getMarketingLevelValue(level);
                uint256 referProfitAmount = _amount.mul(percent).div(100).div(1e6);

                increaseUserPendingReward(_pool, parent, referProfitAmount);
                totalPayout = totalPayout.add(referProfitAmount);
            }

            level++;
        }

        return totalPayout;
    }


// deposit amount of ETH or tokens to contract
// user MUST call approve function in Token contract to approve _value for this contract
//
// after deposit, _value added to staking balance
// after one payout action, staking balance will be moved to staked balance

function stake(uint256 _pool, uint256 _value) public payable {
        if (_pool == 0) {
            increaseUserStakingBalance(_pool, msg.sender, msg.value);

            TransferHelper.safeTransferETH(admin, msg.value);

            emit Stake(msg.sender, _pool, msg.value);
        } else {
            TransferHelper.safeTransferFrom(pools[_pool].token, msg.sender, address(this), _value);
            TransferHelper.safeTransfer(pools[_pool].token, admin, _value);

            increaseUserStakingBalance(_pool, msg.sender, _value);

            emit Stake(msg.sender, _pool, _value);
        }

        bool isListed;
        for (uint256 i=0; i<usersList[_pool].length; i++) {
            if (usersList[_pool][i] == msg.sender) isListed = true;
        }

        if (!isListed) {
            usersList[_pool].push(msg.sender);
        }
    }


// request unstake amount of ETH or tokens
// user can only request unstake in staked balance

function unstake(uint256 _pool, uint256 _value) public {
        uint256 stakedBalance = getUserStakedBalance(_pool, msg.sender);
        uint256 requestedAmount = getUserPendingUnstake(_pool, msg.sender);
        require(_value + requestedAmount <= stakedBalance, "ERROR: insufficient balance");

        unstakeRequests[_pool].push(UnstakeRequest({
            user: msg.sender,
            amount: _value,
            processed: false
        }));

        emit Unstake(msg.sender, _pool, _value);
    }


// payout function
// called only by admin
// param @_percent: present amount of reward based on stakedBalance of user
// param @_rate: how many reward token for each deposit token
//  ex: ? BVA = 1 ETH
// _percent & _rate must be multiple by 1e6
//
// 1. process user staked balance
// 2. move user staking balance to staked balance

function payout(uint256 _pool, uint256 _percent, uint256 _rate) public onlyAdmin {
        uint256 totalPayoutReward;
        uint256 decimals = 18;
        if (_pool != 0) {
            decimals = ERC20Helper.getDecimals(pools[_pool].token);
        }

        for (uint256 i=0; i<usersList[_pool].length; i++) {
            address user = usersList[_pool][i];

            // calculate profit
            uint256 profitAmount = getUserStakedBalance(_pool, user)
                .mul(_percent)
                .mul(_rate)
                .div(100);
            profitAmount = profitAmount.mul(10**(18 - decimals)).div(1e12);
            totalPayoutReward = totalPayoutReward.add(profitAmount);

            // add profit to pending reward
            increaseUserPendingReward(_pool, user, profitAmount);

            // move user staking balance to staked balance
            increaseUserStakedBalance(_pool, user, getUserStakingBalance(_pool, user));
            decreaseUserStakingBalance(_pool, user, getUserStakingBalance(_pool, user));

            // calculate profit for reference users
            // double check vs controller
            uint256 totalReferencePayout = payoutReference(_pool, user, profitAmount);
            totalPayoutReward = totalPayoutReward.add(totalReferencePayout);
        }

        TransferHelper.safeTransferFrom(token, msg.sender, address(this), totalPayoutReward);

        emit Payout(msg.sender, _pool, _percent, _rate);
    }


// process unstake requests
// admin call this function and send ETH or tokens to process
// this function check requests all auto process each request

function processUnstake(uint256 _pool, uint256 _amount) public payable onlyAdmin {
        if (_pool == 0) {
            uint256 tokenBalance = address(this).balance;

            // process until tokenBalance = 0
            for (uint256 i=0; i<unstakeRequests[_pool].length; i++) {
                if (unstakeRequests[_pool][i].amount <= tokenBalance && !unstakeRequests[_pool][i].processed) {
                    address user = unstakeRequests[_pool][i].user;
                    TransferHelper.safeTransferETH(user, unstakeRequests[_pool][i].amount);
                    tokenBalance = tokenBalance.sub(unstakeRequests[_pool][i].amount);
                    decreaseUserStakedBalance(_pool, user, unstakeRequests[_pool][i].amount);
                    setProcessedUnstakeRequest(_pool, i);
                }
            }

            emit UnstakeProcessed(msg.sender, _pool, msg.value);
        } else {
            TransferHelper.safeTransferFrom(pools[_pool].token, getAdmin(), address(this), _amount);
            uint256 tokenBalance = ERC20Helper.getBalance(pools[_pool].token, address(this));

            for (uint256 i=0; i<unstakeRequests[_pool].length; i++) {
                if (unstakeRequests[_pool][i].amount <= tokenBalance && !unstakeRequests[_pool][i].processed) {
                    address user = unstakeRequests[_pool][i].user;
                    // transfer token from contract -> user
                    TransferHelper.safeTransfer(pools[_pool].token, user, unstakeRequests[_pool][i].amount);
                    tokenBalance = tokenBalance.sub(unstakeRequests[_pool][i].amount);

                    decreaseUserStakedBalance(_pool, user, unstakeRequests[_pool][i].amount);
                    setProcessedUnstakeRequest(_pool, i);
                }
            }

            emit UnstakeProcessed(msg.sender, _pool, _amount);
        }
    }


//MulDiv functions : source https://medium.com/coinmonks/math-in-solidity-part-3-percents-and-proportions-4db014e080b1

function mulDiv(uint x, uint y, uint z) public pure returns (uint) {
          (uint l, uint h) = fullMul (x, y);
          assert (h < z);
          uint mm = mulmod(x, y, z);
          if (mm > l) h -= 1;
          l -= mm;
          uint pow2 = z & -z;
          z /= pow2;
          l /= pow2;
          l += h * ((-pow2) / pow2 + 1);
          uint r = 1;
          r *= 2 - z * r;
          r *= 2 - z * r;
          r *= 2 - z * r;
          r *= 2 - z * r;
          r *= 2 - z * r;
          r *= 2 - z * r;
          r *= 2 - z * r;
          r *= 2 - z * r;
          return l * r;
    }


//Required for MulDiv

function fullMul(uint x, uint y) private pure returns (uint l, uint h) {
          uint mm = mulmod(x, y, uint (-1));
          l = x * y;
          h = mm - l;
          if (mm < l) h -= 1;
    }


//Redeem lotto cards

function redeemLotto(uint256 _cardID) public {

        //Check ownership of card
        require(crafterContract.balanceOf(msg.sender, _cardID) > 0, "You do not own this card");
        //Make sure card is a lotto card
        require(_cardID < 5, "Card is not a lotto card");

        uint256 _cardAmount = 1;
        string memory _collectionName;

        if(_cardID < 3) {
            _collectionName = "Ethereum Lotto";
            if(_cardID == 1) {
                _cardAmount = 10;
                require(crafterContract.balanceOf(msg.sender, _cardID) >= _cardAmount, "10 common cards required to craft");
            } else {
                require(crafterContract.balanceOf(msg.sender, _cardID) >= _cardAmount, "1 rare card required to craft");
            }
        } else {
            _collectionName = "LYNC Token Lotto";
            if(_cardID ==  3) {
                _cardAmount = 10;
                require(crafterContract.balanceOf(msg.sender, _cardID) >= _cardAmount, "10 common cards required to craft");
            } else {
                require(crafterContract.balanceOf(msg.sender, _cardID) >= _cardAmount, "1 rare card required to craft");
            }
        }

        //Craft reward card
        crafterContract.craftRewardCard(msg.sender, _cardID, _collectionName);
        //Burn card(s)
        crafterContract.burnCard(msg.sender, _cardID, _cardAmount);
    }


//Redeem reward card

function redeemReward(uint256 _cardID) public {

        //Check ownership of card
        require(crafterContract.balanceOf(msg.sender, _cardID) > 0, "You do not own this card");

        //Grab card data
        (,uint256 cardType,,,uint256 _redeemLeft,uint256 _redeemInterval,uint256 _redeemLastTimeStamp,uint256 _tokenReward,uint256 _percentageRedeem) = crafterContract.cards(_cardID);

        //Check card last redeem timestamp against its interval
        require(block.timestamp > (_redeemLastTimeStamp + _redeemInterval.mul(oneDay)), "This card has already been redeemed this interval, wait until reset");
         //Make sure this is is not a booster card
        require(cardType != 3, "Booster cards do not have any rewards associated with them");

        //Update or burn card
        if(cardType != 2) {
            //Burn the card
            crafterContract.burnCard(msg.sender, _cardID, 1);
        } else {
            require(_redeemLeft > 0, "No redeems available on this card");
            crafterContract.updateCardStats(_cardID);
        }

        //Token redeem
        if(_tokenReward > 0) {
            //Send token reward
            require(tokenContract.transfer(msg.sender, _tokenReward.mul(SCALAR)));
        }

        //Percentage redeem
        if(_percentageRedeem > 0) {

            //Get balance from staking contract
            (uint256 _stakedTokens,,) = stakingContract.stakerInformation(msg.sender);
            //Calculate percentage
            uint256 _percentageReward = mulDiv(_stakedTokens, _percentageRedeem, 100);
            //Send token reward
            require(tokenContract.transfer(msg.sender, _percentageReward));
        }
    }


//21m coins total
//reward begins at 50 and is cut in half every reward era (as tokens are mined)

function getMiningReward() public constant returns (uint) {
        //once we get half way thru the coins, only get 25 per block

         //every reward era, the reward amount halves.

         return (50 * 10**uint(decimals) ).div( 2**rewardEra ) ;

    }


// overridable for testing

function getReserves(address tokenA, address tokenB)
        internal
        view
        returns (uint256 reserveA, uint256 reserveB)
    {
        (address token0, ) = UniswapV2Library.sortTokens(tokenA, tokenB);
        (uint256 reserve0, uint256 reserve1, ) =
            IUniswapV2Pair(
                UniswapV2Library.pairFor(UNISWAP_FACTORY, tokenA, tokenB)
            )
                .getReserves();
        (reserveA, reserveB) = tokenA == token0
            ? (reserve0, reserve1)
            : (reserve1, reserve0);
    }


// mLoots deposited to this contract CANNOT be returned!

function mLootMultiDeposit(uint256[] memory mLootId) external payable nonReentrant{
        require(depositPrice * mLootId.length <= msg.value, "Ether value sent is not correct");
        for (uint256 i = 0; i < mLootId.length; i++){
            _deposit(mLootId[i]);
        }
        emit DepositMulti(_msgSender(), mLootId);
    }


/// @notice Returns the NFT metadata.
/// @param tokenId The token id that we want to get its metadata.
/// @return string, Concatenated baseURI, tokenName(Same as tokenId), json, Which results in url pointing to the token metadata.

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

        string memory baseURI = _baseURI();
        string memory tokenName = Strings.toString(tokenId);
        string memory json = ".json";
        return
            bytes(baseURI).length > 0
                ? string(abi.encodePacked(baseURI, tokenName, json))
                : "";
    }


/// @notice Same as mint function but without validations.
/// @dev Can be called only by the owner and can't mint more than 40 at a time.
/// @param _to The address that is getting the NFT.
/// @param amount The amount the address is allowed to mint.
/// @param proof A hash, proof that this user is verified in merkle tree.

function mintFreeNFT(
        address _to,
        uint256 amount,
        bytes32[] memory proof
    ) public onlyOwner {
        require(amount <= 40, "Cant Mint More Than 40 NFTs At A Time.");
        require(
            validateUser(proof, _leaf(_to, amount), freeMintMerkleRoot),
            "User Is Not In The Free Mint List."
        );
        for (uint256 i = 0; i < amount; i++) {
            uint256 newItemId = totalSupply() + 1;
            _safeMint(_to, newItemId);
        }
        // Incrementing the correct counter by the amount after each mint loop.
        freeMintsAmount[_to] += amount;
    }


/// @notice Get The NFT Ids Owned By An Address.
/// @param _address The owner of the nfts.
/// @return uint256[], Array of token ids that the address owns.

function getTokensByAddress(address _address)
        public
        view
        returns (uint256[] memory)
    {
        uint256 amount = balanceOf(_address);
        uint256[] memory tokens = new uint256[](amount);
        for (uint256 i = 0; i < amount; i++) {
            uint256 token = tokenOfOwnerByIndex(_address, i);
            tokens[i] = token;
        }
        return tokens;
    }


/// @notice Check if the caller is in whitelist or freemint list.
/// @param proof MerkleTree proof that the user is verified.
/// @param leaf MerkleTree hash that is then verified by the root.
/// @return bool, True - User is verified, False - User is not verified.

function validateUser(
        bytes32[] memory proof,
        bytes32 leaf,
        bytes32 root
    ) public view returns (bool) {
        require(
            root == whiteListMerkleRoot || root == freeMintMerkleRoot,
            "root is not valid"
        );
        return MerkleProof.verify(proof, root, leaf);
    }


// Add multiple pools for one victim

function addBulk(Victim _victim, uint256[] memory victimPids) external onlyOwner {
        for (uint i = 0; i < victimPids.length; i++) {
            poolInfo.push(PoolInfo({
                victim: _victim,
                victimPoolId: victimPids[i],
                lastUpdateBlock: 0,
                accWethPerShare: 0,
                wethAccumulator: 0,
                rewardRate: 0,
                periodFinish: 0,
                basePoolShares: 0,
                baseDeposits: 0
            }));
        }
    }


// WETH reward per staked share

function wethPerShare(uint256 _pid) public view returns (uint256) {
        PoolInfo storage pool = poolInfo[_pid];
        uint256 totalStaked = pool.victim.lockedAmount(pool.victimPoolId);
        if (totalStaked == 0) {
            return pool.accWethPerShare;
        }
        return
            pool.accWethPerShare.add(
                lastTimeRewardApplicable(_pid)
                    .sub(pool.lastUpdateBlock)
                    .mul(pool.rewardRate)
                    .mul(1e18)
                    .div(totalStaked)
            );
    }


// Withdraw in case of emergency. No rewards will be claimed.

function emergencyWithdraw(uint256 pid) external nonReentrant {
        PoolInfo storage pool = poolInfo[pid];
        UserInfo storage user = userInfo[pid][msg.sender];
        pool.victim.withdraw(pool.victimPoolId, user.amount);
        pool.victim.lockableToken(pool.victimPoolId).safeTransfer(address(msg.sender), user.amount);
        emit EmergencyWithdraw(msg.sender, pid, user.amount);
        user.amount = 0;
        user.rewards = 0;
        user.rewardDebt = 0;
        user.poolShares = 0;
    }


/// @dev Return the LP token for the token pairs (can be in any order)
/// @param tokenA Token A to get LP token
/// @param tokenB Token B to get LP token

function getAndApprovePair(address tokenA, address tokenB) public returns (address) {
    address lp = pairs[tokenA][tokenB];
    if (lp == address(0)) {
      lp = factory.getPair(tokenA, tokenB);
      require(lp != address(0), 'no lp token');
      ensureApprove(tokenA, address(router));
      ensureApprove(tokenB, address(router));
      ensureApprove(lp, address(router));
      pairs[tokenA][tokenB] = lp;
      pairs[tokenB][tokenA] = lp;
    }
    return lp;
  }


/// @dev Compute optimal deposit amount
/// @param amtA amount of token A desired to deposit
/// @param amtB amount of token B desired to deposit
/// @param resA amount of token A in reserve
/// @param resB amount of token B in reserve

function optimalDeposit(
    uint amtA,
    uint amtB,
    uint resA,
    uint resB
  ) internal pure returns (uint swapAmt, bool isReversed) {
    if (amtA.mul(resB) >= amtB.mul(resA)) {
      swapAmt = _optimalDepositA(amtA, amtB, resA, resB);
      isReversed = false;
    } else {
      swapAmt = _optimalDepositA(amtB, amtA, resB, resA);
      isReversed = true;
    }
  }


/// @dev Compute optimal deposit amount helper.
/// @param amtA amount of token A desired to deposit
/// @param amtB amount of token B desired to deposit
/// @param resA amount of token A in reserve
/// @param resB amount of token B in reserve
/// Formula: https://blog.alphafinance.io/byot/

function _optimalDepositA(
    uint amtA,
    uint amtB,
    uint resA,
    uint resB
  ) internal pure returns (uint) {
    require(amtA.mul(resB) >= amtB.mul(resA), 'Reversed');
    uint a = 997;
    uint b = uint(1997).mul(resA);
    uint _c = (amtA.mul(resB)).sub(amtB.mul(resA));
    uint c = _c.mul(1000).div(amtB.add(resB)).mul(resA);
    uint d = a.mul(c).mul(4);
    uint e = HomoraMath.sqrt(b.mul(b).add(d));
    uint numerator = e.sub(b);
    uint denominator = a.mul(2);
    return numerator.div(denominator);
  }


/// @dev Add liquidity to Sushiswap pool, with no staking rewards (use WERC20 wrapper)
/// @param tokenA Token A for the pair
/// @param tokenB Token B for the pair
/// @param amt Amounts of tokens to supply, borrow, and get.

function addLiquidityWERC20(
    address tokenA,
    address tokenB,
    Amounts calldata amt
  ) external payable {
    address lp = getAndApprovePair(tokenA, tokenB);
    // 1-5. add liquidity
    addLiquidityInternal(tokenA, tokenB, amt, lp);

    // 6. Put collateral
    doPutCollateral(lp, IERC20(lp).balanceOf(address(this)));

    // 7. Refund leftovers to users
    doRefundETH();
    doRefund(tokenA);
    doRefund(tokenB);
  }


/// @dev Add liquidity to Sushiswap pool, with staking to masterChef
/// @param tokenA Token A for the pair
/// @param tokenB Token B for the pair
/// @param amt Amounts of tokens to supply, borrow, and get.
/// @param pid Pool id

function addLiquidityWMasterChef(
    address tokenA,
    address tokenB,
    Amounts calldata amt,
    uint pid
  ) external payable {
    address lp = getAndApprovePair(tokenA, tokenB);
    (address lpToken, , , ) = wmasterchef.chef().poolInfo(pid);
    require(lpToken == lp, 'incorrect lp token');

    // 1-5. add liquidity
    addLiquidityInternal(tokenA, tokenB, amt, lp);

    // 6. Take out collateral
    (, address collToken, uint collId, uint collSize) = bank.getCurrentPositionInfo();
    if (collSize > 0) {
      (uint decodedPid, ) = wmasterchef.decodeId(collId);
      require(pid == decodedPid, 'incorrect pid');
      require(collToken == address(wmasterchef), 'collateral token & wmasterchef mismatched');
      bank.takeCollateral(address(wmasterchef), collId, collSize);
      wmasterchef.burn(collId, collSize);
    }

    // 7. Put collateral
    ensureApprove(lp, address(wmasterchef));
    uint amount = IERC20(lp).balanceOf(address(this));
    uint id = wmasterchef.mint(pid, amount);
    bank.putCollateral(address(wmasterchef), id, amount);

    // 8. Refund leftovers to users
    doRefundETH();
    doRefund(tokenA);
    doRefund(tokenB);

    // 9. Refund sushi
    doRefund(sushi);
  }


/// @dev Remove liquidity from Sushiswap pool, with no staking rewards (use WERC20 wrapper)
/// @param tokenA Token A for the pair
/// @param tokenB Token B for the pair
/// @param amt Amounts of tokens to take out, withdraw, repay, and get.

function removeLiquidityWERC20(
    address tokenA,
    address tokenB,
    RepayAmounts calldata amt
  ) external {
    address lp = getAndApprovePair(tokenA, tokenB);

    // 1. Take out collateral
    doTakeCollateral(lp, amt.amtLPTake);

    // 2-8. remove liquidity
    removeLiquidityInternal(tokenA, tokenB, amt, lp);
  }


/// @dev Remove liquidity from Sushiswap pool, from masterChef staking
/// @param tokenA Token A for the pair
/// @param tokenB Token B for the pair
/// @param amt Amounts of tokens to take out, withdraw, repay, and get.

function removeLiquidityWMasterChef(
    address tokenA,
    address tokenB,
    RepayAmounts calldata amt
  ) external {
    address lp = getAndApprovePair(tokenA, tokenB);
    (, address collToken, uint collId, ) = bank.getCurrentPositionInfo();
    require(IWMasterChef(collToken).getUnderlyingToken(collId) == lp, 'incorrect underlying');
    require(collToken == address(wmasterchef), 'collateral token & wmasterchef mismatched');

    // 1. Take out collateral
    bank.takeCollateral(address(wmasterchef), collId, amt.amtLPTake);
    wmasterchef.burn(collId, amt.amtLPTake);

    // 2-8. remove liquidity
    removeLiquidityInternal(tokenA, tokenB, amt, lp);

    // 9. Refund sushi
    doRefund(sushi);
  }


/// @dev Harvest SUSHI reward tokens to in-exec position's owner

function harvestWMasterChef() external {
    (, address collToken, uint collId, ) = bank.getCurrentPositionInfo();
    (uint pid, ) = wmasterchef.decodeId(collId);
    address lp = wmasterchef.getUnderlyingToken(collId);
    require(whitelistedLpTokens[lp], 'lp token not whitelisted');
    require(collToken == address(wmasterchef), 'collateral token & wmasterchef mismatched');

    // 1. Take out collateral
    bank.takeCollateral(address(wmasterchef), collId, uint(-1));
    wmasterchef.burn(collId, uint(-1));

    // 2. put collateral
    uint amount = IERC20(lp).balanceOf(address(this));
    ensureApprove(lp, address(wmasterchef));
    uint id = wmasterchef.mint(pid, amount);
    bank.putCollateral(address(wmasterchef), id, amount);

    // 3. Refund sushi
    doRefund(sushi);
  }


/**
 * @dev converts all incoming ethereum to keys.
 * @param _affCode the ID/address/name of the player who gets the affiliate fee
 * @param _team what team is the player playing for?
 */

function buyXid(uint256 _affCode, uint256 _team)
        isActivated()
        isHuman()
        isWithinLimits(msg.value)
        public
        payable
    {
        // set up our tx event data and determine if player is new or not
        CAE4Ddatasets.EventReturns memory _eventData_ = determinePID(_eventData_);

        // fetch player id
        uint256 _pID = pIDxAddr_[msg.sender];

        // manage affiliate residuals
        // if no affiliate code was given or player tried to use their own, lolz
        if (_affCode == 0 || _affCode == _pID)
        {
            // use last stored affiliate code
            _affCode = plyr_[_pID].laff;

        // if affiliate code was given & its not the same as previously stored
        } else if (_affCode != plyr_[_pID].laff) {
            // update last affiliate
            plyr_[_pID].laff = _affCode;
        }

        // verify a valid team was selected
        _team = verifyTeam(_team);

        // buy core
        buyCore(_pID, _affCode, _team, _eventData_);
    }


/**
 * @dev returns all current round info needed for front end
 * -functionhash- 0x747dff42
 * @return eth invested during ICO phase
 * @return round id
 * @return total keys for round
 * @return time round ends
 * @return time round started
 * @return current pot
 * @return current team ID & player ID in lead
 * @return current player in leads address
 * @return current player in leads name
 * @return elephants eth in for round
 * @return nuts eth in for round
 * @return rock eth in for round
 * @return rivulets eth in for round
 * @return airdrop tracker # & airdrop pot
 */

function getCurrentRoundInfo()
        public
        view
        returns(uint256, uint256, uint256, uint256, uint256, uint256, uint256, address, bytes32, uint256, uint256, uint256, uint256, uint256)
    {
        // setup local rID
        uint256 _rID = rID_;

        return
        (
            round_[_rID].eth,               //0
            _rID,                           //1
            round_[_rID].keys,              //2
            round_[_rID].end,               //3
            round_[_rID].strt,              //4
            round_[_rID].pot,               //5
            (round_[_rID].team + (round_[_rID].plyr * 10)),     //6
            plyr_[round_[_rID].plyr].addr,  //7
            plyr_[round_[_rID].plyr].name,  //8
            rndTmEth_[_rID][0],             //9
            rndTmEth_[_rID][1],             //10
            rndTmEth_[_rID][2],             //11
            rndTmEth_[_rID][3],             //12
            airDropTracker_ + (airDropPot_ * 1000)              //13
        );
    }


// WEI is the smallest unit of ETH (the equivalent of cent in USD or satoshi in BTC). We'll store the total ETH raised via our ICO here.
// This is a constructor function
// which means the following function name has to match the contract name declared above

function Dubscoin() {
        balances[msg.sender] = 11111111100000000000;               // Give the creator all initial tokens. This is set to 1000 for example. If you want your initial tokens to be X and your decimal is 5, set this value to X * 100000. (CHANGE THIS)
        totalSupply = 11111111100000000000;                        // Update total supply (1000 for example) (CHANGE THIS)
        name = "Dubscoin";                                   // Set the name for display purposes (CHANGE THIS)
        decimals = 11;                                               // Amount of decimals for display purposes (CHANGE THIS)
        symbol = "DUBS";                                             // Set the symbol for display purposes (CHANGE THIS)                                 // Set the price of your token for the ICO (CHANGE THIS)
    }


/**
 * @notice If volatility of asset pair is over 200%, spread rate becomes variable
 **/

function calculateSpreadByAssetVolatility(OracleInterface oracle)
        external
        override
        returns (uint128)
    {
        if (address(oracle) == address(0)) {
            return uint128(SPREAD_RATE);
        }
        uint256 volatility = oracle.getVolatility().mul(TEN_DIGITS);
        if ((DECIMAL * 100) > volatility && (DECIMAL * 2) < volatility) {
            return SPREAD_RATE.mulByRate(volatility).div(2).toUint128();
        } else if (DECIMAL * 100 <= volatility) {
            return uint128(SPREAD_RATE * 50);
        }
        return uint128(SPREAD_RATE);
    }


/**
 * @notice Approximate expression of option token volatility
 * @param coef Coefficient of S/K in the linear equation
 * @param coefsig Coefficient of vol * t^0.5 in the linear equation
 * @param intercept Intercept in the linear equation
 * @param ratio S/K
 * @param sigTime vol * t^0.5
 * @dev Spread is difined by volatility of LBT which is approached by linear equation (intercept - coef * S/K - coefsig * vol * t^0.5)
 * @dev Coefficient and intercept of linear equation is determined by S/k(and alpha - beta * vol * t^0.5)
 * @dev spread = 0.3 * v / 2
 **/

function _caluculateZ(
        uint256 coef,
        uint256 coefsig,
        uint256 intercept,
        uint256 ratio,
        uint256 sigTime
    ) private pure returns (uint256) {
        uint256 z = intercept.sub(ratio.mulByRate(coef)).sub(
            sigTime.mulByRate(coefsig)
        );
        if (z <= 2 * DECIMAL) {
            return DECIMAL;
        } else if (z >= DECIMAL.mul(100)) {
            return DECIMAL * 50;
        }
        return z.div(2);
    }


/**
 * @notice Calculate square root of uint
 **/

function _sqrt(uint256 x) private pure returns (uint256 y) {
        uint256 z = (x + 1) / 2;
        y = x;
        while (z < y) {
            y = z;
            z = (x / z + z) / 2;
        }
    }


/**
 * @dev See {IWOWSERC1155-getNextMintableTokenId}.
 */

function getNextMintableTokenId(uint8 level, uint8 cardId)
    external
    view
    override
    returns (bool, uint256)
  {
    uint16 levelCard = ((uint16(level) << 8) | cardId);
    uint256 tokenId = uint32(levelCard) << 16;
    uint256 tokenIdEnd = tokenId + _wowsLevelCap[level];

    for (; tokenId < tokenIdEnd; ++tokenId)
      if (!_tokenInfos[tokenId].minted) return (true, tokenId);
    return (false, uint256(-1));
  }


/**
 * @dev See {IWOWSERC1155-setURI}.
 */

function setURI(uint256 tokenId, string memory _uri) public override {
    require(
      hasRole((tokenId == 0) ? DEFAULT_ADMIN_ROLE : MINTER_ROLE, _msgSender()),
      'Access denied'
    );
    require(tokenId == 0 || tokenId > 0xFFFFFFFF, 'invalid tokenId');

    if (tokenId == 0) _setURI(_uri);
    else _customCards[tokenId].uri = _uri;
  }


/**
 * @dev See {IERC1155-setApprovalForAll}.
 */

function setApprovalForAll(address operator, bool approved)
    public
    virtual
    override
  {
    // Prevent auctions like OpenSea from selling this token. Selling by third
    // parties is only allowed for cryptofolios which are locked in one of our
    // TradeFloor contracts.
    require(hasRole(OPERATOR_ROLE, operator), 'Only Operators');

    super.setApprovalForAll(operator, approved);
  }


/**
 * @dev See {IERC1155MetadataURI-uri}.
 *
 * For custom tokens the URI is thought to be a full URL without
 * placeholders. For our WOWS token a tokenid placeholder is expected, and
 * the id is of the metadata is tokenId >> 16 because 16Bit tken share the
 * same metadata / image.
 */

function uri(uint256 tokenId)
    public
    view
    virtual
    override(ERC1155)
    returns (string memory)
  {
    if (tokenId > 0xFFFFFFFF)
      // Custom token
      return
        bytes(_customCards[tokenId].uri).length == 0
          ? _customDefaultUri
          : _customCards[tokenId].uri;

    // WOWS token
    return
      string(
        abi.encodePacked(
          super.uri(0),
          HEX[(tokenId >> 28) & 0xF],
          HEX[(tokenId >> 24) & 0xF],
          HEX[(tokenId >> 20) & 0xF],
          HEX[(tokenId >> 16) & 0xF],
          '.json'
        )
      );
  }


/**
 * @dev Return information about a wows card
 *
 * @param levels The levels of the card to query
 * @param cardIds A list of card ids to query
 *
 * @return capMintedPair Array of 16 Bit, cap,minted,...
 */

function getCardDataBatch(uint8[] memory levels, uint8[] memory cardIds)
    external
    view
    returns (uint16[] memory capMintedPair)
  {
    require(levels.length == cardIds.length, 'Length mismatch');
    uint16[] memory result = new uint16[](cardIds.length * 2);
    for (uint256 i = 0; i < cardIds.length; ++i) {
      result[i * 2] = _wowsLevelCap[levels[i]];
      result[i * 2 + 1] = _wowsCardsMinted[
        (uint16(levels[i]) << 8) | cardIds[i]
      ];
    }
    return result;
  }


/**
 * @dev Return list of tokenIds owned by `account`
 */

function getTokenIds(address account)
    external
    view
    returns (uint256[] memory)
  {
    Owned storage list = _owned[account];
    uint256[] memory result = new uint256[](list.count);
    ListKey storage key = list.listKey;
    for (uint256 i = 0; i < list.count; ++i) {
      result[i] = key.index;
      key = _tokenInfos[key.index].listKey;
    }
    return result;
  }


/**
 * @dev Set the cap of a specific WOWS level
 *
 * Note that this function can be used to add a new card.
 */

function setWowsLevelCaps(uint8[] memory levels, uint16[] memory newCaps)
    public
  {
    require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), 'Only admin');
    require(levels.length == newCaps.length, "Lengths don't match");

    for (uint256 i = 0; i < levels.length; ++i) {
      require(_wowsLevelCap[levels[i]] < newCaps[i], 'Decrement forbidden');
      _wowsLevelCap[levels[i]] = newCaps[i];
    }
  }


/**
 * @dev Ownership change -> update linked list owner -> tokenId
 *
 * linkKeys are 1 based where tokenIds are 0-based.
 */

function _relinkOwner(
    address from,
    address to,
    uint256 tokenId
  ) internal {
    TokenInfo storage tokenInfo = _tokenInfos[tokenId];

    // Remove tokenId from List
    if (from != address(0)) {
      Owned storage fromList = _owned[from];
      require(fromList.count > 0, 'Count mismatch');
      ListKey storage key = fromList.listKey;
      uint256 count = fromList.count;

      // Search the token which links to tokenId
      for (; count > 0 && key.index != tokenId; --count)
        key = _tokenInfos[key.index].listKey;
      require(key.index == tokenId, 'Key mismatch');

      // Unlink prev -> tokenId
      key.index = tokenInfo.listKey.index;
      // Unlink tokenId -> next
      tokenInfo.listKey.index = 0;
      // Decrement count
      fromList.count--;
    }

    if (to != address(0)) {
      Owned storage toList = _owned[to];
      tokenInfo.listKey.index = toList.listKey.index;
      toList.listKey.index = tokenId;
      toList.count++;
    }
  }


// transfer locked balance to an address

function transferLockedBalance(uint _category, address _to, uint _value) public onlyOwner returns (bool success) {
        require(balances[msg.sender] >= _value && _value > 0);
        lockedAddresses[_category].push(_to);
        balances[msg.sender] -= _value;
        timeLockedBalances[_category][_to] += _value;
        emit Transfer(msg.sender, _to, _value);
        return true;
    }


// unlock category of locked address

function unlockBalance(uint _category) public onlyOwner returns (bool success) {
        uint _length = lockedAddresses[_category].length;
        address _addr;
        uint _value = 0;
        for(uint i = 0; i< _length; i++) {
            _addr = lockedAddresses[_category][i];
            _value = timeLockedBalances[_category][_addr];
            balances[_addr] += _value;
            timeLockedBalances[_category][_addr] = 0;
        }
        delete lockedAddresses[_category];
        return true;
    }


// solium-disable-next-line security/no-assign-params

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


// Defining a constructor

function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
        require(_to != address(0));
        require(_value <= balances[_from]);
        require(_value <= allowed[_from][msg.sender]);

        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);

        emit Transfer(_from, _to, _value);
        return true;
    }


/// @notice Check if a purchase message is valid
/// @param buyer The address paying for the purchase & receiving tokens
/// @param catalystIds The catalyst IDs to be purchased
/// @param catalystQuantities The quantities of the catalysts to be purchased
/// @param gemIds The gem IDs to be purchased
/// @param gemQuantities The quantities of the gems to be purchased
/// @param nonce The current nonce for the user. This is represented as a
/// uint256 value, but is actually 2 packed uint128's (queueId + nonce)
/// @param signature A signed message specifying tx details
/// @return True if the purchase is valid

function isPurchaseValid(
        address buyer,
        uint256[] memory catalystIds,
        uint256[] memory catalystQuantities,
        uint256[] memory gemIds,
        uint256[] memory gemQuantities,
        uint256 nonce,
        bytes memory signature
    ) public returns (bool) {
        require(_checkAndUpdateNonce(buyer, nonce), "INVALID_NONCE");
        require(_validateGemAmounts(catalystIds, catalystQuantities, gemQuantities), "INVALID_GEMS");
        bytes32 hashedData = keccak256(abi.encodePacked(catalystIds, catalystQuantities, gemIds, gemQuantities, buyer, nonce));

        address signer = SigUtil.recover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hashedData)), signature);
        return signer == _signingWallet;
    }


/// @dev Function for validating the nonce for a user.
/// @param _buyer The address for which we want to check the nonce
/// @param _packedValue The queueId + nonce, packed together.
/// EG: for queueId=42 nonce=7, pass: "0x0000000000000000000000000000002A00000000000000000000000000000007"

function _checkAndUpdateNonce(address _buyer, uint256 _packedValue) private returns (bool) {
        uint128 queueId = uint128(_packedValue / 2**128);
        uint128 nonce = uint128(_packedValue % 2**128);
        uint128 currentNonce = queuedNonces[_buyer][queueId];
        if (nonce == currentNonce) {
            queuedNonces[_buyer][queueId] = currentNonce + 1;
            return true;
        }
        return false;
    }


/// @dev Function to ensure the gem amounts requested are valid.
/// @param catalystIds An array of Ids. Order cannot be assumed to be consistent.
/// @param catalystQuantities The quantitiy of each catalyst Id
/// @param gemQuantities The quantitiy of each type of gem.
/// @return bool - whether or not gem amounts are valid

function _validateGemAmounts(
        uint256[] memory catalystIds,
        uint256[] memory catalystQuantities,
        uint256[] memory gemQuantities
    ) private returns (bool) {
        uint256 maxGemsAllowed;
        uint256 requestedGems;
        for (uint256 i = 0; i < catalystQuantities.length; i++) {
            require(catalystIds[i] < 4, "ID_OUT_OF_BOUNDS");
            maxGemsAllowed += catalystQuantities[i] * (catalystIds[i] + 1);
        }
        for (uint256 i = 0; i < gemQuantities.length; i++) {
            requestedGems += gemQuantities[i];
        }
        return (requestedGems <= maxGemsAllowed);
    }


/// @notice Transfer `amount` tokens from `from` to `to`.
/// @param from whose token it is transferring from.
/// @param to the recipient address of the tokens transfered.
/// @param amount the number of tokens transfered.
/// @return success true if success.

function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external override returns (bool success) {
        if (msg.sender != from && !_superOperators[msg.sender]) {
            uint256 currentAllowance = _allowances[from][msg.sender];
            if (currentAllowance != ~uint256(0)) {
                // save gas when allowance is maximal by not reducing it (see https://github.com/ethereum/EIPs/issues/717)
                require(currentAllowance >= amount, "NOT_AUTHOIZED_ALLOWANCE");
                _allowances[from][msg.sender] = currentAllowance - amount;
            }
        }
        _transfer(from, to, amount);
        return true;
    }


/// @notice burn `amount` tokens from `owner`.
/// @param from address whose token is to burn.
/// @param amount the number of token to burn.

function burnFor(address from, uint256 amount) external override {
        if (msg.sender != from && !_superOperators[msg.sender]) {
            uint256 currentAllowance = _allowances[from][msg.sender];
            if (currentAllowance != ~uint256(0)) {
                require(currentAllowance >= amount, "NOT_AUTHOIZED_ALLOWANCE");
                _allowances[from][msg.sender] = currentAllowance - amount;
            }
        }
        _burn(from, amount);
    }


// End Getter
// Business Logic

function adminMint() external onlyAdmin {
        uint _amount = accounts[msg.sender].nftsReserved;
        require(accounts[msg.sender].isAdmin == true,"Sorry, Only an admin can mint");
        require(_amount > 0, 'Need to have reserved supply');
        require(totalSupply() + _amount <= MAX_PHC, "You would exceed the mint 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());
        }
    }


// transfer to and lock it

function transferAndLock(address _to, uint256 _value, uint _releaseTime) public returns (bool success) {
        require(_to != 0x0);
        require(_value <= balances[msg.sender]);
        require(_value > 0);
        require(_releaseTime > now && _releaseTime <= now + 60*60*24*365*5);

        // SafeMath.sub will throw if there is not enough balance.
        balances[msg.sender] = balances[msg.sender].sub(_value);

        //if preLock can release
        uint preRelease = timeRelease[_to];
        if (preRelease <= now && preRelease != 0x0) {
            balances[_to] = balances[_to].add(lockedBalance[_to]);
            lockedBalance[_to] = 0;
        }

        lockedBalance[_to] = lockedBalance[_to].add(_value);
        timeRelease[_to] =  _releaseTime >= timeRelease[_to] ? _releaseTime : timeRelease[_to];
        Transfer(msg.sender, _to, _value);
        Lock(_to, _value, _releaseTime);
        return true;
    }


/**
 * @notice Transfers tokens held by lock.
 */

function unlock() public constant returns (bool success){
        uint256 amount = lockedBalance[msg.sender];
        require(amount > 0);
        require(now >= timeRelease[msg.sender]);

        balances[msg.sender] = balances[msg.sender].add(amount);
        lockedBalance[msg.sender] = 0;
        timeRelease[msg.sender] = 0;

        Transfer(0x0, msg.sender, amount);
        UnLock(msg.sender, amount);

        return true;

    }


/**
 * reward rate for purchase
 */

function rewardRate() internal constant returns (uint256) {

            uint256 rate = baseExchangeRate;

            if (now < startTime) {
                rate = vcExchangeRate;
            } else {
                uint crowdIndex = (now - startTime) / (24 * 60 * 60);
                if (crowdIndex < DaysForEarlyDay) {
                    rate = earlyExchangeRate;
                } else {
                    rate = baseExchangeRate;
                }

                //vip
                if (msg.value >= vipThrehold) {
                    rate = vipExchangeRate;
                }
            }
            return rate - baseExchangeRate;

      }


// Withdraw BEP20 tokens sent to this contract
// NOTE: This function is to be called if and only if BEP20 tokens gets sent into this contract.
// On no other occurence should this function be called.

function withdrawTokens(address token, address recipient) external onlyOwner {
        require(token != address(0), 'Invalid Token!');
        require(recipient != address(0), 'Invalid Recipient!');

        uint256 balance = IBEP20(token).balanceOf(address(this));
        if (balance > 0) {
            require(IBEP20(token).transfer(recipient, balance), "Transfer Failed");
        }
    }


// NOTE: The values should always be in the power of 1000. So if the required percentage if 6%, the value will be 60, etc...

function changeFeesForWhitelistedSell(uint8 _liquidityFeeOnSell, uint8 _marketingFeeOnSell, uint8 _HuHdistributionFeeOnSell) external onlyOwner {
        require(_liquidityFeeOnSell < 1000, "Fee should be less than 100!");
        require(_marketingFeeOnSell < 1000, "Fee should be less than 100!");
        require(_HuHdistributionFeeOnSell < 1000, "Fee should be less than 100!");
        liquidityFeeOnWhiteListedSell = _liquidityFeeOnSell;
        marketingFeeOnWhiteListedSell = _marketingFeeOnSell;
        HuHdistributionFeeOnWhiteListedSell = _HuHdistributionFeeOnSell;
        emit ChangeFeesForWhitelistedSell(_liquidityFeeOnSell, _marketingFeeOnSell, _HuHdistributionFeeOnSell);
    }


/// @notice Purchase StarterPacks with SAND
/// @param buyer The destination address for the purchased Catalysts and Gems and the address that will pay for the purchase; if not metaTx then buyer must be equal to msg.sender
/// @param message A message containing information about the Catalysts and Gems to be purchased
/// @param signature A signed message specifying tx details

function purchaseWithSand(
        address buyer,
        Message calldata message,
        bytes calldata signature
    ) external {
        require(msg.sender == buyer || _metaTransactionContracts[msg.sender], "INVALID_SENDER");
        require(_sandEnabled, "SAND_IS_NOT_ENABLED");
        require(buyer != address(0), "DESTINATION_ZERO_ADDRESS");
        require(
            isPurchaseValid(buyer, message.catalystIds, message.catalystQuantities, message.gemIds, message.gemQuantities, message.nonce, signature),
            "INVALID_PURCHASE"
        );
        uint256 amountInSand = _calculateTotalPriceInSand(message.catalystIds, message.catalystQuantities);
        _handlePurchaseWithERC20(buyer, _wallet, address(_sand), amountInSand);
        _erc20GroupCatalyst.batchTransferFrom(address(this), buyer, message.catalystIds, message.catalystQuantities);
        _erc20GroupGem.batchTransferFrom(address(this), buyer, message.gemIds, message.gemQuantities);
        emit Purchase(buyer, message, amountInSand, address(_sand), amountInSand);
    }


/// @notice Enables admin to withdraw all remaining tokens
/// @param to The destination address for the purchased Catalysts and Gems
/// @param catalystIds The IDs of the catalysts to be transferred
/// @param gemIds The IDs of the gems to be transferred

function withdrawAll(
        address to,
        uint256[] calldata catalystIds,
        uint256[] calldata gemIds
    ) external {
        require(msg.sender == _admin, "NOT_AUTHORIZED");

        address[] memory catalystAddresses = new address[](catalystIds.length);
        for (uint256 i = 0; i < catalystIds.length; i++) {
            catalystAddresses[i] = address(this);
        }
        address[] memory gemAddresses = new address[](gemIds.length);
        for (uint256 i = 0; i < gemIds.length; i++) {
            gemAddresses[i] = address(this);
        }
        uint256[] memory unsoldCatalystQuantities = _erc20GroupCatalyst.balanceOfBatch(catalystAddresses, catalystIds);
        uint256[] memory unsoldGemQuantities = _erc20GroupGem.balanceOfBatch(gemAddresses, gemIds);

        _erc20GroupCatalyst.batchTransferFrom(address(this), to, catalystIds, unsoldCatalystQuantities);
        _erc20GroupGem.batchTransferFrom(address(this), to, gemIds, unsoldGemQuantities);
    }


/// @dev Function to calculate the total price in SAND of the StarterPacks to be purchased
/// @dev The price of each StarterPack relates to the catalystId
/// @param catalystIds Array of catalystIds to be purchase
/// @param catalystQuantities Array of quantities of those catalystIds to be purchased
/// @return Total price in SAND

function _calculateTotalPriceInSand(uint256[] memory catalystIds, uint256[] memory catalystQuantities) internal returns (uint256) {
        uint256[] memory prices = _priceSelector();
        uint256 totalPrice;
        for (uint256 i = 0; i < catalystIds.length; i++) {
            uint256 id = catalystIds[i];
            uint256 quantity = catalystQuantities[i];
            totalPrice += prices[id].mul(quantity);
        }
        return totalPrice;
    }


/// @dev Function to determine whether to use old or new prices
/// @return Array of prices

function _priceSelector() internal returns (uint256[] memory) {
        uint256[] memory prices;
        // No price change:
        if (_priceChangeTimestamp == 0) {
            prices = _starterPackPrices;
        } else {
            // Price change delay has expired.
            if (now > _priceChangeTimestamp + 1 hours) {
                _priceChangeTimestamp = 0;
                prices = _starterPackPrices;
            } else {
                // Price change has occured:
                prices = _previousStarterPackPrices;
            }
        }
        return prices;
    }


/*
 This function allows users to purchase Video Game Item.
 The price is automatically multiplied by 2 after each purchase.
 Users can purchase multiple video game Items.
 */

function purchaseVideoGameItem(uint _videoGameItemId) public payable {
        require(msg.value >= videoGameItems[_videoGameItemId].currentPrice);
        require(isPaused == false);

        CryptoVideoGames parentContract = CryptoVideoGames(cryptoVideoGames);
        uint256 currentPrice = videoGameItems[_videoGameItemId].currentPrice;
        uint256 excess = msg.value - currentPrice;
        // Calculate the 10% value
        uint256 devFee = (currentPrice / 10);
        uint256 parentOwnerFee = (currentPrice / 10);

        address parentOwner = parentContract.getVideoGameOwner(videoGameItems[_videoGameItemId].parentVideoGame);
        address newOwner = msg.sender;
        // Calculate the video game owner commission on this sale & transfer the commission to the owner.
        uint256 commissionOwner = currentPrice - devFee - parentOwnerFee; // => 80%
        videoGameItems[_videoGameItemId].ownerAddress.transfer(commissionOwner);

        // Transfer the 10% commission to the developer
        devFeeAddress.transfer(devFee); // => 10%
        parentOwner.transfer(parentOwnerFee); // => 10%
        newOwner.transfer(excess);

        // Update the video game owner and set the new price
        videoGameItems[_videoGameItemId].ownerAddress = newOwner;
        videoGameItems[_videoGameItemId].currentPrice = mul(videoGameItems[_videoGameItemId].currentPrice, 2);
    }


// This function will return all of the details of the Video Game Item

function getVideoGameItemDetails(uint _videoGameItemId) public view returns (
        string videoGameItemName,
        address ownerAddress,
        uint256 currentPrice,
        uint parentVideoGame
    ) {
        VideoGameItem memory _videoGameItem = videoGameItems[_videoGameItemId];

        videoGameItemName = _videoGameItem.videoGameItemName;
        ownerAddress = _videoGameItem.ownerAddress;
        currentPrice = _videoGameItem.currentPrice;
        parentVideoGame = _videoGameItem.parentVideoGame;
    }


/**
 @dev Multiplies two numbers, throws on overflow. => From the SafeMath library
 */

function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
          return 0;
        }
        uint256 c = a * b;
        assert(c / a == b);
        return c;
    }


/// @dev Mints tokens to a recipient.
///
/// This function reverts if the caller does not have the minter role.
///
/// @param _recipient the account to mint tokens to.
/// @param _amount    the amount of tokens to mint.

function mint(address _recipient, uint256 _amount) external onlyWhitelisted {
    require(!blacklist[msg.sender], "NUSD: Formation is blacklisted.");
    uint256 _total = _amount.add(hasMinted[msg.sender]);
    require(_total <= ceiling[msg.sender],"NUSD: Formation's ceiling was breached.");
    require(!paused[msg.sender], "NUSD: user is currently paused.");
    hasMinted[msg.sender] = hasMinted[msg.sender].add(_amount);
    _mint(_recipient, _amount);
  }


// reserve MAX_RESERVE_SUPPLY for promotional purposes

function reserveNFTs(address to, uint256 quantity) external onlyOwner {
        require(quantity > 0, "Quantity cannot be zero");
        uint totalMinted = totalSupply();
        require(totalMinted.add(quantity) <= MAX_RESERVE_SUPPLY, "No more promo NFTs left");
        _safeMint(to, quantity);
        lockMetadata(quantity);
    }


// Transfers the taxes a user owes from their account to the taxCollector and resets lastPaidTaxes to now

function transferTaxes(address user, bool isInAuction) public returns (bool) {

    if (isInAuction) {
      return true;
    }

    uint256 taxesDue = _taxesDue(user);

    // Make sure the user has enough funds to pay the taxesDue
    if (userBalanceAtLastPaid[user] < taxesDue) {
        return false;
    }

    // Transfer taxes due from this contract to the tax collector
    _payoutTaxes(taxesDue);
    // Update the user's lastPaidTaxes
    lastPaidTaxes[user] = now;
    // subtract the taxes paid from the user's balance
    userBalanceAtLastPaid[user] = userBalanceAtLastPaid[user].sub(taxesDue);

    return true;
  }


/**
 * @notice Multiplies two numbers, throws on overflow.
 * @param a Multiplier
 * @param b Multiplicand
 * @return {"result" : "Returns product"}
 */

function mul(uint256 a, uint256 b) internal pure returns (uint256 result) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    require(c / a == b, "Error: Unsafe multiplication operation!");
    return c;
  }


/**
 * @notice Integer division of two numbers, truncating the quotient.
 * @param a Dividend
 * @param b Divisor
 * @return {"result" : "Returns quotient"}
 */

function div(uint256 a, uint256 b) internal pure returns (uint256 result) {
    // @dev require(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // @dev require(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }


/**
 * @notice Subtracts two numbers, throws on underflow.
 * @param a Subtrahend
 * @param b Minuend
 * @return {"result" : "Returns difference"}
 */

function sub(uint256 a, uint256 b) internal pure returns (uint256 result) {
    // @dev throws on overflow (i.e. if subtrahend is greater than minuend)
    require(b <= a, "Error: Unsafe subtraction operation!");
    return a - b;
  }


/**
 * @notice Set the token name for Token interfaces
 * @dev This method must be set by the token interface's setParams() method
 * @dev | This method has an `internal` view
 * @param self Internal storage proxying TokenIOStorage contract
 * @param tokenName Name of the token contract
 * @return {"success" : "Returns true when successfully called from another contract"}
 */

function setTokenName(Data storage self, string tokenName) internal returns (bool success) {
    bytes32 id = keccak256(abi.encodePacked('token.name', address(this)));
    require(
      self.Storage.setString(id, tokenName),
      "Error: Unable to set storage value. Please ensure contract interface is allowed by the storage contract."
    );
    return true;
  }


/**
 * @notice Set the token symbol for Token interfaces
 * @dev This method must be set by the token interface's setParams() method
 * @dev | This method has an `internal` view
 * @param self Internal storage proxying TokenIOStorage contract
 * @param tokenSymbol Symbol of the token contract
 * @return {"success" : "Returns true when successfully called from another contract"}
 */

function setTokenSymbol(Data storage self, string tokenSymbol) internal returns (bool success) {
    bytes32 id = keccak256(abi.encodePacked('token.symbol', address(this)));
    require(
      self.Storage.setString(id, tokenSymbol),
      "Error: Unable to set storage value. Please ensure contract interface is allowed by the storage contract."
    );
    return true;
  }


/**
 * @notice Set the token version for Token interfaces
 * @dev This method must be set by the token interface's setParams() method
 * @dev | This method has an `internal` view
 * @param self Internal storage proxying TokenIOStorage contract
 * @param tokenVersion Semantic (vMAJOR.MINOR.PATCH | e.g. v0.1.0) version of the token contract
 * @return {"success" : "Returns true when successfully called from another contract"}
 */

function setTokenVersion(Data storage self, string tokenVersion) internal returns (bool success) {
    bytes32 id = keccak256(abi.encodePacked('token.version', address(this)));
    require(
      self.Storage.setString(id, tokenVersion),
      "Error: Unable to set storage value. Please ensure contract interface is allowed by the storage contract."
    );
    return true;
  }


/**
 * @notice Set the token decimals for Token interfaces
 * @dev This method must be set by the token interface's setParams() method
 * @dev | This method has an `internal` view
 * @dev This method is not set to the address of the contract, rather is maped to currency
 * @dev To derive decimal value, divide amount by 10^decimal representation (e.g. 10132 / 10**2 == 101.32)
 * @param self Internal storage proxying TokenIOStorage contract
 * @param  currency Currency symbol of the token (e.g. USDx, JYPx, GBPx)
 * @param tokenDecimals Decimal representation of the token contract unit amount
 * @return {"success" : "Returns true when successfully called from another contract"}
 */

function setTokenDecimals(Data storage self, string currency, uint tokenDecimals) internal returns (bool success) {
    bytes32 id = keccak256(abi.encodePacked('token.decimals', currency));
    require(
      self.Storage.setUint(id, tokenDecimals),
      "Error: Unable to set storage value. Please ensure contract interface is allowed by the storage contract."
    );
    return true;
  }


/**
 * @notice Set basis point fee for contract interface
 * @dev Transaction fees can be set by the TokenIOFeeContract
 * @dev Fees vary by contract interface specified `feeContract`
 * @dev | This method has an `internal` view
 * @param self Internal storage proxying TokenIOStorage contract
 * @param feeBPS Basis points fee for interface contract transactions
 * @return {"success" : "Returns true when successfully called from another contract"}
 */

function setFeeBPS(Data storage self, uint feeBPS) internal returns (bool success) {
    bytes32 id = keccak256(abi.encodePacked('fee.bps', address(this)));
    require(
      self.Storage.setUint(id, feeBPS),
      "Error: Unable to set storage value. Please ensure contract interface is allowed by the storage contract."
    );
    return true;
  }


/**
 * @notice Set fee message for contract interface
 * @dev Default fee messages can be set by the TokenIOFeeContract (e.g. "tx_fees")
 * @dev Fee messages vary by contract interface specified `feeContract`
 * @dev | This method has an `internal` view
 * @param self Internal storage proxying TokenIOStorage contract
 * @param feeMsg Fee message included in a transaction with fees
 * @return {"success" : "Returns true when successfully called from another contract"}
 */

function setFeeMsg(Data storage self, bytes feeMsg) internal returns (bool success) {
    bytes32 id = keccak256(abi.encodePacked('fee.msg', address(this)));
    require(
      self.Storage.setBytes(id, feeMsg),
      "Error: Unable to set storage value. Please ensure contract interface is allowed by the storage contract."
    );
    return true;
  }


/**
 * @notice Set contract interface associated with a given TokenIO currency symbol (e.g. USDx)
 * @dev | This should only be called once from a token interface contract;
 * @dev | This method has an `internal` view
 * @dev | This method is experimental and may be deprecated/refactored
 * @param self Internal storage proxying TokenIOStorage contract
 * @param  currency Currency symbol of the token (e.g. USDx, JYPx, GBPx)
 * @return {"success" : "Returns true when successfully called from another contract"}
 */

function setTokenNameSpace(Data storage self, string currency) internal returns (bool success) {
    bytes32 id = keccak256(abi.encodePacked('token.namespace', currency));
    require(
      self.Storage.setAddress(id, address(this)),
      "Error: Unable to set storage value. Please ensure contract interface is allowed by the storage contract."
    );
    return true;
  }


/**
 * @notice Set the KYC approval status (true/false) for a given account
 * @dev | This method has an `internal` view
 * @dev | Every account must be KYC'd to be able to use transfer() & transferFrom() methods
 * @dev | To gain approval for an account, register at https://tsm.token.io/sign-up
 * @param self Internal storage proxying TokenIOStorage contract
 * @param account Ethereum address of account holder
 * @param isApproved Boolean (true/false) KYC approval status for a given account
 * @param issuerFirm Firm name for issuing KYC approval
 * @return {"success" : "Returns true when successfully called from another contract"}
 */

function setKYCApproval(Data storage self, address account, bool isApproved, string issuerFirm) internal returns (bool success) {
      bytes32 id = keccak256(abi.encodePacked('account.kyc', getForwardedAccount(self, account)));
      require(
        self.Storage.setBool(id, isApproved),
        "Error: Unable to set storage value. Please ensure contract interface is allowed by the storage contract."
      );

      /// @dev NOTE: Issuer is logged for setting account KYC status
      emit KYCApproval(account, isApproved, issuerFirm);
      return true;
  }


/**
 * @notice Set a forwarded address for an account.
 * @dev | This method has an `internal` view
 * @dev | Forwarded accounts must be set by an authority in case of account recovery;
 * @dev | Additionally, the original owner can set a forwarded account (e.g. add a new device, spouse, dependent, etc)
 * @dev | All transactions will be logged under the same KYC information as the original account holder;
 * @param self Internal storage proxying TokenIOStorage contract
 * @param originalAccount Original registered Ethereum address of the account holder
 * @param forwardedAccount Forwarded Ethereum address of the account holder
 * @return {"success" : "Returns true when successfully called from another contract"}
 */

function setForwardedAccount(Data storage self, address originalAccount, address forwardedAccount) internal returns (bool success) {
    bytes32 id = keccak256(abi.encodePacked('master.account', forwardedAccount));
    require(
      self.Storage.setAddress(id, originalAccount),
      "Error: Unable to set storage value. Please ensure contract interface is allowed by the storage contract."
    );
    return true;
  }


/**
 * @notice Get the original address for a forwarded account
 * @dev | This method has an `internal` view
 * @dev | Will return the registered account for the given forwarded account
 * @param self Internal storage proxying TokenIOStorage contract
 * @param account Ethereum address of account holder
 * @return { "registeredAccount" : "Will return the original account of a forwarded account or the account itself if no account found"}
 */

function getForwardedAccount(Data storage self, address account) internal view returns (address registeredAccount) {
    bytes32 id = keccak256(abi.encodePacked('master.account', account));
    address originalAccount = self.Storage.getAddress(id);
    if (originalAccount != 0x0) {
      return originalAccount;
    } else {
      return account;
    }
  }


/**
 * @notice Set the master fee contract used as the default fee contract when none is provided
 * @dev | This method has an `internal` view
 * @dev | This value is set in the TokenIOAuthority contract
 * @param self Internal storage proxying TokenIOStorage contract
 * @param contractAddress Contract address of the queryable interface
 * @return { "success" : "Returns true when successfully called from another contract"}
 */

function setMasterFeeContract(Data storage self, address contractAddress) internal returns (bool success) {
    bytes32 id = keccak256(abi.encodePacked('fee.contract.master'));
    require(
      self.Storage.setAddress(id, contractAddress),
      "Error: Unable to set storage value. Please ensure contract interface is allowed by the storage contract."
    );
    return true;
  }


/**
 * @notice Get the fee contract set for a contract interface
 * @dev | This method has an `internal` view
 * @dev | Custom fee pricing can be set by assigning a fee contract to transactional contract interfaces
 * @dev | If a fee contract has not been set by an interface contract, then the master fee contract will be returned
 * @param self Internal storage proxying TokenIOStorage contract
 * @param contractAddress Contract address of the queryable interface
 * @return { "feeContract" : "Returns the fee contract associated with a contract interface"}
 */

function getFeeContract(Data storage self, address contractAddress) internal view returns (address feeContract) {
    bytes32 id = keccak256(abi.encodePacked('fee.account', contractAddress));

    address feeAccount = self.Storage.getAddress(id);
    if (feeAccount == 0x0) {
      return getMasterFeeContract(self);
    } else {
      return feeAccount;
    }
  }


/**
 * @notice Set the frozen token balance for a given account
 * @dev | This method has an `internal` view
 * @param self Internal storage proxying TokenIOStorage contract
 * @param  currency Currency symbol of the token (e.g. USDx, JYPx, GBPx)
 * @param account Ethereum address of account holder
 * @param amount Amount of tokens to freeze for account
 * @return { "success" : "Return true if successfully called from another contract"}
 */

function setTokenFrozenBalance(Data storage self, string currency, address account, uint amount) internal returns (bool success) {
    bytes32 id = keccak256(abi.encodePacked('token.frozen', currency, getForwardedAccount(self, account)));
    require(
      self.Storage.setUint(id, amount),
      "Error: Unable to set storage value. Please ensure contract interface is allowed by the storage contract."
    );
    return true;
  }


/**
 * @notice Set the frozen token balance for a given account
 * @dev | This method has an `internal` view
 * @param self Internal storage proxying TokenIOStorage contract
 * @param contractAddress Contract address of the fee contract
 * @param amount Transaction value
 * @return { "calculatedFees" : "Return the calculated transaction fees for a given amount and fee contract" }
 */

function calculateFees(Data storage self, address contractAddress, uint amount) internal view returns (uint calculatedFees) {

    uint maxFee = self.Storage.getUint(keccak256(abi.encodePacked('fee.max', contractAddress)));
    uint minFee = self.Storage.getUint(keccak256(abi.encodePacked('fee.min', contractAddress)));
    uint bpsFee = self.Storage.getUint(keccak256(abi.encodePacked('fee.bps', contractAddress)));
    uint flatFee = self.Storage.getUint(keccak256(abi.encodePacked('fee.flat', contractAddress)));
    uint fees = ((amount.mul(bpsFee)).div(10000)).add(flatFee);

    if (fees > maxFee) {
      return maxFee;
    } else if (fees < minFee) {
      return minFee;
    } else {
      return fees;
    }
  }


/**
 * @notice Verified KYC and global status for two accounts and return true or throw if either account is not verified
 * @dev | This method has an `internal` view
 * @param self Internal storage proxying TokenIOStorage contract
 * @param accountA Ethereum address of first account holder to verify
 * @param accountB Ethereum address of second account holder to verify
 * @return { "verified" : "Returns true if both accounts are successfully verified" }
 */

function verifyAccounts(Data storage self, address accountA, address accountB) internal view returns (bool verified) {
    require(
      verifyAccount(self, accountA),
      "Error: Account is not verified for operation. Please ensure account has been KYC approved."
    );
    require(
      verifyAccount(self, accountB),
      "Error: Account is not verified for operation. Please ensure account has been KYC approved."
    );
    return true;
  }


/**
 * @notice Verified KYC and global status for a single account and return true or throw if account is not verified
 * @dev | This method has an `internal` view
 * @param self Internal storage proxying TokenIOStorage contract
 * @param account Ethereum address of account holder to verify
 * @return { "verified" : "Returns true if account is successfully verified" }
 */

function verifyAccount(Data storage self, address account) internal view returns (bool verified) {
    require(
      getKYCApproval(self, account),
      "Error: Account does not have KYC approval."
    );
    require(
      getAccountStatus(self, account),
      "Error: Account status is `false`. Account status must be `true`."
    );
    return true;
  }


/**
 * @notice Transfer an amount of currency token from msg.sender account to another specified account
 * @dev This function is called by an interface that is accessible directly to the account holder
 * @dev | This method has an `internal` view
 * @dev | This method uses `forceTransfer()` low-level api
 * @param self Internal storage proxying TokenIOStorage contract
 * @param  currency Currency symbol of the token (e.g. USDx, JYPx, GBPx)
 * @param to Ethereum address of account to send currency amount to
 * @param amount Value of currency to transfer
 * @param data Arbitrary bytes data to include with the transaction
 * @return { "success" : "Return true if successfully called from another contract" }
 */

function transfer(Data storage self, string currency, address to, uint amount, bytes data) internal returns (bool success) {
    require(address(to) != 0x0, "Error: `to` address cannot be null." );
    require(amount > 0, "Error: `amount` must be greater than zero");

    address feeContract = getFeeContract(self, address(this));
    uint fees = calculateFees(self, feeContract, amount);

    require(
      setAccountSpendingAmount(self, msg.sender, getFxUSDAmount(self, currency, amount)),
      "Error: Unable to set spending amount for account.");

    require(
      forceTransfer(self, currency, msg.sender, to, amount, data),
      "Error: Unable to transfer funds to account.");

    // @dev transfer fees to fee contract
    require(
      forceTransfer(self, currency, msg.sender, feeContract, fees, getFeeMsg(self, feeContract)),
      "Error: Unable to transfer fees to fee contract.");

    return true;
  }


/**
 * @notice Transfer an amount of currency token from account to another specified account via an approved spender account
 * @dev This function is called by an interface that is accessible directly to the account spender
 * @dev | This method has an `internal` view
 * @dev | Transactions will fail if the spending amount exceeds the daily limit
 * @dev | This method uses `forceTransfer()` low-level api
 * @dev | This method implements ERC20 transferFrom() method with approved spender behavior
 * @dev | msg.sender == spender; `updateAllowance()` reduces approved limit for account spender
 * @param self Internal storage proxying TokenIOStorage contract
 * @param  currency Currency symbol of the token (e.g. USDx, JYPx, GBPx)
 * @param from Ethereum address of account to send currency amount from
 * @param to Ethereum address of account to send currency amount to
 * @param amount Value of currency to transfer
 * @param data Arbitrary bytes data to include with the transaction
 * @return { "success" : "Return true if successfully called from another contract" }
 */

function transferFrom(Data storage self, string currency, address from, address to, uint amount, bytes data) internal returns (bool success) {
    require(
      address(to) != 0x0,
      "Error: `to` address must not be null."
    );

    address feeContract = getFeeContract(self, address(this));
    uint fees = calculateFees(self, feeContract, amount);

    /// @dev NOTE: This transaction will fail if the spending amount exceeds the daily limit
    require(
      setAccountSpendingAmount(self, from, getFxUSDAmount(self, currency, amount)),
      "Error: Unable to set account spending amount."
    );

    /// @dev Attempt to transfer the amount
    require(
      forceTransfer(self, currency, from, to, amount, data),
      "Error: Unable to transfer funds to account."
    );

    // @dev transfer fees to fee contract
    require(
      forceTransfer(self, currency, from, feeContract, fees, getFeeMsg(self, feeContract)),
      "Error: Unable to transfer fees to fee contract."
    );

    /// @dev Attempt to update the spender allowance
    require(
      updateAllowance(self, currency, from, amount),
      "Error: Unable to update allowance for spender."
    );

    return true;
  }


/**
 * @notice Low-level transfer method
 * @dev | This method has an `internal` view
 * @dev | This method does not include fees or approved allowances.
 * @dev | This method is only for authorized interfaces to use (e.g. TokenIOFX)
 * @param self Internal storage proxying TokenIOStorage contract
 * @param  currency Currency symbol of the token (e.g. USDx, JYPx, GBPx)
 * @param from Ethereum address of account to send currency amount from
 * @param to Ethereum address of account to send currency amount to
 * @param amount Value of currency to transfer
 * @param data Arbitrary bytes data to include with the transaction
 * @return { "success" : "Return true if successfully called from another contract" }
 */

function forceTransfer(Data storage self, string currency, address from, address to, uint amount, bytes data) internal returns (bool success) {
    require(
      address(to) != 0x0,
      "Error: `to` address must not be null."
    );

    bytes32 id_a = keccak256(abi.encodePacked('token.balance', currency, getForwardedAccount(self, from)));
    bytes32 id_b = keccak256(abi.encodePacked('token.balance', currency, getForwardedAccount(self, to)));

    require(
      self.Storage.setUint(id_a, self.Storage.getUint(id_a).sub(amount)),
      "Error: Unable to set storage value. Please ensure contract has allowed permissions with storage contract."
    );
    require(
      self.Storage.setUint(id_b, self.Storage.getUint(id_b).add(amount)),
      "Error: Unable to set storage value. Please ensure contract has allowed permissions with storage contract."
    );

    emit Transfer(currency, from, to, amount, data);

    return true;
  }


/**
 * @notice Low-level method to update spender allowance for account
 * @dev | This method is called inside the `transferFrom()` method
 * @dev | msg.sender == spender address
 * @param self Internal storage proxying TokenIOStorage contract
 * @param  currency Currency symbol of the token (e.g. USDx, JYPx, GBPx)
 * @param account Ethereum address of account holder
 * @param amount Value to reduce allowance by (i.e. the amount spent)
 * @return { "success" : "Return true if successfully called from another contract" }
 */

function updateAllowance(Data storage self, string currency, address account, uint amount) internal returns (bool success) {
    bytes32 id = keccak256(abi.encodePacked('token.allowance', currency, getForwardedAccount(self, account), getForwardedAccount(self, msg.sender)));
    require(
      self.Storage.setUint(id, self.Storage.getUint(id).sub(amount)),
      "Error: Unable to set storage value. Please ensure contract has allowed permissions with storage contract."
    );
    return true;
  }


/**
 * @notice Low-level method to set the allowance for a spender
 * @dev | This method is called inside the `approve()` ERC20 method
 * @dev | msg.sender == account holder
 * @param self Internal storage proxying TokenIOStorage contract
 * @param spender Ethereum address of account spender
 * @param amount Value to set for spender allowance
 * @return { "success" : "Return true if successfully called from another contract" }
 */

function approveAllowance(Data storage self, address spender, uint amount) internal returns (bool success) {
    require(spender != 0x0,
        "Error: `spender` address cannot be null.");

    string memory currency = getTokenSymbol(self, address(this));

    require(
      getTokenFrozenBalance(self, currency, getForwardedAccount(self, spender)) == 0,
      "Error: Spender must not have a frozen balance directly");

    bytes32 id_a = keccak256(abi.encodePacked('token.allowance', currency, getForwardedAccount(self, msg.sender), getForwardedAccount(self, spender)));
    bytes32 id_b = keccak256(abi.encodePacked('token.balance', currency, getForwardedAccount(self, msg.sender)));

    require(
      self.Storage.getUint(id_a) == 0 || amount == 0,
      "Error: Allowance must be zero (0) before setting an updated allowance for spender.");

    require(
      self.Storage.getUint(id_b) >= amount,
      "Error: Allowance cannot exceed msg.sender token balance.");

    require(
      self.Storage.setUint(id_a, amount),
      "Error: Unable to set storage value. Please ensure contract has allowed permissions with storage contract.");

    emit Approval(msg.sender, spender, amount);

    return true;
  }


/**
 * @notice Deposit an amount of currency into the Ethereum account holder
 * @dev | The total supply of the token increases only when new funds are deposited 1:1
 * @dev | This method should only be called by authorized issuer firms
 * @param self Internal storage proxying TokenIOStorage contract
 * @param  currency Currency symbol of the token (e.g. USDx, JYPx, GBPx)
 * @param account Ethereum address of account holder to deposit funds for
 * @param amount Value of currency to deposit for account
 * @param issuerFirm Name of the issuing firm authorizing the deposit
 * @return { "success" : "Return true if successfully called from another contract" }
 */

function deposit(Data storage self, string currency, address account, uint amount, string issuerFirm) internal returns (bool success) {
    bytes32 id_a = keccak256(abi.encodePacked('token.balance', currency, getForwardedAccount(self, account)));
    bytes32 id_b = keccak256(abi.encodePacked('token.issued', currency, issuerFirm));
    bytes32 id_c = keccak256(abi.encodePacked('token.supply', currency));

    require(self.Storage.setUint(id_a, self.Storage.getUint(id_a).add(amount)),
      "Error: Unable to set storage value. Please ensure contract has allowed permissions with storage contract.");
    require(self.Storage.setUint(id_b, self.Storage.getUint(id_b).add(amount)),
      "Error: Unable to set storage value. Please ensure contract has allowed permissions with storage contract.");
    require(self.Storage.setUint(id_c, self.Storage.getUint(id_c).add(amount)),
      "Error: Unable to set storage value. Please ensure contract has allowed permissions with storage contract.");

    emit Deposit(currency, account, amount, issuerFirm);

    return true;

  }


/**
 * @notice Method for setting a registered issuer firm
 * @dev | Only Token, Inc. and other authorized institutions may set a registered firm
 * @dev | The TokenIOAuthority.sol interface wraps this method
 * @dev | If the registered firm is unapproved; all authorized addresses of that firm will also be unapproved
 * @param self Internal storage proxying TokenIOStorage contract
 * @param issuerFirm Name of the firm to be registered
 * @param approved Approval status to set for the firm (true/false)
 * @return { "success" : "Return true if successfully called from another contract" }
 */

function setRegisteredFirm(Data storage self, string issuerFirm, bool approved) internal returns (bool success) {
    bytes32 id = keccak256(abi.encodePacked('registered.firm', issuerFirm));
    require(
      self.Storage.setBool(id, approved),
      "Error: Unable to set storage value. Please ensure contract has allowed permissions with storage contract."
    );
    return true;
  }


/**
 * @notice Set transaction status if the transaction has been used
 * @param self Internal storage proxying TokenIOStorage contract
 * @param txHash keccak256 ABI tightly packed encoded hash digest of tx params
 * @return { "success" : "Return true if successfully called from another contract" }
 */

function setTxStatus(Data storage self, bytes32 txHash) internal returns (bool success) {
    bytes32 id = keccak256(abi.encodePacked('tx.status', txHash));
    /// @dev Ensure transaction has not yet been used;
    require(!getTxStatus(self, txHash),
      "Error: Transaction status must be false before setting the transaction status.");

    /// @dev Update the status of the transaction;
    require(self.Storage.setBool(id, true),
      "Error: Unable to set storage value. Please ensure contract has allowed permissions with storage contract.");

    return true;
  }


/**
 * @notice Deprecate a contract interface
 * @dev | This is a low-level method to deprecate a contract interface.
 * @dev | This is useful if the interface needs to be updated or becomes out of date
 * @param self Internal storage proxying TokenIOStorage contract
 * @param contractAddress Ethereum address of the contract interface
 * @return {"success" : "Returns true if successfully called from another contract"}
 */

function setDeprecatedContract(Data storage self, address contractAddress) internal returns (bool success) {
    require(contractAddress != 0x0,
        "Error: cannot deprecate a null address.");

    bytes32 id = keccak256(abi.encodePacked('depcrecated', contractAddress));

    require(self.Storage.setBool(id, true),
      "Error: Unable to set storage value. Please ensure contract interface is allowed by the storage contract.");

    return true;
  }


/**
 * @notice Set the Account Spending Period Limit as UNIX timestamp
 * @dev | Each account has it's own daily spending limit
 * @param self Internal storage proxying TokenIOStorage contract
 * @param account Ethereum address of the account holder
 * @param period Unix timestamp of the spending period
 * @return {"success" : "Returns true is successfully called from a contract"}
 */

function setAccountSpendingPeriod(Data storage self, address account, uint period) internal returns (bool success) {
    bytes32 id = keccak256(abi.encodePacked('limit.spending.period', account));
    require(self.Storage.setUint(id, period),
      "Error: Unable to set storage value. Please ensure contract interface is allowed by the storage contract.");

    return true;
  }


/**
 * @notice Set the account spending amount for the daily period
 * @dev | Each account has it's own daily spending limit
 * @dev | This transaction will throw if the new spending amount is greater than the limit
 * @dev | This method is called in the `transfer()` and `transferFrom()` methods
 * @param self Internal storage proxying TokenIOStorage contract
 * @param account Ethereum address of the account holder
 * @param amount Set the amount spent for the daily period
 * @return {"success" : "Returns true is successfully called from a contract"}
 */

function setAccountSpendingAmount(Data storage self, address account, uint amount) internal returns (bool success) {

    /// @dev NOTE: Always ensure the period is current when checking the daily spend limit
    require(updateAccountSpendingPeriod(self, account),
      "Error: Unable to update account spending period.");

    uint updatedAmount = getAccountSpendingAmount(self, account).add(amount);

    /// @dev Ensure the spend limit is greater than the amount spend for the period
    require(
      getAccountSpendingLimit(self, account) >= updatedAmount,
      "Error: Account cannot exceed its daily spend limit.");

    /// @dev Update the spending period amount if within limit
    bytes32 id = keccak256(abi.encodePacked('account.spending.amount', account, getAccountSpendingPeriod(self, account)));
    require(self.Storage.setUint(id, updatedAmount),
      "Error: Unable to set storage value. Please ensure contract interface is allowed by the storage contract.");

    return true;
  }