/**
 * @notice Creates a new option
 * @param period Option period in seconds (1 days <= period <= 4 weeks)
 * @param amount Option amount
 * @param strike Strike price of the option
 * @return optionID Created option's ID
 */

function create(
        uint256 period,
        uint256 amount,
        uint256 strike
    ) external payable returns (uint256 optionID) {
        (uint256 total, uint256 settlementFee, , ) = fees(
            period,
            amount,
            strike
        );
        uint256 strikeAmount = strike.mul(amount) / PRICE_DECIMALS;

        require(strikeAmount > 0, "Amount is too small");
        require(settlementFee < total, "Premium is too small");
        require(period >= 1 days, "Period is too short");
        require(period <= 4 weeks, "Period is too long");
        require(msg.value == total, "Wrong value");

        uint256 premium = sendPremium(total.sub(settlementFee));
        optionID = options.length;
        options.push(
            Option(
                State.Active,
                msg.sender,
                strike,
                amount,
                premium,
                now + period
            )
        );

        emit Create(optionID, msg.sender, settlementFee, total);
        lockFunds(options[optionID]);
        settlementFeeRecipient.transfer(settlementFee);
    }


/**
 * @notice Exercises an active option
 * @param optionID ID of your option
 */

function exercise(uint256 optionID) external {
        Option storage option = options[optionID];

        require(option.expiration >= now, "Option has expired");
        require(option.holder == msg.sender, "Wrong msg.sender");
        require(option.state == State.Active, "Wrong state");

        option.state = State.Exercised;
        uint256 profit = payProfit(option);

        emit Exercise(optionID, profit);
    }


/**
 * @notice Used for getting the actual options prices
 * @param period Option period in seconds (1 days <= period <= 4 weeks)
 * @param amount Option amount
 * @param strike Strike price of the option
 * @return total Total price to be paid
 * @return settlementFee Amount to be distributed to the HEGIC token holders
 * @return strikeFee Amount that covers the price difference in the ITM options
 * @return periodFee Option period fee amount
 */

function fees(
        uint256 period,
        uint256 amount,
        uint256 strike
    )
        public
        view
        returns (
            uint256 total,
            uint256 settlementFee,
            uint256 strikeFee,
            uint256 periodFee
        )
    {
        uint256 currentPrice = uint256(priceProvider.latestAnswer());
        settlementFee = getSettlementFee(amount);
        periodFee = getPeriodFee(amount, period, strike, currentPrice);
        strikeFee = getStrikeFee(amount, strike, currentPrice);
        total = periodFee.add(strikeFee);
    }


/**
 * @notice Unlock funds locked in the expired options
 * @param optionID ID of the option
 */

function unlock(uint256 optionID) public {
        Option storage option = options[optionID];
        require(option.expiration < now, "Option has not expired yet");
        require(option.state == State.Active, "Option is not active");
        option.state = State.Expired;
        unlockFunds(option);
        emit Expire(optionID, option.premium);
    }


/**
 * @notice Calculates periodFee
 * @param amount Option amount
 * @param period Option period in seconds (1 days <= period <= 4 weeks)
 * @param strike Strike price of the option
 * @param currentPrice Current price of ETH
 * @return fee Period fee amount
 *
 * amount < 1e30        |
 * impliedVolRate < 1e10| => amount * impliedVolRate * strike < 1e60 < 2^uint256
 * strike < 1e20 ($1T)  |
 *
 * in case amount * impliedVolRate * strike >= 2^256
 * transaction will be reverted by the SafeMath
 */

function getPeriodFee(
        uint256 amount,
        uint256 period,
        uint256 strike,
        uint256 currentPrice
    ) internal view returns (uint256 fee) {
        if (optionType == OptionType.Put)
            return amount
                .mul(sqrt(period))
                .mul(impliedVolRate)
                .mul(strike)
                .div(currentPrice)
                .div(PRICE_DECIMALS);
        else
            return amount
                .mul(sqrt(period))
                .mul(impliedVolRate)
                .mul(currentPrice)
                .div(strike)
                .div(PRICE_DECIMALS);
    }


/**
 * @notice Calculates strikeFee
 * @param amount Option amount
 * @param strike Strike price of the option
 * @param currentPrice Current price of ETH
 * @return fee Strike fee amount
 */

function getStrikeFee(
        uint256 amount,
        uint256 strike,
        uint256 currentPrice
    ) internal view returns (uint256 fee) {
        if (strike > currentPrice && optionType == OptionType.Put)
            return strike.sub(currentPrice).mul(amount).div(currentPrice);
        if (strike < currentPrice && optionType == OptionType.Call)
            return currentPrice.sub(strike).mul(amount).div(currentPrice);
        return 0;
    }


//user is buying SVC

function buy() payable public returns (uint256 amount){
        if(!usersCanTrade && !canTrade[msg.sender]) revert();
        amount = msg.value * buyPrice;                    // calculates the amount

        require(balanceOf[this] >= amount);               // checks if it has enough to sell
        balanceOf[msg.sender] += amount;                  // adds the amount to buyer's balance
        balanceOf[this] -= amount;                        // subtracts amount from seller's balance
        Transfer(this, msg.sender, amount);               // execute an event reflecting the change
        return amount;                                    // ends function and returns
    }


//user is selling us SVC, we are selling eth to the user

function sell(uint256 amount) public returns (uint revenue){
        require(!frozen[msg.sender]);
        if(!usersCanTrade && !canTrade[msg.sender]) {
            require(minBalanceForAccounts > amount/sellPrice);
        }
        require(balanceOf[msg.sender] >= amount);         // checks if the sender has enough to sell
        balanceOf[this] += amount;                        // adds the amount to owner's balance
        balanceOf[msg.sender] -= amount;                  // subtracts the amount from seller's balance
        revenue = amount / sellPrice;
        require(msg.sender.send(revenue));                // sends ether to the seller: it's important to do this last to prevent recursion attacks
        Transfer(msg.sender, this, amount);               // executes an event reflecting on the change
        return revenue;                                   // ends function and returns
    }


/*
 * @dev Verifies a Merkle proof proving the existence of a leaf in a Merkle tree. Assumes that each pair of leaves
 * and each pair of pre-images is sorted.
 * @param _proof Merkle proof containing sibling hashes on the branch from the leaf to the root of the Merkle tree
 * @param _root Merkle root
 * @param _leaf Leaf of Merkle tree
 */

