import { PUBLIC_NETWORKS, CCTP_NO_DOMAIN, PRODUCTION_NETWORKS, TEST_NETWORKS } from "@across-protocol/constants";
import { fetchWithTimeout } from "./FetchUtils";
import { BigNumber, ethers } from "ethers";
import { isDefined } from "./TypeGuards";
import { chainIsProd } from "./NetworkUtils";
import assert from "assert";
import { bnZero } from "./BigNumberUtils";
export const CCTPV2_FINALITY_THRESHOLD_STANDARD = 2000;
export const CCTPV2_FINALITY_THRESHOLD_FAST = 1000;
const CCTP_MESSAGE_SENT_TOPIC_HASH = ethers.utils.id("MessageSent(bytes)");
const CCTP_DEPOSIT_FOR_BURN_TOPIC_HASH_V1 = ethers.utils.id("DepositForBurn(uint64,address,uint256,address,bytes32,uint32,bytes32,bytes32)");
const CCTP_DEPOSIT_FOR_BURN_TOPIC_HASH_V2 = ethers.utils.id("DepositForBurn(address,uint256,address,bytes32,uint32,bytes32,bytes32,uint256,uint32,bytes)");
/**
 * @notice Converts an ETH Address string to a 32-byte hex string.
 * @param address The address to convert.
 * @returns The 32-byte hex string representation of the address - required for CCTP messages.
 */
export function cctpAddressToBytes32(address) {
    return ethers.utils.hexZeroPad(address, 32);
}
/**
 * Converts a 32-byte hex string with padding to a standard ETH address.
 * @param bytes32 The 32-byte hex string to convert.
 * @returns The ETH address representation of the 32-byte hex string.
 */
export function cctpBytes32ToAddress(bytes32) {
    // Grab the last 20 bytes of the 32-byte hex string
    return ethers.utils.getAddress(ethers.utils.hexDataSlice(bytes32, 12));
}
/**
 * @notice Returns the CCTP domain for a given chain ID. Throws if the chain ID is not a CCTP domain.
 * @param chainId
 * @returns CCTP Domain ID
 */
export function getCctpDomainForChainId(chainId) {
    const cctpDomain = PUBLIC_NETWORKS[chainId]?.cctpDomain;
    if (!isDefined(cctpDomain) || cctpDomain === CCTP_NO_DOMAIN) {
        throw new Error(`No CCTP domain found for chainId: ${chainId}`);
    }
    return cctpDomain;
}
/**
 * @notice Returns the chain ID for a given CCTP domain. Inverse functionof `getCctpDomainForChainId()`. However,
 * since CCTP Domains are shared between production and test networks, we need to use the `productionNetworks` flag
 * to determine whether to return the production  or test network chain ID.
 * @param domain CCTP domain ID.
 * @param productionNetworks Whether to return the production or test network chain ID.
 * @returns Chain ID.
 */
export function getCctpDestinationChainFromDomain(domain, productionNetworks) {
    if (domain === CCTP_NO_DOMAIN) {
        throw new Error("Cannot input CCTP_NO_DOMAIN to getCctpDestinationChainFromDomain");
    }
    // Test and Production networks use the same CCTP domain, so we need to use the flag passed in to
    // determine whether to use the Test or Production networks.
    const networks = productionNetworks ? PRODUCTION_NETWORKS : TEST_NETWORKS;
    const otherNetworks = productionNetworks ? TEST_NETWORKS : PRODUCTION_NETWORKS;
    const chainId = Object.keys(networks).find((key) => networks[Number(key)].cctpDomain.toString() === domain.toString());
    if (!isDefined(chainId)) {
        const chainId = Object.keys(otherNetworks).find((key) => otherNetworks[Number(key)].cctpDomain.toString() === domain.toString());
        if (!isDefined(chainId)) {
            throw new Error(`No chainId found for domain: ${domain}`);
        }
        return parseInt(chainId);
    }
    return parseInt(chainId);
}
/**
 * @notice Typeguard. Returns whether the event is a CCTP deposit for burn event. Should work for V1 and V2
 * @param event CCTP message event.
 * @returns True if the event is a CCTP deposit for burn event.
 */
export function isDepositForBurnEvent(event) {
    return "amount" in event && "mintRecipient" in event && "burnToken" in event;
}
/**
 * @notice Fetches CCTP V2 attestations for a given list of transaction hashes. If a transaction hash
 * contains multiple CCTP messages, this will return an object where each key is a transaction hash and
 * a value is an array of attestations.
 * @param depositForBurnTxnHashes List of transaction hashes to fetch attestations for.
 * @param sourceChainId Source chain ID of the transaction hashes.
 * @returns Object with transaction hashes as keys and CCTP V2 attestations as values.
 */
export async function fetchCctpV2Attestations(depositForBurnTxnHashes, sourceChainId) {
    // For v2, we fetch an API response for every txn hash we have. API returns an array of both v1 and v2 attestations
    const sourceDomainId = getCctpDomainForChainId(sourceChainId);
    const isMainnet = chainIsProd(sourceChainId);
    // Circle rate limit is 35 requests / second. To avoid getting banned, batch calls into chunks with 1 second delay between chunks
    // For v2, this is actually required because we don't know if message is finalized or not before hitting the API. Therefore as our
    // CCTP v2 list of chains grows, we might require more than 35 calls here to fetch all attestations
    const attestationResponses = {};
    const chunkSize = process.env.CCTP_API_REQUEST_CHUNK_SIZE ? parseInt(process.env.CCTP_API_REQUEST_CHUNK_SIZE) : 8;
    for (let i = 0; i < depositForBurnTxnHashes.length; i += chunkSize) {
        const chunk = depositForBurnTxnHashes.slice(i, i + chunkSize);
        await Promise.all(chunk.map(async (txHash) => {
            const attestations = await fetchAttestationsForTxn(sourceDomainId, txHash, isMainnet);
            // If multiple deposit for burn events, there will be multiple attestations.
            attestationResponses[txHash] = attestations;
        }));
        if (i + chunkSize < depositForBurnTxnHashes.length) {
            await new Promise((resolve) => setTimeout(resolve, 1000));
        }
    }
    return attestationResponses;
}
/**
 * @notice Returns the status of a CCTP attestation.
 * @param attestation Attestation to get the status of.
 * @returns "finalized","pending" or "ready".
 */
