"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PerformZapTool = void 0;
const zod_1 = require("zod");
const ai_1 = require("ai");
const sdk_1 = require("@dzapio/sdk");
const zapRegistry_1 = require("./zapRegistry");
const viem_1 = require("viem");
const accounts_1 = require("viem/accounts");
const tokenaddress_1 = require("../tokenaddress");
const dzapClient_1 = __importDefault(require("../../config/dzapClient"));
const performZapToolInputSchema = zod_1.z.object({
    sessionId: zod_1.z.string().describe("The current session ID for this user"),
});
const PUBLIC_RPC_BY_CHAIN_ID = {
    1: "https://ethereum-rpc.publicnode.com",
    10: "https://mainnet.optimism.io",
    56: "https://bsc-dataseed.binance.org",
    100: "https://rpc.gnosis.gateway.fm",
    137: "https://polygon-rpc.com",
    250: "https://rpc.fantom.network",
    324: "https://mainnet.era.zksync.io",
    169: "https://pacific-rpc.manta.network/http",
    288: "https://mainnet.boba.network",
    8453: "https://mainnet.base.org",
    1088: "https://andromeda.metis.io/?owner=1088",
    1101: "https://zkevm-rpc.com",
    1284: "https://rpc.api.moonbeam.network",
    1285: "https://rpc.api.moonriver.moonbeam.network",
    1329: "https://evm-rpc.sei-apis.com",
    2222: "https://evm.kava.io",
    4200: "https://rpc.merlinchain.io",
    43114: "https://api.avax.network/ext/bc/C/rpc",
    5000: "https://rpc.mantle.xyz",
    59144: "https://rpc.linea.build",
    7000: "https://zetachain-evm.blockpi.network/v1/rpc/public",
    81457: "https://rpc.blast.io",
    42161: "https://arb1.arbitrum.io/rpc",
    42170: "https://nova.arbitrum.io/rpc",
    534352: "https://rpc.scroll.io",
};
const NATIVE_TOKEN_ADDRESSES = new Set([
    "0x0000000000000000000000000000000000000000",
    "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
]);
function normalizePrivateKey(privateKey) {
    const trimmed = privateKey.trim();
    const prefixed = trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
    if (!/^0x[0-9a-fA-F]{64}$/.test(prefixed)) {
        throw new Error("Invalid private key format. Expected a 32-byte hex string.");
    }
    return prefixed;
}
function toBigInt(value, fallback = 0n) {
    if (typeof value === "bigint") {
        return value;
    }
    if (typeof value === "number" && Number.isFinite(value)) {
        return BigInt(Math.trunc(value));
    }
    if (typeof value === "string") {
        const trimmed = value.trim();
        if (!trimmed) {
            return fallback;
        }
        try {
            return trimmed.startsWith("0x")
                ? BigInt(trimmed)
                : BigInt(trimmed);
        }
        catch (_a) {
            return fallback;
        }
    }
    return fallback;
}
function normalizeAddress(value) {
    if (typeof value !== "string") {
        return undefined;
    }
    const trimmed = value.trim();
    if (!/^0x[a-fA-F0-9]{40}$/.test(trimmed)) {
        return undefined;
    }
    return trimmed;
}
function isNativeTokenAddress(address) {
    if (!address) {
        return false;
    }
    return NATIVE_TOKEN_ADDRESSES.has(address.toLowerCase());
}
function getChainId(zapData) {
    var _a, _b, _c;
    const raw = (_b = (_a = zapData.srcToken) === null || _a === void 0 ? void 0 : _a.chainid) !== null && _b !== void 0 ? _b : (_c = zapData.srcToken) === null || _c === void 0 ? void 0 : _c.chainId;
    const numeric = typeof raw === "string" ? Number.parseInt(raw, 10) : Number(raw);
    if (!Number.isInteger(numeric) || numeric <= 0) {
        throw new Error("Unable to determine source chain ID for transaction execution.");
    }
    return numeric;
}
function resolveApprovalService(type) {
    return type === "zap" ? sdk_1.Services.zap : sdk_1.Services.trade;
}
function resolveApprovalAmount(zapData) {
    var _a;
    const candidates = [zapData.amountIn, (_a = zapData.approvalData) === null || _a === void 0 ? void 0 : _a.amount];
    for (const candidate of candidates) {
        const parsed = toBigInt(candidate, 0n);
        if (parsed > 0n) {
            return parsed;
        }
    }
    return 0n;
}
function resolveRpcUrl(chainId) {
    var _a;
    const chainSpecific = (_a = process.env[`DZAP_RPC_URL_${chainId}`]) !== null && _a !== void 0 ? _a : process.env[`RPC_URL_${chainId}`];
    if (chainSpecific && chainSpecific.trim().length > 0) {
        return chainSpecific.trim();
    }
    if (process.env.DZAP_RPC_URL && process.env.DZAP_RPC_URL.trim().length > 0) {
        return process.env.DZAP_RPC_URL.trim();
    }
    const fallback = PUBLIC_RPC_BY_CHAIN_ID[chainId];
    if (!fallback) {
        throw new Error(`RPC URL not configured for chain ${chainId}. Set DZAP_RPC_URL_${chainId} or RPC_URL_${chainId}.`);
    }
    return fallback;
}
function chainConfig(chainId, rpcUrl) {
    return (0, viem_1.defineChain)({
        id: chainId,
        name: `Chain-${chainId}`,
        nativeCurrency: {
            name: "Native Token",
            symbol: "ETH",
            decimals: 18,
        },
        rpcUrls: {
            default: { http: [rpcUrl] },
            public: { http: [rpcUrl] },
        },
    });
}
function txInfoLink(chainId, hash) {
    return `https://app.dzap.io/history/tx/${hash}?chainId=${chainId}`;
}
const tokenResolver = new tokenaddress_1.TokenToAddress();
function resolveTokenSymbol(chainId, tokenAddress) {
    if (!tokenAddress) {
        return undefined;
    }
    if (isNativeTokenAddress(tokenAddress)) {
        return "NATIVE";
    }
    const resolved = tokenResolver.addressToToken(tokenAddress, chainId);
    if (typeof resolved === "string" && !resolved.startsWith("Address ")) {
        return resolved;
    }
    return tokenAddress;
}
function resolveTokenDecimals(chainId, tokenAddress) {
    if (!tokenAddress) {
        return undefined;
    }
    if (isNativeTokenAddress(tokenAddress)) {
        return 18;
    }
    const resolved = tokenResolver.addressToDecimals(tokenAddress, chainId);
    if (typeof resolved === "number" && Number.isInteger(resolved) && resolved >= 0) {
        return resolved;
    }
    return undefined;
}
function buildConfirmationRequest(sessionId, zapData) {
    var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
    const srcChainId = getChainId(zapData);
    const destChainIdRaw = (_d = (_b = (_a = zapData.destToken) === null || _a === void 0 ? void 0 : _a.chainid) !== null && _b !== void 0 ? _b : (_c = zapData.destToken) === null || _c === void 0 ? void 0 : _c.chainId) !== null && _d !== void 0 ? _d : srcChainId;
    const destChainId = typeof destChainIdRaw === "string"
        ? Number.parseInt(destChainIdRaw, 10)
        : Number(destChainIdRaw);
    const normalizedDestChainId = Number.isInteger(destChainId) ? destChainId : srcChainId;
    const srcTokenAddress = normalizeAddress((_e = zapData.srcToken) === null || _e === void 0 ? void 0 : _e.address);
    const destTokenAddress = normalizeAddress((_f = zapData.destToken) === null || _f === void 0 ? void 0 : _f.address);
    const providerName = (_h = (_g = zapData.providerDetails) === null || _g === void 0 ? void 0 : _g.name) !== null && _h !== void 0 ? _h : (_j = zapData.providerDetails) === null || _j === void 0 ? void 0 : _j.id;
    return {
        sessionId,
        type: (_k = zapData.type) !== null && _k !== void 0 ? _k : "unknown",
        srcChainId,
        destChainId: normalizedDestChainId,
        srcTokenAddress,
        destTokenAddress,
        srcTokenSymbol: resolveTokenSymbol(srcChainId, srcTokenAddress),
        destTokenSymbol: resolveTokenSymbol(normalizedDestChainId, destTokenAddress),
        srcTokenDecimals: resolveTokenDecimals(srcChainId, srcTokenAddress),
        destTokenDecimals: resolveTokenDecimals(normalizedDestChainId, destTokenAddress),
        amountIn: zapData.amountIn,
        amountOut: zapData.amountOut,
        provider: providerName,
    };
}
class PerformZapTool {
    constructor() {
        this.tool = (0, ai_1.tool)({
            description: "Execute a previously generated Zap transaction for a session via viem.",
            inputSchema: performZapToolInputSchema,
            execute: async ({ sessionId }) => {
                var _a, _b, _c, _d;
                const zapData = (0, zapRegistry_1.getZapData)(sessionId);
                if (!zapData) {
                    return "No zap data found for this session. Generate call data first.";
                }
                if ((0, zapRegistry_1.getSessionTransactionConfirmationMode)(sessionId)) {
                    const confirmationHandler = (0, zapRegistry_1.getSessionTransactionConfirmationHandler)(sessionId);
                    if (!confirmationHandler) {
                        return "Transaction confirmation is enabled, but no confirmation handler is available for this session.";
                    }
                    const confirmed = await confirmationHandler(buildConfirmationRequest(sessionId, zapData));
                    if (!confirmed) {
                        return "Transaction cancelled by user.";
                    }
                }
                const privateKeyRaw = (0, zapRegistry_1.getSessionPrivateKey)(sessionId);
                if (!privateKeyRaw) {
                    return "No private key found for this session. Re-initialize CLI profile with private key and retry.";
                }
                const privateKey = normalizePrivateKey(privateKeyRaw);
                const chainId = getChainId(zapData);
                const rpcUrl = resolveRpcUrl(chainId);
                const chain = chainConfig(chainId, rpcUrl);
                const account = (0, accounts_1.privateKeyToAccount)(privateKey);
                const publicClient = (0, viem_1.createPublicClient)({
                    chain,
                    transport: (0, viem_1.http)(rpcUrl),
                });
                const walletClient = (0, viem_1.createWalletClient)({
                    account,
                    chain,
                    transport: (0, viem_1.http)(rpcUrl),
                });
                const sourceToken = normalizeAddress((_a = zapData.srcToken) === null || _a === void 0 ? void 0 : _a.address);
                const approvalAmount = resolveApprovalAmount(zapData);
                const approvalService = resolveApprovalService(zapData.type);
                const approvalHashes = [];
                let approvalModeUsed;
                let approvalNote;
                let nonceRetryUsed = false;
                if (sourceToken && approvalAmount > 0n && !isNativeTokenAddress(sourceToken)) {
                    const approvalTokens = [{
                            address: sourceToken,
                            amount: approvalAmount.toString(),
                        }];
                    const sender = account.address;
                    let mode = sdk_1.ApprovalModes.AutoPermit;
                    let allowanceCheck = await dzapClient_1.default.getAllowance({
                        chainId,
                        sender,
                        tokens: approvalTokens,
                        service: approvalService,
                        mode,
                        rpcUrls: [rpcUrl],
                    });
                    if (allowanceCheck.status !== "success") {
                        throw new Error(`Unable to check token allowance (${allowanceCheck.code}).`);
                    }
                    let allowanceData = (_b = allowanceCheck.data) !== null && _b !== void 0 ? _b : {};
                    let tokensForApproval = approvalTokens.filter((token) => { var _a; return (_a = allowanceData[token.address]) === null || _a === void 0 ? void 0 : _a.approvalNeeded; });
                    const signatureRequired = approvalTokens.some((token) => { var _a; return (_a = allowanceData[token.address]) === null || _a === void 0 ? void 0 : _a.signatureNeeded; });
                    if (signatureRequired) {
                        mode = sdk_1.ApprovalModes.Default;
                        allowanceCheck = await dzapClient_1.default.getAllowance({
                            chainId,
                            sender,
                            tokens: approvalTokens,
                            service: approvalService,
                            mode,
                            rpcUrls: [rpcUrl],
                        });
                        if (allowanceCheck.status !== "success") {
                            throw new Error(`Unable to re-check token allowance in default mode (${allowanceCheck.code}).`);
                        }
                        allowanceData = (_c = allowanceCheck.data) !== null && _c !== void 0 ? _c : {};
                        tokensForApproval = approvalTokens.filter((token) => { var _a; return (_a = allowanceData[token.address]) === null || _a === void 0 ? void 0 : _a.approvalNeeded; });
                        approvalNote = "AutoPermit requested permit signatures; executed Default approvals for prebuilt calldata compatibility.";
                    }
                    approvalModeUsed = mode;
                    if (tokensForApproval.length > 0) {
                        const approvalResult = await dzapClient_1.default.approve({
                            chainId,
                            signer: walletClient,
                            tokens: tokensForApproval,
                            service: approvalService,
                            mode,
                            rpcUrls: [rpcUrl],
                            approvalTxnCallback: async ({ txnDetails }) => {
                                if (typeof (txnDetails === null || txnDetails === void 0 ? void 0 : txnDetails.txnHash) === "string" && txnDetails.txnHash.startsWith("0x")) {
                                    approvalHashes.push(txnDetails.txnHash);
                                }
                                return null;
                            },
                        });
                        if (approvalResult.status !== "success") {
                            throw new Error(`Token approval failed (${approvalResult.code}).`);
                        }
                        for (const hash of approvalHashes) {
                            const approvalReceipt = await publicClient.waitForTransactionReceipt({ hash });
                            if (approvalReceipt.status !== "success") {
                                throw new Error(`Approval transaction reverted (${hash}).`);
                            }
                        }
                    }
                }
                const to = normalizeAddress(zapData.callTo);
                const callData = zapData.callData;
                if (!to || typeof callData !== "string" || !callData.startsWith("0x")) {
                    throw new Error("Invalid zap transaction payload. Missing call target or call data.");
                }
                const value = toBigInt((_d = zapData.value) !== null && _d !== void 0 ? _d : 0);
                const sendZapWithPendingNonce = async () => {
                    const nonce = await publicClient.getTransactionCount({
                        address: account.address,
                        blockTag: "pending",
                    });
                    return await walletClient.sendTransaction({
                        account,
                        to,
                        data: callData,
                        value,
                        nonce,
                    });
                };
                let zapHash;
                try {
                    zapHash = await sendZapWithPendingNonce();
                }
                catch (error) {
                    const errorText = error instanceof Error ? error.message : String(error);
                    const isNonceError = /nonce|already known|replacement transaction underpriced/i.test(errorText);
                    if (!isNonceError) {
                        throw error;
                    }
                    nonceRetryUsed = true;
                    zapHash = await sendZapWithPendingNonce();
                }
                const receipt = await publicClient.waitForTransactionReceipt({ hash: zapHash });
                (0, zapRegistry_1.clearZapData)(sessionId);
                const lines = [
                    "Zap transaction executed.",
                    `Chain ID: ${chainId}`,
                    approvalModeUsed ? `Approval mode: ${approvalModeUsed}` : "Approval mode: not required",
                    approvalNote,
                    approvalHashes.length > 0
                        ? `Approval tx hash: ${approvalHashes[approvalHashes.length - 1]}`
                        : "Approval tx hash: not required",
                    nonceRetryUsed ? "Nonce handling: retried with pending nonce." : undefined,
                    `Zap tx hash: ${zapHash}`,
                    `Tx status: ${receipt.status}`,
                    `Track zap tx: ${txInfoLink(chainId, zapHash)}`,
                ].filter((line) => typeof line === "string" && line.length > 0);
                return lines.join("\n");
            },
        });
    }
}
exports.PerformZapTool = PerformZapTool;
