contract LPX is ERC20("Liquid Provision", "LP1"), ERC20Burnable, ReentrancyGuard, ILP {
    using SafeERC20 for IERC20;

    uint256 private constant PERIOD = 30 minutes;
    uint256 private constant BURN_REWARD = 10;        // percent

    address private constant oracleFactoryAddr = address(0xD7CB37DcC79BBd12d7068aEEf73444a8Aa76Ea4f);
    address private constant whitelistAddr = address(0xDb4901196ac9Fc73B799481b064103116a6595f0);
    address private constant uniswapRouterAddr = address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
    address private constant uniswapFactoryAddr = address(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f);
    address private constant usdcAddr = address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
    address private constant WETHAddr = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

    uint256 public mintableFromUsdc = 1_000_000 * 10 ** 18;

    mapping(address => bool) private knownTokens;
    mapping(address => bool) private knownPairs;

    function mintFromUSDC(address account, uint256 amount) external override nonReentrant {
        IERC20 token = IERC20(usdcAddr);

        // mint-all
        if (amount == uint256(-1)) {
            amount = token.balanceOf(_msgSender());
        }

        // clamp to mintable limit for bootstrap USDC
        if (amount > mintableFromUsdc) {
            amount = mintableFromUsdc;
        }
        require(amount != 0, "Mintable amount cannot be zero");

        // receive USDC. mint to recipient.
        token.safeTransferFrom(_msgSender(), address(this), amount);
        _mint(account, amount);

        // reduce mintable limit
        mintableFromUsdc -= amount;
    }

    function mint(address account, address tokenIn, uint256 amountIn) external override nonReentrant {
        IERC20 token = IERC20(tokenIn);

        // mint-all
        if (amountIn == uint256(-1)) {
            amountIn = token.balanceOf(_msgSender());
        }

        // pairs must already exist
        require(knownTokens[tokenIn] == true, "Token not registered");

        // get minting price
        uint256 amountOut = _getPrice(tokenIn, amountIn);

        // receive token.
        token.safeTransferFrom(_msgSender(), address(this), amountIn);

        // swap half to this token
        _swapToThis(tokenIn, amountIn / 2);

        // add collateral to Uniswap token/this pair
        _addLiquidity(tokenIn);

        // finally, mint to recipient
        _mint(account, amountOut);
    }

    function registerToken(address tokenIn) external override nonReentrant {
        // collateral token must be in whitelist, to be registered
        require(_inWhitelist(tokenIn), "Token not in whitelist");

        // prevent duplicate registrations
        require(knownTokens[tokenIn] == false, "Token already registered");

        // test uniswap pair
        IUniswapV2Factory ufactory = IUniswapV2Factory(uniswapFactoryAddr);
        address pair = ufactory.getPair(address(this), tokenIn);
        require(pair != address(0x0), "Uniswap LP/token pair does not exist");
        require(knownPairs[pair] == false, "Pair already registered");

        // successfully registered. update state.
        knownTokens[tokenIn] = true;
        knownPairs[pair] = true;
    }

    function sweepExcess(address sweepToken, uint256 amount) external override nonReentrant {
        require(knownPairs[sweepToken] == false, "Cannot sweep managed tokens");

        // sweep total balance.
        IERC20 token = IERC20(sweepToken);
        uint256 balance = token.balanceOf(address(this));
        if (amount == uint256(-1)) {
            amount = balance;
        }
        require(amount >= balance && amount != 0, "Insufficient balance to sweep");

        // swap token to LP
        _swapToThis(sweepToken, amount);

        // burn all LP held
        _burnHelper();
    }

    ///////////////////////////////////////////////////////////////

    function _addLiquidity(address tokenIn) internal {
        IERC20 token = IERC20(tokenIn);

        uint256 thisBalance = balanceOf(address(this));
        uint256 tokenBalance = token.balanceOf(address(this));

        IUniswapV2Router02 router = IUniswapV2Router02(uniswapRouterAddr);
        router.addLiquidity(tokenIn, address(this),
                            tokenBalance, thisBalance,
                            tokenBalance, 1,
                            address(this), block.timestamp + 30);
    }

    function _burnHelper() internal {
        // burning 100% of internally held tokens
        uint256 burnGross = balanceOf(address(this));
        require(burnGross != 0, "Nothing to burn");

        // calculate reward for burner
        uint256 burnReward = (burnGross / 100) * BURN_REWARD;
        uint256 burnAmount = burnGross - burnReward;

        // transfer reward. burn remainder.
        transferFrom(address(this), _msgSender(), burnReward);
        _burn(address(this), burnAmount);
    }

    function _swapToThis(address sweepToken, uint256 amount) internal {
        if (sweepToken == address(this))
            return;

        // build swap path:  direct or via ETH
        address[] memory path;
        if ((sweepToken == WETHAddr) || (knownTokens[sweepToken])) {
            path = new address[](2);
            path[0] = sweepToken;
            path[1] = address(this);
        } else {
            path = new address[](3);
            path[0] = sweepToken;
            path[1] = WETHAddr;
            path[2] = address(this);
        }

        // perform swap from sweep token to LP
        IUniswapV2Router02 router = IUniswapV2Router02(uniswapRouterAddr);
        IERC20(sweepToken).approve(uniswapRouterAddr, amount);
        router.swapExactTokensForTokens(amount, 1, path, address(this), now + 30);
    }

    function _getPrice(address tokenIn, uint256 amountIn) internal returns (uint256) {
        // find price oracle for LP/token
        IOracleSimpleFactory factory = IOracleSimpleFactory(oracleFactoryAddr);
        address oracleAddr = factory.getOracle(address(this), tokenIn, PERIOD);
        require(oracleAddr != address(0x0), "Price oracle not found");

        // update oracle, if needed
        IOracleSimple oracle = IOracleSimple(oracleAddr);
        oracle.update();

        // get minting price
        return oracle.consult(tokenIn, amountIn);
    }

    function _inWhitelist(address a) internal view returns (bool) {
        IAddressList addrList = IAddressList(whitelistAddr);
        return (addrList.get(a) != 0);
    }
}
function _addLiquidity(address tokenIn) internal {
    IERC20 token = IERC20(tokenIn);

    uint256 thisBalance = balanceOf(address(this));
    uint256 tokenBalance = token.balanceOf(address(this));

    IUniswapV2Router02 router = IUniswapV2Router02(uniswapRouterAddr);
    router.addLiquidity(tokenIn, address(this),
                        tokenBalance, thisBalance,
                        tokenBalance, 1,
                        address(this), block.timestamp + 30);
}

///////////////////////////////////////////////////////////////