export function getPendingAttestationStatus(attestation) {
    if (!isDefined(attestation.attestation)) {
        return "pending";
    }
    else {
        return attestation.status === "pending_confirmations" || attestation.attestation === "PENDING"
            ? "pending"
            : "ready";
    }
}
/**
 * @notice Checks if a CCTP message has been processed by a given contract.
 * @param nonceHash Nonce hash to check.
 * @param contract
 * @returns True if the message has been processed, false otherwise.
 */
export async function hasCCTPMessageBeenProcessedEvm(nonceHash, contract) {
    const resultingCall = await contract.callStatic.usedNonces(nonceHash);
    // If the resulting call is 1, the message has been processed. If it is 0, the message has not been processed.
    return (resultingCall ?? bnZero).toNumber() === 1;
}
/**
 * @notice The maximum amount of USDC that can be sent using a fast transfer.
 * @param isMainnet Toggles whether to call CCTP API on mainnet or sandbox environment.
 * @returns USDC amount in units of USDC.
 * @link https://developers.circle.com/api-reference/cctp/all/get-fast-burn-usdc-allowance
 */
export async function getV2FastBurnAllowance(isMainnet) {
    const data = await fetchWithTimeout(`https://iris-api${isMainnet ? "" : "-sandbox"}.circle.com/v2/fastBurn/USDC/allowance`);
    return data.allowance.toString();
}
/**
 * Returns the minimum transfer fees required for a transfer to be relayed. When calling depositForBurn(), the maxFee
 * parameter must be greater than or equal to the minimum fee.
 * @param sourceChainId The source chain ID of the transfer.
 * @param destinationChainId The destination chain ID of the transfer.
 * @param isMainnet Toggles whether to call CCTP API on mainnet or sandbox environment.
 * @returns The standard and fast transfer fees for the given source and destination chains.
 * @link https://developers.circle.com/api-reference/cctp/all/get-burn-usdc-fees
 */
export async function getV2MinTransferFees(sourceChainId, destinationChainId) {
    const isMainnet = chainIsProd(destinationChainId);
    const sourceDomain = getCctpDomainForChainId(sourceChainId);
    const destinationDomain = getCctpDomainForChainId(destinationChainId);
    const endpoint = `https://iris-api${isMainnet ? "" : "-sandbox"}.circle.com/v2/burn/USDC/fees/${sourceDomain}/${destinationDomain}`;
    const data = await fetchWithTimeout(endpoint);
    const standardFee = data.find((fee) => fee.finalityThreshold === CCTPV2_FINALITY_THRESHOLD_STANDARD);
    assert(isDefined(standardFee?.minimumFee), `CCTPUtils#getTransferFees: Standard fee not found in API response: ${endpoint}`);
    const fastFee = data.find((fee) => fee.finalityThreshold === CCTPV2_FINALITY_THRESHOLD_FAST);
    assert(isDefined(fastFee?.minimumFee), `CCTPUtils#getTransferFees: Fast fee not found in API response: ${endpoint}`);
    return {
        standard: BigNumber.from(standardFee.minimumFee),
        fast: BigNumber.from(fastFee.minimumFee),
    };
}
/**
 * @notice Fetches attestations for a given transaction hash. If transaction hash contains multiple CCTP
 * messages, this will return an array of attestations. Should work for both v1 and v2.
 * @param sourceDomainId
 * @param transactionHash
 * @param isMainnet
 * @returns Attestation response, list of messages with attestations.
 */
export async function fetchAttestationsForTxn(sourceDomainId, transactionHash, isMainnet) {
    return await fetchWithTimeout(`https://iris-api${isMainnet ? "" : "-sandbox"}.circle.com/v2/messages/${sourceDomainId}?transactionHash=${transactionHash}`);
}
/**
 * @notice Returns the CCTP version of the `MessageSent` event.
 * @param log CCTP event log.
 * @returns 0 for v1 `MessageSent` event, 1 for v2, -1 for other events
 */
export function getMessageSentVersion(log) {
    if (log.topics[0] !== CCTP_MESSAGE_SENT_TOPIC_HASH) {
        return -1;
    }
    // v1 and v2 have the same topic hash, so we have to do a bit of decoding here to understand the version
    const messageBytes = ethers.utils.defaultAbiCoder.decode(["bytes"], log.data)[0];
    // Source: https://developers.circle.com/stablecoins/message-format
    const version = parseInt(messageBytes.slice(2, 10), 16); // read version: first 4 bytes (skipping '0x')
    return version;
}
/**
 * @notice Returns the CCTP version of the `DepositForBurn` event.
 * @param log CCTP event log.
 * @returns 0 for v1 `DepositForBurn` event, 1 for v2, -1 for other events
 */
export function getDepositForBurnVersion(log) {
    const topic = log.topics[0];
    switch (topic) {
        case CCTP_DEPOSIT_FOR_BURN_TOPIC_HASH_V1:
            return 0;
        case CCTP_DEPOSIT_FOR_BURN_TOPIC_HASH_V2:
            return 1;
        default:
            return -1;
    }
}
//# sourceMappingURL=CCTPUtils.js.map