/**
 * @dev Revoke permission to mint tokens from a mint delegate.
 * @param _mintDelegate The account to be revoked.
 */

function revokeMintDelegate(address _mintDelegate) onlyOwner public returns (bool) {
    uint length = mintDelegates.length;
    require(length > 0);

    address lastDelegate = mintDelegates[length-1];
    if(_mintDelegate == lastDelegate) {
      delete mintDelegates[length-1];
      mintDelegates.length--;
    }
    else {
      // Game plan: find the delegate, replace it with the very last item in the array, then delete the last item
      for(uint i=0; i<length; i++) {
        if(mintDelegates[i]==_mintDelegate) {
          mintDelegates[i] = lastDelegate;
          delete mintDelegates[length-1];
          mintDelegates.length--;
          break;
        }
      }
    }

    RevokeMintDelegate(_mintDelegate);
    return true;
  }


/**
 * @dev Give a burn delegate permission to burn tokens.
 * @param _burnDelegate The account to be approved.
 */

function approveBurnDelegate(address _burnDelegate) onlyOwner public returns (bool) {
    bool delegateFound = false;
    for(uint i=0; i<burnDelegates.length; i++) {
      if(burnDelegates[i]==_burnDelegate) {
        delegateFound = true;
        break;
      }
    }

    if(!delegateFound) {
      burnDelegates.push(_burnDelegate);
    }

    ApproveBurnDelegate(_burnDelegate);
    return true;
  }


/**
 * @dev Revoke permission to burn tokens from a burn delegate.
 * @param _burnDelegate The account to be revoked.
 */

function revokeBurnDelegate(address _burnDelegate) onlyOwner public returns (bool) {
    uint length = burnDelegates.length;
    require(length > 0);

    address lastDelegate = burnDelegates[length-1];
    if(_burnDelegate == lastDelegate) {
      delete burnDelegates[length-1];
      burnDelegates.length--;
    }
    else {
      // Game plan: find the delegate, replace it with the very last item in the array, then delete the last item
      for(uint i=0; i<length; i++) {
        if(burnDelegates[i]==_burnDelegate) {
          burnDelegates[i] = lastDelegate;
          delete burnDelegates[length-1];
          burnDelegates.length--;
          break;
        }
      }
    }

    RevokeBurnDelegate(_burnDelegate);
    return true;
  }


/**
 * @dev Update base price assigned to a vendor by `_rpid`, `msg.sender`, `_dates`
 *      Throw when `msg.sender` is not a vendor
 *      Throw when `_rpid` not exist
 *      Throw when `_tokens`'s length is not equal to `_prices`'s length or `_tokens`'s length is equal to zero
 * @param _rpid The rate plan identifier
 * @param _inventory The amount that can be sold
 * @param _tokens The pricing currency token
 * @param _prices The currency token selling price
 */

function updateBasePrice(uint256 _rpid, uint256[] _tokens, uint256[] _prices, uint16 _inventory)
        external
        ratePlanExist(dataSource.vendorIds(msg.sender), _rpid)
        returns(bool) {
        require(_tokens.length == _prices.length);
        require(_prices.length > 0);
        uint256 vendorId = dataSource.vendorIds(msg.sender);
        dataSource.updateBaseInventory(vendorId, _rpid, _inventory);
        for (uint256 tindex = 0; tindex < _tokens.length; tindex++) {
            dataSource.updateBasePrice(vendorId, _rpid, _tokens[tindex], _prices[tindex]);
        }
        // Event
        emit RatePlanBasePriceChanged(_rpid);
        return true;
    }


/**
 * @dev Update inventory assigned to a vendor by `_rpid`, `msg.sender`, `_dates`
 *      Throw when `msg.sender` is not a vendor
 *      Throw when `_rpid` not exist
 *      Throw when `_dates`'s length lte 0
 * @param _rpid The rate plan identifier
 * @param _dates The prices to be modified of `_dates`
 * @param _inventory The amount that can be sold
 */

function updateInventories(uint256 _rpid, uint256[] _dates, uint16 _inventory)
        external
        ratePlanExist(dataSource.vendorIds(msg.sender), _rpid)
        returns(bool) {
        for (uint256 index = 0; index < _dates.length; index++) {
            _updateInventories(_rpid, _dates[index], _inventory);
        }

        // Event
        emit RatePlanInventoryChanged(_rpid);
        return true;
    }


/**
 * @dev Returns the inventories of `_vendor`'s RP(`_rpid`) on `_dates`
 *      Throw when `_rpid` not exist
 *      Throw when `_dates`'s count lte 0
 * @param _vendorId The vendor Id
 * @param _rpid The rate plan identifier
 * @param _dates The inventories to be returned of `_dates`
 * @return The inventories
 */

function inventoriesOfDate(uint256 _vendorId, uint256 _rpid, uint256[] _dates)
        external
        view
        ratePlanExist(_vendorId, _rpid)
        returns(uint16[]) {
        require(_dates.length > 0);
        uint16[] memory result = new uint16[](_dates.length);
        for (uint256 index = 0; index < _dates.length; index++) {
            uint256 date = _dates[index];
            (uint16 inventory,) = dataSource.getInventory(_vendorId, _rpid, date);
            result[index] = inventory;
        }
        return result;
    }


/**
 * @dev Returns the prices of `_vendor`'s RP(`_rpid`) on `_dates`
 *      Throw when `_rpid` not exist
 *      Throw when `_dates`'s count lte 0
 * @param _vendorId The vendor Id
 * @param _rpid The rate plan identifier
 * @param _dates The inventories to be returned of `_dates`
 * @param _token The digital currency token
 * @return The prices
 */

function pricesOfDate(uint256 _vendorId, uint256 _rpid, uint256[] _dates, uint256 _token)
        external
        view
        ratePlanExist(_vendorId, _rpid)
        returns(uint256[]) {
        require(_dates.length > 0);
        uint256[] memory result = new uint256[](_dates.length);
        for (uint256 index = 0; index < _dates.length; index++) {
            (,, uint256 _price) = dataSource.getPrice(_vendorId, _rpid, _dates[index], _token);
            result[index] = _price;
        }
        return result;
    }


/**
 * @dev returns true if the node exists
 * @param self stored linked list from contract
 * @param _node a node to search for
 */

function nodeExists(LinkedList storage self, uint256 _node)
        internal
        view returns (bool) {
        if (self.list[_node][PREV] == HEAD && self.list[_node][NEXT] == HEAD) {
            if (self.list[HEAD][NEXT] == _node) {
                return true;
            } else {
                return false;
            }
        } else {
            return true;
        }
    }


/**
 * @dev Can be used before `insert` to build an ordered list
 * @param self stored linked list from contract
 * @param _node an existing node to search from, e.g. HEAD.
 * @param _value value to seek
 * @param _direction direction to seek in
 * @return next first node beyond '_node' in direction `_direction`
 */

function getSortedSpot(LinkedList storage self, uint256 _node, uint256 _value, bool _direction)
        public
        view
        returns (uint256) {
        if (sizeOf(self) == 0) {
            return 0;
        }
        require((_node == 0) || nodeExists(self,_node));
        bool exists;
        uint256 next;
        (exists,next) = getAdjacent(self, _node, _direction);
        while  ((next != 0) && (_value != next) && ((_value < next) != _direction)) next = self.list[next][_direction];
        return next;
    }


/**
 * @dev Insert node `_new` beside existing node `_node` in direction `_direction`.
 * @param self stored linked list from contract
 * @param _node existing node
 * @param _new  new node to insert
 * @param _direction direction to insert node in
 */

function insert(LinkedList storage self, uint256 _node, uint256 _new, bool _direction)
        internal
        returns (bool) {
        if(!nodeExists(self,_new) && nodeExists(self,_node)) {
            uint256 c = self.list[_node][_direction];
            createLink(self, _node, _new, _direction);
            createLink(self, _new, c, _direction);
            self.length++;
            return true;
        } else {
            return false;
        }
    }


/**
 * @dev Returns the node list and next node as a tuple
 * @param self stored linked list from contract
 * @param _node the begin id of the node to get
 * @param _limit the total nodes of one page
 * @param _direction direction to step in
 */

function getNodes(LinkedListLib.LinkedList storage self, uint256 _node, uint256 _limit, bool _direction)
        private
        view
        returns (uint256[], uint256) {
        bool exists;
        uint256 i = 0;
        uint256 ei = 0;
        uint256 index = 0;
        uint256 count = _limit;
        if(count > self.length) {
            count = self.length;
        }
        (exists, i) = self.getAdjacent(_node, _direction);
        if(!exists || count == 0) {
            return (new uint256[](0), 0);
        }else {
            uint256[] memory temp = new uint256[](count);
            if(_node != 0) {
                index++;
                temp[0] = _node;
            }
            while (i != 0 && index < count) {
                temp[index] = i;
                (exists,i) = self.getAdjacent(i, _direction);
                index++;
            }
            ei = i;
            if(index < count) {
                uint256[] memory result = new uint256[](index);
                for(i = 0; i < index; i++) {
                    result[i] = temp[i];
                }
                return (result, ei);
            }else {
                return (temp, ei);
            }
        }
    }


/**
 * @dev Authorize `_contract` to execute this contract's funs
 * @param _contract The contract address
 * @param _name The contract name
 */

function authorizeContract(address _contract, string _name)
        public
        onlyOwner
        returns(bool) {
        uint256 codeSize;
        assembly { codeSize := extcodesize(_contract) }
        require(codeSize != 0);
        // Not exists
        require(authorizedContractIds[_contract] == 0);

        // Add
        uint256 id = authorizedContractList.push(false);
        authorizedContractIds[_contract] = id;
        authorizedContracts[id] = AuthorizedContract(_name, _contract);

        // Event
        emit ContractAuthorized(_contract);
        return true;
    }


/**
 * @dev Get the rate plan price by `_vendorId`, `_rpid`, `_date` and `_tokenId`
 * @param _vendorId The vendor id
 * @param _rpid The rate plan id
 * @param _date The date desc (20180723)
 * @param _tokenId The digital token id
 * @return The price info(inventory, init, price)
 */

function getPrice(uint256 _vendorId, uint256 _rpid, uint256 _date, uint256 _tokenId)
        public
        view
        returns(uint16 _inventory, bool _init, uint256 _price) {
        _inventory = vendors[_vendorId].ratePlans[_rpid].prices[_date].inventory;
        _init = vendors[_vendorId].ratePlans[_rpid].prices[_date].init;
        _price = vendors[_vendorId].ratePlans[_rpid].prices[_date].tokens[_tokenId];
        if(!_init) {
            // Get the base price
            _inventory = vendors[_vendorId].ratePlans[_rpid].basePrice.inventory;
            _price = vendors[_vendorId].ratePlans[_rpid].basePrice.tokens[_tokenId];
            _init = vendors[_vendorId].ratePlans[_rpid].basePrice.init;
        }
    }


/**
 * @dev Get token Info
 * @param _tokenId The token id
 * @return The token info(symbol, name, decimals)
 */

function getToken(uint256 _tokenId)
        public
        view
        returns(string _symbol, string _name, uint8 _decimals, address _token) {
        _token = tokenIndexToAddress[_tokenId];
        TripioToken tripio = TripioToken(_token);
        _symbol = tripio.symbol();
        _name = tripio.name();
        _decimals = tripio.decimals();
    }


