"use strict";
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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCCTPMessageLogURL = exports.getDeliveryHashFromLog = exports.getDeliveryHash = exports.getRelayerTransactionHashFromWormscan = exports.getWormscanRelayerInfo = exports.getWormscanInfo = exports.getDeliveryHashFromVaaFields = exports.getWormholeRelayerInfoByHash = exports.vaaKeyToVaaKeyStruct = exports.getWormholeLog = exports.transformDeliveryLog = exports.deliveryStatus = exports.getWormholeRelayerDeliveryEventsBySourceSequence = exports.getWormholeRelayerInfoBySourceSequence = exports.getDeliveryProvider = exports.getDefaultProvider = exports.estimatedAttestationTimeInSeconds = exports.printCCTPDomain = exports.printChain = exports.parseWormholeLog = void 0;
const __1 = require("../../");
const ethers_1 = require("ethers");
const consts_1 = require("../consts");
const structs_1 = require("../structs");
const ethers_contracts_1 = require("../../ethers-contracts/");
function parseWormholeLog(log) {
    const abi = [
        "event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel)",
    ];
    const iface = new ethers_1.ethers.utils.Interface(abi);
    const parsed = iface.parseLog(log);
    const payload = Buffer.from(parsed.args.payload.substring(2), "hex");
    const type = structs_1.parseWormholeRelayerPayloadType(payload);
    if (type == structs_1.RelayerPayloadId.Delivery) {
        return { type, parsed: structs_1.parseWormholeRelayerSend(payload) };
    }
    else if (type == structs_1.RelayerPayloadId.Redelivery) {
        return { type, parsed: structs_1.parseWormholeRelayerResend(payload) };
    }
    else {
        throw Error("Invalid wormhole log");
    }
}
exports.parseWormholeLog = parseWormholeLog;
function printChain(chainId) {
    if (!(chainId in __1.CHAIN_ID_TO_NAME))
        throw Error(`Invalid Chain ID: ${chainId}`);
    return `${__1.CHAIN_ID_TO_NAME[chainId]} (Chain ${chainId})`;
}
exports.printChain = printChain;
function printCCTPDomain(domain) {
    if (consts_1.getNameFromCCTPDomain(domain) === undefined)
        throw Error(`Invalid cctp domain: ${domain}`);
    return `${consts_1.getNameFromCCTPDomain(domain)} (Domain ${domain})`;
}
exports.printCCTPDomain = printCCTPDomain;
const estimatedAttestationTimeInSeconds = (sourceChain, environment) => {
    const testnetTime = sourceChain === "avalanche" ? 20 : 60;
    const mainnetTime = sourceChain === "avalanche" ? 20 : 60 * 13;
    return environment === "TESTNET" ? testnetTime : mainnetTime;
};
exports.estimatedAttestationTimeInSeconds = estimatedAttestationTimeInSeconds;
function getDefaultProvider(network, chain, ci) {
    let rpc = "";
    if (ci) {
        if (chain == "ethereum")
            rpc = "http://eth-devnet:8545";
        else if (chain == "bsc")
            rpc = "http://eth-devnet2:8545";
        else
            throw Error(`This chain isn't in CI for relayers: ${chain}`);
    }
    else {
        rpc = consts_1.RPCS_BY_CHAIN[network][chain];
    }
    if (!rpc) {
        throw Error(`No default RPC for chain ${chain} or network ${network}`);
    }
    return new ethers_1.ethers.providers.StaticJsonRpcProvider(rpc);
}
exports.getDefaultProvider = getDefaultProvider;
function getDeliveryProvider(address, provider) {
    const contract = ethers_contracts_1.DeliveryProvider__factory.connect(address, provider);
    return contract;
}
exports.getDeliveryProvider = getDeliveryProvider;
function getWormholeRelayerInfoBySourceSequence(environment, targetChain, targetChainProvider, sourceChain, sourceVaaSequence, blockRange, targetWormholeRelayerAddress) {
    return __awaiter(this, void 0, void 0, function* () {
        const deliveryEvents = yield getWormholeRelayerDeliveryEventsBySourceSequence(environment, targetChain, targetChainProvider, sourceChain, sourceVaaSequence, blockRange, targetWormholeRelayerAddress);
        return deliveryEvents;
    });
}
exports.getWormholeRelayerInfoBySourceSequence = getWormholeRelayerInfoBySourceSequence;
function getWormholeRelayerDeliveryEventsBySourceSequence(environment, targetChain, targetChainProvider, sourceChain, sourceVaaSequence, blockRange, targetWormholeRelayerAddress) {
    return __awaiter(this, void 0, void 0, function* () {
        let sourceChainId = undefined;
        if (sourceChain) {
            sourceChainId = __1.CHAINS[sourceChain];
            if (!sourceChainId)
                throw Error(`Invalid source chain: ${sourceChain}`);
        }
        const wormholeRelayer = consts_1.getWormholeRelayer(targetChain, environment, targetChainProvider, targetWormholeRelayerAddress);
        const deliveryEventsFilter = wormholeRelayer.filters.Delivery(null, sourceChainId, sourceVaaSequence);
        const deliveryEvents = yield wormholeRelayer.queryFilter(deliveryEventsFilter, blockRange ? blockRange[0] : -2000, blockRange ? blockRange[1] : "latest");
        const timestamps = yield Promise.all(deliveryEvents.map((e) => __awaiter(this, void 0, void 0, function* () { return (yield targetChainProvider.getBlock(e.blockNumber)).timestamp * 1000; })));
        // There is a max limit on RPCs sometimes for how many blocks to query
        return yield transformDeliveryEvents(deliveryEvents, timestamps);
    });
}
exports.getWormholeRelayerDeliveryEventsBySourceSequence = getWormholeRelayerDeliveryEventsBySourceSequence;
function deliveryStatus(status) {
    switch (status) {
        case 0:
            return structs_1.DeliveryStatus.DeliverySuccess;
        case 1:
            return structs_1.DeliveryStatus.ReceiverFailure;
        default:
            return structs_1.DeliveryStatus.ThisShouldNeverHappen;
    }
}
exports.deliveryStatus = deliveryStatus;
function transformDeliveryLog(log, timestamp) {
    const status = deliveryStatus(log.args[4]);
    if (!__1.isChain(log.args[1]))
        throw Error(`Invalid source chain id: ${log.args[1]}`);
    const sourceChain = __1.CHAIN_ID_TO_NAME[log.args[1]];
    return {
        status,
        transactionHash: log.transactionHash,
        vaaHash: log.args[3],
        sourceVaaSequence: log.args[2],
        sourceChain,
        gasUsed: ethers_1.BigNumber.from(log.args[5]),
        refundStatus: structs_1.parseRefundStatus(log.args[6]),
        revertString: status == structs_1.DeliveryStatus.ReceiverFailure ? log.args[7] : undefined,
        timestamp,
        overrides: Buffer.from(log.args[8].substring(2), "hex").length > 0
            ? structs_1.parseOverrideInfoFromDeliveryEvent(Buffer.from(log.args[8].substring(2), "hex"))
            : undefined,
    };
}
exports.transformDeliveryLog = transformDeliveryLog;
function transformDeliveryEvents(events, timestamps) {
    return __awaiter(this, void 0, void 0, function* () {
        return events.map((x, i) => transformDeliveryLog(x, timestamps[i]));
    });
}
function getWormholeLog(receipt, bridgeAddress, emitterAddress, index, sequence) {
    const bridgeLogs = receipt.logs.filter((l) => {
        return l.address === bridgeAddress;
    });
    if (bridgeLogs.length == 0) {
        throw Error("No core contract interactions found for this transaction.");
    }
    const parsed = bridgeLogs.map((bridgeLog) => {
        const log = ethers_contracts_1.Implementation__factory.createInterface().parseLog(bridgeLog);
        return {
            sequence: log.args[1].toString(),
            nonce: log.args[2].toString(),
            emitterAddress: __1.tryNativeToHexString(log.args[0].toString(), "ethereum"),
            payload: log.args[3],
            log: bridgeLog,
        };
    });
    const filtered = parsed.filter((x) => {
        return (x.emitterAddress == emitterAddress.toLowerCase() &&
            (sequence === undefined ? true : x.sequence + "" === sequence + ""));
    });
    if (filtered.length == 0) {
        throw Error(`No wormhole contract interactions found for this transaction, with emitter address ${emitterAddress} ${sequence === undefined ? "" : `and sequence ${sequence}`}`);
    }
    if (index >= filtered.length) {
        throw Error("Specified delivery index is out of range.");
    }
    else {
        return {
            log: filtered[index].log,
            sequence: filtered[index].sequence,
            payload: filtered[index].payload,
        };
    }
}
exports.getWormholeLog = getWormholeLog;
function vaaKeyToVaaKeyStruct(vaaKey) {
    return {
        chainId: vaaKey.chainId || 0,
        emitterAddress: vaaKey.emitterAddress ||
            "0x0000000000000000000000000000000000000000000000000000000000000000",
        sequence: vaaKey.sequence || 0,
    };
}
exports.vaaKeyToVaaKeyStruct = vaaKeyToVaaKeyStruct;
function getWormholeRelayerInfoByHash(deliveryHash, targetChain, sourceChain, sourceVaaSequence, infoRequest) {
    var _a, _b;
    return __awaiter(this, void 0, void 0, function* () {
        const environment = (infoRequest === null || infoRequest === void 0 ? void 0 : infoRequest.environment) || "MAINNET";
        const targetChainProvider = ((_a = infoRequest === null || infoRequest === void 0 ? void 0 : infoRequest.targetChainProviders) === null || _a === void 0 ? void 0 : _a.get(targetChain)) ||
            getDefaultProvider(environment, targetChain);
        if (!targetChainProvider) {
            throw Error("No default RPC for this chain; pass in your own provider (as targetChainProvider)");
        }
        const targetWormholeRelayerAddress = ((_b = infoRequest === null || infoRequest === void 0 ? void 0 : infoRequest.wormholeRelayerAddresses) === null || _b === void 0 ? void 0 : _b.get(targetChain)) ||
            consts_1.getWormholeRelayerAddress(targetChain, environment);
        const wormholeRelayer = consts_1.getWormholeRelayer(targetChain, environment, targetChainProvider, targetWormholeRelayerAddress);
        const blockNumberSuccess = yield wormholeRelayer.deliverySuccessBlock(deliveryHash);
        const blockNumberFailure = yield wormholeRelayer.deliveryFailureBlock(deliveryHash);
        const blockNumber = blockNumberSuccess.gt(0)
            ? blockNumberSuccess
            : blockNumberFailure;
        if (blockNumber.toNumber() === 0)
            return [];
        // There is weirdness with arbitrum where if you call 'block.number', it gives you the L1 block number (the ethereum one) - and this is what is stored in the 'replay protection mapping' - so basically that value isn't useful in finding the delivery here
        const blockRange = (infoRequest === null || infoRequest === void 0 ? void 0 : infoRequest.targetBlockRange) ||
            (targetChain === "arbitrum"
                ? undefined
                : [blockNumber.toNumber(), blockNumber.toNumber()]);
        return yield getWormholeRelayerInfoBySourceSequence(environment, targetChain, targetChainProvider, sourceChain, ethers_1.BigNumber.from(sourceVaaSequence), blockRange, targetWormholeRelayerAddress);
    });
}
exports.getWormholeRelayerInfoByHash = getWormholeRelayerInfoByHash;
function getDeliveryHashFromVaaFields(sourceChain, emitterAddress, sequence, timestamp, nonce, consistencyLevel, deliveryVaaPayload) {
    const body = ethers_1.ethers.utils.solidityPack(["uint32", "uint32", "uint16", "bytes32", "uint64", "uint8", "bytes"], [
        timestamp,
        nonce,
        sourceChain,
        emitterAddress,
        sequence,
        consistencyLevel,
        deliveryVaaPayload,
    ]);
    const deliveryHash = ethers_1.ethers.utils.keccak256(ethers_1.ethers.utils.keccak256(body));
    return deliveryHash;
}
exports.getDeliveryHashFromVaaFields = getDeliveryHashFromVaaFields;
function getWormscanInfo(network, sourceChain, sequence, emitterAddress) {
    return __awaiter(this, void 0, void 0, function* () {
        const wormscanAPI = consts_1.getWormscanAPI(network);
        const emitterAddressBytes32 = __1.tryNativeToHexString(emitterAddress, sourceChain);
        const sourceChainId = __1.CHAINS[sourceChain];
        const result = yield fetch(`${wormscanAPI}api/v1/vaas/${sourceChainId}/${emitterAddressBytes32}/${sequence}`);
        return result;
    });
}
exports.getWormscanInfo = getWormscanInfo;
function getWormscanRelayerInfo(sourceChain, sequence, optionalParams) {
    return __awaiter(this, void 0, void 0, function* () {
        const network = (optionalParams === null || optionalParams === void 0 ? void 0 : optionalParams.network) || "MAINNET";
        const wormholeRelayerAddress = (optionalParams === null || optionalParams === void 0 ? void 0 : optionalParams.wormholeRelayerAddress) ||
            consts_1.getWormholeRelayerAddress(sourceChain, network);
        return getWormscanInfo(network, sourceChain, sequence, wormholeRelayerAddress);
    });
}
exports.getWormscanRelayerInfo = getWormscanRelayerInfo;
function getRelayerTransactionHashFromWormscan(sourceChain, sequence, optionalParams) {
    return __awaiter(this, void 0, void 0, function* () {
        const wormscanData = (yield (yield getWormscanRelayerInfo(sourceChain, sequence, optionalParams)).json()).data;
        return "0x" + wormscanData.txHash;
    });
}
exports.getRelayerTransactionHashFromWormscan = getRelayerTransactionHashFromWormscan;
function getDeliveryHash(rx, sourceChain, optionalParams) {
    var _a;
    return __awaiter(this, void 0, void 0, function* () {
        const network = (optionalParams === null || optionalParams === void 0 ? void 0 : optionalParams.network) || "MAINNET";
        const provider = (optionalParams === null || optionalParams === void 0 ? void 0 : optionalParams.provider) || getDefaultProvider(network, sourceChain);
        const wormholeAddress = __1.CONTRACTS[network][sourceChain].core;
        if (!wormholeAddress) {
            throw Error(`No wormhole contract on ${sourceChain} for ${network}`);
        }
        const wormholeRelayerAddress = (optionalParams === null || optionalParams === void 0 ? void 0 : optionalParams.wormholeRelayerAddress) ||
            ((_a = consts_1.RELAYER_CONTRACTS[network][sourceChain]) === null || _a === void 0 ? void 0 : _a.wormholeRelayerAddress);
        if (!wormholeRelayerAddress) {
            throw Error(`No wormhole relayer contract on ${sourceChain} for ${network}`);
        }
        const logs = rx.logs.filter((log) => log.address.toLowerCase() === wormholeAddress.toLowerCase() &&
            log.topics[1].toLowerCase() ===
                "0x" +
                    __1.tryNativeToHexString(wormholeRelayerAddress, "ethereum").toLowerCase());
        const index = (optionalParams === null || optionalParams === void 0 ? void 0 : optionalParams.index) || 0;
        if (logs.length === 0)
            throw Error(`No wormhole relayer log found${index > 0 ? ` (the ${index}-th wormhole relayer log was requested)` : ""}`);
        return getDeliveryHashFromLog(logs[index], __1.CHAINS[sourceChain], provider, rx.blockHash);
    });
}
exports.getDeliveryHash = getDeliveryHash;
function getDeliveryHashFromLog(wormholeLog, sourceChain, provider, blockHash) {
    return __awaiter(this, void 0, void 0, function* () {
        const wormholePublishedMessage = ethers_contracts_1.Implementation__factory.createInterface().parseLog(wormholeLog);
        const block = yield provider.getBlock(blockHash);
        return getDeliveryHashFromVaaFields(sourceChain, wormholeLog.topics[1], wormholePublishedMessage.args["sequence"], block.timestamp, wormholePublishedMessage.args["nonce"], wormholePublishedMessage.args["consistencyLevel"], wormholePublishedMessage.args["payload"]);
    });
}
exports.getDeliveryHashFromLog = getDeliveryHashFromLog;
function getCCTPMessageLogURL(cctpKey, sourceChain, receipt, environment) {
    return __awaiter(this, void 0, void 0, function* () {
        let cctpLog;
        let messageSentLog;
        const DepositForBurnTopic = ethers_1.ethers.utils.keccak256(ethers_1.ethers.utils.toUtf8Bytes("DepositForBurn(uint64,address,uint256,address,bytes32,uint32,bytes32,bytes32)"));
        const MessageSentTopic = ethers_1.ethers.utils.keccak256(ethers_1.ethers.utils.toUtf8Bytes("MessageSent(bytes)"));
        try {
            if (consts_1.getNameFromCCTPDomain(cctpKey.domain) === sourceChain) {
                const cctpLogFilter = (log) => {
                    return (log.topics[0] === DepositForBurnTopic &&
                        parseInt(log.topics[1]) === cctpKey.nonce.toNumber());
                };
                cctpLog = receipt.logs.find(cctpLogFilter);
                const index = receipt.logs.findIndex(cctpLogFilter);
                const messageSentLogs = receipt.logs.filter((log, i) => {
                    return log.topics[0] === MessageSentTopic && i <= index;
                });
                messageSentLog = messageSentLogs[messageSentLogs.length - 1];
            }
        }
        catch (e) {
            console.log(e);
        }
        if (!cctpLog || !messageSentLog)
            return undefined;
        const message = new ethers_1.ethers.utils.Interface([
            "event MessageSent(bytes message)",
        ]).parseLog(messageSentLog).args.message;
        const msgHash = ethers_1.ethers.utils.keccak256(message);
        const url = consts_1.getCircleAPI(environment) + msgHash;
        return { message, cctpLog, url };
    });
}
exports.getCCTPMessageLogURL = getCCTPMessageLogURL;
