var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { ethers } from "ethers";
import { Token, TokenAmount, Percent, Route, TradeType, Trade, JSBI, CurrencyAmount } from "../../../libs/uniswap";
import { getAddressByName } from "../../../utils/ethers/addressHelper";
import { Fetcher } from "../../../utils/uniswap/uniswapFetcher";
import { calculateSlippageAmount } from "../../../utils/uniswap/uniswap";
import { triggerContractByContractName } from "../../../utils/ethers/contractHelper";
import BigNumber from "bignumber.js";
import { erc20TotalSupply, erc20BalanceOf } from "../erc20";
export const uniswapGetWETH = (landId, chainId, symbol = "WETH", name = "Wrapped ether") => {
    const WETHAddress = getAddressByName(landId, "TOKEN_WETH");
    return new Token(chainId, WETHAddress, 18, symbol, name);
};
/**
 * Swap Ether to Ring token - Powered by uniswap.
 * @param {string} value - amount for Ring， unit of measurement(wei)
 * @returns {Promise<PromiEvent<any>>}
 */
export const uniswapBuyRING = (landId, signer, value, callback) => __awaiter(void 0, void 0, void 0, function* () {
    var _a;
    const network = yield ((_a = signer.provider) === null || _a === void 0 ? void 0 : _a.getNetwork());
    if (!network) {
        throw Error("no network");
    }
    const RINGAddress = getAddressByName(landId, "TOKEN_RING");
    const RING = new Token(network === null || network === void 0 ? void 0 : network.chainId, RINGAddress, 18, "RING", "Darwinia Network Native Token");
    const WETH = uniswapGetWETH(landId, network === null || network === void 0 ? void 0 : network.chainId);
    const pair = yield Fetcher.fetchPairData(WETH, RING, signer.provider);
    // @ts-ignore
    const route = new Route([pair], WETH);
    const amountIn = value;
    const trade = new Trade(route, new TokenAmount(RING, amountIn), TradeType.EXACT_OUTPUT);
    const slippageTolerance = new Percent("30", "10000"); // 30 bips, or 0.30%
    const amountInMax = trade.maximumAmountIn(slippageTolerance).raw; // needs to be converted to e.g. hex
    const path = [WETH.address, RING.address];
    const to = yield signer.getAddress(); // should be a checksummed recipient address
    const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // 20 minutes from the current Unix time
    const outputAmount = trade.outputAmount.raw; // // needs to be converted to e.g. hex
    return triggerContractByContractName(landId, signer, "uniswapExchange", "swapETHForExactTokens", [outputAmount.toString(10), path, to, deadline], callback, {
        value: ethers.BigNumber.from(amountInMax.toString()),
    });
});
export const uniswapSellRING = (landId, signer, value, callback) => __awaiter(void 0, void 0, void 0, function* () {
    var _b;
    const network = yield ((_b = signer.provider) === null || _b === void 0 ? void 0 : _b.getNetwork());
    if (!network) {
        throw Error("no network");
    }
    const RINGAddress = getAddressByName(landId, "TOKEN_RING");
    const RING = new Token(network === null || network === void 0 ? void 0 : network.chainId, RINGAddress, 18, "RING", "Darwinia Network Native Token");
    const WETH = uniswapGetWETH(landId, network === null || network === void 0 ? void 0 : network.chainId);
    const pair = yield Fetcher.fetchPairData(RING, WETH, signer);
    // @ts-ignore
    const route = new Route([pair], RING);
    const amountIn = value;
    const trade = new Trade(route, new TokenAmount(RING, amountIn), TradeType.EXACT_INPUT);
    const slippageTolerance = new Percent("30", "10000"); // 30 bips, or 0.30%
    const amountOutMin = trade.minimumAmountOut(slippageTolerance).raw; // needs to be converted to e.g. hex
    const path = [RING.address, WETH.address];
    const to = yield signer.getAddress(); // should be a checksummed recipient address
    const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // 20 minutes from the current Unix time
    const inputAmount = trade.inputAmount.raw; // // needs to be converted to e.g. hex
    return triggerContractByContractName(landId, signer, "uniswapExchange", "swapExactTokensForETH", [inputAmount.toString(10), amountOutMin.toString(10), path, to, deadline], callback, {
        value: ethers.BigNumber.from(0),
    });
});
export const uniswapGetDerivedPairInfo = (provider, compactTokenA, compactTokenB) => __awaiter(void 0, void 0, void 0, function* () {
    const network = yield (provider === null || provider === void 0 ? void 0 : provider.getNetwork());
    // decimals 18 just a random value,is useless, not used in the process of obtaining pair information
    const tokenA = new Token(network === null || network === void 0 ? void 0 : network.chainId, compactTokenA.address, compactTokenA.decimals, compactTokenA.symbol || "TokenA", compactTokenA.name || "TokenA");
    const tokenB = new Token(network === null || network === void 0 ? void 0 : network.chainId, compactTokenB.address, compactTokenB.decimals, compactTokenB.symbol || "TokenB", compactTokenB.name || "TokenB");
    const pair = yield Fetcher.fetchPairData(tokenA, tokenB, provider);
    return pair;
});
export const uniswapEthToTokenOutputPrice = (landId, provider, token, tokenBought) => __awaiter(void 0, void 0, void 0, function* () {
    const network = yield (provider === null || provider === void 0 ? void 0 : provider.getNetwork());
    const RING = new Token(network === null || network === void 0 ? void 0 : network.chainId, token.address, token.decimals, token.symbol, token.name);
    const WETH = uniswapGetWETH(landId, network === null || network === void 0 ? void 0 : network.chainId);
    const pair = yield Fetcher.fetchPairData(WETH, RING, provider);
    // @ts-ignore
    const route = new Route([pair], WETH);
    const amountIn = tokenBought;
    const trade = new Trade(route, new TokenAmount(RING, amountIn), TradeType.EXACT_OUTPUT);
    const slippageTolerance = new Percent("30", "10000");
    const amountInMax = trade.maximumAmountIn(slippageTolerance).raw;
    return [
        new BigNumber(amountInMax.toString(10)).times("1000000000000000000").div(tokenBought).toFixed(0),
        amountInMax.toString(10),
    ];
});
export const uniswapTokenToEthInputPrice = (landId, provider, token, tokenBought) => __awaiter(void 0, void 0, void 0, function* () {
    const network = yield (provider === null || provider === void 0 ? void 0 : provider.getNetwork());
    const RING = new Token(network === null || network === void 0 ? void 0 : network.chainId, token.address, token.decimals, token.symbol, token.name);
    const WETH = uniswapGetWETH(landId, network === null || network === void 0 ? void 0 : network.chainId);
    const pair = yield Fetcher.fetchPairData(RING, WETH, provider);
    // @ts-ignore
    const route = new Route([pair], RING);
    const amountIn = tokenBought; // 1 WETH
    const trade = new Trade(route, new TokenAmount(RING, amountIn), TradeType.EXACT_INPUT);
    const slippageTolerance = new Percent("30", "10000"); // 30 bips, or 0.30%
    const amountOutMin = trade.minimumAmountOut(slippageTolerance).raw; // needs to be converted to e.g. hex
    return [
        new BigNumber(amountOutMin.toString(10)).times("1000000000000000000").div(tokenBought).toFixed(0),
        amountOutMin.toString(10),
    ];
});
export const uniswapAddETHLiquidity = (landId, signer, tokenA, tokenB, to, slippage = 100, callback) => __awaiter(void 0, void 0, void 0, function* () {
    var _c, _d;
    const network = yield ((_c = signer.provider) === null || _c === void 0 ? void 0 : _c.getNetwork());
    if (!network || !signer.provider) {
        throw Error("no network");
    }
    const WETH = uniswapGetWETH(landId, network === null || network === void 0 ? void 0 : network.chainId);
    const { pair, parsedAmounts } = yield uniswapGetDerivedMintInfo(signer.provider, tokenA, tokenB);
    if (!pair || !pair.token0.address || !pair.token1.address) {
        throw Error("no pair");
    }
    const amountsMin = {
        [pair.token0.address]: calculateSlippageAmount(parsedAmounts[pair.token0.address].raw, slippage)[0],
        [pair.token1.address]: calculateSlippageAmount(parsedAmounts[pair.token1.address].raw, slippage)[0],
    };
    const erc20Token = pair.token0.address.toLowerCase() === ((_d = WETH.address) === null || _d === void 0 ? void 0 : _d.toLowerCase()) ? pair.token1 : pair.token0;
    const deadline = Math.floor(Date.now() / 1000) + 60 * 120; // 120 minutes from the current Unix time
    return triggerContractByContractName(landId, signer, "uniswapExchange", "addLiquidityETH", [
        erc20Token.address,
        parsedAmounts[erc20Token.address].raw.toString(),
        amountsMin[erc20Token.address].toString(),
        amountsMin[WETH.address].toString(),
        to,
        deadline,
    ], callback, {
        value: ethers.BigNumber.from(parsedAmounts[WETH.address].raw.toString()),
    });
});
export const uniswapAddLiquidity = (landId, signer, tokenA, tokenB, to, slippage = 100, callback) => __awaiter(void 0, void 0, void 0, function* () {
    var _e;
    const network = yield ((_e = signer.provider) === null || _e === void 0 ? void 0 : _e.getNetwork());
    if (!network || !signer.provider) {
        throw Error("no network");
    }
    const { pair, parsedAmounts } = yield uniswapGetDerivedMintInfo(signer.provider, tokenA, tokenB);
    if (!pair || !pair.token0.address || !pair.token1.address) {
        throw Error("no pair");
    }
    const amountsMin = {
        [pair.token0.address]: calculateSlippageAmount(parsedAmounts[pair.token0.address].raw, slippage)[0],
        [pair.token1.address]: calculateSlippageAmount(parsedAmounts[pair.token1.address].raw, slippage)[0],
    };
    const deadline = Math.floor(Date.now() / 1000) + 60 * 120; // 120 minutes from the current Unix time
    return triggerContractByContractName(landId, signer, "uniswapExchange", "addLiquidity", [
        pair.token0.address,
        pair.token1.address,
        parsedAmounts[pair.token0.address].raw.toString(),
        parsedAmounts[pair.token1.address].raw.toString(),
        amountsMin[pair.token0.address].toString(),
        amountsMin[pair.token1.address].toString(),
        to,
        deadline,
    ], callback);
});
export const uniswapRemoveLiquidity = (landId, signer, tokenA, tokenB, liquidityValue, to, slippage = 100, callback) => __awaiter(void 0, void 0, void 0, function* () {
    var _f;
    const network = yield ((_f = signer.provider) === null || _f === void 0 ? void 0 : _f.getNetwork());
    if (!network || !signer.provider) {
        throw Error("no network");
    }
    const { pair, parsedAmounts } = yield uniswapGetDerivedBurnInfo(signer.provider, tokenA, tokenB, ethers.BigNumber.from(liquidityValue), to);
    if (!pair || !pair.token0.address || !pair.token1.address) {
        throw Error("no pair");
    }
    const amountsMin = {
        [pair.token0.address]: calculateSlippageAmount(parsedAmounts[pair.token0.address].raw, slippage)[0],
        [pair.token1.address]: calculateSlippageAmount(parsedAmounts[pair.token1.address].raw, slippage)[0],
    };
    const deadline = Math.floor(Date.now() / 1000) + 60 * 120; // 120 minutes from the current Unix time
    return triggerContractByContractName(landId, signer, "uniswapExchange", "removeLiquidity", [
        pair.token0.address,
        pair.token1.address,
        parsedAmounts[pair.liquidityToken.address].raw.toString(),
        amountsMin[pair.token0.address].toString(),
        amountsMin[pair.token1.address].toString(),
        to,
        deadline,
    ], callback);
});
export const uniswapRemoveETHLiquidity = (landId, signer, tokenA, tokenB, liquidityValue, to, slippage = 100, callback) => __awaiter(void 0, void 0, void 0, function* () {
    var _g;
    const network = yield ((_g = signer.provider) === null || _g === void 0 ? void 0 : _g.getNetwork());
    if (!network || !signer.provider) {
        throw Error("no network");
    }
    const { pair, parsedAmounts } = yield uniswapGetDerivedBurnInfo(signer.provider, tokenA, tokenB, ethers.BigNumber.from(liquidityValue), to);
    const WETH = uniswapGetWETH(landId, network === null || network === void 0 ? void 0 : network.chainId);
    if (!pair || !pair.token0.address || !pair.token1.address) {
        throw Error("no pair");
    }
    const amountsMin = {
        [pair.token0.address]: calculateSlippageAmount(parsedAmounts[pair.token0.address].raw, slippage)[0],
        [pair.token1.address]: calculateSlippageAmount(parsedAmounts[pair.token1.address].raw, slippage)[0],
    };
    const erc20Token = pair.token0.address === WETH ? pair.token1 : pair.token0;
    const deadline = Math.floor(Date.now() / 1000) + 60 * 120; // 120 minutes from the current Unix time
    return triggerContractByContractName(landId, signer, "uniswapExchange", "removeLiquidityETH", [
        erc20Token.address,
        parsedAmounts[pair.liquidityToken.address].raw.toString(),
        amountsMin[erc20Token.address].toString(),
        amountsMin[WETH.address].toString(),
        to,
        deadline,
    ], callback);
});
export const uniswapGetDerivedMintInfo = (provider, tokenA, tokenB) => __awaiter(void 0, void 0, void 0, function* () {
    const network = yield (provider === null || provider === void 0 ? void 0 : provider.getNetwork());
    const pair = yield uniswapGetDerivedPairInfo(provider, tokenA, tokenB);
    const totalSupply = new TokenAmount(pair.liquidityToken, yield erc20TotalSupply(provider, pair.liquidityToken.address));
    const independentToken = tokenA.amount.isZero()
        ? {
            token: new Token(network === null || network === void 0 ? void 0 : network.chainId, tokenB.address, tokenB.decimals, tokenB.symbol, tokenB.name),
            amount: tokenB.amount,
        }
        : {
            token: new Token(network === null || network === void 0 ? void 0 : network.chainId, tokenA.address, tokenA.decimals, tokenA.symbol, tokenA.name),
            amount: tokenA.amount,
        };
    const parsedAmounts = {
        [pair.liquidityToken.address]: totalSupply,
        [pair.token0.address]: new TokenAmount(pair.token0, independentToken.token.equals(pair.token0)
            ? JSBI.BigInt(independentToken.amount)
            : pair
                .priceOf(independentToken.token)
                // @ts-ignore
                .quote(new CurrencyAmount(independentToken.token, JSBI.BigInt(independentToken.amount))).raw),
        [pair.token1.address]: new TokenAmount(pair.token1, independentToken.token.equals(pair.token1)
            ? JSBI.BigInt(independentToken.amount)
            : pair
                .priceOf(independentToken.token)
                // @ts-ignore
                .quote(new CurrencyAmount(independentToken.token, JSBI.BigInt(independentToken.amount))).raw),
    };
    return { pair, parsedAmounts };
});
export const uniswapGetDerivedBurnInfo = (provider, tokenA, tokenB, liquidityValue, to) => __awaiter(void 0, void 0, void 0, function* () {
    const pair = yield uniswapGetDerivedPairInfo(provider, tokenA, tokenB);
    const lpBalanceStr = yield erc20BalanceOf(provider, pair.liquidityToken.address, to);
    const userLiquidity = new TokenAmount(pair.liquidityToken, JSBI.BigInt(lpBalanceStr));
    const totalSupply = new TokenAmount(pair.liquidityToken, yield erc20TotalSupply(provider, pair.liquidityToken.address));
    const liquidityValueA = pair &&
        totalSupply &&
        userLiquidity &&
        pair.token0 &&
        new TokenAmount(pair.token0, pair.getLiquidityValue(pair.token0, totalSupply, userLiquidity, false).raw);
    const liquidityValueB = pair &&
        totalSupply &&
        userLiquidity &&
        pair.token1 &&
        new TokenAmount(pair.token1, pair.getLiquidityValue(pair.token1, totalSupply, userLiquidity, false).raw);
    const percentToRemove = new Percent(JSBI.BigInt(liquidityValue), userLiquidity.raw);
    const parsedAmounts = {
        [pair.liquidityToken.address]: new TokenAmount(userLiquidity.token, percentToRemove.multiply(userLiquidity.raw).quotient),
        [pair.token0.address]: new TokenAmount(pair.token0, percentToRemove.multiply(liquidityValueA.raw).quotient),
        [pair.token1.address]: new TokenAmount(pair.token1, percentToRemove.multiply(liquidityValueB.raw).quotient),
    };
    return { pair, parsedAmounts, liquidityPercent: percentToRemove };
});