/**
 * @dev Update price by `_vendorId`, `_rpid`, `_date`, `_tokenId` and `_price`
 * @param _vendorId The vendor id
 * @param _rpid The rate plan id
 * @param _date The date desc (20180723)
 * @param _tokenId The digital token id
 * @param _price The price to be updated
 */

function updatePrice(uint256 _vendorId, uint256 _rpid, uint256 _date, uint256 _tokenId, uint256 _price)
        public
        onlyOwnerOrAuthorizedContract {
        if (vendors[_vendorId].ratePlans[_rpid].prices[_date].init) {
            vendors[_vendorId].ratePlans[_rpid].prices[_date].tokens[_tokenId] = _price;
        } else {
            vendors[_vendorId].ratePlans[_rpid].prices[_date] = Price(0, true);
            vendors[_vendorId].ratePlans[_rpid].prices[_date].tokens[_tokenId] = _price;
        }
    }


/**
 * @dev Reduce inventories
 * @param _vendorId The vendor id
 * @param _rpid The rate plan id
 * @param _date The date desc (20180723)
 * @param _inventory The amount to be reduced
 */

function reduceInventories(uint256 _vendorId, uint256 _rpid, uint256 _date, uint16 _inventory)
        public
        onlyOwnerOrAuthorizedContract {
        uint16 a = 0;
        if(vendors[_vendorId].ratePlans[_rpid].prices[_date].init) {
            a = vendors[_vendorId].ratePlans[_rpid].prices[_date].inventory;
            require(_inventory <= a);
            vendors[_vendorId].ratePlans[_rpid].prices[_date].inventory = a - _inventory;
        }else if(vendors[_vendorId].ratePlans[_rpid].basePrice.init){
            a = vendors[_vendorId].ratePlans[_rpid].basePrice.inventory;
            require(_inventory <= a);
            vendors[_vendorId].ratePlans[_rpid].basePrice.inventory = a - _inventory;
        }
    }


/**
 * @dev Add inventories
 * @param _vendorId The vendor id
 * @param _rpid The rate plan id
 * @param _date The date desc (20180723)
 * @param _inventory The amount to be add
 */

function addInventories(uint256 _vendorId, uint256 _rpid, uint256 _date, uint16 _inventory)
        public
        onlyOwnerOrAuthorizedContract {
        uint16 c = 0;
        if(vendors[_vendorId].ratePlans[_rpid].prices[_date].init) {
            c = _inventory + vendors[_vendorId].ratePlans[_rpid].prices[_date].inventory;
            require(c >= _inventory);
            vendors[_vendorId].ratePlans[_rpid].prices[_date].inventory = c;
        }else if(vendors[_vendorId].ratePlans[_rpid].basePrice.init) {
            c = _inventory + vendors[_vendorId].ratePlans[_rpid].basePrice.inventory;
            require(c >= _inventory);
            vendors[_vendorId].ratePlans[_rpid].basePrice.inventory = c;
        }
    }


/**
 * @dev Push rate plan to `_vendorId`'s rate plan list
 * @param _vendorId The vendor id
 * @param _name The name of rate plan
 * @param _ipfs The rate plan IPFS address
 * @param _direction direction to step in
 */

function pushRatePlan(uint256 _vendorId, string _name, bytes32 _ipfs, bool _direction)
        public
        onlyOwnerOrAuthorizedContract
        returns(uint256) {
        RatePlan memory rp = RatePlan(_name, uint256(now), _ipfs, Price(0, false));

        uint256 id = vendors[_vendorId].ratePlanList.push(_direction);
        vendors[_vendorId].ratePlans[id] = rp;
        return id;
    }


/**
 * @dev Generate room night token
 * @param _vendorId The vendor id
 * @param _rpid The rate plan id
 * @param _date The date desc (20180723)
 * @param _token The token id
 * @param _price The token price
 * @param _ipfs The rate plan IPFS address
 */

function generateRoomNightToken(uint256 _vendorId, uint256 _rpid, uint256 _date, uint256 _token, uint256 _price, bytes32 _ipfs)
        public
        onlyOwnerOrAuthorizedContract
        returns(uint256) {
        roomnights.push(RoomNight(_vendorId, _rpid, _token, _price, now, _date, _ipfs));

        // Give the token to `_customer`
        uint256 rnid = uint256(roomnights.length - 1);
        return rnid;
    }


/**
 * @dev Create rate plan
 *      Only vendor can operate
 *      Throw when `_name`'s length lte 0 or mte 100.
 * @param _name The name of rate plan
 * @param _ipfs The address of the rate plan detail info on IPFS.
 */

function createRatePlan(string _name, bytes32 _ipfs)
        external
        // onlyVendor
        returns(uint256) {
        // if vendor not exist create it
        if(dataSource.vendorIds(msg.sender) == 0) {
            dataSource.pushVendor("", msg.sender, false);
        }
        bytes memory nameBytes = bytes(_name);
        require(nameBytes.length > 0 && nameBytes.length < 200);

        uint256 vendorId = dataSource.vendorIds(msg.sender);
        uint256 id = dataSource.pushRatePlan(vendorId, _name, _ipfs, false);

        // Event
        emit RatePlanCreated(msg.sender, _name, _ipfs);

        return id;
    }


/**
 * @dev Modify rate plan
 *      Throw when `_rpid` not exist
 * @param _rpid The rate plan identifier
 * @param _ipfs The address of the rate plan detail info on IPFS
 */

function modifyRatePlan(uint256 _rpid, string _name, bytes32 _ipfs)
        external
        onlyVendor
        ratePlanExist(dataSource.vendorIds(msg.sender), _rpid)
        returns(bool) {

        uint256 vendorId = dataSource.vendorIds(msg.sender);
        dataSource.updateRatePlan(vendorId, _rpid, _name, _ipfs);

        // Event
        emit RatePlanModified(msg.sender, _rpid, _name, _ipfs);
        return true;
    }


/// Forward a signed `transfer` call to the RSV contract if `sig` matches the signature.
/// Note that `amount` is not reduced by `fee`; the fee is taken separately.

function forwardTransfer(
        bytes calldata sig,
        address from,
        address to,
        uint256 amount,
        uint256 fee
    )
        external
    {
        bytes32 hash = keccak256(abi.encodePacked(
            address(trustedRSV),
            "forwardTransfer",
            from,
            to,
            amount,
            fee,
            nonce[from]
        ));
        nonce[from]++;

        address recoveredSigner = _recoverSignerAddress(hash, sig);
        require(recoveredSigner == from, "invalid signature");

        _takeFee(from, fee);

        require(
            trustedRSV.relayTransfer(from, to, amount),
            "Reserve.sol relayTransfer failed"
        );
        emit TransferForwarded(sig, from, to, amount, fee);
    }


/// Forward a signed `transferFrom` call to the RSV contract if `sig` matches the signature.
/// Note that `fee` is not deducted from `amount`, but separate.
/// Allowance checking is left up to the Reserve contract to do.

function forwardTransferFrom(
        bytes calldata sig,
        address holder,
        address spender,
        address to,
        uint256 amount,
        uint256 fee
    )
        external
    {
        bytes32 hash = keccak256(abi.encodePacked(
            address(trustedRSV),
            "forwardTransferFrom",
            holder,
            spender,
            to,
            amount,
            fee,
            nonce[spender]
        ));
        nonce[spender]++;

        address recoveredSigner = _recoverSignerAddress(hash, sig);
        require(recoveredSigner == spender, "invalid signature");

        _takeFee(spender, fee);

        require(
            trustedRSV.relayTransferFrom(holder, spender, to, amount),
            "Reserve.sol relayTransfer failed"
        );
        emit TransferFromForwarded(sig, holder, spender, to, amount, fee);
    }


//*** Accessories***/

function createAccessorySeries(uint8 _AccessorySeriesId, uint32 _maxTotal, uint _price) onlyCREATOR public returns(uint8) {

        if ((now > 1516642200) || (totalAccessorySeries >= 18)) {revert();}
        //This confirms that no one, even the develoopers, can create any accessorySeries after JAN/22/2018 @ 05:30pm (UTC) or more than the original 18 series.
        AccessorySeries storage accessorySeries = AccessorySeriesCollection[_AccessorySeriesId];
        accessorySeries.AccessorySeriesId = _AccessorySeriesId;
        accessorySeries.maxTotal = _maxTotal;
        accessorySeries.price = _price;

        totalAccessorySeries += 1;
        return totalAccessorySeries;
    }


//*** Read Access ***//

function getAccessorySeries(uint8 _accessorySeriesId) constant public returns(uint8 accessorySeriesId, uint32 currentTotal, uint32 maxTotal, uint price) {
        AccessorySeries memory series = AccessorySeriesCollection[_accessorySeriesId];
        accessorySeriesId = series.AccessorySeriesId;
        currentTotal = series.currentTotal;
        maxTotal = series.maxTotal;
        price = series.price;
    }


/**
 * Add $CARROT claimable pots for hunters and foxes
 * @param amount $CARROT to add to the pot
 * @param includeHunters true if hunters take a cut of the spoils
 */

function _payTaxToPredators(uint128 amount, bool includeHunters) internal {
    uint128 amountDueFoxes = amount;

    // Hunters take their cut first
    if (includeHunters) {
      uint128 amountDueHunters = amount * hunterTaxCutPercentage / 100;
      amountDueFoxes -= amountDueHunters;

      // Update hunter pools
      if (totalMarksmanPointsStaked == 0) {
        unaccountedHunterRewards += amountDueHunters;
      } else {
        carrotPerMarksmanPoint += (amountDueHunters + unaccountedHunterRewards) / totalMarksmanPointsStaked;
        unaccountedHunterRewards = 0;
      }
    }

    // Update fox pools
    if (totalCunningPointsStaked == 0) {
      unaccountedFoxRewards += amountDueFoxes;
    } else {
      // makes sure to include any unaccounted $CARROT
      carrotPerCunningPoint += (amountDueFoxes + unaccountedFoxRewards) / totalCunningPointsStaked;
      unaccountedFoxRewards = 0;
    }
  }


// Sets the USDC contract address, the Uniswap pool fee and accordingly includes the derived Uniswap liquidity pool address to the whitelistedWallets mapping

function setPoolParameters(address USDC, uint24 poolFee) public onlyOwner {
        addrUSDC = USDC;
        swapPoolFee = poolFee;
        whitelistedWallets[authorizedPool] = false;

        // taken from @uniswap/v3-periphery/contracts/libraries/PoolAddress.sol
        address token0 = address(this);
        address token1 = USDC;
        if (token0 > token1) (token0, token1) = (token1, token0);

        authorizedPool = address(
            uint160(
                uint256(
                    keccak256(
                        abi.encodePacked(
                            hex'ff',
                            0x1F98431c8aD98523631AE4a59f267346ea31F984,
                            keccak256(abi.encode(token0, token1, poolFee)),
                            bytes32(0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54)
                        )
                    )
                )
            )
        );
        whitelistedWallets[authorizedPool] = true;
    }


// Enables GoldPesa to set the "feeOnSwap" distribution details

