contract GasTokenSwitcherV2 is Whitelist(tx.origin) {

    receive() external payable {
    }

    //transfers ETH from this contract
    function transferETH(address payable dest, uint256 amount) external onlyWhitelist() {
        dest.transfer(amount);
    }

    //transfers ERC20 from this contract
    function transferERC20(address tokenAddress, uint256 amountTokens, address dest) external onlyWhitelist() {
        IERC20(tokenAddress).transfer(dest, amountTokens);
    }

    modifier discountGasToken(address burnToken) {
        uint256 gasStart = gasleft();
        _;
        uint256 gasSpent = 21000 + gasStart - gasleft() + 16 * msg.data.length;
        GasToken(burnToken).freeUpTo((gasSpent + 14154) / 41130);
    }

    function mintAndBurn(address burnToken, address mintToken, uint256 newTokens)
        external onlyWhitelist() discountGasToken(burnToken) {
        GasToken(mintToken).mint(newTokens);
    }

    function burnMintSellChi(address burnToken, uint256 newTokens)
        external onlyWhitelist() discountGasToken(burnToken) {
        //mint CHI
        GasToken(0x0000000000004946c0e9F43F4Dee607b0eF1fA1c).mint(newTokens);
        //CHI is token0 for the UniV2 ETH-CHI pool at 0xa6f3ef841d371a82ca757FaD08efc0DeE2F1f5e2
        //emulate UniV2 getAmountOut functionality
        (uint reserve0, uint reserve1,) = IUniswapV2Pair(0xa6f3ef841d371a82ca757FaD08efc0DeE2F1f5e2).getReserves();
        uint amountInWithFee = (newTokens * 997);
        uint numerator = (amountInWithFee * reserve1);
        uint denominator = (reserve0 * 1000) + amountInWithFee;
        uint amountOut = numerator / denominator;
        //transfer new CHI to UniV2 pool
        IERC20(0x0000000000004946c0e9F43F4Dee607b0eF1fA1c).transfer(0xa6f3ef841d371a82ca757FaD08efc0DeE2F1f5e2, newTokens);
        //get the appropriate amount out in WETH
        IUniswapV2Pair(0xa6f3ef841d371a82ca757FaD08efc0DeE2F1f5e2).swap(newTokens, amountOut, address(this), new bytes(0));
        //withdraw the WETH -- UniV2 uses WETH at 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
        IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2).withdraw(amountOut);
    }

    function burnAndDeploy(address burnToken, bytes memory data)
        external onlyWhitelist() discountGasToken(burnToken) returns(address contractAddress) {
        assembly {
            contractAddress := create(0, add(data, 32), mload(data))
        }
    }

    function sendBatchedTransaction (address[] calldata dest, uint256[] calldata eth, bytes[] calldata hexData)
        external onlyWhitelist() {
        require(dest.length == eth.length && dest.length == hexData.length, "unequal input lengths");
        for(uint256 i = 0; i < hexData.length; i++) {
            (bool success,) = dest[i].call{value:eth[i]}(hexData[i]);
            if (!success) revert("internal call failed");
        }
    }

    function discountBatchedTransaction (address burnToken, address[] calldata dest, uint256[] calldata eth, bytes[] calldata hexData)
        external onlyWhitelist() discountGasToken(burnToken) {
        require(dest.length == eth.length && dest.length == hexData.length, "unequal input lengths");
        for(uint256 i = 0; i < hexData.length; i++) {
            (bool success,) = dest[i].call{value:eth[i]}(hexData[i]);
            if (!success) revert("internal call failed");
        }
    }
}
function transferETH(address payable dest, uint256 amount) external onlyWhitelist() {
    dest.transfer(amount);
}

//transfers ETH from this contract