"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VaultSwapService = void 0;
const ethers_1 = require("ethers");
const tronweb_1 = require("tronweb");
const BalanceService_js_1 = require("./BalanceService.js");
const NATIVE_ADDRESS = "0x0000000000000000000000000000000000000000";
const ERC20_APPROVE_ABI = [
    "function approve(address spender, uint256 amount) external returns (bool)",
    "function allowance(address owner, address spender) external view returns (uint256)"
];
const UNISWAP_V3_ROUTER_ABI = [
    "function exactInputSingle(tuple(address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) external payable returns (uint256 amountOut)",
    "function multicall(bytes[] calldata data) external payable returns (bytes[] memory results)",
    "function unwrapWETH9(uint256 amountMinimum, address recipient) external payable",
    "function refundETH() external payable"
];
const UNISWAP_V3_QUOTER_ABI = [
    "function quoteExactInputSingle(tuple(address tokenIn, address tokenOut, uint256 amountIn, uint24 fee, uint160 sqrtPriceLimitX96)) external returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)"
];
const V3_FEE_TIERS = [500, 3000, 10000, 100];
const PANCAKE_V2_ROUTER_ABI = [
    "function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) external returns (uint256[] memory amounts)",
    "function swapExactTokensForETH(uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) external returns (uint256[] memory amounts)",
    "function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory amounts)"
];
const TRADERJOE_V2_ROUTER_ABI = [
    "function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) external returns (uint256[] memory amounts)",
    "function swapExactTokensForAVAX(uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) external returns (uint256[] memory amounts)",
    "function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory amounts)"
];
const SUNSWAP_V2_ROUTER_ABI = [
    {
        constant: false,
        inputs: [
            { name: "amountIn", type: "uint256" },
            { name: "amountOutMin", type: "uint256" },
            { name: "path", type: "address[]" },
            { name: "to", type: "address" },
            { name: "deadline", type: "uint256" }
        ],
        name: "swapExactTokensForTokens",
        outputs: [{ name: "amounts", type: "uint256[]" }],
        payable: false,
        stateMutability: "nonpayable",
        type: "function"
    },
    {
        constant: false,
        inputs: [
            { name: "amountIn", type: "uint256" },
            { name: "amountOutMin", type: "uint256" },
            { name: "path", type: "address[]" },
            { name: "to", type: "address" },
            { name: "deadline", type: "uint256" }
        ],
        name: "swapExactTokensForTRX",
        outputs: [{ name: "amounts", type: "uint256[]" }],
        payable: false,
        stateMutability: "nonpayable",
        type: "function"
    },
    {
        constant: true,
        inputs: [
            { name: "amountIn", type: "uint256" },
            { name: "path", type: "address[]" }
        ],
        name: "getAmountsOut",
        outputs: [{ name: "amounts", type: "uint256[]" }],
        payable: false,
        stateMutability: "view",
        type: "function"
    }
];
class VaultSwapService {
    config;
    logger;
    balanceService;
    constructor(config, logger) {
        this.config = config;
        this.logger = logger;
        this.balanceService = new BalanceService_js_1.BalanceService(config, logger);
    }
    async swapUsdtToNative(network, privateKey, walletAddress, usdtAmount) {
        if (network === "tron") {
            return this.swapTronUsdtToTrx(privateKey, walletAddress, usdtAmount);
        }
        const networkConfig = this.config.paymentNetworks[network];
        if (!networkConfig) {
            throw new Error(`${network} payments are not configured.`);
        }
        switch (networkConfig.swapRouterType) {
            case "uniswap-v3":
                return this.swapUniswapV3(network, privateKey, walletAddress, usdtAmount);
            case "pancake-v2":
                return this.swapPancakeV2(network, privateKey, walletAddress, usdtAmount);
            case "traderjoe-v2":
                return this.swapTraderJoeV2(network, privateKey, walletAddress, usdtAmount);
            default:
                throw new Error(`Unsupported swap router type: ${networkConfig.swapRouterType}`);
        }
    }
    async swapUniswapV3(network, privateKey, walletAddress, usdtAmount) {
        const networkConfig = this.config.paymentNetworks[network];
        const provider = new ethers_1.ethers.JsonRpcProvider(networkConfig.rpcUrl);
        const signer = new ethers_1.ethers.Wallet(privateKey, provider);
        const router = new ethers_1.ethers.Contract(networkConfig.swapRouter, UNISWAP_V3_ROUTER_ABI, signer);
        const quoter = new ethers_1.ethers.Contract(networkConfig.swapQuoter, UNISWAP_V3_QUOTER_ABI, provider);
        const tokenIn = networkConfig.usdtAddress.toLowerCase();
        const tokenOut = networkConfig.wrappedNativeAddress.toLowerCase();
        const deadline = Math.floor(Date.now() / 1000) + 600;
        await this.ensureEvmAllowance(provider, signer, networkConfig.usdtAddress, networkConfig.swapRouter, usdtAmount);
        let bestFee = 3000;
        let bestOutput = 0n;
        for (const fee of V3_FEE_TIERS) {
            try {
                const result = await quoter.quoteExactInputSingle.staticCall({
                    tokenIn,
                    tokenOut,
                    amountIn: usdtAmount,
                    fee,
                    sqrtPriceLimitX96: 0
                });
                const output = result[0];
                if (output > bestOutput) {
                    bestOutput = output;
                    bestFee = fee;
                }
            }
            catch (err) {
                this.logger?.debug(`Fee tier ${fee} failed quoting for ${network}.`, {
                    error: err instanceof Error ? err.message : String(err)
                });
            }
        }
        if (bestOutput === 0n) {
            throw new Error(`No liquidity pool found for USDT->native on ${network}`);
        }
        const slippageBps = 200; // 2%
        const amountOutMinimum = (bestOutput * BigInt(10000 - slippageBps)) / 10000n;
        const routerInterface = new ethers_1.ethers.Interface(UNISWAP_V3_ROUTER_ABI);
        const exactInputSingleData = routerInterface.encodeFunctionData("exactInputSingle", [
            {
                tokenIn,
                tokenOut,
                fee: bestFee,
                recipient: networkConfig.swapRouter,
                deadline,
                amountIn: usdtAmount,
                amountOutMinimum,
                sqrtPriceLimitX96: 0
            }
        ]);
        const unwrapData = routerInterface.encodeFunctionData("unwrapWETH9", [amountOutMinimum, walletAddress]);
        const tx = await router.multicall([exactInputSingleData, unwrapData]);
        const receipt = await tx.wait();
        if (!receipt) {
            throw new Error("Swap receipt not received.");
        }
        return {
            txHash: tx.hash,
            amountIn: usdtAmount.toString(),
            amountOut: bestOutput.toString()
        };
    }
    async swapPancakeV2(network, privateKey, walletAddress, usdtAmount) {
        const networkConfig = this.config.paymentNetworks[network];
        const provider = new ethers_1.ethers.JsonRpcProvider(networkConfig.rpcUrl);
        const signer = new ethers_1.ethers.Wallet(privateKey, provider);
        const router = new ethers_1.ethers.Contract(networkConfig.swapRouter, PANCAKE_V2_ROUTER_ABI, signer);
        const path = [networkConfig.usdtAddress, networkConfig.wrappedNativeAddress];
        const deadline = Math.floor(Date.now() / 1000) + 600;
        await this.ensureEvmAllowance(provider, signer, networkConfig.usdtAddress, networkConfig.swapRouter, usdtAmount);
        const amountsOut = await router.getAmountsOut(usdtAmount, path);
        const expectedOut = amountsOut[amountsOut.length - 1];
        const slippageBps = 200;
        const amountOutMin = (expectedOut * BigInt(10000 - slippageBps)) / 10000n;
        const tx = await router.swapExactTokensForETH(amountOutMin, path, walletAddress, deadline);
        const receipt = await tx.wait();
        if (!receipt) {
            throw new Error("Swap receipt not received.");
        }
        return {
            txHash: tx.hash,
            amountIn: usdtAmount.toString(),
            amountOut: expectedOut.toString()
        };
    }
    async swapTraderJoeV2(network, privateKey, walletAddress, usdtAmount) {
        const networkConfig = this.config.paymentNetworks[network];
        const provider = new ethers_1.ethers.JsonRpcProvider(networkConfig.rpcUrl);
        const signer = new ethers_1.ethers.Wallet(privateKey, provider);
        const router = new ethers_1.ethers.Contract(networkConfig.swapRouter, TRADERJOE_V2_ROUTER_ABI, signer);
        const path = [networkConfig.usdtAddress, networkConfig.wrappedNativeAddress];
        const deadline = Math.floor(Date.now() / 1000) + 600;
        await this.ensureEvmAllowance(provider, signer, networkConfig.usdtAddress, networkConfig.swapRouter, usdtAmount);
        const amountsOut = await router.getAmountsOut(usdtAmount, path);
        const expectedOut = amountsOut[amountsOut.length - 1];
        const slippageBps = 200;
        const amountOutMin = (expectedOut * BigInt(10000 - slippageBps)) / 10000n;
        const tx = await router.swapExactTokensForAVAX(amountOutMin, path, walletAddress, deadline);
        const receipt = await tx.wait();
        if (!receipt) {
            throw new Error("Swap receipt not received.");
        }
        return {
            txHash: tx.hash,
            amountIn: usdtAmount.toString(),
            amountOut: expectedOut.toString()
        };
    }
    async swapTronUsdtToTrx(privateKey, walletAddress, usdtAmount) {
        const networkConfig = this.config.paymentNetworks.tron;
        if (!networkConfig) {
            throw new Error("Tron payments are not configured.");
        }
        const tronWeb = new tronweb_1.TronWeb({
            fullHost: networkConfig.fullHost,
            headers: this.tronHeaders(networkConfig.apiKey),
            privateKey: privateKey.startsWith("0x") ? privateKey.slice(2) : privateKey
        });
        const routerAddress = networkConfig.swapRouter;
        const usdtContract = await tronWeb.contract([
            {
                constant: false,
                inputs: [
                    { name: "_spender", type: "address" },
                    { name: "_value", type: "uint256" }
                ],
                name: "approve",
                outputs: [{ name: "", type: "bool" }],
                payable: false,
                stateMutability: "nonpayable",
                type: "function"
            },
            {
                constant: true,
                inputs: [
                    { name: "_owner", type: "address" },
                    { name: "_spender", type: "address" }
                ],
                name: "allowance",
                outputs: [{ name: "", type: "uint256" }],
                payable: false,
                stateMutability: "view",
                type: "function"
            }
        ], networkConfig.usdtContract);
        const allowanceResponse = await usdtContract.allowance(walletAddress, routerAddress).call();
        const allowance = BigInt(allowanceResponse.toString());
        if (allowance < usdtAmount) {
            const approveTx = await usdtContract.approve(routerAddress, usdtAmount.toString()).send();
            if (!approveTx) {
                throw new Error("Failed to approve USDT for SunSwap router.");
            }
        }
        const router = await tronWeb.contract(SUNSWAP_V2_ROUTER_ABI, routerAddress);
        const path = [networkConfig.usdtContract, networkConfig.wrappedNativeAddress];
        const deadline = Math.floor(Date.now() / 1000) + 600;
        const amountsOut = await router.getAmountsOut(usdtAmount.toString(), path).call();
        const expectedOut = BigInt(amountsOut[amountsOut.length - 1].toString());
        const slippageBps = 200;
        const amountOutMin = (expectedOut * BigInt(10000 - slippageBps)) / 10000n;
        const txResult = await router
            .swapExactTokensForTRX(usdtAmount.toString(), amountOutMin.toString(), path, walletAddress, deadline)
            .send();
        const txHash = typeof txResult === "string" ? txResult : txResult?.transaction?.txID ?? txResult?.txID;
        if (!txHash) {
            throw new Error("Tron swap did not return a transaction hash.");
        }
        return {
            txHash,
            amountIn: usdtAmount.toString(),
            amountOut: expectedOut.toString()
        };
    }
    async ensureEvmAllowance(provider, signer, tokenAddress, spender, amount) {
        const token = new ethers_1.ethers.Contract(tokenAddress, ERC20_APPROVE_ABI, provider);
        const allowance = (await token.allowance(signer.address, spender));
        if (allowance >= amount) {
            return;
        }
        const tokenWithSigner = token.connect(signer);
        const approveFn = tokenWithSigner.getFunction("approve");
        const tx = await approveFn(spender, ethers_1.ethers.MaxUint256);
        await tx.wait();
    }
    tronHeaders(apiKey) {
        return apiKey ? { "TRON-PRO-API-KEY": apiKey } : undefined;
    }
}
exports.VaultSwapService = VaultSwapService;