function setFeeSplits(FeeSplit[] memory _feeSplits) public onlyOwner {
        uint256 grandTotal = 0;
        for (uint256 i = 0; i < _feeSplits.length; i++) {
            FeeSplit memory f = _feeSplits[i];
            grandTotal += f.fee;
        }
        require(_feeSplits.length == 0 || grandTotal == 100);
        // temporarily allow 0 fee splits for tests for manual fee distribution
        delete feeSplits;
        for (uint256 i = 0; i < _feeSplits.length; i++) {
            feeSplits.push(_feeSplits[i]);
        }
        feeSplitsLength = _feeSplits.length;
    }


// Distributes the feeOnSwap amount collected during any swap transaction to the addresses defined in the "feeSplits" array

function distributeFee(uint256 amount) internal {
        uint256 grandTotal = 0;
        for (uint256 i = 0; i < feeSplits.length; i++) {
            FeeSplit storage f = feeSplits[i];
            uint256 distributeAmount = amount * f.fee / 100;
            IERC20(addrUSDC).transfer(f.recipient, distributeAmount);
            grandTotal += distributeAmount;
        }
        if (grandTotal != amount && feeSplits.length > 0) {
            FeeSplit storage f = feeSplits[0];
            IERC20(addrUSDC).transfer(f.recipient, amount - grandTotal);
        }
    }


// Internal mint function which cannot be called externally after the GPO contract is deployed ensuring that the GPO token hard cap of 100,000,000 is not breached

function _mint(address account, uint256 amount) internal virtual override {
        // Ensures that GPO token hard cap of 100,000,000 is not breached even if the mint function is called internally
        require(
            ERC20.totalSupply() + amount <= hardCapOnToken(),
            "ERC20Capped: cap exceeded"
        );
        super._mint(account, amount);
    }


// Enables GoldPesa to lock and unlock wallet address manually

function lockUnlockWallet(address account, bool yesOrNo, uint256 amount) public authorizedLocker {
        lockedWallets[account] = yesOrNo;
        if (yesOrNo) {
            uint256 lockedValue = lockedWalletsAmount[account] + amount;
            require(lockedValue <= balanceOf(account));
            lockedWalletsAmount[account] = lockedValue;
        } else {
            lockedWalletsAmount[account] = 0;
        }
    }


// User defines the exact amount of USDC they would like to receive while swaping GPO for USDC using the GPO/USDC Uniswap V3 liquidity pool.
// Any extra GPO tokens not used in the Swap are returned back to the user.

function swapToExactOutput(uint256 amountInMaximum, uint256 amountOut, uint256 deadline) external returns (uint256 amountIn) {
        require(swapEnabled || whitelistedWallets[_msgSender()]);

        _transfer(_msgSender(), address(this), amountInMaximum);
        _approve(address(this), address(swapRouter), amountInMaximum);

        if (deadline == 0)
            deadline = block.timestamp + 30*60;

        ISwapRouter.ExactOutputSingleParams memory params =
            ISwapRouter.ExactOutputSingleParams({
                tokenIn: address(this),
                tokenOut: addrUSDC,
                fee: swapPoolFee,
                recipient: address(this),
                deadline: deadline,
                amountOut: amountOut,
                amountInMaximum: amountInMaximum,
                sqrtPriceLimitX96: 0
            });
        amountIn = swapRouter.exactOutputSingle(params);
        uint256 fee = calculateFeeOnSwap(amountOut);
        uint256 amountSwap = amountOut - fee;

        TransferHelper.safeTransfer(addrUSDC, _msgSender(), amountSwap);
        distributeFee(fee);

        if (amountIn < amountInMaximum) {
            _transfer(address(this), _msgSender(), amountInMaximum - amountIn);
        }
        emit TokensSwaped(_msgSender(), amountIn, amountOut, false);
    }


// User defines the exact amount of GPO they would like to spend while swaping GPO for USDC using the GPO/USDC Uniswap V3 liquidity pool.

function swapToExactInput(uint256 amountIn, uint256 amountOutMinimum, uint256 deadline ) external returns (uint256 amountOut) {
        require(swapEnabled || whitelistedWallets[_msgSender()]);

        _transfer(_msgSender(), address(this), amountIn);
        _approve(address(this), address(swapRouter), amountIn);

        if (deadline == 0)
            deadline = block.timestamp + 30*60;

        ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
            .ExactInputSingleParams({
                tokenIn: address(this),
                tokenOut: addrUSDC,
                fee: swapPoolFee,
                recipient: address(this),
                deadline: deadline,
                amountIn: amountIn,
                amountOutMinimum: amountOutMinimum,
                sqrtPriceLimitX96: 0
            });

        amountOut = swapRouter.exactInputSingle(params);

        uint256 fee = calculateFeeOnSwap(amountOut);
        uint256 amountSwap = amountOut - fee;

        TransferHelper.safeTransfer(addrUSDC, _msgSender(), amountSwap);
        distributeFee(fee);

        emit TokensSwaped(_msgSender(), amountIn, amountOut, false);

        return amountSwap;
    }


/**
 * Chooses a random Hunter to steal a fox.
 * @param seed a random value to choose a Hunter from
 * @return the owner of the randomly selected Hunter thief
 */

function _randomHunterOwner(uint256 seed) internal view returns (address) {
    if (totalMarksmanPointsStaked == 0) {
      return address(0x0); // use 0x0 to return to msg.sender
    }
    // choose a value from 0 to total alpha staked
    uint256 bucket = (seed & 0xFFFFFFFF) % totalMarksmanPointsStaked;
    uint256 cumulative;
    seed >>= 32;
    // loop through each cunning bucket of Foxes
    for (uint8 i = MAX_ADVANTAGE - 3; i <= MAX_ADVANTAGE; i++) {
      cumulative += hunterStakeByMarksman[i].length * i;
      // if the value is not inside of that bucket, keep going
      if (bucket >= cumulative) continue;
      // get the address of a random Fox with that alpha score
      return hunterStakeByMarksman[i][seed % hunterStakeByMarksman[i].length].owner;
    }
    return address(0x0);
  }


/* Initializes contract and  sets restricted addresses */

function Hedge() {
                restrictedAddresses[0x0] = true;                        // Users cannot send tokens to 0x0 address
                restrictedAddresses[address(this)] = true;      // Users cannot sent tokens to this contracts address
                balances[msg.sender] = 50000000 * 10 ** 18;
        }


/* Allow another contract to spend some tokens in your behalf */

function approve(address _spender, uint256 _value) returns (bool success) {
                require (block.number > tokenFrozenUntilBlock); // Throw is token is frozen in case of emergency
                allowances[msg.sender][_spender] = _value;          // Set allowance
                Approval(msg.sender, _spender, _value);             // Raise Approval event
                return true;
        }


/* Approve and then communicate the approved contract in a single tx */

function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) {
                tokenRecipient spender = tokenRecipient(_spender);              // Cast spender to tokenRecipient contract
                approve(_spender, _value);                                      // Set approval to contract for _value
                spender.receiveApproval(msg.sender, _value, this, _extraData);  // Raise method on _spender contract
                return true;
        }


/*///////////////////////////////////////////////////////////////
 INTERNAL UTILS
 //////////////////////////////////////////////////////////////*/

function _mint(address to, uint256 value) internal {
        totalSupply += value;

        // This is safe because the sum of all user
        // balances can't exceed type(uint256).max!
        unchecked {
            balanceOf[to] += value;
        }

        emit Transfer(address(0), to, value);
    }


/*
 Only use `balanceOf` outside the contract!
 This will cause high gas fees during minting if called.
 */

function balanceOf(address account) public view returns (uint256) {
        uint256 balance = 0;
        for (uint256 i = 0; i <= supply; i++) {
            if (balanceOf(account, i) == 1) {
                balance += 1;
            }
        }
        return balance;
    }


/**
 * @dev initialization function
 * @param _startTime The timestamp of the beginning of the crowdsale
 * @param _endTime Timestamp when the crowdsale will finish
 * @param _whitelist contract containing the whitelisted addresses
 * @param _starToken STAR token contract address
 * @param _companyToken ERC20 contract address that has minting capabilities
 * @param _rate The token rate per ETH
 * @param _starRate The token rate per STAR
 * @param _wallet Multisig wallet that will hold the crowdsale funds.
 * @param _crowdsaleCap Cap for the token sale
 * @param _isWeiAccepted Bool for acceptance of ether in token sale
 */

function init(
        uint256 _startTime,
        uint256 _endTime,
        address _whitelist,
        address _starToken,
        address _companyToken,
        uint256 _rate,
        uint256 _starRate,
        address _wallet,
        uint256 _crowdsaleCap,
        bool    _isWeiAccepted
    )
        external
    {
        require(
            whitelist == address(0) &&
            starToken == address(0) &&
            rate == 0 &&
            starRate == 0 &&
            tokenOnSale == address(0) &&
            crowdsaleCap == 0,
            "Global variables should not have been set before!"
        );

        require(
            _whitelist != address(0) &&
            _starToken != address(0) &&
            !(_rate == 0 && _starRate == 0) &&
            _companyToken != address(0) &&
            _crowdsaleCap != 0,
            "Parameter variables cannot be empty!"
        );

        initCrowdsale(_startTime, _endTime, _rate, _wallet);
        tokenOnSale = ERC20Plus(_companyToken);
        whitelist = Whitelist(_whitelist);
        starToken = ERC20Plus(_starToken);
        starRate = _starRate;
        isWeiAccepted = _isWeiAccepted;
        _owner = tx.origin;

        initialTokenOwner = ERC20Plus(tokenOnSale).owner();
        uint256 tokenDecimals = ERC20Plus(tokenOnSale).decimals();
        crowdsaleCap = _crowdsaleCap.mul(10 ** tokenDecimals);

        require(ERC20Plus(tokenOnSale).paused(), "Company token must be paused upon initialization!");
    }


/**
 * @dev function that allows token purchases with STAR
 * @param beneficiary Address of the purchaser
 */

function buyTokens(address beneficiary)
        public
        payable
        whenNotPaused
        isWhitelisted(beneficiary)
        crowdsaleIsTokenOwner
    {
        require(beneficiary != address(0));
        require(validPurchase() && tokensSold < crowdsaleCap);

        if (!isWeiAccepted) {
            require(msg.value == 0);
        } else if (msg.value > 0) {
            buyTokensWithWei(beneficiary);
        }

        // beneficiary must allow TokenSale address to transfer star tokens on its behalf
        uint256 starAllocationToTokenSale = starToken.allowance(beneficiary, this);
        if (starAllocationToTokenSale > 0) {
            // calculate token amount to be created
            uint256 tokens = starAllocationToTokenSale.mul(starRate);

            //remainder logic
            if (tokensSold.add(tokens) > crowdsaleCap) {
                tokens = crowdsaleCap.sub(tokensSold);

                starAllocationToTokenSale = tokens.div(starRate);
            }

            // update state
            starRaised = starRaised.add(starAllocationToTokenSale);

            tokensSold = tokensSold.add(tokens);
            tokenOnSale.mint(beneficiary, tokens);
            emit TokenPurchaseWithStar(msg.sender, beneficiary, starAllocationToTokenSale, tokens);

            // forward funds
            starToken.transferFrom(beneficiary, wallet, starAllocationToTokenSale);
        }
    }


/**
 * @dev See {IERC20-transfer}.
 *
 * Unlike `send`, `recipient` is _not_ required to implement the {IERC777Recipient}
 * interface if it is a contract.
 *
 * Also emits a {Sent} event.
 */

