"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.bridgeTokenToolDefinition = void 0;
exports.prepareBridgeToken = prepareBridgeToken;
exports.executeBridge = executeBridge;
const viem_1 = require("viem");
const config_1 = require("../config");
const transactionManager_1 = require("../../agent/transactionManager");
const tokens_1 = require("../utils/tokens");
const parser_1 = require("../../config/parser");
const CHAIN_IDS = {
    ethereum: 1,
    base: 8453,
    bsc: 56,
    arbitrum: 42161,
    optimism: 10,
    sepolia: 11155111,
    polygon: 137,
    base_sepolia: 84532,
};
async function getLifiQuote(fromChainId, toChainId, fromToken, toToken, amountWei, userAddress, slippage) {
    const url = new URL('https://li.quest/v1/quote');
    url.searchParams.append('fromChain', fromChainId.toString());
    url.searchParams.append('toChain', toChainId.toString());
    url.searchParams.append('fromToken', fromToken);
    url.searchParams.append('toToken', toToken);
    url.searchParams.append('fromAmount', amountWei);
    url.searchParams.append('fromAddress', userAddress);
    url.searchParams.append('slippage', slippage.toString());
    const res = await fetch(url.toString());
    if (!res.ok) {
        const err = await res.json().catch(() => ({}));
        throw new Error(`Li.Fi API Error: ${err.message || res.statusText}`);
    }
    return await res.json();
}
async function getRelayQuote(fromChainId, toChainId, fromToken, toToken, amountWei, userAddress, slippagePercent) {
    const isTestnet = fromChainId === 11155111 || toChainId === 11155111 || fromChainId === 84532 || toChainId === 84532;
    const baseUrl = isTestnet ? "https://api.testnets.relay.link" : "https://api.relay.link";
    const body = {
        user: userAddress,
        originChainId: fromChainId,
        destinationChainId: toChainId,
        originCurrency: fromToken,
        destinationCurrency: toToken,
        amount: amountWei,
        tradeType: "EXACT_INPUT",
        slippageTolerance: Math.round(slippagePercent * 100).toString()
    };
    let retries = 3;
    while (retries > 0) {
        try {
            const res = await fetch(`${baseUrl}/quote`, {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify(body),
                signal: AbortSignal.timeout(10000)
            });
            if (!res.ok) {
                const err = await res.json().catch(() => ({}));
                throw new Error(`Relay API Error: ${err.message || res.statusText}`);
            }
            return await res.json();
        }
        catch (error) {
            if (error.message.includes('Relay API Error'))
                throw error; // Don't retry logic errors
            retries--;
            if (retries === 0)
                throw error;
            await new Promise(r => setTimeout(r, 1000)); // wait 1s before retry
        }
    }
}
async function prepareBridgeToken(fromChainName, toChainName, fromToken, toToken, amountStr, mode = "auto", providerName = "lifi", slippagePercent) {
    try {
        const publicClient = (0, config_1.getPublicClient)(fromChainName);
        const userAddress = await (0, config_1.getAddress)();
        const account = userAddress;
        const fromChainId = CHAIN_IDS[fromChainName];
        const toChainId = CHAIN_IDS[toChainName];
        const fromTokenAddress = (0, tokens_1.resolveToken)(fromToken, fromChainName);
        const toTokenAddress = (0, tokens_1.resolveToken)(toToken, toChainName);
        const isNativeIn = fromTokenAddress === "0x0000000000000000000000000000000000000000";
        // Get decimals
        let decimals = 18;
        if (!isNativeIn) {
            const { getTokenMetadata } = await Promise.resolve().then(() => __importStar(require('../utils/tokens')));
            const metadata = await getTokenMetadata(publicClient, fromTokenAddress);
            decimals = metadata.decimals;
        }
        const amountWei = (0, viem_1.parseUnits)(amountStr, decimals).toString();
        let txRequest = null;
        let approvalAddress = null;
        let expectedOutputStr = "";
        let actualSlippage = slippagePercent;
        if (actualSlippage === undefined || actualSlippage === null) {
            try {
                const config = (0, parser_1.loadConfig)();
                actualSlippage = config.agent.default_slippage || 0.5;
            }
            catch (e) {
                actualSlippage = 0.5;
            }
        }
        const isTestnet = fromChainId === 11155111 || toChainId === 11155111 || fromChainId === 84532 || toChainId === 84532;
        let actualProvider = mode === "auto" ? (isTestnet ? "relay" : "lifi") : providerName;
        if (actualProvider === "lifi") {
            const quote = await getLifiQuote(fromChainId, toChainId, fromTokenAddress, toTokenAddress, amountWei, userAddress, actualSlippage / 100);
            txRequest = quote.transactionRequest;
            approvalAddress = quote.estimate.approvalAddress;
            const toDecimals = quote.action.toToken.decimals;
            expectedOutputStr = (0, viem_1.formatUnits)(BigInt(quote.estimate.toAmount), toDecimals);
        }
        else if (actualProvider === "relay") {
            const relayQuote = await getRelayQuote(fromChainId, toChainId, fromTokenAddress, toTokenAddress, amountWei, userAddress, actualSlippage);
            if (!relayQuote.steps || relayQuote.steps.length === 0)
                throw new Error("No route found by Relay.");
            const txStep = relayQuote.steps.find((s) => s.id === "execute" || s.id === "deposit" || s.kind === "transaction");
            if (!txStep || !txStep.items || txStep.items.length === 0)
                throw new Error("Relay steps invalid.");
            const item = txStep.items[0];
            txRequest = item.data;
            if (!isNativeIn && txRequest.to.toLowerCase() !== fromTokenAddress.toLowerCase()) {
                approvalAddress = txRequest.to;
            }
            expectedOutputStr = relayQuote.details?.currencyOut?.amountFormatted || "Unknown";
        }
        let needsApprove = false;
        if (!isNativeIn && approvalAddress && approvalAddress !== "0x0000000000000000000000000000000000000000") {
            // @ts-ignore
            const allowance = await publicClient.readContract({
                address: fromTokenAddress,
                abi: tokens_1.ERC20_ABI,
                functionName: 'allowance',
                args: [userAddress, approvalAddress],
            });
            if (allowance < BigInt(amountWei)) {
                needsApprove = true;
            }
        }
        const tx = transactionManager_1.txManager.createPendingTransaction('bridge', fromChainName, {
            txRequest,
            needsApprove,
            fromTokenAddress,
            approvalAddress,
            amountWei
        });
        return `TRANSACTION_PENDING: Bridge simulated via ${actualProvider.toUpperCase()}. Expected Output on ${toChainName}: ~${expectedOutputStr} ${toToken.toUpperCase()}. ${needsApprove ? '(Auto-Approve required) ' : ''}Gas est: ${txRequest.gasLimit || 'auto'}. Transaction ID: ${tx.id}. Wait for user to approve.`;
    }
    catch (error) {
        return `Simulation failed! Cannot prepare bridge. Error: ${error.message}`;
    }
}
async function executeBridge(chainName, params, autoApprove = false) {
    try {
        const { txRequest, needsApprove, fromTokenAddress, approvalAddress, amountWei } = params;
        const token = process.env.INTERNAL_AUTH_TOKEN;
        const payload = {
            type: 'bridge',
            chainName,
            autoApprove,
            details: {
                txRequest, needsApprove, fromTokenAddress, approvalAddress, amountWei
            }
        };
        if (autoApprove && token) {
            const crypto = require('crypto');
            payload.internalSignature = crypto.createHmac('sha256', token).update(chainName + amountWei).digest('hex');
        }
        const res = await fetch('http://127.0.0.1:3001/request-tx', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${token}`
            },
            body: JSON.stringify(payload),
            signal: AbortSignal.timeout(180000)
        });
        const data = await res.json();
        if (!res.ok)
            throw new Error(data.error || 'Unknown error from Policy API');
        if (data.status === 'pending') {
            return `Transaction pending approval via Policy API. Tx ID: ${data.txId}`;
        }
        if (data.signedHash) {
            return `Bridge successfully executed on-chain! Transaction Hash: ${data.signedHash}`;
        }
        return `Bridge executed. Result: ${JSON.stringify(data)}`;
    }
    catch (error) {
        return `Failed to execute bridge: ${error.message}`;
    }
}
exports.bridgeTokenToolDefinition = {
    type: "function",
    function: {
        name: "bridge_token",
        description: "Executes a cross-chain token bridge from one network to another using Li.Fi or Relay. Automatically simulates to fetch quotes.",
        parameters: {
            type: "object",
            properties: {
                fromChainName: {
                    type: "string",
                    enum: config_1.SUPPORTED_CHAIN_NAMES,
                    description: "The source blockchain network",
                },
                toChainName: {
                    type: "string",
                    enum: config_1.SUPPORTED_CHAIN_NAMES,
                    description: "The destination blockchain network",
                },
                fromToken: {
                    type: "string",
                    description: "The token symbol to sell on source chain (e.g., 'ETH', 'USDC')",
                },
                toToken: {
                    type: "string",
                    description: "The token symbol to buy on destination chain (e.g., 'USDC', 'UNI')",
                },
                amountStr: {
                    type: "string",
                    description: "The amount of fromToken to bridge",
                },
                mode: {
                    type: "string",
                    enum: ["auto", "manual"],
                    description: "auto uses lifi. manual uses the specified provider."
                },
                providerName: {
                    type: "string",
                    enum: ["lifi", "relay"],
                    description: "Used if mode is manual."
                },
                slippagePercent: {
                    type: "number",
                    description: "Optional slippage tolerance percentage (e.g. 0.5, 5, 10). If not specified, defaults to the globally configured slippage."
                }
            },
            required: ["fromChainName", "toChainName", "fromToken", "toToken", "amountStr"],
        },
    },
};