function verifyProof(bytes _proof, bytes32 _root, bytes32 _leaf) public pure returns (bool) {
    // Check if proof length is a multiple of 32
    if (_proof.length % 32 != 0) return false;

    bytes32 proofElement;
    bytes32 computedHash = _leaf;

    for (uint256 i = 32; i <= _proof.length; i += 32) {
      assembly {
        // Load the current element of the proof
        proofElement := mload(add(_proof, i))
      }

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

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


/**
 * @dev Extract a bytes32 subarray from an arbitrary length bytes array.
 * @param data Bytes array from which to extract the subarray
 * @param pos Starting position from which to copy
 * @return Extracted length 32 byte array
 */

function extract(bytes data, uint pos) private pure returns (bytes32 result) {
        for (uint i = 0; i < 32; i++) {
            result ^= (bytes32(0xff00000000000000000000000000000000000000000000000000000000000000) & data[i + pos]) >> (i * 8);
        }
        return result;
    }


/**
 * @dev Calculate the Bitcoin-style address associated with an ECDSA public key
 * @param pubKey ECDSA public key to convert
 * @param isCompressed Whether or not the Bitcoin address was generated from a compressed key
 * @return Raw Bitcoin address (no base58-check encoding)
 */

function pubKeyToBitcoinAddress(bytes pubKey, bool isCompressed) public pure returns (bytes20) {
        /* Helpful references:
           - https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
           - https://github.com/cryptocoinjs/ecurve/blob/master/lib/point.js
        */

        /* x coordinate - first 32 bytes of public key */
        uint x = uint(extract(pubKey, 0));
        /* y coordinate - second 32 bytes of public key */
        uint y = uint(extract(pubKey, 32));
        uint8 startingByte;
        if (isCompressed) {
            /* Hash the compressed public key format. */
            startingByte = y % 2 == 0 ? 0x02 : 0x03;
            return ripemd160(sha256(startingByte, x));
        } else {
            /* Hash the uncompressed public key format. */
            startingByte = 0x04;
            return ripemd160(sha256(startingByte, x, y));
        }
    }


/**
 * @dev Convenience helper function to check if a UTXO can be redeemed
 * @param txid Transaction hash
 * @param originalAddress Raw Bitcoin address (no base58-check encoding)
 * @param outputIndex Output index of UTXO
 * @param satoshis Amount of UTXO in satoshis
 * @param proof Merkle tree proof
 * @return Whether or not the UTXO can be redeemed
 */

function canRedeemUTXO(bytes32 txid, bytes20 originalAddress, uint8 outputIndex, uint satoshis, bytes proof) public constant returns (bool) {
        /* Calculate the hash of the Merkle leaf associated with this UTXO. */
        bytes32 merkleLeafHash = keccak256(txid, originalAddress, outputIndex, satoshis);

        /* Verify the proof. */
        return canRedeemUTXOHash(merkleLeafHash, proof);
    }


/**
 * @dev Verify that a UTXO with the specified Merkle leaf hash can be redeemed
 * @param merkleLeafHash Merkle tree hash of the UTXO to be checked
 * @param proof Merkle tree proof
 * @return Whether or not the UTXO with the specified hash can be redeemed
 */

function canRedeemUTXOHash(bytes32 merkleLeafHash, bytes proof) public constant returns (bool) {
        /* Check that the UTXO has not yet been redeemed and that it exists in the Merkle tree. */
        return((redeemedUTXOs[merkleLeafHash] == false) && verifyProof(proof, merkleLeafHash));
    }


/**
 * @dev Initialize the Wyvern token
 * @param merkleRoot Merkle tree root of the UTXO set
 * @param totalUtxoAmount Total satoshis of the UTXO set
 */

function WyvernToken (bytes32 merkleRoot, uint totalUtxoAmount) public {
        /* Total number of tokens that can be redeemed from UTXOs. */
        uint utxoTokens = SATS_TO_TOKENS * totalUtxoAmount;

        /* Configure DelayedReleaseToken. */
        temporaryAdmin = msg.sender;
        numberOfDelayedTokens = MINT_AMOUNT - utxoTokens;

        /* Configure UTXORedeemableToken. */
        rootUTXOMerkleTreeHash = merkleRoot;
        totalSupply = MINT_AMOUNT;
        maximumRedeemable = utxoTokens;
        multiplier = SATS_TO_TOKENS;
    }


// Guardian state

function _getGuardianStakingRewards(address guardian, bool inCommittee, bool inCommitteeAfter, uint256 guardianWeight, uint256 guardianDelegatedStake, StakingRewardsState memory _stakingRewardsState, Settings memory _settings) private view returns (GuardianStakingRewards memory guardianStakingRewards, uint256 rewardsAdded) {
        guardianStakingRewards = guardiansStakingRewards[guardian];

        if (inCommittee) {
            uint256 totalRewards = uint256(_stakingRewardsState.stakingRewardsPerWeight)
                .sub(guardianStakingRewards.lastStakingRewardsPerWeight)
                .mul(guardianWeight);

            uint256 delegatorRewardsRatioPercentMille = _getGuardianDelegatorsStakingRewardsPercentMille(guardian, _settings);

            uint256 delegatorRewardsPerTokenDelta = guardianDelegatedStake == 0 ? 0 : totalRewards
                .div(guardianDelegatedStake)
                .mul(delegatorRewardsRatioPercentMille)
                .div(PERCENT_MILLIE_BASE);

            uint256 guardianCutPercentMille = PERCENT_MILLIE_BASE.sub(delegatorRewardsRatioPercentMille);

            rewardsAdded = totalRewards
                    .mul(guardianCutPercentMille)
                    .div(PERCENT_MILLIE_BASE)
                    .div(TOKEN_BASE);

            guardianStakingRewards.delegatorRewardsPerToken = guardianStakingRewards.delegatorRewardsPerToken.add(delegatorRewardsPerTokenDelta);
            guardianStakingRewards.balance = guardianStakingRewards.balance.add(rewardsAdded);
        }

        guardianStakingRewards.lastStakingRewardsPerWeight = inCommitteeAfter ? _stakingRewardsState.stakingRewardsPerWeight : 0;
    }


// Delegator state

function _getDelegatorStakingRewards(address delegator, uint256 delegatorStake, GuardianStakingRewards memory guardianStakingRewards) private view returns (DelegatorStakingRewards memory delegatorStakingRewards, uint256 delegatorRewardsAdded) {
        delegatorStakingRewards = delegatorsStakingRewards[delegator];

        delegatorRewardsAdded = uint256(guardianStakingRewards.delegatorRewardsPerToken)
                .sub(delegatorStakingRewards.lastDelegatorRewardsPerToken)
                .mul(delegatorStake)
                .div(TOKEN_BASE);

        delegatorStakingRewards.balance = delegatorStakingRewards.balance.add(delegatorRewardsAdded);
        delegatorStakingRewards.lastDelegatorRewardsPerToken = guardianStakingRewards.delegatorRewardsPerToken;
    }


// Governance and misc.

function _setAnnualStakingRewardsRate(uint256 annualRateInPercentMille, uint256 annualCap) private {
        require(uint256(uint96(annualCap)) == annualCap, "annualCap must fit in uint96");

        Settings memory _settings = settings;
        _settings.annualRateInPercentMille = uint32(annualRateInPercentMille);
        _settings.annualCap = uint96(annualCap);
        settings = _settings;

        emit AnnualStakingRewardsRateChanged(annualRateInPercentMille, annualCap);
    }


// ------------------------------------------------------------------------
// Send ETH to get 1PL tokens
// ------------------------------------------------------------------------

function () public payable {
        require(now >= startDate && now <= endDate);
        uint256 weiAmount = msg.value;
        uint256 tokens = _getTokenAmount(weiAmount);
        if(tokens >= bonus1 && tokens < bonus2){
            tokens = safeMul(tokens, 105);
            tokens = safeDiv(tokens, 100);
        }
        if(tokens >= bonus2 && tokens < bonus3){
            tokens = safeMul(tokens, 110);
            tokens = safeDiv(tokens, 100);
        }
        if(tokens >= bonus3 && tokens < bonus4){
            tokens = safeMul(tokens, 115);
            tokens = safeDiv(tokens, 100);
        }
        if(tokens >= bonus4 && tokens < bonus5){
            tokens = safeMul(tokens, 120);
            tokens = safeDiv(tokens, 100);
        }
        if(tokens >= bonus5 && tokens < bonus6){
            tokens = safeMul(tokens, 130);
            tokens = safeDiv(tokens, 100);
        }
        if(tokens >= bonus6){
            tokens = safeMul(tokens, 140);
            tokens = safeDiv(tokens, 100);
        }
        require(_maxSupply >= safeAdd(_totalSupply, tokens), "Maximum token amount reached. No more tokens to sell");
        balances[msg.sender] = safeAdd(balances[msg.sender], tokens);
        _totalSupply = safeAdd(_totalSupply, tokens);
        Transfer(address(0), msg.sender, tokens);
        owner.transfer(msg.value);
    }


/***********************************|
 |          Only Admin/DAO           |
 |__________________________________*/

function addLeptonType(
    string calldata tokenUri,
    uint256 price,
    uint32 supply,
    uint32 multiplier,
    uint32 bonus
  )
    external
    virtual
    onlyOwner
  {
    _maxSupply = _maxSupply.add(uint256(supply));

    Classification memory lepton = Classification({
      tokenUri: tokenUri,
      price: price,
      supply: supply,
      multiplier: multiplier,
      bonus: bonus,
      _upperBounds: uint128(_maxSupply)
    });
    _leptonTypes.push(lepton);

    emit LeptonTypeAdded(tokenUri, price, supply, multiplier, bonus, _maxSupply);
  }


/**
 * @dev See {IAdminControl-getAdmins}.
 */

function getAdmins() external view override returns (address[] memory admins) {
        admins = new address[](_admins.length());
        for (uint i = 0; i < _admins.length(); i++) {
            admins[i] = _admins.at(i);
        }
        return admins;
    }


/**
 * @notice Create a new token and return it to the caller.
 * @dev The caller will become the only minter and burner and the new owner capable of assigning the roles.
 * @param tokenName used to describe the new token.
 * @param tokenSymbol short ticker abbreviation of the name. Ideally < 5 chars.
 * @param tokenDecimals used to define the precision used in the token's numerical representation.
 * @return newToken an instance of the newly created token interface.
 */

function createToken(
        string calldata tokenName,
        string calldata tokenSymbol,
        uint8 tokenDecimals
    ) external nonReentrant() returns (ExpandedIERC20 newToken) {
        SyntheticToken mintableToken = new SyntheticToken(tokenName, tokenSymbol, tokenDecimals);
        mintableToken.addMinter(msg.sender);
        mintableToken.addBurner(msg.sender);
        mintableToken.resetOwner(msg.sender);
        newToken = ExpandedIERC20(address(mintableToken));
    }


/**
 * @notice Migrates the tokenHolder's old tokens to new tokens.
 * @dev This function can only be called once per `tokenHolder`. Anyone can call this method
 * on behalf of any other token holder since there is no disadvantage to receiving the tokens earlier.
 * @param tokenHolder address of the token holder to migrate.
 */

function migrateTokens(address tokenHolder) external {
        require(!hasMigrated[tokenHolder], "Already migrated tokens");
        hasMigrated[tokenHolder] = true;

        FixedPoint.Unsigned memory oldBalance = FixedPoint.Unsigned(oldToken.balanceOfAt(tokenHolder, snapshotId));

        if (!oldBalance.isGreaterThan(0)) {
            return;
        }

        FixedPoint.Unsigned memory newBalance = oldBalance.div(rate);
        require(newToken.mint(tokenHolder, newBalance.rawValue), "Mint failed");
    }


// build contract

function PPBC_Ether_Claim(){
        ppbc = msg.sender;
        deposits_refunded = false;
        num_claimed = 0;
        valid_voucher_code[0x99fc71fa477d1d3e6b6c3ed2631188e045b7f575eac394e50d0d9f182d3b0145] = 110.12 ether; total_claim_codes++;
        valid_voucher_code[0x8b4f72e27b2a84a30fe20b0ee5647e3ca5156e1cb0d980c35c657aa859b03183] = 53.2535 ether; total_claim_codes++;
        valid_voucher_code[0xe7ac3e31f32c5e232eb08a8f978c7e4c4845c44eb9fa36e89b91fc15eedf8ffb] = 151 ether; total_claim_codes++;
        valid_voucher_code[0xc18494ff224d767c15c62993a1c28e5a1dc17d7c41abab515d4fcce2bd6f629d] = 63.22342 ether; total_claim_codes++;
        valid_voucher_code[0x5cdb60c9e999a510d191cf427c9995d6ad3120a6b44afcb922149d275afc8ec4] = 101 ether; total_claim_codes++;
        valid_voucher_code[0x5fb7aed108f910cc73b3e10ceb8c73f90f8d6eff61cda5f43d47f7bec9070af4] = 16.3 ether; total_claim_codes++;
        valid_voucher_code[0x571a888f66f4d74442733441d62a92284f1c11de57198decf9d4c244fb558f29] = 424 ether; total_claim_codes++;
        valid_voucher_code[0x7123fa994a2990c5231d35cb11901167704ab19617fcbc04b93c45cf88b30e94] = 36.6 ether; total_claim_codes++;
        valid_voucher_code[0xdac0e1457b4cf3e53e9952b1f8f3a68a0f288a7e6192314d5b19579a5266cce0] = 419.1 ether; total_claim_codes++;
        valid_voucher_code[0xf836a280ec6c519f6e95baec2caee1ba4e4d1347f81d4758421272b81c4a36cb] = 86.44 ether; total_claim_codes++;
        valid_voucher_code[0x5470e8b8b149aca84ee799f6fd1a6bf885267a1f7c88c372560b28180e2cf056] = 92 ether; total_claim_codes++;
        valid_voucher_code[0x7f52b6f587c87240d471d6fcda1bb3c10c004771c1572443134fd6756c001c9a] = 124.2 ether; total_claim_codes++;
        valid_voucher_code[0x5d435968b687edc305c3adc29523aba1128bd9acd2c40ae2c9835f2e268522e1] = 95.102 ether; total_claim_codes++;
     }


// function to register claim
//

function register_claim(string password) payable {
          // claim deposit 50 ether (returned with claim, used to prevent "brute force" password cracking attempts)
          if (msg.value != 50 ether || valid_voucher_code[sha3(password)] == 0) return; // if user does not provide the right password, the deposit is being kept.

          // dont claim twice either, and check if deposits have already been refunded -- > throw
          if (redeemed[sha3(password)] || deposits_refunded ) throw;

          // if we get here the user has provided a valid claim code, and paid deposit
          num_claimed++;
          redeemed[sha3(password)] = true;
          who_claimed[sha3(password)] = msg.sender;
          valid_voucher_code[sha3(password)] += 50 ether;  // add the deposit paid to the claim
          claimers[num_claimed] = sha3(password);
     }


// Refund Step 1: this function will return the deposits paid first
//                (this step is separate to avoid issues in case the claim refund amounts haven't been loaded yet,
//                 so at least the deposits won't get stuck)

function refund_deposits(string password){ //anyone with a code can call this
            if (deposits_refunded) throw; // already refunded
            if (valid_voucher_code[sha3(password)] == 0) throw;

            // wait till everyone has claimed or claim period ended, and refund-pool has been loaded
            if (num_claimed >= total_claim_codes || block.number >= 2850000 ){  // ~ 21/12/2017
                // first refund the deposits
                for (uint256 index = 1; index <= num_claimed; index++){
                    bytes32 claimcode = claimers[index];
                    address receiver = who_claimed[claimcode];
                    if (!receiver.send(50 ether)) throw; // refund deposit, or throw in case of any error
                    valid_voucher_code[claimcode] -= 50 ether;  // deduct the deposit paid from the claim
                }
                deposits_refunded = true; // can only use this function once
            }
            else throw;
            //
     }


// Refund Step 2: this function will refund actual claim amount. But wait for our notification
//             before calling this function (you can check the contract balance after deposit return)

function refund_claims(string password){ //anyone with a code can call this
            if (!deposits_refunded) throw; // first step 1 (refund_deposits) has to be called
            if (valid_voucher_code[sha3(password)] == 0) throw;

            for (uint256 index = 1; index <= num_claimed; index++){
                bytes32 claimcode = claimers[index];
                address receiver = who_claimed[claimcode];
                uint256 refund_amount = valid_voucher_code[claimcode];

                // only refund claims if there is enough left in the claim bucket

                if (this.balance >= refund_amount){
                    if (!receiver.send(refund_amount)) throw; // refund deposit, or throw in case of any error
                    valid_voucher_code[claimcode] = 0;  // deduct the deposit paid from the claim
                }

            }
     }


// Enqueues a request (if a request isn't already present) for the given (identifier, time) pair.

function requestPrice(bytes32 identifier, uint256 time) public override {
        require(_getIdentifierWhitelist().isIdentifierSupported(identifier));
        Price storage lookup = verifiedPrices[identifier][time];
        if (!lookup.isAvailable && !queryIndices[identifier][time].isValid) {
            // New query, enqueue it for review.
            queryIndices[identifier][time] = QueryIndex(true, requestedPrices.length);
            requestedPrices.push(QueryPoint(identifier, time));
        }
    }


// Pushes the verified price for a requested query.

function pushPrice(
        bytes32 identifier,
        uint256 time,
        int256 price
    ) external {
        verifiedPrices[identifier][time] = Price(true, price, getCurrentTime());

        QueryIndex storage queryIndex = queryIndices[identifier][time];
        require(queryIndex.isValid, "Can't push prices that haven't been requested");
        // Delete from the array. Instead of shifting the queries over, replace the contents of `indexToReplace` with
        // the contents of the last index (unless it is the last index).
        uint256 indexToReplace = queryIndex.index;
        delete queryIndices[identifier][time];
        uint256 lastIndex = requestedPrices.length - 1;
        if (lastIndex != indexToReplace) {
            QueryPoint storage queryToCopy = requestedPrices[lastIndex];
            queryIndices[queryToCopy.identifier][queryToCopy.time].index = indexToReplace;
            requestedPrices[indexToReplace] = queryToCopy;
        }
    }


/*
 @notice deposit token into curve
 */

function _depositIntoCurve(address _token, uint _index) private {
        // token to LP
        uint bal = IERC20(_token).balanceOf(address(this));
        if (bal > 0) {
            IERC20(_token).safeApprove(DEPOSIT, 0);
            IERC20(_token).safeApprove(DEPOSIT, bal);

            // mint LP
            uint[4] memory amounts;
            amounts[_index] = bal;

            /*
            shares = underlying amount * precision div * 1e18 / price per share
            */
            uint pricePerShare = StableSwapBBTC(SWAP).get_virtual_price();
            uint shares = bal.mul(PRECISION_DIV[_index]).mul(1e18).div(pricePerShare);
            uint min = shares.mul(SLIPPAGE_MAX - slippage) / SLIPPAGE_MAX;

            DepositBBTC(DEPOSIT).add_liquidity(amounts, min);
        }

        // stake into LiquidityGaugeV2
        uint lpBal = IERC20(LP).balanceOf(address(this));
        if (lpBal > 0) {
            IERC20(LP).safeApprove(GAUGE, 0);
            IERC20(LP).safeApprove(GAUGE, lpBal);
            LiquidityGaugeV2(GAUGE).deposit(lpBal);
        }
    }


/*
 @notice Returns address and index of token with lowest balance in Curve SWAP
 */

function _getMostPremiumToken() private view returns (address, uint) {
        uint[2] memory balances;
        balances[0] = StableSwapBBTC(SWAP).balances(0).mul(1e10); // BBTC
        balances[1] = StableSwapBBTC(SWAP).balances(1); // SBTC pool

        if (balances[0] <= balances[1]) {
            return (BBTC, 0);
        } else {
            uint[3] memory baseBalances;
            baseBalances[0] = StableSwapSBTC(BASE_POOL).balances(0).mul(1e10); // REN_BTC
            baseBalances[1] = StableSwapSBTC(BASE_POOL).balances(1).mul(1e10); // WBTC
            baseBalances[2] = StableSwapSBTC(BASE_POOL).balances(2); // SBTC

            uint minIndex = 0;
            for (uint i = 1; i < baseBalances.length; i++) {
                if (baseBalances[i] <= baseBalances[minIndex]) {
                    minIndex = i;
                }
            }

            /*
            REN_BTC 1
            WBTC    2
            SBTC    3
            */

            if (minIndex == 0) {
                return (REN_BTC, 1);
            }
            if (minIndex == 1) {
                return (WBTC, 2);
            }
            // SBTC has low liquidity, so buying is disabled by default
            if (!disableSbtc) {
                return (SBTC, 3);
            }
            return (WBTC, 2);
        }
    }


/*
 @notice Claim CRV and deposit most premium token into Curve
 */

function harvest() external override onlyAuthorized {
        (address token, uint index) = _getMostPremiumToken();

        _claimRewards(token);

        uint bal = IERC20(token).balanceOf(address(this));
        if (bal > 0) {
            // transfer fee to treasury
            uint fee = bal.mul(performanceFee) / PERFORMANCE_FEE_MAX;
            if (fee > 0) {
                address treasury = IController(controller).treasury();
                require(treasury != address(0), "treasury = zero address");

                IERC20(token).safeTransfer(treasury, fee);
            }

            _depositIntoCurve(token, index);
        }
    }


/**
 * @dev Add multiple vesting to contract by arrays of data
 * @param _users[] addresses of holders
 * @param _startTokens[] tokens that can be withdrawn at startDate
 * @param _totalTokens[] total tokens in vesting
 * @param _startDate date from when tokens can be claimed
 * @param _endDate date after which all tokens can be claimed
 */

function massAddHolders(
        address[] calldata _users,
        uint256[] calldata _startTokens,
        uint256[] calldata _totalTokens,
        uint256 _startDate,
        uint256 _endDate
    ) external onlyOwner whenNotLocked {
        uint256 len = _users.length; //cheaper to use one variable
        require((len == _startTokens.length) && (len == _totalTokens.length), "data size mismatch");
        require(_startDate < _endDate, "startDate cannot exceed endDate");
        uint256 i;
        for (i; i < len; i++) {
            _addHolder(_users[i], _startTokens[i], _totalTokens[i], _startDate, _endDate);
        }
    }


/**
 * @dev Add new vesting to contract
 * @param _user address of a holder
 * @param _startTokens how many tokens are claimable at start date
 * @param _totalTokens total number of tokens in added vesting
 * @param _startDate date from when tokens can be claimed
 * @param _endDate date after which all tokens can be claimed
 */

function _addHolder(
        address _user,
        uint256 _startTokens,
        uint256 _totalTokens,
        uint256 _startDate,
        uint256 _endDate
    ) internal {
        require(_user != address(0), "user address cannot be 0");
        Vest memory v;
        v.startTokens = _startTokens;
        v.totalTokens = _totalTokens;
        v.dateStart = _startDate;
        v.dateEnd = _endDate;

        totalVested += _totalTokens;
        vestings.push(v);
        user2vesting[_user].push(vestings.length); // we are skipping index "0" for reasons
        emit Vested(_user, _totalTokens, _endDate);
    }


/**
 * @dev internal claim function
 * @param _user address of holder
 * @param _target where tokens should be send
 * @return amt number of tokens claimed
 */

function _claim(address _user, address _target) internal nonReentrant returns (uint256 amt) {
        require(_target != address(0), "Claim, then burn");
        if (!vestingAdded[_user] && !refunded[_user]) {
            _addVesting(_user);
        }
        uint256 len = user2vesting[_user].length;
        require(len > 0, "No vestings for user");
        uint256 cl;
        uint256 i;
        for (i; i < len; i++) {
            Vest storage v = vestings[user2vesting[_user][i] - 1];
            cl = _claimable(v);
            v.claimedTokens += cl;
            amt += cl;
        }
        if (amt > 0) {
            totalClaimed += amt;
            _transfer(_target, amt);
            emit Claimed(_user, amt);
        } else revert("Nothing to claim");
    }


/**
 * @dev Count how many tokens can be claimed from vesting to date
 * @param _vesting Vesting object
 * @return canWithdraw number of tokens
 */

function _claimable(Vest memory _vesting) internal view returns (uint256 canWithdraw) {
        uint256 currentTime = block.timestamp;
        if (_vesting.dateStart > currentTime) return 0;
        // we are somewhere in the middle
        if (currentTime < _vesting.dateEnd) {
            // how much time passed (as fraction * 10^18)
            // timeRatio = (time passed * 1e18) / duration
            uint256 timeRatio = (currentTime - _vesting.dateStart).divPrecisely(_vesting.dateEnd - _vesting.dateStart);
            // how much tokens we can get in total to date
            canWithdraw = (_vesting.totalTokens - _vesting.startTokens).mulTruncate(timeRatio) + _vesting.startTokens;
        }
        // time has passed, we can take all tokens
        else {
            canWithdraw = _vesting.totalTokens;
        }
        // but maybe we take something earlier?
        canWithdraw -= _vesting.claimedTokens;
    }


/**
 * @dev Read total amount of tokens that user can claim to date from all vestings
 *      Function also includes tokens to claim from sale contracts that were not
 *      yet initiated for user.
 * @param _user address of holder
 * @return amount number of tokens
 */

function getAllClaimable(address _user) public view returns (uint256 amount) {
        uint256 len = user2vesting[_user].length;
        uint256 i;
        for (i; i < len; i++) {
            amount += _claimable(vestings[user2vesting[_user][i] - 1]);
        }

        if (!vestingAdded[_user]) {
            amount += _claimableFromSaleContracts(_user);
        }
    }


/**
 * @dev Extract all the vestings for the user
 *      Also extract not initialized vestings from
 *      sale contracts.
 * @param _user address of holder
 * @return v array of Vest objects
 */

function getVestings(address _user) external view returns (Vest[] memory) {
        // array of pending vestings
        Vest[] memory pV;

        if (!vestingAdded[_user]) {
            pV = _vestingsFromSaleContracts(_user);
        }
        uint256 pLen = pV.length;
        uint256 len = user2vesting[_user].length;
        Vest[] memory v = new Vest[](len + pLen);

        // copy normal vestings
        uint256 i;
        for (i; i < len; i++) {
            v[i] = vestings[user2vesting[_user][i] - 1];
        }

        // copy not initialized vestings
        if (!vestingAdded[_user]) {
            uint256 j;
            for (j; j < pLen; j++) {
                v[i + j] = pV[j];
            }
        }

        return v;
    }


/**
 * @dev Read registered vesting list by range from-to
 * @param _start first index
 * @param _end last index
 * @return array of Vest objects
 */

function getVestingsByRange(uint256 _start, uint256 _end) external view returns (Vest[] memory) {
        uint256 cnt = _end - _start + 1;
        uint256 len = vestings.length;
        require(_end < len, "range error");
        Vest[] memory v = new Vest[](cnt);
        uint256 i;
        for (i; i < cnt; i++) {
            v[i] = vestings[_start + i];
        }
        return v;
    }


/**
 * @dev Register sale contract
 * @param _contractAddresses  addresses of sale contracts
 * @param _tokensPerCent      sale price
 * @param _maxAmount          the maximum amount in USD cents for which user could buy
 * @param _percentOnStart     percentage of vested coins that can be claimed on start date
 * @param _startDate          date when initial vesting can be released
 * @param _endDate            final date of vesting, where all tokens can be claimed
 */

function addSaleContract(
        address[] memory _contractAddresses,
        uint256 _tokensPerCent,
        uint256 _maxAmount,
        uint256 _percentOnStart,
        uint256 _startDate,
        uint256 _endDate
    ) external onlyOwner whenNotLocked {
        require(_contractAddresses.length > 0, "data is missing");
        require(_startDate < _endDate, "startDate cannot exceed endDate");
        SaleContract memory s;
        s.contractAddresses = _contractAddresses;
        s.tokensPerCent = _tokensPerCent;
        s.maxAmount = _maxAmount;
        s.startDate = _startDate;
        s.percentOnStart = _percentOnStart;
        s.endDate = _endDate;
        saleContracts.push(s);
    }


/**
 * @dev Function iterate sale contracts and initialize corresponding
 *      vesting for user.
 * @param _user address that will be initialized
 */

function _addVesting(address _user) internal {
        require(!refunded[_user], "User refunded");
        require(!vestingAdded[_user], "Already done");
        uint256 len = saleContracts.length;
        uint256 i;
        for (i; i < len; i++) {
            SaleContract memory s = saleContracts[i];
            uint256 sLen = s.contractAddresses.length;
            uint256 j;
            uint256 amt;
            for (j; j < sLen; j++) {
                amt += ISaleContract(s.contractAddresses[j]).balanceOf(_user);
            }
            // amt is in cents, so $100 = 10000
            if (amt > 0) {
                if (amt > s.maxAmount) {
                    amt = s.maxAmount;
                }
                // create Vest object
                Vest memory v = _vestFromSaleContractAndAmount(s, amt);
                // update contract data
                totalVested += v.totalTokens;
                vestings.push(v);
                user2vesting[_user].push(vestings.length);
                emit Vested(_user, v.totalTokens, v.dateEnd);
            }
        }
        vestingAdded[_user] = true;
    }


/**
 * @dev Function iterate sale contracts and count claimable amounts for given user.
 *      Used to calculate claimable amounts from not initialized vestings.
 * @param _user address of user to count claimable
 * @return claimable amount of tokens
 */

function _claimableFromSaleContracts(address _user) internal view returns (uint256 claimable) {
        if (refunded[_user]) return 0;
        uint256 len = saleContracts.length;
        if (len == 0) return 0;
        uint256 i;
        for (i; i < len; i++) {
            SaleContract memory s = saleContracts[i];
            uint256 sLen = s.contractAddresses.length;
            uint256 j;
            uint256 amt;
            for (j; j < sLen; j++) {
                amt += ISaleContract(s.contractAddresses[j]).balanceOf(_user);
            }
            // amt is in cents, so $100 = 10000
            if (amt > 0) {
                if (amt > s.maxAmount) {
                    amt = s.maxAmount;
                }
                claimable += _claimable(_vestFromSaleContractAndAmount(s, amt));
            }
        }
    }


/**
 * @dev Function iterate sale contracts and extract not initialized user vestings.
 *      Used to return all stored and not initialized vestings.
 * @param _user address of user to extract vestings
 * @return v vesting array
 */

function _vestingsFromSaleContracts(address _user) internal view returns (Vest[] memory) {
        uint256 len = saleContracts.length;
        if (refunded[_user] || len == 0) return new Vest[](0);

        Vest[] memory v = new Vest[](_numberOfVestingsFromSaleContracts(_user));
        uint256 i;
        uint256 idx;
        for (i; i < len; i++) {
            SaleContract memory s = saleContracts[i];
            uint256 sLen = s.contractAddresses.length;
            uint256 j;
            uint256 amt;
            for (j; j < sLen; j++) {
                amt += ISaleContract(s.contractAddresses[j]).balanceOf(_user);
            }
            // amt is in cents, so $100 = 10000
            if (amt > 0) {
                if (amt > s.maxAmount) {
                    amt = s.maxAmount;
                }
                v[idx] = _vestFromSaleContractAndAmount(s, amt);
                idx++;
            }
        }
        return v;
    }


/**
 * @dev Function iterate sale contracts and return number of not initialized vestings for user.
 * @param _user address of user to extract vestings
 * @return number of not not initialized user vestings
 */

function _numberOfVestingsFromSaleContracts(address _user) internal view returns (uint256 number) {
        uint256 len = saleContracts.length;
        uint256 i;
        for (i; i < len; i++) {
            SaleContract memory s = saleContracts[i];
            uint256 sLen = s.contractAddresses.length;
            uint256 j;
            uint256 amt;
            for (j; j < sLen; j++) {
                amt += ISaleContract(s.contractAddresses[j]).balanceOf(_user);
            }
            // amt is in cents, so $100 = 10000
            if (amt > 0) {
                number++;
            }
        }
    }


/**
 * @dev Return vesting created from given sale and usd cent amount.
 * @param _sale address of user to extract vestings
 * @param _amount address of user to extract vestings
 * @return v vesting from given parameters
 */

function _vestFromSaleContractAndAmount(SaleContract memory _sale, uint256 _amount) internal pure returns (Vest memory v) {
        v.dateStart = _sale.startDate;
        v.dateEnd = _sale.endDate;
        uint256 total = _amount * _sale.tokensPerCent;
        v.totalTokens = total;
        v.startTokens = (total * _sale.percentOnStart) / 100;
    }


/**
 * @dev Send tokens to other multi addresses in one function
 *
 * @param _destAddrs address The addresses which you want to send tokens to
 * @param _values uint256 the amounts of tokens to be sent
 */

function multiSend(address[] _destAddrs, uint256[] _values) onlyOwner public returns (uint256) {
        require(_destAddrs.length == _values.length);

        uint256 i = 0;
        for (; i < _destAddrs.length; i = i.add(1)) {
            if (!erc20tk.transfer(_destAddrs[i], _values[i])) {
                break;
            }
        }

        return (i);
    }


/**
 * @dev Send tokens to other multi addresses in one function
 *
 * @param _rand a random index for choosing a FlyDropToken contract address
 * @param _from address The address which you want to send tokens from
 * @param _value uint256 the amounts of tokens to be sent
 * @param _token address the ERC20 token address
 */

function prepare(uint256 _rand,
                     address _from,
                     address _token,
                     uint256 _value) onlyOwner public returns (bool) {
        require(_token != address(0));
        require(_from != address(0));
        require(_rand > 0);

        if (ERC20(_token).allowance(_from, this) < _value) {
            return false;
        }

        if (_rand > dropTokenAddrs.length) {
            SimpleFlyDropToken dropTokenContract = new SimpleFlyDropToken();
            dropTokenAddrs.push(address(dropTokenContract));
            currentDropTokenContract = dropTokenContract;
        } else {
            currentDropTokenContract = SimpleFlyDropToken(dropTokenAddrs[_rand.sub(1)]);
        }

        currentDropTokenContract.setToken(_token);
        return ERC20(_token).transferFrom(_from, currentDropTokenContract, _value);
        // budgets[_token][_from] = budgets[_token][_from].sub(_value);
        // return itoken(_token).approveAndCall(currentDropTokenContract, _value, _extraData);
        // return true;
    }


/**
 * @notice Distributes drained rewards
 */

function distribute(uint256 tipAmount) external payable {
        require(drainController != address(0), "drainctrl not set");
        require(WETH.balanceOf(address(this)) >= wethThreshold, "weth balance too low");
        uint256 drainWethBalance = WETH.balanceOf(address(this));
        uint256 gasAmt = drainWethBalance.mul(gasShare).div(1000);
        uint256 devAmt = drainWethBalance.mul(treasuryShare).div(1000);
        uint256 lpRewardPoolAmt = drainWethBalance.mul(lpRewardPoolShare).div(1000);
        uint256 drcRewardPoolAmt = drainWethBalance.mul(drcRewardPoolShare).div(1000);

        // Unwrap WETH and transfer ETH to DrainController to cover drain gas fees
        WETH.withdraw(gasAmt);
        drainController.transfer(gasAmt);

        // Treasury
        WETH.transfer(treasury, devAmt);

        // Reward pools
        IRewardPool(lpRewardPool).fundPool(lpRewardPoolAmt);

        // Buy-back
        address[] memory path = new address[](2);
        path[0] = address(WETH);
        path[1] = drc;

        if (tipAmount > 0) {
            (bool success, ) = block.coinbase.call{value: tipAmount}("");
            require(success, "Could not tip miner");
        }

        uint[] memory amounts = swapRouter.getAmountsOut(drcRewardPoolAmt, path);
        swapRouter.swapExactTokensForTokens(drcRewardPoolAmt, amounts[amounts.length - 1], path, drcRewardPool, block.timestamp);
    }


/**
 * @notice Changes the distribution percentage
 *         Percentages are using decimal base of 1000 ie: 10% = 100
 */

function changeDistribution(
        uint256 gasShare_,
        uint256 treasuryShare_,
        uint256 lpRewardPoolShare_,
        uint256 drcRewardPoolShare_)
        external
        onlyOwner
    {
        require((gasShare_ + treasuryShare_ + lpRewardPoolShare_ + drcRewardPoolShare_) == 1000, "invalid distribution");
        gasShare = gasShare_;
        treasuryShare = treasuryShare_;
        lpRewardPoolShare = lpRewardPoolShare_;
        drcRewardPoolShare = drcRewardPoolShare_;
    }


/**
 * @dev Add or remove an address to the supervisor whitelist
 * @param _supervisor - Address to be allowed or not
 * @param _allowed - Whether the contract will be allowed or not
 */

function setSupervisor(address _supervisor, bool _allowed) external onlyOwner {
        if (_allowed) {
            require(!supervisorWhitelist[_supervisor], "The supervisor is already whitelisted");
        } else {
            require(supervisorWhitelist[_supervisor], "The supervisor is not whitelisted");
        }

        supervisorWhitelist[_supervisor] = _allowed;
        emit SupervisorSet(_supervisor, _allowed, msg.sender);
    }


/**
 * @dev Withdraw an NFT by committing a valid signature
 * @param _beneficiary - Beneficiary's address
 * @param _contract - NFT contract' address
 * @param _tokenId - Token id
 * @param _expires - Expiration of the signature
 * @param _userId - User id
 * @param _signature - Signature
 */

function withdraw(
        address _beneficiary,
        address  _contract,
        uint256 _tokenId,
        uint256 _expires,
        bytes calldata _userId,
        bytes calldata _signature
    ) external {
        require(_expires >= block.timestamp, "Expired signature");

        bytes32 messageHash = keccak256(
            abi.encodePacked(
                _beneficiary,
                _contract,
                _tokenId,
                _expires,
                _userId
            )
        ).toEthSignedMessageHash();

        _validateMessageAndSignature(messageHash, _signature);

        _withdraw(_beneficiary, _contract, _tokenId, _userId);
    }


/**
 * @dev Withdraw many NFTs
 * @param _beneficiary - Beneficiary's address
 * @param _contracts - NFT contract' addresses
 * @param _tokenIds - Token ids
 * @param _userId - User id
 */

function withdrawMany(
        address _beneficiary,
        address[] calldata _contracts,
        uint256[] calldata _tokenIds,
        bytes calldata _userId
    ) external onlyAdmin {
        require(
            _contracts.length == _tokenIds.length,
            "Contracts and token ids must have the same length"
        );

        _withdrawMany(_beneficiary, _contracts, _tokenIds, _userId);
    }


/**
 * @dev Withdraw many NFTs by committing a valid signature
 * @param _beneficiary - Beneficiary's address
 * @param _contracts - NFT contract' addresses
 * @param _tokenIds - Token ids
 * @param _expires - Expiration of the signature
 * @param _userId - User id
 * @param _signature - Signature
 */

function withdrawMany(
        address _beneficiary,
        address[] calldata _contracts,
        uint256[] calldata _tokenIds,
        uint256 _expires,
        bytes calldata _userId,
        bytes calldata _signature
    ) external {
        require(_expires >= block.timestamp, "Expired signature");
        require(
            _contracts.length == _tokenIds.length,
            "Contracts and token ids must have the same length"
        );

        bytes memory transferData;

        for (uint256 i = 0; i < _contracts.length; i++) {
            transferData = abi.encodePacked(
                transferData,
                abi.encode(
                    _contracts[i],
                    _tokenIds[i]
                )
            );
        }

        bytes32 messageHash = keccak256(
            abi.encodePacked(
                _beneficiary,
                transferData,
                _expires,
                _userId
            )
        )
        .toEthSignedMessageHash();

        _validateMessageAndSignature(messageHash, _signature);

        _withdrawMany(_beneficiary, _contracts, _tokenIds, _userId);
    }


/**
 * @dev Withdraw NFTs by minting them
 * @param _beneficiary - Beneficiary's address
 * @param _contracts - NFT contract' addresses
 * @param _optionIds - Option ids
 * @param _issuedIds - Issued ids
 * @param _userId - User id
 */

function issueManyTokens(
        address _beneficiary,
        address[] calldata _contracts,
        uint256[] calldata _optionIds,
        uint256[] calldata _issuedIds,
        bytes calldata _userId
    ) external onlyAdmin {
        require(
            _contracts.length == _optionIds.length,
            "Contracts and option ids must have the same length"
        );
        require(
            _optionIds.length == _issuedIds.length,
            "Option ids and issued ids must have the same length"
        );

        _issueManyTokens(
            _beneficiary,
            _contracts,
            _optionIds,
            _issuedIds,
            _userId
        );
    }


/**
 * @dev Withdraw an NFT by minting it committing a valid signature
 * @param _beneficiary - Beneficiary's address
 * @param _contract - NFT contract' address
 * @param _optionId - Option id
 * @param _issuedId - Issued id
 * @param _expires - Expiration of the signature
 * @param _userId - User id
 * @param _signature - Signature
 */

function issueToken(
        address _beneficiary,
        address _contract,
        uint256 _optionId,
        uint256 _issuedId,
        uint256 _expires,
        bytes calldata _userId,
        bytes calldata _signature
    ) external {
        require(_expires >= block.timestamp, "Expired signature");

        bytes32 messageHash = keccak256(
            abi.encodePacked(
                _beneficiary,
                _contract,
                _optionId,
                _issuedId,
                _expires,
                _userId
            )
        ).toEthSignedMessageHash();

        _validateMessageAndSignature(messageHash, _signature);

        _issueToken(
            _beneficiary,
            _contract,
            _optionId,
            _issuedId,
            _userId
        );
    }


/**
 * @dev Withdraw NFTs by minting them
 * @param _beneficiary - Beneficiary's address
 * @param _contracts - NFT contract' addresses
 * @param _optionIds - Option ids
 * @param _issuedIds - Issued ids
 * @param _expires - Expiration of the signature
 * @param _userId - User id
 * @param _signature - Signature
 */

function issueManyTokens(
        address _beneficiary,
        address[] calldata _contracts,
        uint256[] calldata _optionIds,
        uint256[] calldata _issuedIds,
        uint256 _expires,
        bytes calldata _userId,
        bytes calldata _signature
    ) external {
        require(_expires >= block.timestamp, "Expired signature");
        require(
            _contracts.length == _optionIds.length,
            "Contracts and option ids must have the same length"
        );
        require(
            _optionIds.length == _issuedIds.length,
            "Option ids and issued ids must have the same length"
        );

        bytes memory mintData;

        for (uint256 i = 0; i < _contracts.length; i++) {
            mintData = abi.encodePacked(
                mintData,
                abi.encode(
                    _contracts[i],
                    _optionIds[i],
                    _issuedIds[i]
                )
            );
        }

        bytes32 messageHash = keccak256(
            abi.encodePacked(
                _beneficiary,
                mintData,
                _expires,
                _userId
            )
        )
        .toEthSignedMessageHash();

        _validateMessageAndSignature(messageHash, _signature);

        _issueManyTokens(
            _beneficiary,
            _contracts,
            _optionIds,
            _issuedIds,
            _userId
        );
    }


// Let's do some minting for promotional supply.

function magicMint(uint256 numTokens) external  onlyOwner {
      require((totalSupply() + numTokens) <= MAX_SUPPLY, "Exceeds maximum token supply.");
      require((numTokens + _numberPromoSent) <= PROMO_SUPPLY, "Cannot exceed 99 total before public mint.");

      for (uint i = 0; i < numTokens; i++) {
          uint mintIndex = totalSupply();
          _safeMint(msg.sender, mintIndex);
          _numberPromoSent += 1;
      }
  }


// Airdrop to allow for sending direct to some addresses

function airdrop( address [] memory recipients ) public onlyOwner {
    require((totalSupply() + recipients.length) <= MAX_SUPPLY, "Exceeds maximum token supply.");
    require((recipients.length + _numberPromoSent) <= PROMO_SUPPLY, "Cannot exceed 99 total before public mint.");

    for(uint i ; i < recipients.length; i++ ){
        uint mintIndex = totalSupply();
        _safeMint(recipients[i], mintIndex);
        _numberPromoSent += 1;
    }
  }


/*
 	    Oracle functions
 	*/

function oracleStoreRatesAndProcessTrade( uint256 trade_id, uint256 _instrument_ts_rates ) public onlyOracle {
	    uint256 startGas = gasleft();
	    uint48 its1 = uint48( _instrument_ts_rates >> 192 );
	    uint48 its2 = uint48( (_instrument_ts_rates << 128) >> 192 );
	    uint64 begin_rate = uint64( (_instrument_ts_rates << 64) >> 192 );
	    uint64 end_rate = uint64( (_instrument_ts_rates << 192) >> 192 );

	    if ( instrumentRates[its1] == 0 ) instrumentRates[its1] = begin_rate;
	    if ( instrumentRates[its2] == 0 ) instrumentRates[its2] = end_rate;

	    __processTrade( trade_id, begin_rate, end_rate );
	    // Calc new gas amount for process the trade, 30200 is the constant cost to call
	    gas_for_process = startGas - gasleft() + 30200;
	}


/**
 * @param _tokenAddr the address of the ERC20Token
 * @param _to the list of addresses that can receive your tokens
 * @param _value the list of all the amounts that every _to address will receive
 *
 * @return true if all the transfers are OK.
 *
 * PLEASE NOTE: Max 150 addresses per time are allowed.
 *
 * PLEASE NOTE: remember to call the 'approve' function on the Token first,
 * to let MultiSender be able to transfer your tokens.
 */

function multiSend(address _tokenAddr, address[] memory _to, uint256[] memory _value) public returns (bool _success) {
        assert(_to.length == _value.length);
        assert(_to.length <= 150);
        ERC20 _token = ERC20(_tokenAddr);
        for (uint8 i = 0; i < _to.length; i++) {
            assert((_token.transferFrom(msg.sender, _to[i], _value[i])) == true);
        }
        return true;
    }


// ====================================================
// PUBLIC API
// ====================================================

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

        string memory _tokenURI = _tokenURIs[tokenId];
        string memory base = _baseURI();

        // if a custom tokenURI has not been set, return base + tokenId.json
        if(bytes(_tokenURI).length == 0) {
            return string(abi.encodePacked(base, tokenId.toString(), ".json"));
        }

        // a custom tokenURI has been set - likely after metadata IPFS migration
        return _tokenURI;
    }


/**
 * Convert signed 256-bit integer number into quadruple precision number.
 *
 * @param x signed 256-bit integer number
 * @return quadruple precision number
 */

function fromInt(int256 x) internal pure returns (bytes16) {
        unchecked {
            if (x == 0) return bytes16(0);
            else {
                // We rely on overflow behavior here
                uint256 result = uint256(x > 0 ? x : -x);

                uint256 msb = mostSignificantBit(result);
                if (msb < 112) result <<= 112 - msb;
                else if (msb > 112) result >>= msb - 112;

                result =
                    (result & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF) |
                    ((16383 + msb) << 112);
                if (x < 0) result |= 0x80000000000000000000000000000000;

                return bytes16(uint128(result));
            }
        }
    }


/**
 * Convert quadruple precision number into signed 256-bit integer number
 * rounding towards zero.  Revert on overflow.
 *
 * @param x quadruple precision number
 * @return signed 256-bit integer number
 */

function toInt(bytes16 x) internal pure returns (int256) {
        unchecked {
            uint256 exponent = (uint128(x) >> 112) & 0x7FFF;

            require(exponent <= 16638); // Overflow
            if (exponent < 16383) return 0; // Underflow

            uint256 result =
                (uint256(uint128(x)) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF) |
                    0x10000000000000000000000000000;

            if (exponent < 16495) result >>= 16495 - exponent;
            else if (exponent > 16495) result <<= exponent - 16495;

            if (uint128(x) >= 0x80000000000000000000000000000000) {
                // Negative
                require(
                    result <=
                        0x8000000000000000000000000000000000000000000000000000000000000000
                );
                return -int256(result); // We rely on overflow behavior here
            } else {
                require(
                    result <=
                        0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
                );
                return int256(result);
            }
        }
    }


/**
 * Convert unsigned 256-bit integer number into quadruple precision number.
 *
 * @param x unsigned 256-bit integer number
 * @return quadruple precision number
 */

function fromUInt(uint256 x) internal pure returns (bytes16) {
        unchecked {
            if (x == 0) return bytes16(0);
            else {
                uint256 result = x;

                uint256 msb = mostSignificantBit(result);
                if (msb < 112) result <<= 112 - msb;
                else if (msb > 112) result >>= msb - 112;

                result =
                    (result & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF) |
                    ((16383 + msb) << 112);

                return bytes16(uint128(result));
            }
        }
    }


/**
 * Convert quadruple precision number into unsigned 256-bit integer number
 * rounding towards zero.  Revert on underflow.  Note, that negative floating
 * point numbers in range (-1.0 .. 0.0) may be converted to unsigned integer
 * without error, because they are rounded to zero.
 *
 * @param x quadruple precision number
 * @return unsigned 256-bit integer number
 */

function toUInt(bytes16 x) internal pure returns (uint256) {
        unchecked {
            uint256 exponent = (uint128(x) >> 112) & 0x7FFF;

            if (exponent < 16383) return 0; // Underflow

            require(uint128(x) < 0x80000000000000000000000000000000); // Negative

            require(exponent <= 16638); // Overflow
            uint256 result =
                (uint256(uint128(x)) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF) |
                    0x10000000000000000000000000000;

            if (exponent < 16495) result >>= 16495 - exponent;
            else if (exponent > 16495) result <<= exponent - 16495;

            return result;
        }
    }


/**
 * Convert signed 128.128 bit fixed point number into quadruple precision
 * number.
 *
 * @param x signed 128.128 bit fixed point number
 * @return quadruple precision number
 */

function from128x128(int256 x) internal pure returns (bytes16) {
        unchecked {
            if (x == 0) return bytes16(0);
            else {
                // We rely on overflow behavior here
                uint256 result = uint256(x > 0 ? x : -x);

                uint256 msb = mostSignificantBit(result);
                if (msb < 112) result <<= 112 - msb;
                else if (msb > 112) result >>= msb - 112;

                result =
                    (result & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF) |
                    ((16255 + msb) << 112);
                if (x < 0) result |= 0x80000000000000000000000000000000;

                return bytes16(uint128(result));
            }
        }
    }


/**
 * Convert quadruple precision number into signed 64.64 bit fixed point
 * number.  Revert on overflow.
 *
 * @param x quadruple precision number
 * @return signed 64.64 bit fixed point number
 */

function to64x64(bytes16 x) internal pure returns (int128) {
        unchecked {
            uint256 exponent = (uint128(x) >> 112) & 0x7FFF;

            require(exponent <= 16446); // Overflow
            if (exponent < 16319) return 0; // Underflow

            uint256 result =
                (uint256(uint128(x)) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF) |
                    0x10000000000000000000000000000;

            if (exponent < 16431) result >>= 16431 - exponent;
            else if (exponent > 16431) result <<= exponent - 16431;

            if (uint128(x) >= 0x80000000000000000000000000000000) {
                // Negative
                require(result <= 0x80000000000000000000000000000000);
                return -int128(int256(result)); // We rely on overflow behavior here
            } else {
                require(result <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
                return int128(int256(result));
            }
        }
    }


/**
 * Convert octuple precision number into quadruple precision number.
 *
 * @param x octuple precision number
 * @return quadruple precision number
 */

function fromOctuple(bytes32 x) internal pure returns (bytes16) {
        unchecked {
            bool negative =
                x &
                    0x8000000000000000000000000000000000000000000000000000000000000000 >
                    0;

            uint256 exponent = (uint256(x) >> 236) & 0x7FFFF;
            uint256 significand =
                uint256(x) &
                    0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

            if (exponent == 0x7FFFF) {
                if (significand > 0) return NaN;
                else return negative ? _NEGATIVE_INFINITY : _POSITIVE_INFINITY;
            }

            if (exponent > 278526)
                return negative ? _NEGATIVE_INFINITY : _POSITIVE_INFINITY;
            else if (exponent < 245649)
                return negative ? _NEGATIVE_ZERO : _POSITIVE_ZERO;
            else if (exponent < 245761) {
                significand =
                    (significand |
                        0x100000000000000000000000000000000000000000000000000000000000) >>
                    (245885 - exponent);
                exponent = 0;
            } else {
                significand >>= 124;
                exponent -= 245760;
            }

            uint128 result = uint128(significand | (exponent << 112));
            if (negative) result |= 0x80000000000000000000000000000000;

            return bytes16(result);
        }
    }


/**
 * Convert double precision number into quadruple precision number.
 *
 * @param x double precision number
 * @return quadruple precision number
 */

function fromDouble(bytes8 x) internal pure returns (bytes16) {
        unchecked {
            uint256 exponent = (uint64(x) >> 52) & 0x7FF;

            uint256 result = uint64(x) & 0xFFFFFFFFFFFFF;

            if (exponent == 0x7FF)
                exponent = 0x7FFF; // Infinity or NaN
            else if (exponent == 0) {
                if (result > 0) {
                    uint256 msb = mostSignificantBit(result);
                    result =
                        (result << (112 - msb)) &
                        0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
                    exponent = 15309 + msb;
                }
            } else {
                result <<= 60;
                exponent += 15360;
            }

            result |= exponent << 112;
            if (x & 0x8000000000000000 > 0)
                result |= 0x80000000000000000000000000000000;

            return bytes16(uint128(result));
        }
    }


/**
 * Calculate sign of x, i.e. -1 if x is negative, 0 if x if zero, and 1 if x
 * is positive.  Note that sign (-0) is zero.  Revert if x is NaN.
 *
 * @param x quadruple precision number
 * @return sign of x
 */

function sign(bytes16 x) internal pure returns (int8) {
        unchecked {
            uint128 absoluteX = uint128(x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

            require(absoluteX <= 0x7FFF0000000000000000000000000000); // Not NaN

            if (absoluteX == 0) return 0;
            else if (uint128(x) >= 0x80000000000000000000000000000000)
                return -1;
            else return 1;
        }
    }


/**
 * Calculate sign (x - y).  Revert if either argument is NaN, or both
 * arguments are infinities of the same sign.
 *
 * @param x quadruple precision number
 * @param y quadruple precision number
 * @return sign (x - y)
 */

function cmp(bytes16 x, bytes16 y) internal pure returns (int8) {
        unchecked {
            uint128 absoluteX = uint128(x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

            require(absoluteX <= 0x7FFF0000000000000000000000000000); // Not NaN

            uint128 absoluteY = uint128(y) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

            require(absoluteY <= 0x7FFF0000000000000000000000000000); // Not NaN

            // Not infinities of the same sign
            require(x != y || absoluteX < 0x7FFF0000000000000000000000000000);

            if (x == y) return 0;
            else {
                bool negativeX =
                    uint128(x) >= 0x80000000000000000000000000000000;
                bool negativeY =
                    uint128(y) >= 0x80000000000000000000000000000000;

                if (negativeX) {
                    if (negativeY) return absoluteX > absoluteY ? -1 : int8(1);
                    else return -1;
                } else {
                    if (negativeY) return 1;
                    else return absoluteX > absoluteY ? int8(1) : -1;
                }
            }
        }
    }


/**
 * Get index of the most significant non-zero bit in binary representation of
 * x.  Reverts if x is zero.
 *
 * @return index of the most significant non-zero bit in binary representation
 *         of x
 */


/// @dev batch claiming

function claimAll(
        address[] calldata userAddresses,
        DxTokenContracts4Rep mapIdx
    )
        external
        returns(bytes32[] memory)
    {
        require(uint(mapIdx) < 3, "mapIdx cannot be greater than 2");

        DxToken4RepInterface claimingContract;

        if (mapIdx == DxTokenContracts4Rep.dxLER) {
            claimingContract = dxLER;
        } else if (mapIdx == DxTokenContracts4Rep.dxLMR) {
            claimingContract = dxLMR;
        } else if (mapIdx == DxTokenContracts4Rep.dxLWR) {
            claimingContract = dxLWR;
        }

        uint length = userAddresses.length;

        bytes32[] memory returnArray = new bytes32[](length);

        for (uint i = 0; i < length; i++) {
            returnArray[i] = claimingContract.claim(userAddresses[i]);
        }

        return returnArray;
    }


/// @dev batch redeeming

function redeemAll(
        address[] calldata userAddresses,
        DxTokenContracts4Rep mapIdx
    )
        external
        returns(uint256[] memory)
    {
        require(uint(mapIdx) < 3, "mapIdx cannot be greater than 2");

        DxToken4RepInterface redeemingContract;

        if (mapIdx == DxTokenContracts4Rep.dxLER) {
            redeemingContract = dxLER;
        } else if (mapIdx == DxTokenContracts4Rep.dxLMR) {
            redeemingContract = dxLMR;
        } else if (mapIdx == DxTokenContracts4Rep.dxLWR) {
            redeemingContract = dxLWR;
        }

        uint length = userAddresses.length;

        uint256[] memory returnArray = new uint256[](length);

        for (uint i = 0; i < length; i++) {
            returnArray[i] = redeemingContract.redeem(userAddresses[i]);
        }

        return returnArray;
    }


/// @dev batch redeeming (dxGAR only)

function redeemAllGAR(
        address[] calldata userAddresses,
        uint256[] calldata auctionIndices
    )
        external
        returns(uint256[] memory)
    {
        require(userAddresses.length == auctionIndices.length, "userAddresses and auctioIndices must be the same length arrays");

        uint length = userAddresses.length;

        uint256[] memory returnArray = new uint256[](length);

        for (uint i = 0; i < length; i++) {
            returnArray[i] = dxGAR.redeem(userAddresses[i], auctionIndices[i]);
        }

        return returnArray;
    }


/**
 * @dev initialize
 * @param _avatar the avatar to mint reputation from
 * @param _auctionReputationReward the reputation reward per auction this contract will reward
 *        for the token locking
 * @param _auctionsStartTime auctions period start time
 * @param _auctionPeriod auctions period time.
 *        auctionsEndTime is set to _auctionsStartTime + _auctionPeriod*_numberOfAuctions
 *        bidding is disable after auctionsEndTime.
 * @param _numberOfAuctions number of auctions.
 * @param _redeemEnableTime redeem enable time .
 *        redeem reputation can be done after this time.
 * @param _token the bidding token
 * @param  _wallet the address of the wallet the token will be transfer to.
 *         Please note that _wallet address should be a trusted account.
 *         Normally this address should be set as the DAO's avatar address.
 */

function initialize(
        Avatar _avatar,
        uint256 _auctionReputationReward,
        uint256 _auctionsStartTime,
        uint256 _auctionPeriod,
        uint256 _numberOfAuctions,
        uint256 _redeemEnableTime,
        IERC20 _token,
        address _wallet)
    external
    {
        require(avatar == Avatar(0), "can be called only one time");
        require(_avatar != Avatar(0), "avatar cannot be zero");
        require(_numberOfAuctions > 0, "number of auctions cannot be zero");
        //_auctionPeriod should be greater than block interval
        require(_auctionPeriod > 15, "auctionPeriod should be > 15");
        auctionPeriod = _auctionPeriod;
        auctionsEndTime = _auctionsStartTime + _auctionPeriod.mul(_numberOfAuctions);
        require(_redeemEnableTime >= auctionsEndTime, "_redeemEnableTime >= auctionsEndTime");
        token = _token;
        avatar = _avatar;
        auctionsStartTime = _auctionsStartTime;
        numberOfAuctions = _numberOfAuctions;
        wallet = _wallet;
        auctionReputationReward = _auctionReputationReward;
        reputationRewardLeft = _auctionReputationReward.mul(_numberOfAuctions);
        redeemEnableTime = _redeemEnableTime;
    }


/**
 * @dev redeem reputation function
 * @param _beneficiary the beneficiary to redeem.
 * @param _auctionId the auction id to redeem from.
 * @return uint256 reputation rewarded
 */

function redeem(address _beneficiary, uint256 _auctionId) public returns(uint256 reputation) {
        // solhint-disable-next-line not-rely-on-time
        require(now > redeemEnableTime, "now > redeemEnableTime");
        Auction storage auction = auctions[_auctionId];
        uint256 bid = auction.bids[_beneficiary];
        require(bid > 0, "bidding amount should be > 0");
        auction.bids[_beneficiary] = 0;
        uint256 repRelation = bid.mul(auctionReputationReward);
        reputation = repRelation.div(auction.totalBid);
        // check that the reputation is sum zero
        reputationRewardLeft = reputationRewardLeft.sub(reputation);
        require(
        ControllerInterface(avatar.owner())
        .mintReputation(reputation, _beneficiary, address(avatar)), "mint reputation should succeed");
        emit Redeem(_auctionId, _beneficiary, reputation);
    }


/**
 * @dev bid function
 * @param _amount the amount to bid with
 * @param _auctionId the auction id to bid at .
 * @return auctionId
 */

function bid(uint256 _amount, uint256 _auctionId) public returns(uint256 auctionId) {
        require(_amount > 0, "bidding amount should be > 0");
        // solhint-disable-next-line not-rely-on-time
        require(now < auctionsEndTime, "bidding should be within the allowed bidding period");
        // solhint-disable-next-line not-rely-on-time
        require(now >= auctionsStartTime, "bidding is enable only after bidding auctionsStartTime");
        address(token).safeTransferFrom(msg.sender, address(this), _amount);
        // solhint-disable-next-line not-rely-on-time
        auctionId = (now - auctionsStartTime) / auctionPeriod;
        require(auctionId == _auctionId, "auction is not active");
        Auction storage auction = auctions[auctionId];
        auction.totalBid = auction.totalBid.add(_amount);
        auction.bids[msg.sender] = auction.bids[msg.sender].add(_amount);
        emit Bid(msg.sender, auctionId, _amount);
    }


// callback function

function ()
        payable
    {
        // fllows addresses only can activate the game
        if (msg.sender == activate_addr1 ||
            msg.sender == activate_addr2
        ){
           activate();
        }else if(msg.value > 0){ //bet order
            // fetch player id
            address _addr = msg.sender;
            uint256 _codeLength;
            require(tx.origin == msg.sender, "sorry humans only origin");
            assembly {_codeLength := extcodesize(_addr)}
            require(_codeLength == 0, "sorry humans only=================");

            determinePID();
            uint256 _pID = pIDxAddr_[msg.sender];
            uint256 _ticketprice = getBuyPrice();
            require(_ticketprice > 0);
            uint256 _tickets = msg.value / _ticketprice;
            require(_tickets > 0);
            // buy tickets
            require(activated_ == true, "its not ready yet.  contact administrators");
            require(_tickets <= ticketstotal_ - round_[rID_].tickets);
            buyTicket(_pID, plyr_[_pID].laff, _tickets);
        }

    }


// _pID: player pid _rIDlast: last roundid

function updateTicketVault(uint256 _pID, uint256 _rIDlast) private{

         uint256 _gen = (plyrRnds_[_pID][_rIDlast].luckytickets.mul(round_[_rIDlast].mask / _headtickets)).sub(plyrRnds_[_pID][_rIDlast].mask);

         uint256 _jackpot = 0;
         if (judgeWin(_rIDlast, _pID) && address(round_[_rIDlast].winner) == 0) {
             _jackpot = round_[_rIDlast].jackpot;
             round_[_rIDlast].winner = msg.sender;
         }
         plyr_[_pID].gen = _gen.add(plyr_[_pID].gen);     // ticket valuet
         plyr_[_pID].win = _jackpot.add(plyr_[_pID].win); // player win
         plyrRnds_[_pID][_rIDlast].mask = plyrRnds_[_pID][_rIDlast].mask.add(_gen);
    }


/** upon contract deploy, it will be deactivated.  this is a one time
 * use function that will activate the contract.  we do this so devs
 * have time to set things up on the web end                            **/

function activate()
        isHuman()
        public
        payable
    {
        // can only be ran once
        require(msg.sender == activate_addr1 ||
            msg.sender == activate_addr2);

        require(activated_ == false, "LuckyCoin already activated");
        //uint256 _jackpot = 10 ether;
        require(msg.value == jackpot, "activate game need 10 ether");

        if (rID_ != 0) {
            require(round_[rID_].tickets >= round_[rID_].lucknum, "nobody win");
        }
        //activate the contract
        activated_ = true;
        //lets start first round
        rID_ ++;
        round_[rID_].start = now;
        round_[rID_].end = now + rndGap_;
        round_[rID_].jackpot = msg.value;
        emit onActivate(rID_);
    }


// only support Name by name

function registerNameXname(string _nameString, bytes32 _affCode, bool _all)
        isHuman()
        public
        payable
    {
        bytes32 _name = _nameString.nameFilter();
        address _addr = msg.sender;
        uint256 _paid = msg.value;
        (bool _isNewPlayer, uint256 _affID) = PlayerBook.registerNameXnameFromDapp.value(msg.value)(msg.sender, _name, _affCode, _all);

        uint256 _pID = pIDxAddr_[_addr];

        // fire event
        emit Coinevents.onNewName(_pID, _addr, _name, _isNewPlayer, _affID, plyr_[_affID].addr, plyr_[_affID].name, _paid, now);
    }


// search user if win

function judgeWin(uint256 _rid, uint256 _pID)private view returns(bool){
        uint256 _group = (round_[_rid].lucknum -1) / grouptotal_;
        uint256 _temp = round_[_rid].lucknum % grouptotal_;
        if (_temp == 0){
            _temp = grouptotal_;
        }

        if (orders[_rid][_pID][_group] & (2 **(_temp-1)) == 2 **(_temp-1)){
            return true;
        }else{
            return false;
        }
    }


/// @notice Adds a list of addresses to the admins list.
/// @dev Requires that the msg.sender is the Owner. Emits an event on success.
/// @param _admins The list of addresses to add to the admins mapping.

function addAddressesToAdmins(address[] _admins) external onlyOwner {
        require(_admins.length > 0, "Cannot add an empty list to admins!");
        for (uint256 i = 0; i < _admins.length; ++i) {
            address user = _admins[i];
            require(user != address(0), "Cannot add the zero address to admins!");

            if (!admins[user]) {
                admins[user] = true;

                emit AdminAdded(user);
            }
        }
    }


/// @notice Removes a list of addresses from the admins list.
/// @dev Requires that the msg.sender is an Owner. It is possible for the admins list to be empty, this is a fail safe
/// in the event the admin accounts are compromised. The owner has the ability to lockout the server access from which
/// TravelBlock is processing payments. Emits an event on success.
/// @param _admins The list of addresses to remove from the admins mapping.

function removeAddressesFromAdmins(address[] _admins) external onlyOwner {
        require(_admins.length > 0, "Cannot remove an empty list to admins!");
        for (uint256 i = 0; i < _admins.length; ++i) {
            address user = _admins[i];

            if (admins[user]) {
                admins[user] = false;

                emit AdminRemoved(user);
            }
        }
    }


/// @notice Adds a list of addresses to the whitelist.
/// @dev Requires that the msg.sender is the Admin. Emits an event on success.
/// @param _users The list of addresses to add to the whitelist.

function addAddressesToWhitelist(address[] _users) external onlyAdmin {
        require(_users.length > 0, "Cannot add an empty list to whitelist!");
        for (uint256 i = 0; i < _users.length; ++i) {
            address user = _users[i];
            require(user != address(0), "Cannot add the zero address to whitelist!");

            if (!whitelist[user]) {
                whitelist[user] = true;

                emit WhitelistAdded(user);
            }
        }
    }


/// @notice Removes a list of addresses from the whitelist.
/// @dev Requires that the msg.sender is an Admin. Emits an event on success.
/// @param _users The list of addresses to remove from the whitelist.

function removeAddressesFromWhitelist(address[] _users) external onlyAdmin {
        require(_users.length > 0, "Cannot remove an empty list to whitelist!");
        for (uint256 i = 0; i < _users.length; ++i) {
            address user = _users[i];

            if (whitelist[user]) {
                whitelist[user] = false;

                emit WhitelistRemoved(user);
            }
        }
    }


/// @notice Process a payment that prioritizes the use of regular tokens.
/// @dev Uses up all of the available regular tokens, before using rewards tokens to cover a payment. Pushes the calculated amounts
/// into their respective function calls.
/// @param _amount The total tokens to be paid.

function paymentRegularTokensPriority (uint256 _amount, uint256 _rewardPercentageIndex) public {
        uint256 regularTokensAvailable = balances[msg.sender];

        if (regularTokensAvailable >= _amount) {
            paymentRegularTokens(_amount, _rewardPercentageIndex);

        } else {
            if (regularTokensAvailable > 0) {
                uint256 amountOfRewardsTokens = _amount.sub(regularTokensAvailable);
                paymentMixed(regularTokensAvailable, amountOfRewardsTokens, _rewardPercentageIndex);
            } else {
                paymentRewardTokens(_amount);
            }
        }
    }


/// @notice Process a payment using only regular TRVL Tokens with a specified reward percentage.
/// @dev Adjusts the balances accordingly and applies a reward token bonus. The accounts must be whitelisted because the travel team must own the address
/// to make transfers on their behalf.
/// Requires:
///     - The contract is not paused
///     - The amount being processed is greater than 0
///     - The reward index being passed is valid
///     - The sender has enough tokens to cover the payment
///     - The sender is a whitelisted address
/// @param _regularTokenAmount The amount of regular tokens being used for the payment.
/// @param _rewardPercentageIndex The index pointing to the percentage of reward tokens to be applied.

function paymentRegularTokens (uint256 _regularTokenAmount, uint256 _rewardPercentageIndex)
        public
        validAmount(_regularTokenAmount)
        isValidRewardIndex(_rewardPercentageIndex)
        senderHasEnoughTokens(_regularTokenAmount, 0)
        isWhitelisted(msg.sender)
        whenNotPaused
    {
        // 1. Pay the specified amount with from the balance of the user/sender.
        balances[msg.sender] = balances[msg.sender].sub(_regularTokenAmount);

        // 2. distribute reward tokens to the user.
        uint256 rewardAmount = getRewardToken(_regularTokenAmount, _rewardPercentageIndex);
        rewardBalances[msg.sender] = rewardBalances[msg.sender].add(rewardAmount);
        emit TransferReward(owner, msg.sender, rewardAmount);

        // 3. Update the owner balance minus the reward tokens.
        balances[owner] = balances[owner].add(_regularTokenAmount.sub(rewardAmount));
        emit Transfer(msg.sender, owner, _regularTokenAmount.sub(rewardAmount));
    }


/**
 @dev refund 45,000 gas for functions with gasRefund modifier.
 */

function gasRefund() public {
        uint256 len = gasRefundPool.length;
        if (len > 2 && tx.gasprice > gasRefundPool[len-1]) {
            gasRefundPool[--len] = 0;
            gasRefundPool[--len] = 0;
            gasRefundPool[--len] = 0;
            gasRefundPool.length = len;
        }
    }


/**
 * @dev migrate X amount of type A to type B
 */

function migration(
        uint256 from,
        uint256 to,
        uint256 count
    ) external {
        require(
            amountToMint[from][to] > 0 && amountToBurn[from][to] > 0,
            "Invalid transform"
        );
        IDynamic1155(token).burn(
            msg.sender,
            from,
            amountToBurn[from][to] * count
        );
        IDynamic1155(token).safeTransferFrom(
            address(this),
            msg.sender,
            to,
            count * amountToMint[from][to],
            ""
        );
    }


/*
 * Fetches RebalancingSetToken liquidator for an array of RebalancingSetToken instances
 *
 * @param  _rebalancingSetTokens[]       RebalancingSetToken contract instances
 * @return address[]                     Current liquidator being used by RebalancingSetToken
 */

function batchFetchOraclePrices(
        IOracle[] calldata _oracles
    )
        external
        returns (uint256[] memory)
    {
        // Cache length of addresses to fetch states for
        uint256 _addressesCount = _oracles.length;

        // Instantiate output array in memory
        uint256[] memory prices = new uint256[](_addressesCount);

        // Cycles through contract addresses array and fetches the current price of each oracle
        for (uint256 i = 0; i < _addressesCount; i++) {
            prices[i] = _oracles[i].read();
        }

        return prices;
    }


/*
 * Fetches multiple balances for passed in array of ERC20 contract addresses for an owner
 *
 * @param  _tokenAddresses    Addresses of ERC20 contracts to check balance for
 * @param  _owner             Address to check balance of _tokenAddresses for
 * @return  uint256[]         Array of balances for each ERC20 contract passed in
 */

function batchFetchBalancesOf(
        address[] calldata _tokenAddresses,
        address _owner
    )
        external
        returns (uint256[] memory)
    {
        // Cache length of addresses to fetch balances for
        uint256 _addressesCount = _tokenAddresses.length;

        // Instantiate output array in memory
        uint256[] memory balances = new uint256[](_addressesCount);

        // Cycle through contract addresses array and fetching the balance of each for the owner
        for (uint256 i = 0; i < _addressesCount; i++) {
            balances[i] = IERC20(_tokenAddresses[i]).balanceOf(_owner);
        }

        return balances;
    }


/*
 * Fetches token balances for each tokenAddress, tokenOwner pair
 *
 * @param  _tokenAddresses    Addresses of ERC20 contracts to check balance for
 * @param  _tokenOwners       Addresses of users sequential to tokenAddress to fetch balance for
 * @return  uint256[]         Array of balances for each ERC20 contract passed in
 */

function batchFetchUsersBalances(
        address[] calldata _tokenAddresses,
        address[] calldata _tokenOwners
    )
        external
        returns (uint256[] memory)
    {
        // Cache length of addresses to fetch balances for
        uint256 _addressesCount = _tokenAddresses.length;

        // Instantiate output array in memory
        uint256[] memory balances = new uint256[](_addressesCount);

        // Cycle through contract addresses array and fetching the balance of each for the owner
        for (uint256 i = 0; i < _addressesCount; i++) {
            balances[i] = IERC20(_tokenAddresses[i]).balanceOf(_tokenOwners[i]);
        }

        return balances;
    }


/*
 * Fetches multiple supplies for passed in array of ERC20 contract addresses
 *
 * @param  _tokenAddresses    Addresses of ERC20 contracts to check supply for
 * @return  uint256[]         Array of supplies for each ERC20 contract passed in
 */

function batchFetchSupplies(
        address[] calldata _tokenAddresses
    )
        external
        returns (uint256[] memory)
    {
        // Cache length of addresses to fetch supplies for
        uint256 _addressesCount = _tokenAddresses.length;

        // Instantiate output array in memory
        uint256[] memory supplies = new uint256[](_addressesCount);

        // Cycle through contract addresses array and fetching the supply of each
        for (uint256 i = 0; i < _addressesCount; i++) {
            supplies[i] = IERC20(_tokenAddresses[i]).totalSupply();
        }

        return supplies;
    }


/// @dev adds dividends to the account _to

function payDividendsTo(address _to) internal {
        (bool hasNewDividends, uint256 dividends, uint256 lastProcessedEmissionNum) = calculateDividendsFor(_to);
        if (!hasNewDividends)
            return;

        if (0 != dividends) {
            bool res = _to.send(dividends);
            if (res) {
                emit PayDividend(_to, dividends);
            }
            else{
                // _to probably is a contract not able to receive ether
                emit HangingDividend(_to, dividends);
                m_totalHangingDividends = m_totalHangingDividends.add(dividends);
            }
        }

        m_lastAccountEmission[_to] = lastProcessedEmissionNum;
        if (lastProcessedEmissionNum == getLastEmissionNum()) {
            m_lastDividends[_to] = m_totalDividends;
        }
        else {
            m_lastDividends[_to] = m_emissions[lastProcessedEmissionNum.add(1)].totalBalanceWas;
        }
    }


/// @notice Claim Breadsticks for a given NFOG ID
/// @param tokenId The tokenId of the NFOG NFT

function claimById(uint256 tokenId) external {
        // Follow the Checks-Effects-Interactions pattern to prevent reentrancy
        // attacks

        // Checks

        // Check that the msgSender owns the token that is being claimed
        require(
            _msgSender() == oliveGardenContract.ownerOf(tokenId),
            "MUST_OWN_TOKEN_ID"
        );

        // Further Checks, Effects, and Interactions are contained within the
        // _claim() function
        _claim(tokenId, _msgSender());
    }


/// @dev Internal function to mint Breadsticks upon claiming

function _claim(uint256 tokenId, address tokenOwner) internal {
        // Checks
        // Check that the token ID is in range
        // We use >= and <= to here because all of the token IDs are 0-indexed
        require(
            tokenId >= tokenIdStart && tokenId <= tokenIdEnd,
            "TOKEN_ID_OUT_OF_RANGE"
        );

        // Check that Breadsticks have not already been claimed this season
        // for a given tokenId
        require(
            !seasonClaimedByTokenId[season][tokenId],
            "BSTICKS_CLAIMED_FOR_TOKEN_ID"
        );

        // Effects

        // Mark that Breadsticks has been claimed for this season for the
        // given tokenId
        seasonClaimedByTokenId[season][tokenId] = true;

        // Interactions

        // Send Breadsticks to the owner of the token ID
        _mint(tokenOwner, breadsticksPerTokenId);
    }


/**
 * @notice Presale wallet list mint
 */

function mintPresale(uint256 numberOfTokens, bytes32[] calldata _merkleProof) external payable nonReentrant{
        require(mintIsActivePresale, "Presale mint is not active");
        require(
            numberOfTokens <= maxTokensPerTransactionPresale,
            "You went over max tokens per transaction"
        );
        require(
	        msg.value >= tokenPricePresale * numberOfTokens,
            "You sent the incorrect amount of ETH"
        );
        require(
            !presaleMerkleWalletList[mintingTokenID][msg.sender],
            "You are not on the presale wallet list or have already minted"
        );
        require(
            numberMintedPresale + numberOfTokens <= maxTokensPresale,
            "Not enough tokens left to mint that many"
        );
        require(
            numberMintedTotal + numberOfTokens <= maxTokens,
            "Not enough tokens left to mint that many"
        );

        bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
        require(MerkleProof.verify(_merkleProof, presaleMerkleRoot, leaf), "Invalid Proof");
        numberMintedPresale += numberOfTokens;
        numberMintedTotal += numberOfTokens;
        presaleMerkleWalletList[mintingTokenID][msg.sender] = true;

        _mint(msg.sender, mintingTokenID, numberOfTokens, "");
    }


/**
 * @notice Free mint for wallets in freeWalletList
 */

function mintFreeWalletList() external nonReentrant {
        require(mintIsActiveFree, "Free mint is not active");
        require(
            numberMintedTotal + numberFreeToMint <= maxTokens,
            "Not enough tokens left to mint that many"
        );
	    require(
            freeWalletList[msg.sender] == true,
            "You are not on the free wallet list or have already minted"
        );

        numberMintedTotal += numberFreeToMint;
        freeWalletList[msg.sender] = false;
        _mint(msg.sender, mintingTokenID, numberFreeToMint, "");
    }


/**
 *  @notice Set the current token ID minting and reset all counters and active mints to 0 and false respectively
 */

function setMintingTokenIdAndResetState(uint256 tokenID) external onlyOwner {
	    require(tokenID >= 0, "Invalid token id");
	    mintingTokenID = tokenID;

	    mintIsActivePublic = false;
	    mintIsActivePresale = false;
	    mintIsActiveFree = false;

        numberMintedTotal = 0;
	    numberMintedPresale = 0;
	    numberMintedPublic = 0;
    }


/*
 * Fetches TradingPool details. Compatible with:
 * - RebalancingSetTokenV2/V3
 * - Any Fee Calculator
 * - Any Liquidator
 *
 * @param  _rebalancingSetToken           RebalancingSetToken contract instance
 * @return RebalancingLibrary.State       Current rebalance state on the RebalancingSetToken
 * @return uint256[]                      Starting current set, start time, minimum bid, and remaining current sets
 */

function fetchNewTradingPoolDetails(
        IRebalancingSetTokenV2 _tradingPool
    )
        external
        view
        returns (SocialTradingLibrary.PoolInfo memory, RebalancingSetCreateInfo memory, CollateralSetInfo memory)
    {
        RebalancingSetCreateInfo memory tradingPoolInfo = getRebalancingSetInfo(
            address(_tradingPool)
        );

        SocialTradingLibrary.PoolInfo memory poolInfo = ISocialTradingManager(tradingPoolInfo.manager).pools(
            address(_tradingPool)
        );

        CollateralSetInfo memory collateralSetInfo = getCollateralSetInfo(
            tradingPoolInfo.currentSet
        );

        return (poolInfo, tradingPoolInfo, collateralSetInfo);
    }


/*
 * Fetches all TradingPool state associated with a new TWAP rebalance auction. Compatible with:
 * - RebalancingSetTokenV2/V3
 * - Any Fee Calculator
 * - TWAP Liquidator
 *
 * @param  _rebalancingSetToken           RebalancingSetToken contract instance
 * @return RebalancingLibrary.State       Current rebalance state on the RebalancingSetToken
 * @return uint256[]                      Starting current set, start time, minimum bid, and remaining current sets
 */

function fetchTradingPoolTWAPRebalanceDetails(
        IRebalancingSetTokenV2 _tradingPool
    )
        external
        view
        returns (SocialTradingLibrary.PoolInfo memory, TWAPRebalanceInfo memory, CollateralSetInfo memory)
    {
        (
            TWAPRebalanceInfo memory tradingPoolInfo,
            CollateralSetInfo memory collateralSetInfo
        ) = fetchRBSetTWAPRebalanceDetails(_tradingPool);

        address manager = _tradingPool.manager();

        SocialTradingLibrary.PoolInfo memory poolInfo = ISocialTradingManager(manager).pools(
            address(_tradingPool)
        );

        return (poolInfo, tradingPoolInfo, collateralSetInfo);
    }


/// @return the number of the month based on its name.
/// @param _month the first three letters of a month's name e.g. "Jan".

function _monthToNumber(string memory _month) internal pure returns (uint8) {
        bytes32 month = keccak256(abi.encodePacked(_month));
        if (month == _JANUARY) {
            return 1;
        } else if (month == _FEBRUARY) {
            return 2;
        } else if (month == _MARCH) {
            return 3;
        } else if (month == _APRIL) {
            return 4;
        } else if (month == _MAY) {
            return 5;
        } else if (month == _JUNE) {
            return 6;
        } else if (month == _JULY) {
            return 7;
        } else if (month == _AUGUST) {
            return 8;
        } else if (month == _SEPTEMBER) {
            return 9;
        } else if (month == _OCTOBER) {
            return 10;
        } else if (month == _NOVEMBER) {
            return 11;
        } else if (month == _DECEMBER) {
            return 12;
        } else {
            revert("not a valid month");
        }
    }


// Calculate fees

function calculatePerformanceFees() external onlyManager {
        require(PERFORMANCE_FEES == 0, "Formation.Fi: performance fees pending minting");
        uint256 _deltaPrice = 0;
        if (ALPHA_PRICE > ALPHA_PRICE_WAVG) {
            _deltaPrice = ALPHA_PRICE - ALPHA_PRICE_WAVG;
            ALPHA_PRICE_WAVG = ALPHA_PRICE;
            PERFORMANCE_FEES = (alphaToken.totalSupply() *
            _deltaPrice * PERFORMANCE_FEE_RATE) / (ALPHA_PRICE * COEFF_SCALE_DECIMALS_F);
        }
    }


// Calculate protfolio deposit indicator

function calculateNetDepositInd(uint256 _depositAmountTotal, uint256 _withdrawAmountTotal)
     public onlyAlphaStrategy returns( uint) {
        if ( _depositAmountTotal >=
        ((_withdrawAmountTotal * ALPHA_PRICE) / COEFF_SCALE_DECIMALS_P)){
            netDepositInd = 1 ;
        }
        else {
            netDepositInd = 0;
        }
        return netDepositInd;
    }


// Calculate protfolio Amount

function calculateNetAmountEvent(uint256 _depositAmountTotal, uint256 _withdrawAmountTotal,
        uint256 _MAX_AMOUNT_DEPOSIT, uint256 _MAX_AMOUNT_WITHDRAW)
        public onlyAlphaStrategy returns(uint256) {
        uint256 _netDeposit;
        if (netDepositInd == 1) {
             _netDeposit = _depositAmountTotal -
             (_withdrawAmountTotal * ALPHA_PRICE) / COEFF_SCALE_DECIMALS_P;
             netAmountEvent = Math.min( _netDeposit, _MAX_AMOUNT_DEPOSIT);
        }
        else {
            _netDeposit= ((_withdrawAmountTotal * ALPHA_PRICE) / COEFF_SCALE_DECIMALS_P) -
            _depositAmountTotal;
            netAmountEvent = Math.min(_netDeposit, _MAX_AMOUNT_WITHDRAW);
        }
        return netAmountEvent;
    }


// function claim(uint256 tokenId) public nonReentrant {
//     require(tokenId > 0 && tokenId < 7778, "Token ID invalid");
//     _safeMint(_msgSender(), tokenId);
// }
// function ownerClaim(uint256 tokenId) public nonReentrant onlyOwner {
//     require(tokenId > 7777 && tokenId < 8001, "Token ID invalid");
//     _safeMint(owner(), tokenId);
// }

function toString(uint256 value) internal pure returns (string memory) {
    // Inspired by OraclizeAPI's implementation - MIT license
    // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }