"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasCCTPV1MessageBeenProcessed = exports.createReceiveMessageInstruction = exports.createCloseFillPdaInstruction = exports.createRequestSlowFillInstruction = exports.createDepositInstruction = exports.createFillInstruction = exports.SOLANA_TX_SIZE_LIMIT = exports.SLOT_DURATION_MS = void 0;
exports.getSlot = getSlot;
exports.getTimestampForSlot = getTimestampForSlot;
exports.getFillDeadline = getFillDeadline;
exports.getDepositIdAtBlock = getDepositIdAtBlock;
exports.findDeposit = findDeposit;
exports.relayFillStatus = relayFillStatus;
exports.fillStatusArray = fillStatusArray;
exports.findFillEvent = findFillEvent;
exports.fillRelayInstruction = fillRelayInstruction;
exports.createTokenAccountsInstruction = createTokenAccountsInstruction;
exports.getFillRelayTx = getFillRelayTx;
exports.getIPFillRelayTx = getIPFillRelayTx;
exports.deserializeMessage = deserializeMessage;
exports.getSlowFillRequestTx = getSlowFillRequestTx;
exports.getAssociatedTokenAddress = getAssociatedTokenAddress;
exports.getRelayDataHash = getRelayDataHash;
exports.getIPForFillRelayTxs = getIPForFillRelayTxs;
exports.getFillRelayViaInstructionParamsInstructions = getFillRelayViaInstructionParamsInstructions;
exports.getDepositDelegatePda = getDepositDelegatePda;
exports.getDepositNowDelegatePda = getDepositNowDelegatePda;
exports.getFillRelayDelegatePda = getFillRelayDelegatePda;
exports.getAccountMetasForTokenlessMessage = getAccountMetasForTokenlessMessage;
exports.getCCTPDepositAccounts = getCCTPDepositAccounts;
exports.isSVMFillTooLarge = isSVMFillTooLarge;
exports.base64StrToByteSize = base64StrToByteSize;
exports.calculateFillSizeBytes = calculateFillSizeBytes;
exports.getCCTPV1ReceiveMessageTx = getCCTPV1ReceiveMessageTx;
exports.finalizeCCTPV1Messages = finalizeCCTPV1Messages;
exports.getMintInfo = getMintInfo;
const tslib_1 = require("tslib");
const contracts_1 = require("@across-protocol/contracts");
const SvmSpoke_1 = require("@across-protocol/contracts/dist/src/svm/clients/SvmSpoke");
const web3_v1_1 = require("@across-protocol/contracts/dist/src/svm/web3-v1");
const system_1 = require("@solana-program/system");
const token_1 = require("@solana-program/token");
const kit_1 = require("@solana/kit");
const assert_1 = tslib_1.__importDefault(require("assert"));
const utils_1 = require("ethers/lib/utils");
const constants_1 = require("../../constants");
const interfaces_1 = require("../../interfaces");
const utils_2 = require("../../utils");
const _1 = require("./");
const provider_1 = require("./provider");
const types_1 = require("./types");
const utils_3 = require("./utils");
exports.SLOT_DURATION_MS = 400;
exports.SOLANA_TX_SIZE_LIMIT = 1232;
function getSlot(provider, commitment, logger) {
    return _callGetSlotWithRetry(provider, commitment, logger);
}
async function _callGetSlotWithRetry(provider, commitment, logger) {
    try {
        return await provider.getSlot({ commitment }).send();
    }
    catch (err) {
        if ((0, provider_1.isSolanaError)(err)) {
            const { __code: code } = err.context;
            logger?.debug({
                at: "_getSlotWithRetry",
                message: "Caught error from getSlot()",
                code,
                commitment,
            });
        }
        throw err;
    }
}
function getTimestampForSlot(provider, slotNumber, maxRetries = 2, logger) {
    return _callGetTimestampForSlotWithRetry(provider, slotNumber, 0, maxRetries, logger);
}
async function _callGetTimestampForSlotWithRetry(provider, slotNumber, retryAttempt, maxRetries, logger) {
    const slot = slotNumber.toString();
    let _timestamp;
    try {
        _timestamp = await provider.getBlockTime(slotNumber).send();
    }
    catch (err) {
        if (!(0, provider_1.isSolanaError)(err)) {
            throw err;
        }
        const at = "getTimestampForSlot";
        const { __code: code } = err.context;
        switch (code) {
            case provider_1.SVM_SLOT_SKIPPED:
            case provider_1.SVM_LONG_TERM_STORAGE_SLOT_SKIPPED:
                return undefined;
            default: {
                const message = "Caught error from getBlockTime()";
                logger?.debug({ at, message, errorCode: code, slot, retryAttempt, maxRetries });
                throw new Error(`Unhandled SVM getBlockTime() error for slot ${slot}: ${code}`, { cause: err });
            }
        }
    }
    const timestamp = Number(_timestamp);
    (0, assert_1.default)(!(0, utils_2.isDefined)(_timestamp) || BigInt(timestamp) === _timestamp, `Unexpected block timestamp for SVM slot ${slot}: ${_timestamp}`);
    return timestamp;
}
async function getFillDeadline(provider, statePda) {
    const state = await (0, SvmSpoke_1.fetchState)(provider, statePda);
    return state.data.fillDeadlineBuffer;
}
function getDepositIdAtBlock(_contract, _blockTag) {
    throw new Error("getDepositIdAtBlock: not implemented");
}
async function findDeposit(eventClient, depositId, logger, slot, secondsLookback = 2 * 24 * 60 * 60) {
    if ((0, utils_2.isUnsafeDepositId)(depositId)) {
        throw new Error(`Cannot binary search for depositId ${depositId}`);
    }
    const provider = eventClient.getRpc();
    const opts = undefined;
    const { slot: currentSlot } = await (0, utils_3.getNearestSlotTime)(provider, opts, logger);
    const endSlot = slot !== undefined ? BigInt(Math.min(Number(slot), Number(currentSlot))) : currentSlot;
    const slotsInElapsed = BigInt(Math.round((secondsLookback * 1000) / exports.SLOT_DURATION_MS));
    const startSlot = endSlot - slotsInElapsed;
    const depositEvent = (await eventClient.queryEvents("FundsDeposited", startSlot, endSlot))?.find((event) => depositId.eq(event.data.depositId));
    if (!depositEvent) {
        return undefined;
    }
    const txnIndex = 0;
    const logIndex = 0;
    const blockNumber = Number(depositEvent.slot);
    const txnRef = depositEvent.signature.toString();
    const rawData = (0, _1.unwrapEventData)(depositEvent.data, ["depositId", "outputAmount"]);
    const { originChainId, ...deposit } = (0, utils_2.unpackDepositEvent)({ ...rawData, blockNumber, txnRef, txnIndex, logIndex }, constants_1.CHAIN_IDs.SOLANA);
    return {
        ...deposit,
    };
}
async function relayFillStatus(programId, relayData, destinationChainId, svmEventsClient, logger, atHeight) {
    (0, assert_1.default)((0, utils_2.chainIsSvm)(destinationChainId), "Destination chain must be an SVM chain");
    const provider = svmEventsClient.getRpc();
    const fillStatusPda = await (0, _1.getFillStatusPda)(programId, relayData, destinationChainId);
    let toSlot = BigInt(atHeight ?? 0);
    if (atHeight === undefined) {
        const commitment = "confirmed";
        const [fillStatusAccount, { slot: currentSlot, timestamp }] = await Promise.all([
            (0, kit_1.fetchEncodedAccount)(provider, fillStatusPda, { commitment }),
            (0, utils_3.getNearestSlotTime)(provider, { commitment }, logger),
        ]);
        toSlot = currentSlot;
        if (fillStatusAccount.exists) {
            const decodedAccountData = (0, SvmSpoke_1.decodeFillStatusAccount)(fillStatusAccount);
            return decodedAccountData.data.status;
        }
        else if (timestamp < relayData.fillDeadline) {
            return interfaces_1.FillStatus.Unfilled;
        }
    }
    return resolveFillStatusFromPdaEvents(fillStatusPda, toSlot, svmEventsClient);
}
async function fillStatusArray(programId, relayData, destinationChainId, svmEventsClient, logger, atHeight) {
    (0, assert_1.default)((0, utils_2.chainIsSvm)(destinationChainId), "Destination chain must be an SVM chain");
    const provider = svmEventsClient.getRpc();
    const chunkSize = 100;
    const chunkedRelayData = (0, utils_2.chunk)(relayData, chunkSize);
    const fillStatusPdas = (await Promise.all(chunkedRelayData.map((relayDataChunk) => Promise.all(relayDataChunk.map((relayData) => (0, _1.getFillStatusPda)(programId, relayData, destinationChainId)))))).flat();
    if (atHeight !== undefined && logger) {
        logger.warn({
            at: "SvmSpokeUtils#fillStatusArray",
            message: "Querying specific slots for large arrays is slow. For current status, omit 'atHeight' param to use latest confirmed slot instead.",
        });
    }
    const fillStatuses = atHeight === undefined
        ? await fetchBatchFillStatusFromPdaAccounts(provider, fillStatusPdas, relayData, logger)
        : new Array(relayData.length).fill(undefined);
    const missingStatuses = fillStatuses.reduce((acc, status, index) => {
        if (status === undefined) {
            acc.push(index);
        }
        return acc;
    }, []);
    const missingChunked = (0, utils_2.chunk)(missingStatuses, chunkSize);
    const missingResults = [];
    const opts = undefined;
    const toSlot = atHeight ? BigInt(atHeight) : (await (0, utils_3.getNearestSlotTime)(provider, opts, logger)).slot;
    for (const chunk of missingChunked) {
        const chunkResults = await Promise.all(chunk.map(async (missingIndex) => {
            return {
                index: missingIndex,
                fillStatus: await resolveFillStatusFromPdaEvents(fillStatusPdas[missingIndex], toSlot, svmEventsClient),
            };
        }));
        missingResults.push(...chunkResults);
    }
    missingResults.forEach(({ index, fillStatus }) => {
        fillStatuses[index] = fillStatus;
    });
    return fillStatuses;
}
async function findFillEvent(relayData, destinationChainId, svmEventsClient, fromSlot, toSlot, logger) {
    (0, assert_1.default)((0, utils_2.chainIsSvm)(destinationChainId), "Destination chain must be an SVM chain");
    const opts = undefined;
    toSlot ??= Number((await (0, utils_3.getNearestSlotTime)(svmEventsClient.getRpc(), opts, logger)).slot);
    const programId = svmEventsClient.getProgramAddress();
    const fillStatusPda = await (0, _1.getFillStatusPda)(programId, relayData, destinationChainId);
    const fillEvents = await svmEventsClient.queryDerivedAddressEvents(types_1.SVMEventNames.FilledRelay, fillStatusPda, BigInt(fromSlot), BigInt(toSlot), { limit: 10 });
    (0, assert_1.default)(fillEvents.length <= 1, `Expected at most one fill event for ${fillStatusPda}, got ${fillEvents.length}`);
    const [rawEvent] = fillEvents;
    if (!(0, utils_2.isDefined)(rawEvent)) {
        return;
    }
    const rawFill = (0, _1.unwrapEventData)(rawEvent.data, ["depositId", "inputAmount"]);
    const fill = (0, utils_2.unpackFillEvent)(rawFill, destinationChainId);
    return fill;
}
async function fillRelayInstruction(spokePool, relayData, signer, recipientTokenAccount, repaymentAddress, repaymentChainId) {
    const program = (0, _1.toAddress)(spokePool);
    (0, assert_1.default)(repaymentAddress.isValidOn(repaymentChainId), `Invalid repayment address for chain ${repaymentChainId}: ${repaymentAddress.toNative()}.`);
    const messageHash = (0, utils_2.getMessageHash)(relayData.message);
    const _relayDataHash = getRelayDataHash({ ...relayData, messageHash }, relayData.destinationChainId);
    const relayDataHash = new Uint8Array(Buffer.from(_relayDataHash.slice(2), "hex"));
    const relayer = utils_2.SvmAddress.from(signer.address);
    const [statePda, fillStatusPda, eventAuthority, delegatePda, relayerTokenAccount] = await Promise.all([
        (0, _1.getStatePda)(program),
        (0, _1.getFillStatusPda)(program, relayData, relayData.destinationChainId),
        (0, _1.getEventAuthority)(program),
        getFillRelayDelegatePda(relayDataHash, BigInt(repaymentChainId), signer.address, program),
        getAssociatedTokenAddress(relayer, relayData.outputToken),
    ]);
    const svmRelayData = (0, _1.toSvmRelayData)(relayData);
    return contracts_1.SvmSpokeClient.getFillRelayInstruction({
        signer,
        state: statePda,
        delegate: delegatePda,
        mint: svmRelayData.outputToken,
        relayerTokenAccount: relayerTokenAccount,
        recipientTokenAccount: recipientTokenAccount,
        fillStatus: fillStatusPda,
        eventAuthority,
        program,
        relayHash: relayDataHash,
        relayData: (0, kit_1.some)(svmRelayData),
        repaymentChainId: (0, kit_1.some)(BigInt(repaymentChainId)),
        repaymentAddress: (0, kit_1.some)((0, _1.toAddress)(repaymentAddress)),
    });
}
function createTokenAccountsInstruction(mint, relayer) {
    return contracts_1.SvmSpokeClient.getCreateTokenAccountsInstruction({
        signer: relayer,
        mint: (0, _1.toAddress)(mint),
    });
}
async function getFillRelayTx(spokePoolAddr, solanaClient, relayData, signer, repaymentChainId, repaymentAddress) {
    const svmRelayData = (0, _1.toSvmRelayData)(relayData);
    (0, assert_1.default)(repaymentAddress.isValidOn(repaymentChainId), `getFillRelayTx: repayment address ${repaymentAddress} not valid on chain ${repaymentChainId})`);
    const program = (0, _1.toAddress)(spokePoolAddr);
    const messageHash = (0, utils_2.getMessageHash)(relayData.message);
    const _relayDataHash = getRelayDataHash({ ...relayData, messageHash }, relayData.destinationChainId);
    const relayDataHash = new Uint8Array(Buffer.from(_relayDataHash.slice(2), "hex"));
    const [state, delegate, mintInfo, fillStatus, eventAuthority] = await Promise.all([
        (0, _1.getStatePda)(program),
        getFillRelayDelegatePda(relayDataHash, BigInt(repaymentChainId), (0, _1.toAddress)(repaymentAddress), program),
        getMintInfo(solanaClient, svmRelayData.outputToken),
        (0, _1.getFillStatusPda)(program, relayData, relayData.destinationChainId),
        (0, _1.getEventAuthority)(program),
    ]);
    const [recipientAta, relayerAta] = await Promise.all([
        getAssociatedTokenAddress(relayData.recipient, relayData.outputToken, mintInfo.programAddress),
        getAssociatedTokenAddress(utils_2.SvmAddress.from(signer.address), relayData.outputToken, mintInfo.programAddress),
    ]);
    const recipientAtaEncodedAccount = await (0, kit_1.fetchEncodedAccount)(solanaClient, recipientAta);
    const remainingAccounts = [];
    if (relayData.message !== "0x") {
        const acrossPlusMessage = deserializeMessage(relayData.message);
        remainingAccounts.push((0, _1.getAccountMeta)(acrossPlusMessage.handler, true));
        remainingAccounts.push(...acrossPlusMessage.accounts.map((account, idx) => (0, _1.getAccountMeta)(account, idx < acrossPlusMessage.accounts.length - acrossPlusMessage.read_only_len)));
    }
    const fillInput = {
        signer: signer,
        state,
        delegate,
        mint: svmRelayData.outputToken,
        relayerTokenAccount: relayerAta,
        recipientTokenAccount: recipientAta,
        fillStatus,
        tokenProgram: mintInfo.programAddress,
        associatedTokenProgram: token_1.ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
        systemProgram: system_1.SYSTEM_PROGRAM_ADDRESS,
        eventAuthority,
        program,
        relayHash: relayDataHash,
        relayData: svmRelayData,
        repaymentChainId: BigInt(repaymentChainId),
        repaymentAddress: (0, _1.toAddress)(repaymentAddress),
    };
    return (0, exports.createFillInstruction)(signer, solanaClient, fillInput, svmRelayData, mintInfo.data.decimals, !recipientAtaEncodedAccount.exists, remainingAccounts);
}
async function getIPFillRelayTx(spokePoolAddr, solanaClient, relayData, signer, repaymentChainId, repaymentAddress) {
    const program = (0, _1.toAddress)(spokePoolAddr);
    const _relayDataHash = getRelayDataHash({ ...relayData, messageHash: (0, utils_2.getMessageHash)(relayData.message) }, relayData.destinationChainId);
    const relayDataHash = new Uint8Array(Buffer.from(_relayDataHash.slice(2), "hex"));
    const [state, delegate, instructionParams] = await Promise.all([
        (0, _1.getStatePda)(program),
        getFillRelayDelegatePda(relayDataHash, BigInt(repaymentChainId), (0, _1.toAddress)(repaymentAddress), program),
        (0, _1.getInstructionParamsPda)(program, signer.address),
    ]);
    const mint = (0, _1.toAddress)(relayData.outputToken);
    const mintInfo = await getMintInfo(solanaClient, mint);
    const [recipientAta, relayerAta, fillStatus, eventAuthority] = await Promise.all([
        getAssociatedTokenAddress(relayData.recipient, relayData.outputToken, mintInfo.programAddress),
        getAssociatedTokenAddress(utils_2.SvmAddress.from(signer.address), relayData.outputToken, mintInfo.programAddress),
        (0, _1.getFillStatusPda)(program, relayData, relayData.destinationChainId),
        (0, _1.getEventAuthority)(program),
    ]);
    const recipientAtaEncodedAccount = await (0, kit_1.fetchEncodedAccount)(solanaClient, recipientAta);
    const remainingAccounts = [];
    if (relayData.message !== "0x") {
        const acrossPlusMessage = deserializeMessage(relayData.message);
        remainingAccounts.push((0, _1.getAccountMeta)(acrossPlusMessage.handler, true));
        remainingAccounts.push(...acrossPlusMessage.accounts.map((account, idx) => (0, _1.getAccountMeta)(account, idx < acrossPlusMessage.accounts.length - acrossPlusMessage.read_only_len)));
    }
    const fillInput = {
        signer: signer,
        state,
        delegate,
        mint,
        relayerTokenAccount: relayerAta,
        recipientTokenAccount: recipientAta,
        fillStatus,
        tokenProgram: mintInfo.programAddress,
        associatedTokenProgram: token_1.ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
        systemProgram: system_1.SYSTEM_PROGRAM_ADDRESS,
        eventAuthority,
        program,
        instructionParams,
        relayHash: relayDataHash,
        relayData: null,
        repaymentChainId: null,
        repaymentAddress: null,
    };
    return (0, exports.createFillInstruction)(signer, solanaClient, fillInput, { outputAmount: relayData.outputAmount.toBigInt(), recipient: (0, _1.toAddress)(relayData.recipient) }, mintInfo.data.decimals, !recipientAtaEncodedAccount.exists, remainingAccounts);
}
const createFillInstruction = async (signer, solanaClient, fillInput, relayData, tokenDecimals, createRecipientAta = false, remainingAccounts = []) => {
    const mintInfo = await getMintInfo(solanaClient, fillInput.mint);
    const approveIx = (0, token_1.getApproveCheckedInstruction)({
        source: fillInput.relayerTokenAccount,
        mint: fillInput.mint,
        delegate: fillInput.delegate,
        owner: fillInput.signer,
        amount: relayData.outputAmount,
        decimals: tokenDecimals,
    }, {
        programAddress: mintInfo.programAddress,
    });
    const getCreateAssociatedTokenIx = () => (0, token_1.getCreateAssociatedTokenInstruction)({
        payer: signer,
        owner: relayData.recipient,
        mint: fillInput.mint,
        ata: fillInput.recipientTokenAccount,
        systemProgram: system_1.SYSTEM_PROGRAM_ADDRESS,
        tokenProgram: fillInput.tokenProgram,
    });
    const createFillIx = contracts_1.SvmSpokeClient.getFillRelayInstruction(fillInput);
    createFillIx.accounts.push(...remainingAccounts);
    return (0, kit_1.pipe)(await (0, _1.createDefaultTransaction)(solanaClient, signer), (tx) => (createRecipientAta ? (0, kit_1.appendTransactionMessageInstruction)(getCreateAssociatedTokenIx(), tx) : tx), (tx) => (0, kit_1.appendTransactionMessageInstruction)(approveIx, tx), (tx) => (0, kit_1.appendTransactionMessageInstruction)(createFillIx, tx));
};
exports.createFillInstruction = createFillInstruction;
function deserializeMessage(_message) {
    const message = new Uint8Array(Buffer.from(_message.startsWith("0x") ? _message.slice(2) : _message, "hex"));
    const acrossPlusMessageDecoder = (0, _1.getAcrossPlusMessageDecoder)();
    const deserialized = acrossPlusMessageDecoder.decode(message);
    const valueAmountMethod2 = extractValueAmount(message);
    (0, assert_1.default)(deserialized.value_amount === valueAmountMethod2, "svm | deserializeMessage: Deserialization mismatch for value_amount");
    return deserialized;
}
const createDepositInstruction = async (signer, solanaClient, depositInput, tokenDecimals, createVaultAtaIfNeeded = true) => {
    const getCreateAssociatedTokenIdempotentIx = () => (0, token_1.getCreateAssociatedTokenIdempotentInstruction)({
        payer: signer,
        owner: depositInput.state,
        mint: depositInput.mint,
        ata: depositInput.vault,
        systemProgram: depositInput.systemProgram,
        tokenProgram: depositInput.tokenProgram,
    });
    const mintInfo = await getMintInfo(solanaClient, depositInput.mint);
    const approveIx = (0, token_1.getApproveCheckedInstruction)({
        source: depositInput.depositorTokenAccount,
        mint: depositInput.mint,
        delegate: depositInput.delegate,
        owner: depositInput.depositor,
        amount: depositInput.inputAmount,
        decimals: tokenDecimals,
    }, {
        programAddress: mintInfo.programAddress,
    });
    const depositIx = contracts_1.SvmSpokeClient.getDepositInstruction(depositInput);
    return (0, kit_1.pipe)(await (0, _1.createDefaultTransaction)(solanaClient, signer), (tx) => createVaultAtaIfNeeded ? (0, kit_1.appendTransactionMessageInstruction)(getCreateAssociatedTokenIdempotentIx(), tx) : tx, (tx) => (0, kit_1.appendTransactionMessageInstruction)(approveIx, tx), (tx) => (0, kit_1.appendTransactionMessageInstruction)(depositIx, tx));
};
exports.createDepositInstruction = createDepositInstruction;
const createRequestSlowFillInstruction = async (signer, solanaClient, requestSlowFillInput) => {
    const requestSlowFillIx = contracts_1.SvmSpokeClient.getRequestSlowFillInstruction(requestSlowFillInput);
    return (0, kit_1.pipe)(await (0, _1.createDefaultTransaction)(solanaClient, signer), (tx) => (0, kit_1.appendTransactionMessageInstruction)(requestSlowFillIx, tx));
};
exports.createRequestSlowFillInstruction = createRequestSlowFillInstruction;
async function getSlowFillRequestTx(spokePoolAddr, solanaClient, relayData, signer) {
    const program = (0, _1.toAddress)(spokePoolAddr);
    const messageHash = (0, utils_2.getMessageHash)(relayData.message);
    const relayDataHash = getRelayDataHash({ ...relayData, messageHash }, relayData.destinationChainId);
    const [state, fillStatus, eventAuthority] = await Promise.all([
        (0, _1.getStatePda)(program),
        (0, _1.getFillStatusPda)(program, relayData, relayData.destinationChainId),
        (0, _1.getEventAuthority)(program),
    ]);
    const svmRelayData = (0, _1.toSvmRelayData)(relayData);
    const requestSlowFillInput = {
        signer,
        state,
        fillStatus,
        eventAuthority,
        program,
        relayHash: (0, utils_1.arrayify)(relayDataHash),
        relayData: svmRelayData,
        systemProgram: system_1.SYSTEM_PROGRAM_ADDRESS,
    };
    return (0, exports.createRequestSlowFillInstruction)(signer, solanaClient, requestSlowFillInput);
}
const createCloseFillPdaInstruction = async (signer, solanaClient, fillStatusPda) => {
    const closeFillPdaIx = contracts_1.SvmSpokeClient.getCloseFillPdaInstruction({
        signer,
        state: await (0, _1.getStatePda)(contracts_1.SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS),
        fillStatus: fillStatusPda,
    });
    return (0, kit_1.pipe)(await (0, _1.createDefaultTransaction)(solanaClient, signer), (tx) => (0, kit_1.appendTransactionMessageInstruction)(closeFillPdaIx, tx));
};
exports.createCloseFillPdaInstruction = createCloseFillPdaInstruction;
const createReceiveMessageInstruction = async (signer, solanaClient, input, remainingAccounts) => {
    const receiveMessageIx = contracts_1.MessageTransmitterClient.getReceiveMessageInstruction(input);
    receiveMessageIx.accounts.push(...remainingAccounts);
    return (0, kit_1.pipe)(await (0, _1.createDefaultTransaction)(solanaClient, signer), (tx) => (0, kit_1.appendTransactionMessageInstruction)(receiveMessageIx, tx));
};
exports.createReceiveMessageInstruction = createReceiveMessageInstruction;
async function getAssociatedTokenAddress(owner, mint, tokenProgramId = token_1.TOKEN_PROGRAM_ADDRESS) {
    const encoder = (0, kit_1.getAddressEncoder)();
    const [associatedToken] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: token_1.ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
        seeds: [encoder.encode((0, _1.toAddress)(owner)), encoder.encode(tokenProgramId), encoder.encode((0, _1.toAddress)(mint))],
    });
    return associatedToken;
}
function getRelayDataHash(relayData, destinationChainId) {
    (0, assert_1.default)(relayData.messageHash.startsWith("0x"), "Message hash must be a hex string");
    const uint64Encoder = (0, kit_1.getU64Encoder)();
    const svmRelayData = (0, _1.toSvmRelayData)(relayData);
    const relayDataEncoder = contracts_1.SvmSpokeClient.getRelayDataEncoder();
    const encodedRelayData = relayDataEncoder.encode(svmRelayData);
    const encodedMessage = Buffer.from(relayData.message.slice(2), "hex");
    const encodedMessageHash = Uint8Array.from(Buffer.from(relayData.messageHash.slice(2), "hex"));
    const messageOffset = encodedRelayData.length - 4 - encodedMessage.length;
    const contentToHash = Buffer.concat([
        encodedRelayData.slice(0, messageOffset),
        encodedMessageHash,
        Uint8Array.from(uint64Encoder.encode(BigInt(destinationChainId))),
    ]);
    return (0, utils_2.keccak256)(contentToHash);
}
async function resolveFillStatusFromPdaEvents(fillStatusPda, toSlot, svmEventsClient) {
    const eventsToQuery = [types_1.SVMEventNames.FilledRelay, types_1.SVMEventNames.RequestedSlowFill];
    const relevantEvents = (await Promise.all(eventsToQuery.map((eventName) => svmEventsClient.queryDerivedAddressEvents(eventName, fillStatusPda, undefined, toSlot, { limit: 10 })))).flat();
    if (relevantEvents.length === 0) {
        return interfaces_1.FillStatus.Unfilled;
    }
    relevantEvents.sort((a, b) => Number(a.slot - b.slot));
    const fillStatusEvent = relevantEvents.pop();
    switch (fillStatusEvent.name) {
        case types_1.SVMEventNames.FilledRelay:
            return interfaces_1.FillStatus.Filled;
        case types_1.SVMEventNames.RequestedSlowFill:
            return interfaces_1.FillStatus.RequestedSlowFill;
        default:
            throw new Error(`Unexpected event name: ${fillStatusEvent.name}`);
    }
}
async function fetchBatchFillStatusFromPdaAccounts(provider, fillStatusPdas, relayDataArray, logger) {
    const chunkSize = 100;
    const commitment = "confirmed";
    const [pdaAccounts, { timestamp }] = await Promise.all([
        Promise.all((0, utils_2.chunk)(fillStatusPdas, chunkSize).map((chunk) => (0, kit_1.fetchEncodedAccounts)(provider, chunk, { commitment }))),
        (0, utils_3.getNearestSlotTime)(provider, { commitment }, logger),
    ]);
    const fillStatuses = pdaAccounts.flat().map((account, index) => {
        if (account.exists) {
            const decodedAccount = (0, SvmSpoke_1.decodeFillStatusAccount)(account);
            return decodedAccount.data.status;
        }
        if (timestamp < relayDataArray[index].fillDeadline) {
            return interfaces_1.FillStatus.Unfilled;
        }
        return undefined;
    });
    return fillStatuses;
}
async function getIPForFillRelayTxs(spokePool, relayData, repaymentChainId, repaymentAddress, signer, provider) {
    const ixs = await getFillRelayViaInstructionParamsInstructions((0, _1.toAddress)(spokePool), relayData, repaymentChainId, repaymentAddress, signer, provider);
    const txns = await (0, utils_2.mapAsync)(ixs, async (ix) => {
        return (0, kit_1.pipe)(await (0, _1.createDefaultTransaction)(provider, signer), (tx) => (0, kit_1.appendTransactionMessageInstruction)(ix, tx));
    });
    return txns;
}
async function getFillRelayViaInstructionParamsInstructions(spokePool, relayData, repaymentChainId, repaymentAddress, signer, provider, maxWriteSize = 450) {
    const instructionParams = await (0, _1.getInstructionParamsPda)(spokePool, signer.address);
    const encodedInstructionParams = await (0, kit_1.fetchEncodedAccount)(provider, instructionParams);
    const relayDataEncoder = contracts_1.SvmSpokeClient.getFillRelayParamsEncoder();
    const svmRelayData = (0, _1.toSvmRelayData)(relayData);
    const encodedRelayData = relayDataEncoder.encode({
        relayData: svmRelayData,
        repaymentChainId,
        repaymentAddress: (0, _1.toAddress)(repaymentAddress),
    });
    const initInstructionParamsIx = contracts_1.SvmSpokeClient.getInitializeInstructionParamsInstruction({
        signer,
        instructionParams,
        totalSize: encodedRelayData.length,
    });
    const instructions = [initInstructionParamsIx];
    for (let i = 0; i <= encodedRelayData.length / maxWriteSize; ++i) {
        const offset = i * maxWriteSize;
        const offsetEnd = Math.min(offset + maxWriteSize, encodedRelayData.length);
        const fragment = encodedRelayData.slice(offset, offsetEnd);
        const writeInstructionParamsIx = contracts_1.SvmSpokeClient.getWriteInstructionParamsFragmentInstruction({
            signer,
            instructionParams,
            offset,
            fragment,
        });
        instructions.push(writeInstructionParamsIx);
    }
    return encodedInstructionParams.exists
        ? [contracts_1.SvmSpokeClient.getCloseInstructionParamsInstruction({ signer, instructionParams }), ...instructions]
        : instructions;
}
async function getDepositDelegatePda(depositData, programId) {
    const addrEnc = (0, kit_1.getAddressEncoder)();
    const u64 = (0, kit_1.getU64Encoder)();
    const u32 = (0, kit_1.getU32Encoder)();
    const parts = [
        Uint8Array.from(addrEnc.encode(depositData.depositor)),
        Uint8Array.from(addrEnc.encode(depositData.recipient)),
        Uint8Array.from(addrEnc.encode(depositData.inputToken)),
        Uint8Array.from(addrEnc.encode(depositData.outputToken)),
        Uint8Array.from(u64.encode(depositData.inputAmount)),
        Uint8Array.from(depositData.outputAmount),
        Uint8Array.from(u64.encode(depositData.destinationChainId)),
        Uint8Array.from(addrEnc.encode(depositData.exclusiveRelayer)),
        Uint8Array.from(u32.encode(depositData.quoteTimestamp)),
        Uint8Array.from(u32.encode(depositData.fillDeadline)),
        Uint8Array.from(u32.encode(depositData.exclusivityParameter)),
        Uint8Array.from(u32.encode(BigInt(depositData.message.length))),
        depositData.message,
    ];
    const seedHash = Buffer.from((0, utils_2.keccak256)(Buffer.concat(parts)).slice(2), "hex");
    const [pda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: programId,
        seeds: [Buffer.from("delegate"), seedHash],
    });
    return pda;
}
async function getDepositNowDelegatePda(depositData, programId) {
    const addrEnc = (0, kit_1.getAddressEncoder)();
    const u64 = (0, kit_1.getU64Encoder)();
    const u32 = (0, kit_1.getU32Encoder)();
    const parts = [
        Uint8Array.from(addrEnc.encode(depositData.depositor)),
        Uint8Array.from(addrEnc.encode(depositData.recipient)),
        Uint8Array.from(addrEnc.encode(depositData.inputToken)),
        Uint8Array.from(addrEnc.encode(depositData.outputToken)),
        Uint8Array.from(u64.encode(depositData.inputAmount)),
        Uint8Array.from(depositData.outputAmount),
        Uint8Array.from(u64.encode(depositData.destinationChainId)),
        Uint8Array.from(addrEnc.encode(depositData.exclusiveRelayer)),
        Uint8Array.from(u32.encode(depositData.fillDeadlineOffset)),
        Uint8Array.from(u32.encode(depositData.exclusivityPeriod)),
        Uint8Array.from(u32.encode(BigInt(depositData.message.length))),
        depositData.message,
    ];
    const seedHash = Buffer.from((0, utils_2.keccak256)(Buffer.concat(parts)).slice(2), "hex");
    const [pda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: programId,
        seeds: [Buffer.from("delegate"), seedHash],
    });
    return pda;
}
async function getFillRelayDelegatePda(relayHash, repaymentChainId, repaymentAddress, programId) {
    const addrEnc = (0, kit_1.getAddressEncoder)();
    const u64 = (0, kit_1.getU64Encoder)();
    const parts = [
        relayHash,
        Uint8Array.from(u64.encode(repaymentChainId)),
        Uint8Array.from(addrEnc.encode(repaymentAddress)),
    ];
    const seedHash = Buffer.from((0, utils_2.keccak256)(Buffer.concat(parts)).slice(2), "hex");
    const [pda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: programId,
        seeds: [Buffer.from("delegate"), seedHash],
    });
    return pda;
}
const hasCCTPV1MessageBeenProcessed = async (solanaClient, signer, nonce, sourceDomain, latestBlockhash) => {
    const noncePda = await (0, _1.getCCTPNoncePda)(solanaClient, signer, nonce, sourceDomain);
    const isNonceUsedIx = contracts_1.MessageTransmitterClient.getIsNonceUsedInstruction({
        nonce: nonce,
        usedNonces: noncePda,
    });
    const parserFunction = (buf) => {
        if (buf.length != 1) {
            throw new Error("Invalid buffer length for isNonceUsedIx");
        }
        return Boolean(buf[0]);
    };
    return await (0, _1.simulateAndDecode)(solanaClient, isNonceUsedIx, signer, parserFunction, latestBlockhash);
};
exports.hasCCTPV1MessageBeenProcessed = hasCCTPV1MessageBeenProcessed;
async function getAccountMetasForTokenlessMessage(solanaClient, signer, messageBytes) {
    const messageHex = messageBytes.slice(2);
    const messageHeader = (0, web3_v1_1.decodeMessageHeader)(Buffer.from(messageHex, "hex"));
    const programAddress = contracts_1.SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS;
    const statePda = await (0, _1.getStatePda)(programAddress);
    const selfAuthority = await (0, _1.getSelfAuthority)();
    const eventAuthority = await (0, _1.getEventAuthority)(programAddress);
    const base = [
        { address: statePda, role: kit_1.AccountRole.READONLY },
        { address: selfAuthority, role: kit_1.AccountRole.READONLY },
        { address: programAddress, role: kit_1.AccountRole.READONLY },
    ];
    if ((0, utils_3.isRelayRootBundleMessageBody)(messageHeader.messageBody)) {
        const { data: { rootBundleId }, } = await contracts_1.SvmSpokeClient.fetchState(solanaClient, statePda);
        const rootBundle = await (0, _1.getRootBundlePda)(programAddress, rootBundleId);
        return [
            ...base,
            { address: signer.address, role: kit_1.AccountRole.WRITABLE },
            { address: statePda, role: kit_1.AccountRole.WRITABLE },
            { address: rootBundle, role: kit_1.AccountRole.WRITABLE },
            { address: system_1.SYSTEM_PROGRAM_ADDRESS, role: kit_1.AccountRole.READONLY },
            { address: eventAuthority, role: kit_1.AccountRole.READONLY },
            { address: programAddress, role: kit_1.AccountRole.READONLY },
        ];
    }
    if ((0, utils_3.isEmergencyDeleteRootBundleMessageBody)(messageHeader.messageBody)) {
        const rootBundleId = (0, utils_3.getEmergencyDeleteRootBundleRootBundleId)(messageHeader.messageBody);
        const rootBundle = await (0, _1.getRootBundlePda)(programAddress, rootBundleId);
        return [
            ...base,
            { address: signer.address, role: kit_1.AccountRole.READONLY },
            { address: statePda, role: kit_1.AccountRole.READONLY },
            { address: rootBundle, role: kit_1.AccountRole.WRITABLE },
            { address: eventAuthority, role: kit_1.AccountRole.READONLY },
            { address: programAddress, role: kit_1.AccountRole.READONLY },
        ];
    }
    return [
        ...base,
        { address: statePda, role: kit_1.AccountRole.WRITABLE },
        { address: eventAuthority, role: kit_1.AccountRole.READONLY },
        { address: programAddress, role: kit_1.AccountRole.READONLY },
    ];
}
async function getCCTPDepositAccounts(hubChainId, cctpDestinationDomainId, tokenMessengerMinterAddress, messageTransmitterAddress) {
    const l2Usdc = utils_2.SvmAddress.from(constants_1.TOKEN_SYMBOLS_MAP.USDC.addresses[(0, utils_2.chainIsProd)(hubChainId) ? constants_1.CHAIN_IDs.SOLANA : constants_1.CHAIN_IDs.SOLANA_DEVNET]);
    const [[tokenMessenger], [tokenMinter], [localToken], [cctpEventAuthority], [remoteTokenMessenger], [tokenMessengerMinterSenderAuthority], [messageTransmitter],] = await Promise.all([
        (0, kit_1.getProgramDerivedAddress)({
            programAddress: tokenMessengerMinterAddress,
            seeds: ["token_messenger"],
        }),
        (0, kit_1.getProgramDerivedAddress)({
            programAddress: tokenMessengerMinterAddress,
            seeds: ["token_minter"],
        }),
        (0, kit_1.getProgramDerivedAddress)({
            programAddress: tokenMessengerMinterAddress,
            seeds: ["local_token", utils_2.bs58.decode(l2Usdc.toBase58())],
        }),
        (0, kit_1.getProgramDerivedAddress)({
            programAddress: tokenMessengerMinterAddress,
            seeds: ["__event_authority"],
        }),
        (0, kit_1.getProgramDerivedAddress)({
            programAddress: tokenMessengerMinterAddress,
            seeds: ["remote_token_messenger", String(cctpDestinationDomainId)],
        }),
        (0, kit_1.getProgramDerivedAddress)({
            programAddress: tokenMessengerMinterAddress,
            seeds: ["sender_authority"],
        }),
        (0, kit_1.getProgramDerivedAddress)({
            programAddress: messageTransmitterAddress,
            seeds: ["message_transmitter"],
        }),
    ]);
    return {
        tokenMessenger,
        tokenMinter,
        localToken,
        cctpEventAuthority,
        remoteTokenMessenger,
        tokenMessengerMinterSenderAuthority,
        messageTransmitter,
    };
}
function isSVMFillTooLarge(fillRelayTx) {
    const sizeBytes = calculateFillSizeBytes(fillRelayTx);
    return {
        tooLarge: sizeBytes > exports.SOLANA_TX_SIZE_LIMIT,
        sizeBytes,
    };
}
function base64StrToByteSize(base64TxString) {
    const paddingLen = base64TxString.endsWith("==") ? 2 : base64TxString.endsWith("=") ? 1 : 0;
    return (base64TxString.length * 3) / 4 - paddingLen;
}
function calculateFillSizeBytes(fillTx) {
    const signedTransaction = (0, kit_1.compileTransaction)(fillTx);
    const serializedTx = (0, kit_1.getBase64EncodedWireTransaction)(signedTransaction);
    return base64StrToByteSize(serializedTx);
}
async function getAccountMetasForDepositMessage(message, hubChainId, tokenMessengerMinter, recipientAta) {
    const l1Usdc = utils_2.EvmAddress.from(constants_1.TOKEN_SYMBOLS_MAP.USDC.addresses[hubChainId]);
    const l2Usdc = utils_2.SvmAddress.from(constants_1.TOKEN_SYMBOLS_MAP.USDC.addresses[(0, utils_2.chainIsProd)(hubChainId) ? constants_1.CHAIN_IDs.SOLANA : constants_1.CHAIN_IDs.SOLANA_DEVNET]);
    const [tokenMessengerPda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: tokenMessengerMinter,
        seeds: ["token_messenger"],
    });
    const [tokenMinterPda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: tokenMessengerMinter,
        seeds: ["token_minter"],
    });
    const [localTokenPda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: tokenMessengerMinter,
        seeds: ["local_token", utils_2.bs58.decode(l2Usdc.toBase58())],
    });
    const [tokenMessengerEventAuthorityPda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: tokenMessengerMinter,
        seeds: ["__event_authority"],
    });
    const [custodyTokenAccountPda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: tokenMessengerMinter,
        seeds: ["custody", utils_2.bs58.decode(l2Usdc.toBase58())],
    });
    const [tokenPairPda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: tokenMessengerMinter,
        seeds: [
            new Uint8Array(Buffer.from("token_pair")),
            new Uint8Array(Buffer.from(String(message.sourceDomain))),
            new Uint8Array(Buffer.from(l1Usdc.toBytes32().slice(2), "hex")),
        ],
    });
    const [remoteTokenMessengerPda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: tokenMessengerMinter,
        seeds: ["remote_token_messenger", String(message.sourceDomain)],
    });
    return [
        { address: tokenMessengerPda, role: kit_1.AccountRole.READONLY },
        { address: remoteTokenMessengerPda, role: kit_1.AccountRole.READONLY },
        { address: tokenMinterPda, role: kit_1.AccountRole.WRITABLE },
        { address: localTokenPda, role: kit_1.AccountRole.WRITABLE },
        { address: tokenPairPda, role: kit_1.AccountRole.READONLY },
        { address: (0, _1.toAddress)(recipientAta), role: kit_1.AccountRole.WRITABLE },
        { address: custodyTokenAccountPda, role: kit_1.AccountRole.WRITABLE },
        { address: token_1.TOKEN_PROGRAM_ADDRESS, role: kit_1.AccountRole.READONLY },
        { address: tokenMessengerEventAuthorityPda, role: kit_1.AccountRole.READONLY },
        { address: tokenMessengerMinter, role: kit_1.AccountRole.READONLY },
    ];
}
async function getCCTPV1ReceiveMessageTx(solanaClient, signer, message, hubChainId, recipientAta) {
    const [messageTransmitterPda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: contracts_1.MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
        seeds: ["message_transmitter"],
    });
    const [eventAuthorityPda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: contracts_1.MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
        seeds: ["__event_authority"],
    });
    const cctpMessageReceiver = (0, _1.isDepositForBurnEvent)(message)
        ? contracts_1.TokenMessengerMinterClient.TOKEN_MESSENGER_MINTER_PROGRAM_ADDRESS
        : contracts_1.SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS;
    const [authorityPda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: contracts_1.MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
        seeds: ["message_transmitter_authority", utils_2.bs58.decode(cctpMessageReceiver)],
    });
    const usedNonces = await (0, _1.getCCTPNoncePda)(solanaClient, signer, message.nonce, message.sourceDomain);
    const accountMetas = (0, _1.isDepositForBurnEvent)(message)
        ? await getAccountMetasForDepositMessage(message, hubChainId, contracts_1.TokenMessengerMinterClient.TOKEN_MESSENGER_MINTER_PROGRAM_ADDRESS, recipientAta)
        : await getAccountMetasForTokenlessMessage(solanaClient, signer, message.messageBytes);
    const messageBytes = message.messageBytes.startsWith("0x")
        ? Buffer.from(message.messageBytes.slice(2), "hex")
        : Buffer.from(message.messageBytes, "hex");
    const input = {
        program: contracts_1.MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
        payer: signer,
        caller: signer,
        authorityPda,
        messageTransmitter: messageTransmitterPda,
        eventAuthority: eventAuthorityPda,
        usedNonces,
        receiver: cctpMessageReceiver,
        systemProgram: system_1.SYSTEM_PROGRAM_ADDRESS,
        message: messageBytes,
        attestation: Buffer.from(message.attestation.slice(2), "hex"),
    };
    return (0, exports.createReceiveMessageInstruction)(signer, solanaClient, input, accountMetas);
}
function finalizeCCTPV1Messages(solanaClient, attestedMessages, signer, recipientAta, simulate = false, hubChainId = 1) {
    return (0, utils_2.mapAsync)(attestedMessages, async (message) => {
        const receiveMessageIx = await getCCTPV1ReceiveMessageTx(solanaClient, signer, message, hubChainId, recipientAta);
        if (simulate) {
            const result = await solanaClient
                .simulateTransaction((0, kit_1.getBase64EncodedWireTransaction)(await (0, kit_1.signTransactionMessageWithSigners)(receiveMessageIx)), {
                encoding: "base64",
            })
                .send();
            if (result.value.err) {
                throw new Error(result.value.err.toString());
            }
            return "";
        }
        const signedTransaction = await (0, kit_1.signTransactionMessageWithSigners)(receiveMessageIx);
        const signature = (0, kit_1.getSignatureFromTransaction)(signedTransaction);
        const encodedTransaction = (0, kit_1.getBase64EncodedWireTransaction)(signedTransaction);
        await solanaClient
            .sendTransaction(encodedTransaction, { preflightCommitment: "confirmed", encoding: "base64" })
            .send();
        return signature;
    });
}
async function getMintInfo(solanaClient, mint, config) {
    return await (0, token_1.fetchMint)(solanaClient, mint, config);
}
function extractValueAmount(acrossPlusMessageBytes) {
    const VALUE_OFFSET = 32 + 1;
    const VALUE_END = VALUE_OFFSET + 8;
    if (acrossPlusMessageBytes.length < VALUE_END) {
        throw new Error(`svm | extractValueAmount: Message too short, need at least ${VALUE_END} bytes, got ${acrossPlusMessageBytes.length}`);
    }
    return readU64LEExact(acrossPlusMessageBytes.subarray(VALUE_OFFSET, VALUE_END));
}
function readU64LEExact(bytes) {
    if (bytes.length !== 8) {
        throw new Error(`readU64LEExact expected 8 bytes, received ${bytes.length}`);
    }
    const view = new DataView(bytes.buffer, bytes.byteOffset, 8);
    return view.getBigUint64(0, true);
}
//# sourceMappingURL=SpokeUtils.js.map