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());
    });
};
import { CHAIN_ID_TO_NAME, isChain, CONTRACTS, CHAINS, tryNativeToHexString, ethers_contracts, } from "../..";
import { BigNumber, ethers } from "ethers";
import { getWormholeRelayerAddress } from "../consts";
import { RelayerPayloadId, RefundStatus, parseEVMExecutionInfoV1, } from "../structs";
import { getDefaultProvider, printChain, getWormholeRelayerLog, parseWormholeLog, getBlockRange, getWormholeRelayerInfoBySourceSequence, } from "./helpers";
export function getPriceAndRefundInfo(sourceChain, targetChain, gasAmount, optionalParams) {
    return __awaiter(this, void 0, void 0, function* () {
        const environment = (optionalParams === null || optionalParams === void 0 ? void 0 : optionalParams.environment) || "MAINNET";
        const sourceChainProvider = (optionalParams === null || optionalParams === void 0 ? void 0 : optionalParams.sourceChainProvider) ||
            getDefaultProvider(environment, sourceChain);
        if (!sourceChainProvider)
            throw Error("No default RPC for this chain; pass in your own provider (as sourceChainProvider)");
        const wormholeRelayerAddress = (optionalParams === null || optionalParams === void 0 ? void 0 : optionalParams.wormholeRelayerAddress) ||
            getWormholeRelayerAddress(sourceChain, environment);
        const sourceWormholeRelayer = ethers_contracts.IWormholeRelayer__factory.connect(wormholeRelayerAddress, sourceChainProvider);
        const deliveryProviderAddress = (optionalParams === null || optionalParams === void 0 ? void 0 : optionalParams.deliveryProviderAddress) ||
            (yield sourceWormholeRelayer.getDefaultDeliveryProvider());
        const targetChainId = CHAINS[targetChain];
        const priceAndRefundInfo = yield sourceWormholeRelayer["quoteEVMDeliveryPrice(uint16,uint256,uint256,address)"](targetChainId, (optionalParams === null || optionalParams === void 0 ? void 0 : optionalParams.receiverValue) || 0, gasAmount, deliveryProviderAddress);
        return priceAndRefundInfo;
    });
}
export function getPrice(sourceChain, targetChain, gasAmount, optionalParams) {
    return __awaiter(this, void 0, void 0, function* () {
        const priceAndRefundInfo = yield getPriceAndRefundInfo(sourceChain, targetChain, gasAmount, optionalParams);
        return priceAndRefundInfo[0];
    });
}
export function getWormholeRelayerInfo(sourceChain, sourceTransaction, infoRequest) {
    var _a, _b, _c, _d;
    return __awaiter(this, void 0, void 0, function* () {
        const environment = (infoRequest === null || infoRequest === void 0 ? void 0 : infoRequest.environment) || "MAINNET";
        const sourceChainProvider = (infoRequest === null || infoRequest === void 0 ? void 0 : infoRequest.sourceChainProvider) ||
            getDefaultProvider(environment, sourceChain);
        if (!sourceChainProvider)
            throw Error("No default RPC for this chain; pass in your own provider (as sourceChainProvider)");
        const receipt = yield sourceChainProvider.getTransactionReceipt(sourceTransaction);
        if (!receipt)
            throw Error("Transaction has not been mined");
        const bridgeAddress = CONTRACTS[environment][sourceChain].core;
        const wormholeRelayerAddress = ((_a = infoRequest === null || infoRequest === void 0 ? void 0 : infoRequest.wormholeRelayerAddresses) === null || _a === void 0 ? void 0 : _a.get(sourceChain)) ||
            getWormholeRelayerAddress(sourceChain, environment);
        if (!bridgeAddress || !wormholeRelayerAddress) {
            throw Error(`Invalid chain ID or network: Chain ${sourceChain}, ${environment}`);
        }
        const deliveryLog = getWormholeRelayerLog(receipt, bridgeAddress, tryNativeToHexString(wormholeRelayerAddress, "ethereum"), (infoRequest === null || infoRequest === void 0 ? void 0 : infoRequest.wormholeRelayerWhMessageIndex)
            ? infoRequest.wormholeRelayerWhMessageIndex
            : 0);
        const { type, parsed } = parseWormholeLog(deliveryLog.log);
        const instruction = parsed;
        const targetChainId = instruction.targetChainId;
        if (!isChain(targetChainId))
            throw Error(`Invalid Chain: ${targetChainId}`);
        const targetChain = CHAIN_ID_TO_NAME[targetChainId];
        const targetChainProvider = ((_b = infoRequest === null || infoRequest === void 0 ? void 0 : infoRequest.targetChainProviders) === null || _b === void 0 ? void 0 : _b.get(targetChain)) ||
            getDefaultProvider(environment, targetChain);
        if (!targetChainProvider) {
            throw Error("No default RPC for this chain; pass in your own provider (as targetChainProvider)");
        }
        const [blockStartNumber, blockEndNumber] = ((_c = infoRequest === null || infoRequest === void 0 ? void 0 : infoRequest.targetChainBlockRanges) === null || _c === void 0 ? void 0 : _c.get(targetChain)) ||
            getBlockRange(targetChainProvider);
        const targetChainStatus = yield getWormholeRelayerInfoBySourceSequence(environment, targetChain, targetChainProvider, sourceChain, BigNumber.from(deliveryLog.sequence), blockStartNumber, blockEndNumber, ((_d = infoRequest === null || infoRequest === void 0 ? void 0 : infoRequest.wormholeRelayerAddresses) === null || _d === void 0 ? void 0 : _d.get(targetChain)) ||
            getWormholeRelayerAddress(targetChain, environment));
        return {
            type: RelayerPayloadId.Delivery,
            sourceChain: sourceChain,
            sourceTransactionHash: sourceTransaction,
            sourceDeliverySequenceNumber: BigNumber.from(deliveryLog.sequence).toNumber(),
            deliveryInstruction: instruction,
            targetChainStatus,
        };
    });
}
export function printWormholeRelayerInfo(info) {
    console.log(stringifyWormholeRelayerInfo(info));
}
export function stringifyWormholeRelayerInfo(info, excludeSourceInformation, overrides) {
    let stringifiedInfo = "";
    if (info.type == RelayerPayloadId.Delivery &&
        info.deliveryInstruction.targetAddress.toString("hex") !==
            "0000000000000000000000000000000000000000000000000000000000000000") {
        if (!excludeSourceInformation) {
            stringifiedInfo += `Found delivery request in transaction ${info.sourceTransactionHash} on ${info.sourceChain}\nfrom sender ${info.deliveryInstruction.senderAddress.toString("hex")} from ${info.sourceChain} with delivery sequence number ${info.sourceDeliverySequenceNumber}\n`;
        }
        else {
            stringifiedInfo += `Found delivery request from sender ${info.deliveryInstruction.senderAddress.toString("hex")}\n`;
        }
        const numMsgs = info.deliveryInstruction.vaaKeys.length;
        const payload = info.deliveryInstruction.payload.toString("hex");
        if (payload.length > 0) {
            stringifiedInfo += `\nPayload to be relayed (as hex string): 0x${payload}`;
        }
        if (numMsgs > 0) {
            stringifiedInfo += `\nThe following ${numMsgs} wormhole messages (VAAs) were ${payload.length > 0 ? "also " : ""}requested to be relayed:\n`;
            stringifiedInfo += info.deliveryInstruction.vaaKeys
                .map((msgInfo, i) => {
                var _a;
                let result = "";
                result += `(VAA ${i}): `;
                result += `Message from ${msgInfo.chainId ? printChain(msgInfo.chainId) : ""}, with emitter address ${(_a = msgInfo.emitterAddress) === null || _a === void 0 ? void 0 : _a.toString("hex")} and sequence number ${msgInfo.sequence}`;
                return result;
            })
                .join(",\n");
        }
        if (payload.length == 0 && numMsgs == 0) {
            stringifiedInfo += `\nAn empty payload was requested to be sent`;
        }
        const instruction = info.deliveryInstruction;
        if (overrides) {
            instruction.requestedReceiverValue = overrides.newReceiverValue;
            instruction.encodedExecutionInfo = overrides.newExecutionInfo;
        }
        const targetChainName = CHAIN_ID_TO_NAME[instruction.targetChainId];
        stringifiedInfo += `${numMsgs == 0
            ? payload.length == 0
                ? ""
                : "\n\nPayload was requested to be relayed"
            : "\n\nThese were requested to be sent"} to 0x${instruction.targetAddress.toString("hex")} on ${printChain(instruction.targetChainId)}\n`;
        const totalReceiverValue = instruction.requestedReceiverValue.add(instruction.extraReceiverValue);
        stringifiedInfo += totalReceiverValue.gt(0)
            ? `Amount to pass into target address: ${ethers.utils.formatEther(totalReceiverValue)} of ${targetChainName} currency ${instruction.extraReceiverValue.gt(0)
                ? `\n${ethers.utils.formatEther(instruction.requestedReceiverValue)} requested, ${ethers.utils.formatEther(instruction.extraReceiverValue)} additionally paid for`
                : ""}\n`
            : ``;
        const [executionInfo] = parseEVMExecutionInfoV1(instruction.encodedExecutionInfo, 0);
        stringifiedInfo += `Gas limit: ${executionInfo.gasLimit} ${targetChainName} gas\n`;
        const refundAddressChosen = instruction.refundAddress !== instruction.refundDeliveryProvider;
        if (refundAddressChosen) {
            stringifiedInfo += `Refund rate: ${ethers.utils.formatEther(executionInfo.targetChainRefundPerGasUnused)} of ${targetChainName} currency per unit of gas unused\n`;
            stringifiedInfo += `Refund address: ${instruction.refundAddress.toString("hex")}\n`;
        }
        stringifiedInfo += `\n`;
        stringifiedInfo += info.targetChainStatus.events
            .map((e, i) => `Delivery attempt ${i + 1}: ${e.transactionHash
            ? ` ${targetChainName} transaction hash: ${e.transactionHash}`
            : ""}\nStatus: ${e.status}\n${e.revertString
            ? `Failure reason: ${e.gasUsed.eq(executionInfo.gasLimit)
                ? "Gas limit hit"
                : e.revertString}\n`
            : ""}Gas used: ${e.gasUsed.toString()}\nTransaction fee used: ${ethers.utils.formatEther(executionInfo.targetChainRefundPerGasUnused.mul(e.gasUsed))} of ${targetChainName} currency\n${!refundAddressChosen || e.status === "Forward Request Success"
            ? ""
            : `Refund amount: ${ethers.utils.formatEther(executionInfo.targetChainRefundPerGasUnused.mul(executionInfo.gasLimit.sub(e.gasUsed)))} of ${targetChainName} currency \nRefund status: ${e.refundStatus}\n`}`)
            .join("\n");
    }
    else if (info.type == RelayerPayloadId.Delivery &&
        info.deliveryInstruction.targetAddress.toString("hex") ===
            "0000000000000000000000000000000000000000000000000000000000000000") {
        stringifiedInfo += `Found delivery request in transaction ${info.sourceTransactionHash} on ${info.sourceChain}\n`;
        const instruction = info.deliveryInstruction;
        const targetChainName = CHAIN_ID_TO_NAME[instruction.targetChainId];
        stringifiedInfo += `\nA refund of ${ethers.utils.formatEther(instruction.extraReceiverValue)} ${targetChainName} currency was requested to be sent to ${targetChainName}, address 0x${info.deliveryInstruction.refundAddress.toString("hex")}\n\n`;
        stringifiedInfo += info.targetChainStatus.events
            .map((e, i) => `Delivery attempt ${i + 1}: ${e.transactionHash
            ? ` ${targetChainName} transaction hash: ${e.transactionHash}`
            : ""}\nStatus: ${e.refundStatus == RefundStatus.RefundSent
            ? "Refund Successful"
            : "Refund Failed"}`)
            .join("\n");
    }
    return stringifiedInfo;
}