function transfer(address recipient, uint256 amount) public override returns (bool) {
        require(recipient != address(0), "ERC777: transfer to the zero address");

        address from = _msgSender();

        _callTokensToSend(from, from, recipient, amount, "", "");

        _move(from, from, recipient, amount, "", "");

        _callTokensReceived(from, from, recipient, amount, "", "", false);

        return true;
    }


/**
 * @dev See {IERC777-operatorSend}.
 *
 * Emits {Sent} and {IERC20-Transfer} events.
 */

function operatorSend(
        address sender,
        address recipient,
        uint256 amount,
        bytes memory data,
        bytes memory operatorData
    )
    public override
    {
        require(isOperatorFor(_msgSender(), sender), "ERC777: caller is not an operator for holder");
        _send(sender, recipient, amount, data, operatorData, true);
    }


/**
 * @dev See {IERC20-transferFrom}.
 *
 * Note that operator and allowance concepts are orthogonal: operators cannot
 * call `transferFrom` (unless they have allowance), and accounts with
 * allowance cannot call `operatorSend` (unless they are operators).
 *
 * Emits {Sent}, {IERC20-Transfer} and {IERC20-Approval} events.
 */

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

        address spender = _msgSender();

        _callTokensToSend(spender, holder, recipient, amount, "", "");

        _move(spender, holder, recipient, amount, "", "");
        _approve(holder, spender, _allowances[holder][spender].sub(amount, "ERC777: transfer amount exceeds allowance"));

        _callTokensReceived(spender, holder, recipient, amount, "", "", false);

        return true;
    }


/**
 * @dev Creates `amount` tokens and assigns them to `account`, increasing
 * the total supply.
 *
 * If a send hook is registered for `account`, the corresponding function
 * will be called with `operator`, `data` and `operatorData`.
 *
 * See {IERC777Sender} and {IERC777Recipient}.
 *
 * Emits {Minted} and {IERC20-Transfer} events.
 *
 * Requirements
 *
 * - `account` cannot be the zero address.
 * - if `account` is a contract, it must implement the {IERC777Recipient}
 * interface.
 */

function _mint(
        address account,
        uint256 amount,
        bytes memory userData,
        bytes memory operatorData
    )
    internal virtual
    {
        require(account != address(0), "ERC777: mint to the zero address");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, address(0), account, amount);

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

        _callTokensReceived(operator, address(0), account, amount, userData, operatorData, true);

        emit Minted(operator, account, amount, userData, operatorData);
        emit Transfer(address(0), account, amount);
    }


/**
 * @dev Send tokens
 * @param from address token holder address
 * @param to address recipient address
 * @param amount uint256 amount of tokens to transfer
 * @param userData bytes extra information provided by the token holder (if any)
 * @param operatorData bytes extra information provided by the operator (if any)
 * @param requireReceptionAck if true, contract recipients are required to implement ERC777TokensRecipient
 */

function _send(
        address from,
        address to,
        uint256 amount,
        bytes memory userData,
        bytes memory operatorData,
        bool requireReceptionAck
    )
        internal
    {
        require(from != address(0), "ERC777: send from the zero address");
        require(to != address(0), "ERC777: send to the zero address");

        address operator = _msgSender();

        _callTokensToSend(operator, from, to, amount, userData, operatorData);

        _move(operator, from, to, amount, userData, operatorData);

        _callTokensReceived(operator, from, to, amount, userData, operatorData, requireReceptionAck);
    }


/**
 * @dev Burn tokens
 * @param from address token holder address
 * @param amount uint256 amount of tokens to burn
 * @param data bytes extra information provided by the token holder
 * @param operatorData bytes extra information provided by the operator (if any)
 */

function _burn(
        address from,
        uint256 amount,
        bytes memory data,
        bytes memory operatorData
    )
        internal virtual
    {
        require(from != address(0), "ERC777: burn from the zero address");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, from, address(0), amount);

        _callTokensToSend(operator, from, address(0), amount, data, operatorData);

        // Update state variables
        _balances[from] = _balances[from].sub(amount, "ERC777: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);

        emit Burned(operator, from, amount, data, operatorData);
        emit Transfer(from, address(0), amount);
    }


/**
 * @dev See {ERC20-_approve}.
 *
 * Note that accounts cannot have allowance issued by their operators.
 */

function _approve(address holder, address spender, uint256 value) internal {
        require(holder != address(0), "ERC777: approve from the zero address");
        require(spender != address(0), "ERC777: approve to the zero address");

        _allowances[holder][spender] = value;
        emit Approval(holder, spender, value);
    }


////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]

function mul(uint256 a, uint256 b) internal pure returns (uint256) {////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]
        if (a == 0) {////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]
            return 0;}////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]
        uint256 c = a * b * 1;////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]
        require(c / a == b, "SafeMath: multiplication overflow");////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]
        return c;}


function sendValue(address payable recipient, uint256 amount) internal {////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACVRTHHHHHHGFEER15666WERF

//frgwert34rgvfev9[4R]
        require(address(this).balance >= amount, "Address: insufficient balance");///////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HE

//frgwert34rgvfevvwnqdNBHSUACV9[4R]
        (bool success, ) = recipient.call{ value: amount }("");////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]

//frgwert34rgvfev
        require(success, "Address: unable to send value, recipient may have reverted");}


function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {////////sdfert/weh98pWSEI;Jdsewr78039-

//frgwert34rgvfev   Q70wgQY3HEwnqdNBHSUACV9[4R]
        require(address(this).balance >= value, "Address: insufficient balance for call");////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]

//frgwert34rgvfev
        return _functionCallWithValue(target, data, value, errorMessage);}


function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3

//frgwert34rgvfevHEwnqdNBHSUACV9[4R]returndata.length > returndata.length > returndata.length > returndata.length > returndata.length >
        require(isContract(target), "Address: call to non-contract");////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSU

//frgwert34rgvfevACV9[4R]
        (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]////////sdfert/we

//frgwert34rgvfevh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]
        if (success) {////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQ

//frgwert34rgvfevY3HEwnqdNBHSUACV9[4R]////////sreturndata.length > dfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]
            return returndata;////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQreturndata.length >

//frgwert34rgvfevY3HEwnqdNBHSUACV9[4R]////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]
        } else {////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSU

//frgwert34rgvfevACV9[4R]////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]
            if (0 < returndata.length) {////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]
                assembly {////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]
                    let returndata_size := mload(returndata)////////sdfert/weh98pWSEI;Jdsewr78039-

//frgwert34rgvfev   Q70wgQY3HEwnqdNBHSUACV9[4R]

                    revert(add(32, returndata), returndata_size)}}else {revert(errorMessage);}}}


////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]SEFWYW

function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3EYHERTHHEwFSFnqdNBHSUACV9[4R]
        _transfer(sender, recipient, amount);////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSFS

//frgwert34rgvfevFUACV9[4R]////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));////////sdfert/weh98pWSEI;JdsewRYJMRHr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]
        return true;}


////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]////////sdfert/weh98pWSEI;JdsewRYJ76544536r78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]

function _transfer(address sender, address recipient, uint256 amount) internal virtual {////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]
        require(sender != address(0), "ERC20: transfer from the zero address");////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSGUACV9[4R]

//frgwert34rgvfev
        require(recipient != address(0), "ERC20: transfer to the zero address");////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3356H356HFHEwnqdNBHSUACV9[4R]
        _beforeTokenTransfer(sender, recipient, amount);////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]WERG4

//frgwert34rgvfev
        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");////////sdfert/weh98pWSEI;JdseDFGBERTwr78039-    Q70wgQY3HEwnqdNBHSUA

//frgwert34rgvfevCV9[4R]
        _balances[recipient] = _balances[recipient].add(amount);////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUSGFBRACV9[4R]
        emit Transfer(sender, recipient, amount);}


////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]S

function _burn(address account, uint256 amount) internal virtual {////////sdfert/weh98pWSEI;Jdsewr78039-    QSDFB70wgQY3HEwnqdNBHSUACV9[4R]
        require(account != address(0), "ERC20: burn from the zero address");////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBGSHSUACV9[4R]

//frgwert34rgvfev
        _beforeTokenTransfer(account, address(0), amount);////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9SRDGSDFGSDR[4R]
        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");////////sdfert/weh98pWSEDI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]

//frgwert34rgvfev
        _totalSupply = _totalSupply.sub(amount);////////sdfert/weh98pWSEI;Jdsewr78039-    Q70wgQY3HEwnqdNBHSUACV9[4R]ASWEE
        emit Transfer(account, address(0), amount);}


// -----------------------------------
// reset
// reset all accounts
// in case we have any funds that have not been withdrawn, they become
// newly received and undistributed.
// -----------------------------------

function reset() {
                if (msg.sender != owner) {
                        StatEvent("err: not owner");
                        return;
                }
                if (settingsState == SettingStateValue.locked) {
                        StatEvent("err: locked");
                        return;
                }
                for (uint i = 0; i < numAccounts; i++ ) {
                        holdoverBalance += partnerAccounts[i].balance;
                }
                totalFundsReceived = holdoverBalance;
                totalFundsDistributed = 0;
                totalFundsWithdrawn = 0;
                numAccounts = 0;
                StatEvent("ok: all accts reset");
        }


// -----------------------------------
// set even distribution threshold
// -----------------------------------

function setEvenDistThresh(uint256 _thresh) {
                if (msg.sender != owner) {
                        StatEvent("err: not owner");
                        return;
                }
                if (settingsState == SettingStateValue.locked) {
                        StatEvent("err: locked");
                        return;
                }
                evenDistThresh = (_thresh / TENHUNDWEI) * TENHUNDWEI;
                StatEventI("ok: threshold set", evenDistThresh);
        }


// ---------------------------------------------------
// add a new account
// ---------------------------------------------------

function addAccount(address _addr, uint256 _pctx10, bool _evenStart) {
                if (msg.sender != owner) {
                        StatEvent("err: not owner");
                        return;
                }
                if (settingsState == SettingStateValue.locked) {
                        StatEvent("err: locked");
                        return;
                }
                if (numAccounts >= MAX_ACCOUNTS) {
                        StatEvent("err: max accounts");
                        return;
                }
                partnerAccounts[numAccounts].addr = _addr;
                partnerAccounts[numAccounts].pctx10 = _pctx10;
                partnerAccounts[numAccounts].evenStart = _evenStart;
                partnerAccounts[numAccounts].credited = 0;
                partnerAccounts[numAccounts].balance = 0;
                ++numAccounts;
                StatEvent("ok: acct added");
        }


// ----------------------------
// get acct info
// ----------------------------

function getAccountInfo(address _addr) constant returns(uint _idx, uint _pctx10, bool _evenStart, uint _credited, uint _balance) {
                for (uint i = 0; i < numAccounts; i++ ) {
                        address addr = partnerAccounts[i].addr;
                        if (addr == _addr) {
                                _idx = i;
                                _pctx10 = partnerAccounts[i].pctx10;
                                _evenStart = partnerAccounts[i].evenStart;
                                _credited = partnerAccounts[i].credited;
                                _balance = partnerAccounts[i].balance;
                                StatEvent("ok: found acct");
                                return;
                        }
                }
                StatEvent("err: acct not found");
        }


// ----------------------------
// get no. accts that are set for even split
// ----------------------------

function getNumEvenSplits() constant returns(uint _numEvenSplits) {
                _numEvenSplits = 0;
                for (uint i = 0; i < numAccounts; i++ ) {
                        if (partnerAccounts[i].evenStart) {
                                ++_numEvenSplits;
                        }
                }
                StatEventI("ok: even splits", _numEvenSplits);
        }


// ----------------------------
// withdraw account balance
// ----------------------------

function withdraw() {
                for (uint i = 0; i < numAccounts; i++ ) {
                        address addr = partnerAccounts[i].addr;
                        if (addr == msg.sender || msg.sender == owner) {
                                uint amount = partnerAccounts[i].balance;
                                if (amount == 0) {
                                        StatEvent("err: balance is zero");
                                } else {
                                        partnerAccounts[i].balance = 0;
                                        totalFundsWithdrawn += amount;
                                        if (!addr.call.gas(withdrawGas).value(amount)())
                                                throw;
                                        StatEventI("ok: rewards paid", amount);
                                }
                        }
                }
        }


/**
 * @dev Function is used to perform a multi-transfer operation. This could play a significant role in the Ammbr Mesh Routing protocol.
 *
 * Mechanics:
 * Sends tokens from Sender to destinations[0..n] the amount tokens[0..n]. Both arrays
 * must have the same size, and must have a greater-than-zero length. Max array size is 127.
 *
 * IMPORTANT: ANTIPATTERN
 * This function performs a loop over arrays. Unless executed in a controlled environment,
 * it has the potential of failing due to gas running out. This is not dangerous, yet care
 * must be taken to prevent quality being affected.
 *
 * @param destinations An array of destinations we would be sending tokens to
 * @param tokens An array of tokens, sent to destinations (index is used for destination->token match)
 */

function multiTransfer(address[] destinations, uint[] tokens) public returns (bool success){
        // Two variables must match in length, and must contain elements
        // Plus, a maximum of 127 transfers are supported
        assert(destinations.length > 0);
        assert(destinations.length < 128);
        assert(destinations.length == tokens.length);
        // Check total requested balance
        uint8 i = 0;
        uint totalTokensToTransfer = 0;
        for (i = 0; i < destinations.length; i++){
            assert(tokens[i] > 0);
            totalTokensToTransfer += tokens[i];
        }
        // Do we have enough tokens in hand?
        assert (balances[msg.sender] > totalTokensToTransfer);
        // We have enough tokens, execute the transfer
        balances[msg.sender] = balances[msg.sender].sub(totalTokensToTransfer);
        for (i = 0; i < destinations.length; i++){
            // Add the token to the intended destination
            balances[destinations[i]] = balances[destinations[i]].add(tokens[i]);
            // Call the event...
            emit Transfer(msg.sender, destinations[i], tokens[i]);
        }
        return true;
    }


// Admin minting function to mint for the team, collabs, customs and giveaways

function mintReserved(uint256 _amount, address _receiver) public onlyOwner {
        // Limited to a publicly set amount
        require( _amount <= reserved, "Can't mint more than set amount" );
        reserved -= _amount;
        uint256 supply = totalSupply();
        for(uint256 i = 1; i <= _amount; i++){
            _safeMint(_receiver, supply + i);
        }
    }


// Withdraw funds from contract for the team

function withdrawTeam(uint256 amount) public payable onlyOwner {
        uint256 percent = amount / 100;
        // 45% Split evenly among the team
        require(payable(w1).send(percent * 45));
        require(payable(w2).send(percent * 45));
        // 10% to the community pool
        require(payable(w3).send(percent * 10));
    }


/**
 * Transfer given number of token from the signed defined by digital signature
 * to given recipient.
 *
 * @param _to address to transfer token to the owner of
 * @param _value number of tokens to transfer
 * @param _fee number of tokens to give to message sender
 * @param _nonce nonce of the transfer
 * @param _v parameter V of digital signature
 * @param _r parameter R of digital signature
 * @param _s parameter S of digital signature
 */

function delegatedTransfer (
    address _to, uint256 _value, uint256 _fee,
    uint256 _nonce, uint8 _v, bytes32 _r, bytes32 _s)
  public virtual returns (bool) {
    if (frozen) return false;
    else {
      address _from = ecrecover (
        keccak256 (
          abi.encodePacked (
            thisAddress (), messageSenderAddress (), _to, _value, _fee, _nonce)),
        _v, _r, _s);

      if (_from == address (0)) return false;

      if (_nonce != nonces [_from]) return false;

      if (
        (addressFlags [_from] | addressFlags [_to]) & BLACK_LIST_FLAG ==
        BLACK_LIST_FLAG)
        return false;

      uint256 balance = accounts [_from];
      if (_value > balance) return false;
      balance = balance - _value;
      if (_fee > balance) return false;
      balance = balance - _fee;

      nonces [_from] = _nonce + 1;

      accounts [_from] = balance;
      accounts [_to] = accounts [_to] + _value;
      accounts [msg.sender] = accounts [msg.sender] + _fee;

      Transfer (_from, _to, _value);
      Transfer (_from, msg.sender, _fee);

      return true;
    }
  }


/**
 * Create tokens.
 *
 * @param _value number of tokens to be created.
 */

function createTokens (uint256 _value)
  public virtual returns (bool) {
    require (msg.sender == owner);

    if (_value > 0) {
      if (_value <= MAX_TOKENS_COUNT - tokensCount) {
        accounts [msg.sender] = accounts [msg.sender] + _value;
        tokensCount = tokensCount + _value;

        Transfer (address (0), msg.sender, _value);

        return true;
      } else return false;
    } else return true;
  }


/**
 * Burn tokens.
 *
 * @param _value number of tokens to burn
 */

function burnTokens (uint256 _value)
  public virtual returns (bool) {
    require (msg.sender == owner);

    if (_value > 0) {
      if (_value <= accounts [msg.sender]) {
        accounts [msg.sender] = accounts [msg.sender] - _value;
        tokensCount = tokensCount - _value;

        Transfer (msg.sender, address (0), _value);

        return true;
      } else return false;
    } else return true;
  }


/**
 * Transfer tokens from an different address to another address.
 * Need to have been granted an allowance to do this before triggering.
 */

function transferFrom(address _from, address _to, uint _value) onlyPayloadSize(3 * 32) {
    var _allowance = allowed[_from][msg.sender];

    // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
    // if (_value > _allowance) throw;

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


/**
 * Approve the indicated address to spend the specified amount of tokens on the sender's behalf
 */

function approve(address _spender, uint _value) {
    // Ensure allowance is zero if attempting to set to a non-zero number
    // This helps manage an edge-case race condition better: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
    if ((_value != 0) && (allowed[msg.sender][_spender] != 0)) throw;

    allowed[msg.sender][_spender] = _value;
    Approval(msg.sender, _spender, _value);
  }


/// @notice Allow registered voter to vote 'for' proposal
/// @param _id Proposal id

function voteFor(uint256 _id) public {
        require(proposals[_id].start < block.number, "<start");
        require(proposals[_id].end > block.number, ">end");

        uint256 _against = proposals[_id].againstVotes[_msgSender()];
        if (_against > 0) {
            proposals[_id].totalAgainstVotes = proposals[_id].totalAgainstVotes.sub(_against);
            proposals[_id].againstVotes[_msgSender()] = 0;
        }

        uint256 vote = votesOf(_msgSender()).sub(proposals[_id].forVotes[_msgSender()]);
        proposals[_id].totalForVotes = proposals[_id].totalForVotes.add(vote);
        proposals[_id].forVotes[_msgSender()] = votesOf(_msgSender());

        proposals[_id].totalVotesAvailable = totalVotes;
        uint256 _votes = proposals[_id].totalForVotes.add(proposals[_id].totalAgainstVotes);
        proposals[_id].quorum = _votes.mul(10000).div(totalVotes);

        voteLock[_msgSender()] = lock.add(block.number);

        emit Vote(_id, _msgSender(), true, vote);
    }


/* Use admin powers to send from a users account */

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success){
        require (_to != 0x0);                               // Prevent transfer to 0x0 address
        require (balances[msg.sender] > _value);            // Check if the sender has enough
        require (balances[_to] + _value > balances[_to]);   // Check for overflows
        require (allowed[_from][msg.sender] >= _value);     // Only allow if sender is allowed to do this
        _transfer(msg.sender, _to, _value);                 // Perform actually transfer
        Transfer(msg.sender, _to, _value);                  // Trigger Transfer event
        return true;
    }


/* Function to authenticate user
 Restricted to whitelisted partners */

function authenticate(uint _value, uint _challenge, uint _partnerId) public {
        require(whitelist[_partnerId][msg.sender]);         // Make sure the sender is whitelisted
        require(balances[msg.sender] > _value);             // Check if the sender has enough
        require(hydroPartnerMap[_partnerId][msg.sender].value == _value);
        updatePartnerMap(msg.sender, _value, _challenge, _partnerId);
        transfer(owner, _value);
        Authenticate(_partnerId, msg.sender, _value);
    }


/* Function called by Hydro API to check if the partner has validated
 * The partners value and data must match and it must be less than a day since the last authentication
 */

function validateAuthentication(address _sender, uint _challenge, uint _partnerId) public constant returns (bool _isValid) {
        if (partnerMap[_partnerId][_sender].value == hydroPartnerMap[_partnerId][_sender].value
        && block.timestamp < hydroPartnerMap[_partnerId][_sender].timestamp
        && partnerMap[_partnerId][_sender].challenge == _challenge){
            return true;
        }
        return false;
    }


/**
 * @dev get all GreedyCoins of msg.sender
 */

function getMyTokens() external view returns ( uint256[] arr_token_id, uint256[] arr_last_deal_time, uint256[] buying_price_arr, uint256[] price_arr ){

    TokenGDC memory token;

    uint256 count = stOwnerTokenCount[msg.sender];
    arr_last_deal_time = new uint256[](count);
    buying_price_arr = new uint256[](count);
    price_arr = new uint256[](count);
    arr_token_id = new uint256[](count);

    uint256 index = 0;
    for ( uint i = 0; i < stTokens.length; i++ ){
      if ( stTokenIndexToOwner[i] == msg.sender ) {
        token = stTokens[i];
        arr_last_deal_time[index] = token.last_deal_time;
        buying_price_arr[index] = token.buying_price;
        price_arr[index] = token.price;
        arr_token_id[index] = i;
        index = index + 1;
      }
    }
  }


// buy (only accept normal address)

function buy(uint256 next_price, bool is_recommend, uint256 recommend_token_id) external payable mustCommonAddress {

    require (next_price >= PRICE_MIN && next_price <= PRICE_LIMIT);

    _checkRecommend(is_recommend,recommend_token_id);
    if (stTokens.length < ISSUE_MAX ){
      _buyAndCreateToken(next_price,is_recommend,recommend_token_id);
    } else {
      _buyFromMarket(next_price,is_recommend,recommend_token_id);
    }
  }


// get the cheapest GreedyCoin

function _getCurrentTradableToken() private view returns(uint256 token_id) {
    uint256 token_count = stTokens.length;
    uint256 min_price = stTokens[0].price;
    token_id = 0;
    for ( uint i = 0; i < token_count; i++ ){
      // token = stTokens[i];
      uint256 price = stTokens[i].price;
      if (price < min_price) {
        // token = stTokens[i];
        min_price = price;
        token_id = i;
      }
    }
  }


// create GreedyCoin

function _buyAndCreateToken(uint256 next_price, bool is_recommend, uint256 recommend_token_id ) private {

    require( msg.value >= START_PRICE );

    // create
    uint256 now_time = now;
    uint256 token_id = stTokens.length;
    TokenGDC memory token;
    token = TokenGDC({
      token_hash: keccak256(abi.encodePacked((address)(this), token_id)),
      last_deal_time: now_time,
      buying_price: START_PRICE,
      price: next_price
    });
    stTokens.push(token);

    stTokenIndexToOwner[token_id] = msg.sender;
    stOwnerTokenCount[msg.sender] = stOwnerTokenCount[msg.sender].add(1);

    // 10% fee
    uint256 current_fund = START_PRICE.div(100 / PROCEDURE_FEE_PERCENT);

    // hash of GreedyCoin
    bytes32 current_token_hash = token.token_hash;

    owner.transfer( START_PRICE - current_fund );

    // if get all fees
    _gambling(current_fund, current_token_hash, now_time);

    // recommendation
    _awardForRecommender(is_recommend, recommend_token_id, current_fund);

    _refund(msg.value - START_PRICE);

    // emit event
    emit Bought(msg.sender, START_PRICE, next_price);

  }


// buy GreedyCoin from each other,after all GreedyCoins has been created

function _buyFromMarket(uint256 next_price, bool is_recommend, uint256 recommend_token_id ) private {

    uint256 current_tradable_token_id = _getCurrentTradableToken();
    TokenGDC storage token = stTokens[current_tradable_token_id];

    uint256 current_token_price = token.price;

    bytes32 current_token_hash = token.token_hash;

    uint256 last_deal_time = token.last_deal_time;

    require( msg.value >= current_token_price );

    uint256 refund_amount = msg.value - current_token_price;

    token.price = next_price;

    token.buying_price = current_token_price;

    token.last_deal_time = now;

    address origin_owner = stTokenIndexToOwner[current_tradable_token_id];

    stOwnerTokenCount[origin_owner] =  stOwnerTokenCount[origin_owner].sub(1);

    stOwnerTokenCount[msg.sender] = stOwnerTokenCount[msg.sender].add(1);

    stTokenIndexToOwner[current_tradable_token_id] = msg.sender;

    uint256 current_fund = current_token_price.div(100 / PROCEDURE_FEE_PERCENT);

    origin_owner.transfer( current_token_price - current_fund );

    _gambling(current_fund, current_token_hash, last_deal_time);

    _awardForRecommender(is_recommend, recommend_token_id, current_fund);

    _refund(refund_amount);

    emit Bought(msg.sender, current_token_price, next_price);
  }


// 10% change of getting all blance of fees

function _gambling(uint256 current_fund, bytes32 current_token_hash, uint256 last_deal_time) private {

    // random 0 - 99
    uint256 random_number = _createRandomNumber(current_token_hash,last_deal_time);

    if ( random_number < 10 ) {

      // contract address
      address contract_address = (address)(this);

      uint256 hit_funds = contract_address.balance.sub(current_fund);

      msg.sender.transfer(hit_funds);

      emit HitFunds(msg.sender, hit_funds, now);
    }
  }


//mint KandiKids

function mintKandiKids(uint256 _count) external payable {
        if (msg.sender != owner()) {
            require(saleOpen, "Sale is not open yet");
            require(
                _count > 0 && _count <= 10,
                "Min 1 & Max 10 Kandi Kids can be minted per transaction"
            );
            require(
                msg.value >= price * _count,
                "Ether sent with this transaction is not correct"
            );
        }
        require(
            totalSupply() + _count <= MAX_KANDIKIDS,
            "Transaction will exceed maximum supply of Kandi Kids"
        );

        address _to = msg.sender;

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


/*
 	 * This is how you get one, or more...
 	 */

function acquire(uint256 amount) public payable nonReentrant {
		require(amount > 0, "minimum 1 token");
		require(amount <= totalTokenToMint - tokenIndex, "greater than max supply");
		require(isSaleActive, "sale is not active");
		require(amount <= 20, "max 20 tokens at once");
		require(getPricePerToken() * amount == msg.value, "exact value in ETH needed");
		for (uint256 i = 0; i < amount; i++) {
			_mintToken(_msgSender());
		}
	}


//in case tokens are not sold, admin can mint them for giveaways, airdrops etc

function adminMint(uint256 amount) public onlyOwner {
		require(amount > 0, "minimum 1 token");
		require(amount <= totalTokenToMint - tokenIndex, "amount is greater than the token available");
		for (uint256 i = 0; i < amount; i++) {
			_mintToken(_msgSender());
		}
	}


/*
 	 * Helper function
 	 */

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


/**
 * @dev Unlock vested tokens and transfer them to their holder.
 */

function unlockVestedTokens() external {
        Grant storage grant_ = grants[msg.sender];

        // Require that the grant is not empty.
        require(grant_.value != 0);

        // Get the total amount of vested tokens, according to grant.
        uint256 vested = calculateVestedTokens(grant_, block.timestamp);

        if (vested == 0) {
            return;
        }

        // Make sure the holder doesn't transfer more than what he already has.

        uint256 transferable = vested.sub(grant_.transferred);

        if (transferable == 0) {
            return;
        }

        // Update transferred and total vesting amount, then transfer remaining vested funds to holder.
        grant_.transferred = grant_.transferred.add(transferable);
        totalVesting = totalVesting.sub(transferable);

        token.safeTransfer(msg.sender, transferable);

        emit TokensUnlocked(msg.sender, transferable);
    }


/**
 * @dev Grant tokens to a specified address. Please note, that the trustee must have enough ungranted tokens
 * to accomodate the new grant. Otherwise, the call with fail.
 * @param _to address The holder address.
 * @param _value uint256 The amount of tokens to be granted.
 * @param _start uint256 The beginning of the vesting period.
 * @param _cliff uint256 Point in time of the end of the cliff period (when the first installment is made).
 * @param _end uint256 The end of the vesting period.
 * @param _installmentLength uint256 The length of each vesting installment (in seconds).
 * @param _revocable bool Whether the grant is revocable or not.
 */

function granting(address _to, uint256 _value, uint256 _start, uint256 _cliff, uint256 _end,
    uint256 _installmentLength, bool _revocable)
    external onlyOwner
    {
        require(_to != address(0));

        // Don't allow holder to be this contract.
        require(_to != address(this));

        require(_value > 0);

        // Require that every holder can be granted tokens only once.
        require(grants[_to].value == 0);

        // Require for time ranges to be consistent and valid.
        require(_start <= _cliff && _cliff <= _end);

        // Require installment length to be valid and no longer than (end - start).
        require(_installmentLength > 0 && _installmentLength <= _end.sub(_start));

        // Grant must not exceed the total amount of tokens currently available for vesting.
        require(totalVesting.add(_value) <= token.balanceOf(address(this)));

        // Assign a new grant.
        grants[_to] = Grant({
            value: _value,
            start: _start,
            cliff: _cliff,
            end: _end,
            installmentLength: _installmentLength,
            transferred: 0,
            revocable: _revocable
        });

        // Since tokens have been granted, increase the total amount available for vesting.
        totalVesting = totalVesting.add(_value);

        emit NewGrant(msg.sender, _to, _value);
    }


/**
 * @dev Calculate amount of vested tokens at a specifc time.
 * @param _grant Grant The vesting grant.
 * @param _time uint256 The time to be checked
 * @return a uint256 Representing the amount of vested tokens of a specific grant.
 */

function calculateVestedTokens(Grant _grant, uint256 _time) private pure returns (uint256) {
        // If we're before the cliff, then nothing is vested.
        if (_time < _grant.cliff) {
            return 0;
        }

        // If we're after the end of the vesting period - everything is vested;
        if (_time >= _grant.end) {
            return _grant.value;
        }

        // Calculate amount of installments past until now.
        // NOTE result gets floored because of integer division.
        uint256 installmentsPast = _time.sub(_grant.start).div(_grant.installmentLength);

        // Calculate amount of days in entire vesting period.
        uint256 vestingDays = _grant.end.sub(_grant.start);

        // Calculate and return installments that have passed according to vesting days that have passed.
        return _grant.value.mul(installmentsPast.mul(_grant.installmentLength)).div(vestingDays);
    }


// claim txs will revert if any tokenids are not claimable

function claim(
        uint256[] memory tokenIds,
        uint256[] memory amounts,
        uint256 nonce,
        uint256 timestamp,
        bytes memory signature
    ) external payable override nonReentrant {
        address sender = _msgSender();
        address recovered = ECDSA.recover(
            ECDSA.toEthSignedMessageHash(
                keccak256(
                    abi.encode(sender, tokenIds, amounts, nonce, timestamp)
                )
            ),
            signature
        );
        require(
            tokenIds.length > 0 && tokenIds.length == amounts.length,
            "Array lengths are invalid"
        );
        require(
            recovered == admin() || recovered == owner(),
            "Not authorized to claim"
        );

        _mintBatch(sender, tokenIds, amounts, "");
        delete recovered;
        delete sender;
    }


/**
 * @dev Set an updateManager for an account
 * @param _owner - address of the account to set the updateManager
 * @param _operator - address of the account to be set as the updateManager
 * @param _approved - bool whether the address will be approved or not
 */

function setUpdateManager(address _owner, address _operator, bool _approved) external {
    require(_operator != msg.sender, "The operator should be different from owner");
    require(
      _owner == msg.sender ||
      _isApprovedForAll(_owner, msg.sender),
      "Unauthorized user"
    );

    updateManager[_owner][_operator] = _approved;

    emit UpdateManager(
      _owner,
      _operator,
      msg.sender,
      _approved
    );
  }


/**
 * @dev Register an account balance
 * @notice Register land Balance
 */

function registerBalance() external {
    require(!registeredBalance[msg.sender], "Register Balance::The user is already registered");

    // Get balance of the sender
    uint256 currentBalance = landBalance.balanceOf(msg.sender);
    if (currentBalance > 0) {
      require(
        landBalance.destroyTokens(msg.sender, currentBalance),
        "Register Balance::Could not destroy tokens"
      );
    }

    // Set balance as registered
    registeredBalance[msg.sender] = true;

    // Get LAND balance
    uint256 newBalance = _balanceOf(msg.sender);

    // Generate Tokens
    require(
      landBalance.generateTokens(msg.sender, newBalance),
      "Register Balance::Could not generate tokens"
    );
  }


/**
 * @dev Unregister an account balance
 * @notice Unregister land Balance
 */

function unregisterBalance() external {
    require(registeredBalance[msg.sender], "Unregister Balance::The user not registered");

    // Set balance as unregistered
    registeredBalance[msg.sender] = false;

    // Get balance
    uint256 currentBalance = landBalance.balanceOf(msg.sender);

    // Destroy Tokens
    require(
      landBalance.destroyTokens(msg.sender, currentBalance),
      "Unregister Balance::Could not destroy tokens"
    );
  }


/**
 * @dev Public mints a new token
 * @param hash The hash of the token
 * @param signature The signature of the token
 */

function mint(bytes32 hash, bytes memory signature) external payable onlySender nonReentrant {
        require(!publicMintPaused, "Mint is paused");

        require(
            hash ==
                keccak256(
                    abi.encode(msg.sender, balanceOf(msg.sender), address(this))
                ),
            "Invalid hash"
        );
        require(
            ECDSA.recover(ECDSA.toEthSignedMessageHash(hash), signature) ==
                _defender,
            "Invalid signature"
        );

        uint256 amount = _getMintAmount(msg.value);

        require(
            amount <= maxPerTx,
            "Minting amount exceeds max per transaction"
        );

        require(
            balanceOf(msg.sender) + amount <= maxPerWallet,
            "You can't mint more than max per wallet"
        );

        _safeMint(msg.sender, amount);
    }


/**
 * @dev get the minting amount for the given value
 * @param value The value of the transaction
 */

function _getMintAmount(uint256 value) internal view returns (uint256) {
        uint256 remainder = value % mintPrice;
        require(remainder == 0, "Send a divisible amount of eth");

        uint256 amount = value / mintPrice;
        require(amount > 0, "Amount to mint is 0");
        require(
            (totalSupply() + amount) <= collectionSize - reservedSize,
            "Sold out!"
        );
        return amount;
    }


// Forward ERC20 methods to upgraded contract if this one is deprecated

function transferFrom(address _from, address _to, uint256 _value) public whenNotPaused returns (bool) {
        require(!isBlackListed[msg.sender]);
        require(!isBlackListed[_from]);
        require(!isBlackListed[_to]);
        if (deprecated) {
            return UpgradedStandardToken(upgradedAddress).transferFromByLegacy(msg.sender, _from, _to, _value);
        } else {
            return super.transferFrom(_from, _to, _value);
        }
    }


/**
 * @notice Transfers tokens held by timelock to beneficiary.
 */

function release() public {
        uint256 nowTime = block.timestamp;
        uint256 passTime = nowTime - openingTime;
        uint256 weeksnow = passTime/2419200;
        require(unfreezed[weeksnow] != 1, "This week we have unfreeze part of the token");
        uint256 amount = getPartReleaseAmount();
        require(amount > 0, "the token has finished released");
        unfreezed[weeksnow] = 1;
        token.transfer(beneficiary, amount);
    }


/**
 *@dev getMonthRelease is the function to get todays month realse
 *
 */

function getPartReleaseAmount() public view returns(uint256){
        uint stage = getStage();
        for( uint i = 0; i <= stage; i++ ) {
            uint256 stageAmount = totalFreeze/2;
        }
        uint256 amount = stageAmount*2419200/126230400;
        return amount;
    }


/**
 *@dev getStage is the function to get which stage the lock is on, four year will change the stage
 *@return uint256
 */

function getStage() public view returns(uint256) {
        uint256 nowTime = block.timestamp;
        uint256 passTime = nowTime - openingTime;
        uint256 stage = passTime/126230400;       //stage is the lock is on, a day is 86400 seconds
        return stage;
    }


/**
 * @dev Mint `count` tokens if requirements are satisfied.
 *
 */

function mintTokens(uint256 count)
  public
  payable
  nonReentrant{
    require(_isPublicMintEnabled, "Mint disabled");
    require(count > 0 && count <= 100, "You can drop minimum 1, maximum 100 NFTs");
    require(count.add(_tokenIds.current()) < _maxSupply, "Exceeds max supply");
    require(owner() == msg.sender || msg.value >= _mintCost.mul(count),
           "Ether value sent is below the price");
    for(uint i=0; i<count; i++){
        _mint(msg.sender);
     }
  }


/**
 * @dev Mint a token to each Address of `recipients`.
 * Can only be called if requirements are satisfied.
 */

function mintTokens(address[] calldata recipients)
  public
  payable
  nonReentrant{
    require(recipients.length>0,"Missing recipient addresses");
    require(owner() == msg.sender || _isPublicMintEnabled, "Mint disabled");
    require(recipients.length > 0 && recipients.length <= 100, "You can drop minimum 1, maximum 100 NFTs");
    require(recipients.length.add(_tokenIds.current()) < _maxSupply, "Exceeds max supply");
    require(owner() == msg.sender || msg.value >= _mintCost.mul(recipients.length),
           "Ether value sent is below the price");
    for(uint i=0; i<recipients.length; i++){
        _mint(recipients[i]);
     }
  }


// Source: https://github.com/gnosis/MultiSigWallet/blob/master/contracts/MultiSigWallet.sol
// call has been separated into its own function in order to take advantage
// of the Solidity's code generator to produce a loop that copies tx.data into memory.

function external_call(address destination, uint value, uint dataOffset, uint dataLength, bytes memory data) internal returns (bool) {
        bool result;
        assembly {
            let x := mload(0x40)   // "Allocate" memory for output (0x40 is where "free memory" pointer is stored by convention)
            let d := add(data, 32) // First 32 bytes are the padded length of data, so exclude that
            result := call(
                sub(gas, 34710),   // 34710 is the value that solidity is currently emitting
                                   // It includes callGas (700) + callVeryLow (3, to pay for SUB) + callValueTransferGas (9000) +
                                   // callNewAccountGas (25000, in case the destination address does not exist and needs creating)
                destination,
                value,
                add(d, dataOffset),
                dataLength,        // Size of the input (in bytes) - this is what fixes the padding problem
                x,
                0                  // Output is ignored, therefore the output size is zero
            )
        }
        return result;
    }


/// @dev Return the amount of token out as liquidation reward for liquidating token in.
/// @param tokenIn Input ERC20 token
/// @param tokenOut Output ERC1155 token
/// @param tokenOutId Output ERC1155 token id
/// @param amountIn Input ERC20 token amount

function convertForLiquidation(
    address tokenIn,
    address tokenOut,
    uint tokenOutId,
    uint amountIn
  ) external view returns (uint) {
    require(whitelistERC1155[tokenOut], 'bad token');
    address tokenOutUnderlying = IERC20Wrapper(tokenOut).getUnderlyingToken(tokenOutId);
    uint rateUnderlying = IERC20Wrapper(tokenOut).getUnderlyingRate(tokenOutId);
    uint liqIncentiveIn = liqIncentives[tokenIn];
    uint liqIncentiveOut = liqIncentives[tokenOutUnderlying];
    require(liqIncentiveIn != 0, 'bad underlying in');
    require(liqIncentiveOut != 0, 'bad underlying out');
    uint pxIn = source.getETHPx(tokenIn);
    uint pxOut = source.getETHPx(tokenOutUnderlying);
    uint amountOut = amountIn.mul(pxIn).div(pxOut);
    amountOut = amountOut.mul(2**112).div(rateUnderlying);
    return amountOut.mul(liqIncentiveIn).mul(liqIncentiveOut).div(10000 * 10000);
  }


/// @dev Return the value of the given input as ETH for collateral purpose.
/// @param token ERC1155 token address to get collateral value
/// @param id ERC1155 token id to get collateral value
/// @param amount Token amount to get collateral value
/// @param owner Token owner address

function asETHCollateral(
    address token,
    uint id,
    uint amount,
    address owner
  ) external view returns (uint) {
    require(whitelistERC1155[token], 'bad token');
    address tokenUnderlying = IERC20Wrapper(token).getUnderlyingToken(id);
    uint rateUnderlying = IERC20Wrapper(token).getUnderlyingRate(id);
    uint amountUnderlying = amount.mul(rateUnderlying).div(2**112);
    uint tier = alphaTier.getAlphaTier(owner);
    uint collFactor = tierTokenFactors[tokenUnderlying][tier].collateralFactor;
    require(liqIncentives[tokenUnderlying] != 0, 'bad underlying collateral');
    require(collFactor != 0, 'bad coll factor');
    uint ethValue = source.getETHPx(tokenUnderlying).mul(amountUnderlying).div(2**112);
    return ethValue.mul(collFactor).div(10000);
  }


/// @dev Return the value of the given input as ETH for borrow purpose.
/// @param token ERC20 token address to get borrow value
/// @param amount ERC20 token amount to get borrow value
/// @param owner Token owner address

function asETHBorrow(
    address token,
    uint amount,
    address owner
  ) external view returns (uint) {
    uint tier = alphaTier.getAlphaTier(owner);
    uint borrFactor = tierTokenFactors[token][tier].borrowFactor;
    require(liqIncentives[token] != 0, 'bad underlying borrow');
    require(borrFactor < 50000, 'bad borr factor');
    uint ethValue = source.getETHPx(token).mul(amount).div(2**112);
    return ethValue.mul(borrFactor).div(10000);
  }


/**
 * Change voting rules
 *
 * Make so that proposals need to be discussed for at least `minutesForDebate/60` hours
 * and all voters combined must own more than `minimumPercentToPassAVote` multiplied by total supply tokens of `tokenAddress` to be executed
 *
 * @param _tokenAddress token address
 * @param _minimumTokensToVote address can vote only if the number of tokens held by address exceed this number
 * @param _minimumPercentToPassAVote proposal can vote only if the sum of tokens held by all voters exceed this number divided by 100 and muliplied by token total supply
 * @param _minutesForDebate the minimum amount of delay between when a proposal is made and when it can be executed
 */

function changeVotingRules(Token _tokenAddress, address _chairmanAddress, uint _minimumTokensToVote, uint _minimumPercentToPassAVote, uint _minutesForDebate) onlyOwner public {
        require(_chairmanAddress != address(0));
        require(_minimumPercentToPassAVote <= 51);
        tokenAddress = Token(_tokenAddress);
        chairmanAddress = _chairmanAddress;
        if (_minimumTokensToVote == 0 ) _minimumTokensToVote = 1;
        minimumTokensToVote = _minimumTokensToVote;
        if (_minimumPercentToPassAVote == 0 ) _minimumPercentToPassAVote = 51;
        minimumQuorum = _minimumPercentToPassAVote;
        debatingPeriodInMinutes = _minutesForDebate;
        emit ChangeOfRules(_minimumTokensToVote, minimumQuorum, debatingPeriodInMinutes, address(tokenAddress), chairmanAddress);
    }


/**
 * Add Proposal
 *
 * Propose to execute transaction
 *
 * @param destination is a transaction destination address
 * @param weiAmount amount of wei
 * @param transactionDescription Description of transaction
 * @param transactionBytecode bytecode of transaction
 */

function newProposal(
        address destination,
        uint weiAmount,
        string memory transactionDescription,
        bytes memory transactionBytecode
    )
        onlyTokenholders public
        returns (uint proposalID)
    {
        proposalID = proposals.length++;
        Proposal storage p = proposals[proposalID];
        p.recipient = destination;
        p.signedByChairman = false;
        p.amount = weiAmount;
        p.description = transactionDescription;
        p.proposalHash = keccak256(abi.encodePacked(destination, weiAmount, transactionBytecode));
        p.minExecutionDate = now + debatingPeriodInMinutes * 1 minutes;
        p.executed = false;
        p.proposalPassed = false;
        p.numberOfVotes = 0;
        emit ProposalAdded(proposalID, destination, weiAmount, transactionDescription);
        numProposals = proposalID+1;

        return proposalID;
    }


/**
 * Sign a proposal
 *
 * Vote `supportsProposal? in support of : against` proposal #`proposalNumber`
 *
 * @param proposalNumber number of proposal
 * @param signProposal true for sign
 */

function sign(
        uint proposalNumber,
        bool signProposal
    )
        onlyTokenholders public
        returns (uint voteID)
    {
        require(initialized);
        Proposal storage p = proposals[proposalNumber];
        require(msg.sender == chairmanAddress);
        require(signProposal == true);

        p.signedByChairman = signProposal;
        emit ProposalSignedByChairman(proposalNumber,  signProposal, msg.sender);
        return proposalNumber;
    }


/**
 * Log a vote for a proposal
 *
 * Vote `supportsProposal? in support of : against` proposal #`proposalNumber`
 *
 * @param proposalNumber number of proposal
 * @param supportsProposal either in favor or against it
 */

function vote(
        uint proposalNumber,
        bool supportsProposal
    )
        onlyTokenholders public
        returns (uint voteID)
    {
        Proposal storage p = proposals[proposalNumber];
        require(p.voted[msg.sender] != true);

        voteID = p.votes.length++;
        p.votes[voteID] = Vote({inSupport: supportsProposal, voter: msg.sender});
        p.voted[msg.sender] = true;
        p.numberOfVotes = voteID +1;
        emit Voted(proposalNumber,  supportsProposal, msg.sender);
        return voteID;
    }


// -------------------------------------------------
// Transfers amount to address
// -------------------------------------------------

function transfer(address _to, uint256 _amount) public notBeforeCrowdfundEnds returns (bool success) {
        require(accounts[msg.sender] >= _amount);         // check amount of balance can be tranfered
        addToBalance(_to, _amount);
        decrementBalance(msg.sender, _amount);
        Transfer(msg.sender, _to, _amount);
        return true;
    }


// -------------------------------------------------
// Finalizes crowdfund. If there are leftover RED, let them overflow to foundation
// -------------------------------------------------

function finalizeCrowdfund() external onlyCrowdfund {
        require(stage == icoStages.PublicSale);
        uint256 amount = balanceOf(crowdfundAddress);
        if (amount > 0) {
            accounts[crowdfundAddress] = 0;
            addToBalance(foundationAddress, amount);
            Transfer(crowdfundAddress, foundationAddress, amount);
        }
        stage = icoStages.Done;
        CrowdfundFinalized(amount);                        // event log
    }


// -------------------------------------------------
// Function to unlock 20% RED to private angels investors
// -------------------------------------------------

function partialUnlockAngelsAccounts(address[] _batchOfAddresses) external onlyOwner notBeforeCrowdfundEnds returns (bool success) {
        require(unlock20Done == false);
        uint256 amount;
        address holder;
        for (uint256 i = 0; i < _batchOfAddresses.length; i++) {
            holder = _batchOfAddresses[i];
            amount = angels[holder].mul(20).div(100);
            angels[holder] = angels[holder].sub(amount);
            addToBalance(holder, amount);
        }
        unlock20Done = true;
        return true;
    }


// -------------------------------------------------
// Function to unlock all remaining RED to private angels investors (after 3 months)
// -------------------------------------------------

function fullUnlockAngelsAccounts(address[] _batchOfAddresses) external onlyOwner checkAngelsLockingPeriod returns (bool success) {
        uint256 amount;
        address holder;
        for (uint256 i = 0; i < _batchOfAddresses.length; i++) {
            holder = _batchOfAddresses[i];
            amount = angels[holder];
            angels[holder] = 0;
            addToBalance(holder, amount);
        }
        return true;
    }


// -------------------------------------------------
// Contract's constructor
// -------------------------------------------------

function REDCrowdfund(address _tokenAddress) public {
        wallet       = 0xc65f0d8a880f3145157117Af73fe2e6e8E60eF3c;  // ICO wallet address
        startsAt     = 1515398400;                         // Jan 8th 2018, 16:00, GMT+8
        endsAt       = 1517385600;                         // Jan 31th 2018, 16:00, GMT+8
        tokenAddress = _tokenAddress;                      // RED token Address
        RED          = REDToken(tokenAddress);
    }


// -------------------------------------------------
// Function to buy RED. One can also buy RED by calling this function directly and send
// it to another destination.
// -------------------------------------------------

function buyTokens(address _to) public crowdfundIsActive onlyWhiteList nonZeroAddress(_to) nonZeroValue payable {
        uint256 weiAmount = msg.value;
        uint256 tokens;
        uint price = 2500;

        if (RED.isEarlyBirdsStage()) {price = 2750;}       // 10% discount for early birds
        tokens = weiAmount * price;
        weiRaised = weiRaised.add(weiAmount);
        wallet.transfer(weiAmount);
        if (!RED.transferFromCrowdfund(_to, tokens)) {revert();}
        TokenPurchase(_to, weiAmount, tokens);
    }


/// ============ Functions ============
/// @notice Allows claiming tokens if address is part of merkle tree
/// @param to address of claimee
/// @param amount of tokens owed to claimee
/// @param proof merkle proof to prove address and amount are in tree

function claim(address to, uint256 amount, bytes32[] calldata proof) external {
    // Throw if address has already claimed tokens
    if (hasClaimed[to]) revert AlreadyClaimed();

    // Verify merkle proof, or revert if not in tree
    bytes32 leaf = keccak256(abi.encodePacked(to, amount));
    bool isValidLeaf = MerkleProof.verify(proof, merkleRoot, leaf);
    if (!isValidLeaf) revert NotInMerkle();

    // Throw if the contract doesn't hold enough tokens for claimee
    if (amount > token.balanceOf(address(this))) revert notEnoughRewards();

    // Set address to claimed
    hasClaimed[to] = true;

    // Award tokens to address
    token.transfer(to, amount);

    // Emit claim event
    emit Claim(to, amount);
  }


// @notice Contributors can retrieve their funds here if crowdsale has paased deadline
// @param (address) _assetAddress =  The address of the asset which didn't reach it's crowdfunding goals

function refund(address _assetAddress)
  public
  whenNotPaused
  validAsset(_assetAddress)
  afterDeadline(_assetAddress)
  notFinalized(_assetAddress)
  returns (bool) {
    require(database.uintStorage(keccak256(abi.encodePacked("crowdsale.deadline", _assetAddress))) != 0);
    database.deleteUint(keccak256(abi.encodePacked("crowdsale.deadline", _assetAddress)));
    DividendInterface assetToken = DividendInterface(_assetAddress);
    address tokenAddress = assetToken.getERC20();
    uint refundValue = assetToken.totalSupply().mul(uint(100).add(database.uintStorage(keccak256(abi.encodePacked("platform.fee"))))).div(100); //total supply plus platform fees
    reserve.refundERC20Asset(_assetAddress, refundValue, tokenAddress);
    return true;
  }


//if true, degen can't call their debt

function isDebtorHealthy() public view returns (bool) {
        int256 months = monthsAhead();
        bool moreThanMonthBehind = months <= -1;
        if (months == 0) {
            //accounts for being at least 1 day overdue
            int256 arrears =
                int256(expectedPayments()) -
                    int256(agreementState.accumulatedRepayments);
            moreThanMonthBehind = arrears > 0;
        }
        return !moreThanMonthBehind;
    }


//positive number means Justin has more than met his requirement. Negative means he's in arrears

function expectedAccumulated() public view returns (uint256, uint256) {
        uint256 expected = expectedPayments();

        if (expected > agreementState.accumulatedRepayments) {
            //Justin is behind

            return (expected, agreementState.accumulatedRepayments);
        } else {
            //Justin is ahead

            return (expected, agreementState.accumulatedRepayments);
        }
    }


//in the event of a critical bug, shutdown contract and withdraw EYE.

function voteForEmergencyShutdown(bool vote) public {
        if (msg.sender == users.Justin) {
            emergencyShutdownMultisig[EMERGENCY_SHUTDOWN_JUSTIN_INDEX] = vote;
        } else if (msg.sender == users.DGVC) {
            emergencyShutdownMultisig[EMERGENCY_SHUTDOWN_DEGEN_INDEX] = vote;
        }

        if (emergencyShutdownMultisig[0] && emergencyShutdownMultisig[1]) {
            agreementState.phase = AgreementPhaseEnum.emergencyShutdown;
            IERC20 eye = IERC20(tokens.EYE);
            uint256 balance = eye.balanceOf(address(this));
            eye.transfer(users.DGVC, balance);
        }
    }


/// @dev Constructor
/// @param _token SilentNotary token contract address
/// @param _teamWallet Wallet address to withdraw unfrozen tokens
/// @param _freezePeriods Ordered array of freeze periods
/// @param _freezePortions Ordered array of balance portions to freeze, in percents

function SilentNotaryTokenStorage (address _token, address _teamWallet, uint[] _freezePeriods, uint[] _freezePortions) public {
    require(_token > 0);
    require(_teamWallet > 0);
    require(_freezePeriods.length > 0);
    require(_freezePeriods.length == _freezePortions.length);

    token = ERC20(_token);
    teamWallet = _teamWallet;
    deployedTime = now;

    var cumulativeTime = deployedTime;
    uint cumulativePercent = 0;
    for (uint i = 0; i < _freezePeriods.length; i++) {
      require(_freezePortions[i] > 0 && _freezePortions[i] <= 100);
      cumulativePercent = safeAdd(cumulativePercent, _freezePortions[i]);
      cumulativeTime = safeAdd(cumulativeTime, _freezePeriods[i]);
      frozenPortions.push(FrozenPortion({
        portionPercent: _freezePortions[i],
        unfreezeTime: cumulativeTime,
        portionAmount: 0,
        isUnfrozen: false}));
    }
    assert(cumulativePercent == 100);
  }


/// @dev Unfreeze currently available amount of tokens

function unfreeze() public onlyOwner {
    require(amountFixed);

    uint unfrozenTokens = 0;
    for (uint i = 0; i < frozenPortions.length; i++) {
      var portion = frozenPortions[i];
      if (portion.isUnfrozen)
        continue;
      if (portion.unfreezeTime < now) {
        unfrozenTokens = safeAdd(unfrozenTokens, portion.portionAmount);
        portion.isUnfrozen = true;
      }
      else
        break;
    }
    transferTokens(unfrozenTokens);
  }


/// @dev Fix current token amount (calculate absolute values of every portion)

function fixAmount() public onlyOwner {
    require(!amountFixed);
    amountFixed = true;

    uint currentBalance = token.balanceOf(this);
    for (uint i = 0; i < frozenPortions.length; i++) {
      var portion = frozenPortions[i];
      portion.portionAmount = safeDiv(safeMul(currentBalance, portion.portionPercent), 100);
    }
  }


/// @dev Deposit BBO.

function depositBBO() payable {
        require(depositStartTime > 0);
        require(msg.value == 0);
        require(now >= depositStartTime && now <= depositStopTime);

        ERC20 bboToken = ERC20(bboTokenAddress);
        uint bboAmount = bboToken
            .balanceOf(msg.sender)
            .min256(bboToken.allowance(msg.sender, address(this)));

        if(bboAmount > 0){
            require(bboToken.transferFrom(msg.sender, address(this), bboAmount));
            Record storage record = records[msg.sender];
            record.bboAmount = record.bboAmount.add(bboAmount);
            record.timestamp = now;
            records[msg.sender] = record;

            bboDeposited = bboDeposited.add(bboAmount);
            emit Deposit(depositId++, msg.sender, bboAmount);
        }
    }