import { __assign, __awaiter, __generator } from "tslib";
import { MessageTransmitterClient, SvmSpokeClient, TokenMessengerMinterClient } from "@across-protocol/contracts";
import { decodeFillStatusAccount, fetchState } from "@across-protocol/contracts/dist/src/svm/clients/SvmSpoke";
import { hashNonEmptyMessage } from "@across-protocol/contracts/dist/src/svm/web3-v1";
import { intToU8Array32 } from "@across-protocol/contracts/dist/src/svm/web3-v1/conversionUtils";
import { SYSTEM_PROGRAM_ADDRESS } from "@solana-program/system";
import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, TOKEN_PROGRAM_ADDRESS, fetchMint, getApproveCheckedInstruction, getCreateAssociatedTokenIdempotentInstruction, } from "@solana-program/token";
import { AccountRole, appendTransactionMessageInstruction, fetchEncodedAccount, fetchEncodedAccounts, getAddressEncoder, getBase64EncodedWireTransaction, getProgramDerivedAddress, getSignatureFromTransaction, getU32Encoder, getU64Encoder, pipe, signTransactionMessageWithSigners, some, } from "@solana/kit";
import assert from "assert";
import { arrayify, hexZeroPad, hexlify } from "ethers/lib/utils";
import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "../../constants";
import { FillStatus } from "../../interfaces";
import { EvmAddress, SvmAddress, bs58, chainIsProd, chainIsSvm, chunk, isUnsafeDepositId, keccak256, mapAsync, toAddressType, } from "../../utils";
import { bigToU8a32, createDefaultTransaction, getCCTPNoncePda, getEventAuthority, getFillStatusPda, getSelfAuthority, getStatePda, isDepositForBurnEvent, simulateAndDecode, toAddress, unwrapEventData, } from "./";
import { SVM_NO_BLOCK_AT_SLOT, isSolanaError } from "./provider";
import { SVMEventNames } from "./types";
import { getNearestSlotTime } from "./utils";
/**
 * @note: Average Solana slot duration is about 400-500ms. We can be conservative
 *        and choose 400 to ensure that the most slots get included in our ranges
 */
export var SLOT_DURATION_MS = 400;
/**
 * Retrieves the chain time at a particular slot.
 */
export function getTimestampForSlot(provider, slotNumber) {
    return __awaiter(this, void 0, void 0, function () {
        var _timestamp, err_1, code, timestamp;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    _a.trys.push([0, 2, , 3]);
                    return [4 /*yield*/, provider.getBlockTime(slotNumber).send()];
                case 1:
                    _timestamp = _a.sent();
                    return [3 /*break*/, 3];
                case 2:
                    err_1 = _a.sent();
                    if (!isSolanaError(err_1)) {
                        throw err_1;
                    }
                    code = err_1.context.__code;
                    if ([SVM_NO_BLOCK_AT_SLOT].includes(code)) {
                        return [2 /*return*/, undefined];
                    }
                    throw err_1; // Unhandled Solana error.
                case 3:
                    timestamp = Number(_timestamp);
                    assert(BigInt(timestamp) === _timestamp, "Unexpected SVM block timestamp: ".concat(_timestamp)); // No truncation.
                    return [2 /*return*/, timestamp];
            }
        });
    });
}
/**
 * Returns the current fill deadline buffer.
 * @param provider SVM Provider instance
 * @param statePda Spoke Pool's State PDA
 * @returns fill deadline buffer
 */
export function getFillDeadline(provider, statePda) {
    return __awaiter(this, void 0, void 0, function () {
        var state;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, fetchState(provider, statePda)];
                case 1:
                    state = _a.sent();
                    return [2 /*return*/, state.data.fillDeadlineBuffer];
            }
        });
    });
}
/**
 * Finds the deposit id at a specific block number.
 * @param blockTag The block number to search for the deposit ID at.
 * @returns The deposit ID.
 */
export function getDepositIdAtBlock(_contract, _blockTag) {
    throw new Error("getDepositIdAtBlock: not implemented");
}
/**
 * Finds deposit events within a 2-day window ending at the specified slot.
 *
 * @remarks
 * This implementation uses a slot-limited search approach because Solana PDA state has
 * limitations that prevent directly referencing old deposit IDs. Unlike EVM chains where
 * we might use binary search across the entire chain history, in Solana we must query within
 * a constrained slot range.
 *
 * The search window is calculated by:
 * 1. Using the provided slot (or current confirmed slot if none is provided)
 * 2. Looking back 2 days worth of slots from that point
 *
 * We use a 2-day window because:
 * 1. Most valid deposits that need to be processed will be recent
 * 2. This covers multiple bundle submission periods
 * 3. It balances performance with practical deposit age
 *
 * @important
 * This function may return `undefined` for valid deposit IDs that are older than the search
 * window (approximately 2 days before the specified slot). This is an acceptable limitation
 * as deposits this old are typically not relevant to current operations.
 *
 * @param eventClient - SvmCpiEventsClient instance
 * @param depositId - The deposit ID to search for
 * @param slot - The slot to search up to (defaults to current slot). The search will look
 *              for deposits between (slot - secondsLookback) and slot.
 * @param secondsLookback - The number of seconds to look back for deposits (defaults to 2 days).
 * @returns The deposit if found within the slot window, undefined otherwise
 */
export function findDeposit(eventClient, depositId, slot, secondsLookback // 2 days
) {
    var _a;
    if (secondsLookback === void 0) { secondsLookback = 2 * 24 * 60 * 60; }
    return __awaiter(this, void 0, void 0, function () {
        var provider, currentSlot, endSlot, slotsInElapsed, startSlot, depositEvent, unwrappedDepositEvent, destinationChainId;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0:
                    // We can only perform this search when we have a safe deposit ID.
                    if (isUnsafeDepositId(depositId)) {
                        throw new Error("Cannot binary search for depositId ".concat(depositId));
                    }
                    provider = eventClient.getRpc();
                    return [4 /*yield*/, getNearestSlotTime(provider)];
                case 1:
                    currentSlot = (_b.sent()).slot;
                    endSlot = slot !== undefined ? BigInt(Math.min(Number(slot), Number(currentSlot))) : currentSlot;
                    slotsInElapsed = BigInt(Math.round((secondsLookback * 1000) / SLOT_DURATION_MS));
                    startSlot = endSlot - slotsInElapsed;
                    return [4 /*yield*/, eventClient.queryEvents("FundsDeposited", startSlot, endSlot)];
                case 2:
                    depositEvent = (_a = (_b.sent())) === null || _a === void 0 ? void 0 : _a.find(function (event) {
                        return depositId.eq(event.data.depositId);
                    });
                    // If no deposit event is found, return undefined
                    if (!depositEvent) {
                        return [2 /*return*/, undefined];
                    }
                    unwrappedDepositEvent = unwrapEventData(depositEvent.data, ["depositId", "outputAmount"]);
                    destinationChainId = unwrappedDepositEvent.destinationChainId;
                    // Return the deposit event with block info
                    return [2 /*return*/, __assign(__assign({ txnRef: depositEvent.signature.toString(), blockNumber: Number(depositEvent.slot), txnIndex: 0, logIndex: 0 }, unwrappedDepositEvent), { depositor: toAddressType(unwrappedDepositEvent.depositor, CHAIN_IDs.SOLANA), recipient: toAddressType(unwrappedDepositEvent.recipient, destinationChainId), inputToken: toAddressType(unwrappedDepositEvent.inputToken, CHAIN_IDs.SOLANA), outputToken: toAddressType(unwrappedDepositEvent.outputToken, destinationChainId), exclusiveRelayer: toAddressType(unwrappedDepositEvent.exclusiveRelayer, destinationChainId) })];
            }
        });
    });
}
/**
 * Resolves the fill status of a deposit at a specific slot or at the current confirmed one.
 *
 * If no slot is provided, attempts to solve the fill status using the PDA. Otherwise, it is reconstructed from PDA events.
 *
 * @param programId - The spoke pool program ID.
 * @param relayData - Deposit information used to locate the fill status.
 * @param destinationChainId - Destination chain ID (must be an SVM chain).
 * @param provider - SVM provider instance.
 * @param svmEventsClient - SVM events client for querying events.
 * @param atHeight - (Optional) Specific slot number to query. Defaults to the latest confirmed slot.
 * @returns The fill status for the deposit at the specified or current slot.
 */
export function relayFillStatus(programId, relayData, destinationChainId, svmEventsClient, atHeight) {
    return __awaiter(this, void 0, void 0, function () {
        var provider, fillStatusPda, toSlot, commitment, _a, fillStatusAccount, _b, currentSlot, timestamp, decodedAccountData;
        return __generator(this, function (_c) {
            switch (_c.label) {
                case 0:
                    assert(chainIsSvm(destinationChainId), "Destination chain must be an SVM chain");
                    provider = svmEventsClient.getRpc();
                    return [4 /*yield*/, getFillStatusPda(programId, relayData, destinationChainId)];
                case 1:
                    fillStatusPda = _c.sent();
                    toSlot = BigInt(atHeight !== null && atHeight !== void 0 ? atHeight : 0);
                    if (!(atHeight === undefined)) return [3 /*break*/, 3];
                    commitment = "confirmed";
                    return [4 /*yield*/, Promise.all([
                            fetchEncodedAccount(provider, fillStatusPda, { commitment: commitment }),
                            getNearestSlotTime(provider, { commitment: commitment }),
                        ])];
                case 2:
                    _a = _c.sent(), fillStatusAccount = _a[0], _b = _a[1], currentSlot = _b.slot, timestamp = _b.timestamp;
                    toSlot = currentSlot;
                    // If the PDA exists, return the stored fill status
                    if (fillStatusAccount.exists) {
                        decodedAccountData = decodeFillStatusAccount(fillStatusAccount);
                        return [2 /*return*/, decodedAccountData.data.status];
                    }
                    // If the PDA doesn't exist and the deadline hasn't passed yet, the deposit must be unfilled,
                    // since PDAs can't be closed before the fill deadline.
                    else if (timestamp < relayData.fillDeadline) {
                        return [2 /*return*/, FillStatus.Unfilled];
                    }
                    _c.label = 3;
                case 3: 
                // If status couldn't be determined from the PDA, or if a specific slot was requested, reconstruct from events.
                return [2 /*return*/, resolveFillStatusFromPdaEvents(fillStatusPda, toSlot, svmEventsClient)];
            }
        });
    });
}
/**
 * Resolves fill statuses for multiple deposits at a specific or latest confirmed slot,
 * using PDAs when possible and falling back to events if needed.
 *
 * @param programId The spoke pool program ID.
 * @param relayData An array of relay data to resolve fill statuses for.
 * @param destinationChainId The destination chain ID (must be an SVM chain).
 * @param provider SVM Provider instance.
 * @param svmEventsClient SVM events client instance for querying events.
 * @param atHeight (Optional) The slot number to query at. If omitted, queries the latest confirmed slot.
 * @returns An array of fill statuses for the specified deposits at the requested slot (or at the current confirmed slot).
 */
export function fillStatusArray(programId, relayData, destinationChainId, svmEventsClient, atHeight, logger) {
    return __awaiter(this, void 0, void 0, function () {
        var provider, chunkSize, chunkedRelayData, fillStatusPdas, fillStatuses, _a, missingStatuses, missingChunked, missingResults, toSlot, _b, _i, missingChunked_1, chunk_1, chunkResults;
        var _this = this;
        return __generator(this, function (_c) {
            switch (_c.label) {
                case 0:
                    assert(chainIsSvm(destinationChainId), "Destination chain must be an SVM chain");
                    provider = svmEventsClient.getRpc();
                    chunkSize = 100;
                    chunkedRelayData = chunk(relayData, chunkSize);
                    return [4 /*yield*/, Promise.all(chunkedRelayData.map(function (relayDataChunk) {
                            return Promise.all(relayDataChunk.map(function (relayData) { return getFillStatusPda(programId, relayData, destinationChainId); }));
                        }))];
                case 1:
                    fillStatusPdas = (_c.sent()).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.",
                        });
                    }
                    if (!(atHeight === undefined)) return [3 /*break*/, 3];
                    return [4 /*yield*/, fetchBatchFillStatusFromPdaAccounts(provider, fillStatusPdas, relayData)];
                case 2:
                    _a = _c.sent();
                    return [3 /*break*/, 4];
                case 3:
                    _a = new Array(relayData.length).fill(undefined);
                    _c.label = 4;
                case 4:
                    fillStatuses = _a;
                    missingStatuses = fillStatuses.reduce(function (acc, status, index) {
                        if (status === undefined) {
                            acc.push(index);
                        }
                        return acc;
                    }, []);
                    missingChunked = chunk(missingStatuses, chunkSize);
                    missingResults = [];
                    if (!atHeight) return [3 /*break*/, 5];
                    _b = BigInt(atHeight);
                    return [3 /*break*/, 7];
                case 5: return [4 /*yield*/, getNearestSlotTime(provider)];
                case 6:
                    _b = (_c.sent()).slot;
                    _c.label = 7;
                case 7:
                    toSlot = _b;
                    _i = 0, missingChunked_1 = missingChunked;
                    _c.label = 8;
                case 8:
                    if (!(_i < missingChunked_1.length)) return [3 /*break*/, 11];
                    chunk_1 = missingChunked_1[_i];
                    return [4 /*yield*/, Promise.all(chunk_1.map(function (missingIndex) { return __awaiter(_this, void 0, void 0, function () {
                            var _a;
                            return __generator(this, function (_b) {
                                switch (_b.label) {
                                    case 0:
                                        _a = {
                                            index: missingIndex
                                        };
                                        return [4 /*yield*/, resolveFillStatusFromPdaEvents(fillStatusPdas[missingIndex], toSlot, svmEventsClient)];
                                    case 1: return [2 /*return*/, (_a.fillStatus = _b.sent(),
                                            _a)];
                                }
                            });
                        }); }))];
                case 9:
                    chunkResults = _c.sent();
                    missingResults.push.apply(missingResults, chunkResults);
                    _c.label = 10;
                case 10:
                    _i++;
                    return [3 /*break*/, 8];
                case 11:
                    // Fill in missing statuses back to the result array
                    missingResults.forEach(function (_a) {
                        var index = _a.index, fillStatus = _a.fillStatus;
                        fillStatuses[index] = fillStatus;
                    });
                    return [2 /*return*/, fillStatuses];
            }
        });
    });
}
/**
 * Finds the `FilledRelay` event for a given deposit within the provided slot range.
 *
 * @param relayData - Deposit information that is used to complete a fill.
 * @param destinationChainId - Destination chain ID (must be an SVM chain).
 * @param svmEventsClient - SVM events client instance for querying events.
 * @param fromSlot - Starting slot to search.
 * @param toSlot (Optional) Ending slot to search. If not provided, the current confirmed slot will be used.
 * @returns The fill event with block info, or `undefined` if not found.
 */
export function findFillEvent(relayData, destinationChainId, svmEventsClient, fromSlot, toSlot) {
    return __awaiter(this, void 0, void 0, function () {
        var _a, _b, programId, fillStatusPda, fillEvents, rawFillEvent, eventData, originChainId, parsedFillEvent;
        return __generator(this, function (_c) {
            switch (_c.label) {
                case 0:
                    assert(chainIsSvm(destinationChainId), "Destination chain must be an SVM chain");
                    if (!(toSlot !== null && toSlot !== void 0)) return [3 /*break*/, 1];
                    _a = toSlot;
                    return [3 /*break*/, 3];
                case 1:
                    _b = Number;
                    return [4 /*yield*/, getNearestSlotTime(svmEventsClient.getRpc())];
                case 2:
                    _a = (toSlot = _b.apply(void 0, [(_c.sent()).slot]));
                    _c.label = 3;
                case 3:
                    _a;
                    programId = svmEventsClient.getProgramAddress();
                    return [4 /*yield*/, getFillStatusPda(programId, relayData, destinationChainId)];
                case 4:
                    fillStatusPda = _c.sent();
                    return [4 /*yield*/, svmEventsClient.queryDerivedAddressEvents(SVMEventNames.FilledRelay, fillStatusPda, BigInt(fromSlot), BigInt(toSlot), { limit: 10 })];
                case 5:
                    fillEvents = _c.sent();
                    assert(fillEvents.length <= 1, "Expected at most one fill event for ".concat(fillStatusPda, ", got ").concat(fillEvents.length));
                    if (fillEvents.length > 0) {
                        rawFillEvent = fillEvents[0];
                        eventData = unwrapEventData(rawFillEvent.data, ["depositId", "inputAmount"]);
                        originChainId = eventData.originChainId;
                        parsedFillEvent = __assign(__assign({}, eventData), { transactionHash: rawFillEvent.signature, blockNumber: Number(rawFillEvent.slot), transactionIndex: 0, logIndex: 0, destinationChainId: destinationChainId, inputToken: toAddressType(eventData.inputToken, originChainId), outputToken: toAddressType(eventData.outputToken, destinationChainId), relayer: toAddressType(eventData.relayer, destinationChainId), exclusiveRelayer: toAddressType(eventData.exclusiveRelayer, destinationChainId), depositor: toAddressType(eventData.depositor, originChainId), recipient: toAddressType(eventData.recipient, destinationChainId), relayExecutionInfo: __assign(__assign({}, eventData.relayExecutionInfo), { updatedRecipient: eventData.relayExecutionInfo.updatedRecipient }) });
                        return [2 /*return*/, parsedFillEvent];
                    }
                    return [2 /*return*/, undefined];
            }
        });
    });
}
/**
 * @param spokePool Address (program ID) of the SvmSpoke.
 * @param relayData RelayData instance, supplemented with destinationChainId
 * @param relayer Address of the relayer filling the deposit.
 * @param repaymentChainId Optional repaymentChainId (defaults to destinationChainId).
 * @returns An Ethers UnsignedTransaction instance.
 */
export function fillRelayInstruction(spokePool, relayData, signer, recipientTokenAccount, repaymentAddress, repaymentChainId) {
    if (repaymentAddress === void 0) { repaymentAddress = SvmAddress.from(signer.address); }
    if (repaymentChainId === void 0) { repaymentChainId = relayData.destinationChainId; }
    return __awaiter(this, void 0, void 0, function () {
        var program, _relayDataHash, relayDataHash, relayer, relayerTokenAccount, _a, statePda, fillStatusPda, eventAuthority, depositIdBuffer, shortenedBuffer, delegatePda, _b, recipient, outputToken, exclusiveRelayer, depositor, inputToken;
        return __generator(this, function (_c) {
            switch (_c.label) {
                case 0:
                    program = toAddress(spokePool);
                    assert(repaymentAddress.isValidOn(repaymentChainId), "Invalid repayment address for chain ".concat(repaymentChainId, ": ").concat(repaymentAddress.toNative(), "."));
                    _relayDataHash = getRelayDataHash(relayData, relayData.destinationChainId);
                    relayDataHash = new Uint8Array(Buffer.from(_relayDataHash.slice(2), "hex"));
                    relayer = SvmAddress.from(signer.address);
                    return [4 /*yield*/, getAssociatedTokenAddress(relayer, relayData.outputToken)];
                case 1:
                    relayerTokenAccount = _c.sent();
                    return [4 /*yield*/, Promise.all([
                            getStatePda(program),
                            getFillStatusPda(program, relayData, relayData.destinationChainId),
                            getEventAuthority(program),
                        ])];
                case 2:
                    _a = _c.sent(), statePda = _a[0], fillStatusPda = _a[1], eventAuthority = _a[2];
                    depositIdBuffer = new Uint8Array(32);
                    shortenedBuffer = new Uint8Array(Buffer.from(relayData.depositId.toHexString().slice(2), "hex"));
                    depositIdBuffer.set(shortenedBuffer, 32 - shortenedBuffer.length);
                    return [4 /*yield*/, getFillRelayDelegatePda(relayDataHash, BigInt(repaymentChainId), toAddress(relayer), program)];
                case 3:
                    delegatePda = _c.sent();
                    _b = [
                        relayData.recipient,
                        relayData.outputToken,
                        relayData.exclusiveRelayer,
                        relayData.depositor,
                        relayData.inputToken,
                    ].map(toAddress), recipient = _b[0], outputToken = _b[1], exclusiveRelayer = _b[2], depositor = _b[3], inputToken = _b[4];
                    return [2 /*return*/, SvmSpokeClient.getFillRelayInstruction({
                            signer: signer,
                            state: statePda,
                            delegate: toAddress(SvmAddress.from(delegatePda.toString())),
                            mint: outputToken,
                            relayerTokenAccount: relayerTokenAccount,
                            recipientTokenAccount: recipientTokenAccount,
                            fillStatus: fillStatusPda,
                            eventAuthority: eventAuthority,
                            program: program,
                            relayHash: relayDataHash,
                            relayData: some({
                                depositor: depositor,
                                recipient: recipient,
                                exclusiveRelayer: exclusiveRelayer,
                                inputToken: inputToken,
                                outputToken: outputToken,
                                inputAmount: bigToU8a32(relayData.inputAmount.toBigInt()),
                                outputAmount: relayData.outputAmount.toBigInt(),
                                originChainId: BigInt(relayData.originChainId),
                                fillDeadline: relayData.fillDeadline,
                                exclusivityDeadline: relayData.exclusivityDeadline,
                                depositId: depositIdBuffer,
                                message: new Uint8Array(Buffer.from(relayData.message.slice(2), "hex")),
                            }),
                            repaymentChainId: some(BigInt(repaymentChainId)),
                            repaymentAddress: toAddress(repaymentAddress),
                        })];
            }
        });
    });
}
/**
 * @param mint Address of the token corresponding to the account being made.
 * @param relayer Address of the relayer filling the deposit.
 * @returns An instruction for creating a new token account.
 */
export function createTokenAccountsInstruction(mint, relayer) {
    return SvmSpokeClient.getCreateTokenAccountsInstruction({
        signer: relayer,
        mint: toAddress(mint),
    });
}
/**
 * @notice Return the fillRelay transaction for a given deposit
 * @param spokePoolAddr Address of the spoke pool we're trying to fill through
 * @param solanaClient RPC client to interact with Solana chain
 * @param relayData RelayData instance, supplemented with destinationChainId
 * @param signer signer associated with the relayer creating a Fill. Can be VoidSigner for gas estimation
 * @param repaymentChainId Chain id where relayer repayment is desired
 * @param repaymentAddress Address to which repayment will go to on repaymentChainId
 * @returns FillRelay transaction
 */
export function getFillRelayTx(spokePoolAddr, solanaClient, relayData, signer, repaymentChainId, repaymentAddress) {
    return __awaiter(this, void 0, void 0, function () {
        var depositor, recipient, inputToken, outputToken, exclusiveRelayer, destinationChainId, program, _relayDataHash, relayDataHash, _a, state, delegate, mint, mintInfo, _b, recipientAta, relayerAta, fillStatus, eventAuthority, svmRelayData, fillInput;
        return __generator(this, function (_c) {
            switch (_c.label) {
                case 0:
                    depositor = relayData.depositor, recipient = relayData.recipient, inputToken = relayData.inputToken, outputToken = relayData.outputToken, exclusiveRelayer = relayData.exclusiveRelayer, destinationChainId = relayData.destinationChainId;
                    assert(repaymentAddress.isValidOn(repaymentChainId), "getFillRelayTx: repayment address ".concat(repaymentAddress, " not valid on chain ").concat(repaymentChainId, ")"));
                    program = toAddress(spokePoolAddr);
                    _relayDataHash = getRelayDataHash(relayData, destinationChainId);
                    relayDataHash = new Uint8Array(Buffer.from(_relayDataHash.slice(2), "hex"));
                    return [4 /*yield*/, Promise.all([
                            getStatePda(program),
                            getFillRelayDelegatePda(relayDataHash, BigInt(repaymentChainId), toAddress(repaymentAddress), program),
                        ])];
                case 1:
                    _a = _c.sent(), state = _a[0], delegate = _a[1];
                    mint = toAddress(outputToken);
                    return [4 /*yield*/, fetchMint(solanaClient, mint)];
                case 2:
                    mintInfo = _c.sent();
                    return [4 /*yield*/, Promise.all([
                            getAssociatedTokenAddress(recipient, outputToken, mintInfo.programAddress),
                            getAssociatedTokenAddress(SvmAddress.from(signer.address), outputToken, mintInfo.programAddress),
                            getFillStatusPda(program, relayData, destinationChainId),
                            getEventAuthority(program),
                        ])];
                case 3:
                    _b = _c.sent(), recipientAta = _b[0], relayerAta = _b[1], fillStatus = _b[2], eventAuthority = _b[3];
                    svmRelayData = {
                        depositor: toAddress(depositor),
                        recipient: toAddress(recipient),
                        exclusiveRelayer: toAddress(exclusiveRelayer),
                        inputToken: toAddress(inputToken),
                        outputToken: mint,
                        inputAmount: bigToU8a32(relayData.inputAmount.toBigInt()),
                        outputAmount: relayData.outputAmount.toBigInt(),
                        originChainId: relayData.originChainId,
                        depositId: new Uint8Array(intToU8Array32(relayData.depositId.toNumber())),
                        fillDeadline: relayData.fillDeadline,
                        exclusivityDeadline: relayData.exclusivityDeadline,
                        message: new Uint8Array(Buffer.from(relayData.message, "hex")),
                    };
                    fillInput = {
                        signer: signer,
                        state: state,
                        delegate: delegate,
                        mint: mint,
                        relayerTokenAccount: relayerAta,
                        recipientTokenAccount: recipientAta,
                        fillStatus: fillStatus,
                        tokenProgram: mintInfo.programAddress,
                        associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
                        systemProgram: SYSTEM_PROGRAM_ADDRESS,
                        eventAuthority: eventAuthority,
                        program: program,
                        relayHash: relayDataHash,
                        relayData: svmRelayData,
                        repaymentChainId: BigInt(repaymentChainId),
                        repaymentAddress: toAddress(repaymentAddress),
                    };
                    // Pass createRecipientAtaIfNeeded =true to the createFillInstruction function to create the recipient token account
                    // if it doesn't exist.
                    return [2 /*return*/, createFillInstruction(signer, solanaClient, fillInput, mintInfo.data.decimals, true)];
            }
        });
    });
}
/**
 * Creates a fill instruction.
 * @param signer - The signer of the transaction.
 * @param solanaClient - The Solana client.
 * @param fillInput - The fill input.
 * @param tokenDecimals - The token decimals.
 * @param createRecipientAtaIfNeeded - Whether to create a recipient token account.
 * @returns The fill instruction.
 */
export var createFillInstruction = function (signer, solanaClient, fillInput, tokenDecimals, createRecipientAtaIfNeeded) {
    if (createRecipientAtaIfNeeded === void 0) { createRecipientAtaIfNeeded = true; }
    return __awaiter(void 0, void 0, void 0, function () {
        var mintInfo, approveIx, getCreateAssociatedTokenIdempotentIx, createFillIx, _a;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0: return [4 /*yield*/, fetchMint(solanaClient, fillInput.mint)];
                case 1:
                    mintInfo = _b.sent();
                    approveIx = getApproveCheckedInstruction({
                        source: fillInput.relayerTokenAccount,
                        mint: fillInput.mint,
                        delegate: fillInput.delegate,
                        owner: fillInput.signer,
                        amount: fillInput.relayData.outputAmount,
                        decimals: tokenDecimals,
                    }, {
                        programAddress: mintInfo.programAddress,
                    });
                    getCreateAssociatedTokenIdempotentIx = function () {
                        return getCreateAssociatedTokenIdempotentInstruction({
                            payer: signer,
                            owner: fillInput.relayData.recipient,
                            mint: fillInput.mint,
                            ata: fillInput.recipientTokenAccount,
                            systemProgram: SYSTEM_PROGRAM_ADDRESS,
                            tokenProgram: fillInput.tokenProgram,
                        });
                    };
                    createFillIx = SvmSpokeClient.getFillRelayInstruction(fillInput);
                    _a = pipe;
                    return [4 /*yield*/, createDefaultTransaction(solanaClient, signer)];
                case 2: return [2 /*return*/, _a.apply(void 0, [_b.sent(), function (tx) {
                            return createRecipientAtaIfNeeded ? appendTransactionMessageInstruction(getCreateAssociatedTokenIdempotentIx(), tx) : tx;
                        },
                        function (tx) { return appendTransactionMessageInstruction(approveIx, tx); },
                        function (tx) { return appendTransactionMessageInstruction(createFillIx, tx); }])];
            }
        });
    });
};
/**
 * Creates a deposit instruction.
 * @param signer - The signer of the transaction.
 * @param solanaClient - The Solana client.
 * @param depositInput - The deposit input.
 * @param tokenDecimals - The token decimals.
 * @param createVaultAtaIfNeeded - Whether to create a vault token account.
 * @returns The deposit instruction.
 */
export var createDepositInstruction = function (signer, solanaClient, depositInput, tokenDecimals, createVaultAtaIfNeeded) {
    if (createVaultAtaIfNeeded === void 0) { createVaultAtaIfNeeded = true; }
    return __awaiter(void 0, void 0, void 0, function () {
        var getCreateAssociatedTokenIdempotentIx, mintInfo, approveIx, depositIx, _a;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0:
                    getCreateAssociatedTokenIdempotentIx = function () {
                        return getCreateAssociatedTokenIdempotentInstruction({
                            payer: signer,
                            owner: depositInput.state,
                            mint: depositInput.mint,
                            ata: depositInput.vault,
                            systemProgram: depositInput.systemProgram,
                            tokenProgram: depositInput.tokenProgram,
                        });
                    };
                    return [4 /*yield*/, fetchMint(solanaClient, depositInput.mint)];
                case 1:
                    mintInfo = _b.sent();
                    approveIx = getApproveCheckedInstruction({
                        source: depositInput.depositorTokenAccount,
                        mint: depositInput.mint,
                        delegate: depositInput.delegate,
                        owner: depositInput.depositor,
                        amount: depositInput.inputAmount,
                        decimals: tokenDecimals,
                    }, {
                        programAddress: mintInfo.programAddress,
                    });
                    depositIx = SvmSpokeClient.getDepositInstruction(depositInput);
                    _a = pipe;
                    return [4 /*yield*/, createDefaultTransaction(solanaClient, signer)];
                case 2: return [2 /*return*/, _a.apply(void 0, [_b.sent(), function (tx) {
                            return createVaultAtaIfNeeded ? appendTransactionMessageInstruction(getCreateAssociatedTokenIdempotentIx(), tx) : tx;
                        },
                        function (tx) { return appendTransactionMessageInstruction(approveIx, tx); },
                        function (tx) { return appendTransactionMessageInstruction(depositIx, tx); }])];
            }
        });
    });
};
/**
 * Creates a request slow fill instruction.
 * @param signer - The signer of the transaction.
 * @param solanaClient - The Solana client.
 * @param requestSlowFillInput - The input arguments for the `requestSlowFill` instruction.
 * @returns The request slow fill instruction.
 */
export var createRequestSlowFillInstruction = function (signer, solanaClient, requestSlowFillInput) { return __awaiter(void 0, void 0, void 0, function () {
    var requestSlowFillIx, _a;
    return __generator(this, function (_b) {
        switch (_b.label) {
            case 0:
                requestSlowFillIx = SvmSpokeClient.getRequestSlowFillInstruction(requestSlowFillInput);
                _a = pipe;
                return [4 /*yield*/, createDefaultTransaction(solanaClient, signer)];
            case 1: return [2 /*return*/, _a.apply(void 0, [_b.sent(), function (tx) {
                        return appendTransactionMessageInstruction(requestSlowFillIx, tx);
                    }])];
        }
    });
}); };
/**
 * @notice Return the requestSlowFill transaction for a given deposit
 * @param spokePoolAddr Address of the spoke pool we're trying to fill through
 * @param solanaClient RPC client to interact with Solana chain
 * @param relayData RelayData instance, supplemented with destinationChainId
 * @param signer signer associated with the relayer creating a Fill.
 * @returns requestSlowFill transaction
 */
export function getSlowFillRequestTx(spokePoolAddr, solanaClient, relayData, signer) {
    return __awaiter(this, void 0, void 0, function () {
        var depositor, recipient, inputToken, outputToken, exclusiveRelayer, destinationChainId, originChainId, depositId, fillDeadline, exclusivityDeadline, message, program, relayDataHash, _a, state, fillStatus, eventAuthority, depositIdBuffer, shortenedBuffer, relayDataInput, requestSlowFillInput;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0:
                    depositor = relayData.depositor, recipient = relayData.recipient, inputToken = relayData.inputToken, outputToken = relayData.outputToken, exclusiveRelayer = relayData.exclusiveRelayer, destinationChainId = relayData.destinationChainId, originChainId = relayData.originChainId, depositId = relayData.depositId, fillDeadline = relayData.fillDeadline, exclusivityDeadline = relayData.exclusivityDeadline, message = relayData.message;
                    program = toAddress(spokePoolAddr);
                    relayDataHash = getRelayDataHash(relayData, destinationChainId);
                    return [4 /*yield*/, Promise.all([
                            getStatePda(program),
                            getFillStatusPda(program, relayData, destinationChainId),
                            getEventAuthority(program),
                        ])];
                case 1:
                    _a = _b.sent(), state = _a[0], fillStatus = _a[1], eventAuthority = _a[2];
                    depositIdBuffer = new Uint8Array(32);
                    shortenedBuffer = arrayify(depositId.toHexString());
                    depositIdBuffer.set(shortenedBuffer, 32 - shortenedBuffer.length);
                    relayDataInput = {
                        depositor: toAddress(depositor),
                        recipient: toAddress(recipient),
                        exclusiveRelayer: toAddress(exclusiveRelayer),
                        inputToken: toAddress(inputToken),
                        outputToken: toAddress(outputToken),
                        inputAmount: bigToU8a32(relayData.inputAmount.toBigInt()),
                        outputAmount: relayData.outputAmount.toBigInt(),
                        originChainId: BigInt(originChainId),
                        depositId: depositIdBuffer,
                        fillDeadline: fillDeadline,
                        exclusivityDeadline: exclusivityDeadline,
                        message: arrayify(message),
                    };
                    requestSlowFillInput = {
                        signer: signer,
                        state: state,
                        fillStatus: fillStatus,
                        eventAuthority: eventAuthority,
                        program: program,
                        relayHash: arrayify(relayDataHash),
                        relayData: relayDataInput,
                        systemProgram: SYSTEM_PROGRAM_ADDRESS,
                    };
                    return [2 /*return*/, createRequestSlowFillInstruction(signer, solanaClient, requestSlowFillInput)];
            }
        });
    });
}
/**
 * Creates a close fill PDA instruction.
 * @param signer - The signer of the transaction.
 * @param solanaClient - The Solana client.
 * @param fillStatusPda - The fill status PDA.
 * @returns The close fill PDA instruction.
 */
export var createCloseFillPdaInstruction = function (signer, solanaClient, fillStatusPda) { return __awaiter(void 0, void 0, void 0, function () {
    var closeFillPdaIx, _a, _b, _c;
    var _d;
    return __generator(this, function (_e) {
        switch (_e.label) {
            case 0:
                _b = (_a = SvmSpokeClient).getCloseFillPdaInstruction;
                _d = {
                    signer: signer
                };
                return [4 /*yield*/, getStatePda(SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS)];
            case 1:
                closeFillPdaIx = _b.apply(_a, [(_d.state = _e.sent(),
                        _d.fillStatus = fillStatusPda,
                        _d)]);
                _c = pipe;
                return [4 /*yield*/, createDefaultTransaction(solanaClient, signer)];
            case 2: return [2 /*return*/, _c.apply(void 0, [_e.sent(), function (tx) {
                        return appendTransactionMessageInstruction(closeFillPdaIx, tx);
                    }])];
        }
    });
}); };
export var createReceiveMessageInstruction = function (signer, solanaClient, input, remainingAccounts) { return __awaiter(void 0, void 0, void 0, function () {
    var receiveMessageIx, _a;
    var _b;
    return __generator(this, function (_c) {
        switch (_c.label) {
            case 0:
                receiveMessageIx = MessageTransmitterClient.getReceiveMessageInstruction(input);
                (_b = receiveMessageIx.accounts).push.apply(_b, remainingAccounts);
                _a = pipe;
                return [4 /*yield*/, createDefaultTransaction(solanaClient, signer)];
            case 1: return [2 /*return*/, _a.apply(void 0, [_c.sent(), function (tx) {
                        return appendTransactionMessageInstruction(receiveMessageIx, tx);
                    }])];
        }
    });
}); };
export function getAssociatedTokenAddress(owner, mint, tokenProgramId) {
    if (tokenProgramId === void 0) { tokenProgramId = TOKEN_PROGRAM_ADDRESS; }
    return __awaiter(this, void 0, void 0, function () {
        var encoder, associatedToken;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    encoder = getAddressEncoder();
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
                            seeds: [encoder.encode(toAddress(owner)), encoder.encode(tokenProgramId), encoder.encode(toAddress(mint))],
                        })];
                case 1:
                    associatedToken = (_a.sent())[0];
                    return [2 /*return*/, associatedToken];
            }
        });
    });
}
export function getRelayDataHash(relayData, destinationChainId) {
    var addressEncoder = getAddressEncoder();
    var uint64Encoder = getU64Encoder();
    var uint32Encoder = getU32Encoder();
    assert(relayData.message.startsWith("0x"), "Message must be a hex string");
    var encodeAddress = function (data) { return Uint8Array.from(addressEncoder.encode(toAddress(data))); };
    var contentToHash = Buffer.concat([
        encodeAddress(relayData.depositor),
        encodeAddress(relayData.recipient),
        encodeAddress(relayData.exclusiveRelayer),
        encodeAddress(relayData.inputToken),
        encodeAddress(relayData.outputToken),
        arrayify(hexZeroPad(hexlify(relayData.inputAmount), 32)),
        Uint8Array.from(uint64Encoder.encode(BigInt(relayData.outputAmount.toString()))),
        Uint8Array.from(uint64Encoder.encode(BigInt(relayData.originChainId.toString()))),
        arrayify(hexZeroPad(hexlify(relayData.depositId), 32)),
        Uint8Array.from(uint32Encoder.encode(relayData.fillDeadline)),
        Uint8Array.from(uint32Encoder.encode(relayData.exclusivityDeadline)),
        hashNonEmptyMessage(Buffer.from(arrayify(relayData.message))),
        Uint8Array.from(uint64Encoder.encode(BigInt(destinationChainId))),
    ]);
    return keccak256(contentToHash);
}
function resolveFillStatusFromPdaEvents(fillStatusPda, toSlot, svmEventsClient) {
    return __awaiter(this, void 0, void 0, function () {
        var eventsToQuery, relevantEvents, fillStatusEvent;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    eventsToQuery = [SVMEventNames.FilledRelay, SVMEventNames.RequestedSlowFill];
                    return [4 /*yield*/, Promise.all(eventsToQuery.map(function (eventName) {
                            // PDAs should have only a few events, requesting up to 10 should be enough.
                            return svmEventsClient.queryDerivedAddressEvents(eventName, fillStatusPda, undefined, toSlot, { limit: 10 });
                        }))];
                case 1:
                    relevantEvents = (_a.sent()).flat();
                    if (relevantEvents.length === 0) {
                        // No fill or requested slow fill events found for this PDA
                        return [2 /*return*/, FillStatus.Unfilled];
                    }
                    // Sort events in ascending order of slot number
                    relevantEvents.sort(function (a, b) { return Number(a.slot - b.slot); });
                    fillStatusEvent = relevantEvents.pop();
                    switch (fillStatusEvent.name) {
                        case SVMEventNames.FilledRelay:
                            return [2 /*return*/, FillStatus.Filled];
                        case SVMEventNames.RequestedSlowFill:
                            return [2 /*return*/, FillStatus.RequestedSlowFill];
                        default:
                            throw new Error("Unexpected event name: ".concat(fillStatusEvent.name));
                    }
                    return [2 /*return*/];
            }
        });
    });
}
/**
 * Attempts to resolve the fill status for an array of deposits by reading their fillStatus PDAs.
 *
 * - If a PDA exists, the status is read directly from it.
 * - If the PDA does not exist but the deposit's fill deadline has not passed, the deposit is considered unfilled.
 * - If the PDA does not exist and the fill deadline has passed, the status cannot be determined and is set to undefined.
 *
 * Assumes PDAs can only be closed after the fill deadline expires.
 *
 * @param provider SVM provider instance
 * @param fillStatusPdas An array of fill status PDAs to retrieve the fill status for.
 * @param relayData An array of relay data from which the fill status PDAs were derived.
 */
function fetchBatchFillStatusFromPdaAccounts(provider, fillStatusPdas, relayDataArray) {
    return __awaiter(this, void 0, void 0, function () {
        var chunkSize, commitment, _a, pdaAccounts, timestamp, fillStatuses;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0:
                    chunkSize = 100;
                    commitment = "confirmed";
                    return [4 /*yield*/, Promise.all([
                            Promise.all(chunk(fillStatusPdas, chunkSize).map(function (chunk) { return fetchEncodedAccounts(provider, chunk, { commitment: commitment }); })),
                            getNearestSlotTime(provider, { commitment: commitment }),
                        ])];
                case 1:
                    _a = _b.sent(), pdaAccounts = _a[0], timestamp = _a[1].timestamp;
                    fillStatuses = pdaAccounts.flat().map(function (account, index) {
                        // If the PDA exists, we can fetch the status directly.
                        if (account.exists) {
                            var decodedAccount = decodeFillStatusAccount(account);
                            return decodedAccount.data.status;
                        }
                        // If the PDA doesn't exist and the deadline hasn't passed yet, the deposit must be unfilled,
                        // since PDAs can't be closed before the fill deadline.
                        if (timestamp < relayDataArray[index].fillDeadline) {
                            return FillStatus.Unfilled;
                        }
                        // If the PDA doesn't exist and the fill deadline has passed, then the status can't be determined and is set to undefined.
                        return undefined;
                    });
                    return [2 /*return*/, fillStatuses];
            }
        });
    });
}
/**
 * Returns the delegate PDA for deposit.
 */
export function getDepositDelegatePda(depositData, programId) {
    return __awaiter(this, void 0, void 0, function () {
        var addrEnc, u64, u32, parts, seedHash, pda;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    addrEnc = getAddressEncoder();
                    u64 = getU64Encoder();
                    u32 = getU32Encoder();
                    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,
                    ];
                    seedHash = Buffer.from(keccak256(Buffer.concat(parts)).slice(2), "hex");
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: programId,
                            seeds: [Buffer.from("delegate"), seedHash],
                        })];
                case 1:
                    pda = (_a.sent())[0];
                    return [2 /*return*/, pda];
            }
        });
    });
}
/**
 * Returns the delegate PDA for depositNow.
 */
export function getDepositNowDelegatePda(depositData, programId) {
    return __awaiter(this, void 0, void 0, function () {
        var addrEnc, u64, u32, parts, seedHash, pda;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    addrEnc = getAddressEncoder();
                    u64 = getU64Encoder();
                    u32 = getU32Encoder();
                    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,
                    ];
                    seedHash = Buffer.from(keccak256(Buffer.concat(parts)).slice(2), "hex");
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: programId,
                            seeds: [Buffer.from("delegate"), seedHash],
                        })];
                case 1:
                    pda = (_a.sent())[0];
                    return [2 /*return*/, pda];
            }
        });
    });
}
/**
 * Returns the fill-delegate PDA for fillRelay.
 */
export function getFillRelayDelegatePda(relayHash, repaymentChainId, repaymentAddress, programId) {
    return __awaiter(this, void 0, void 0, function () {
        var addrEnc, u64, parts, seedHash, pda;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    addrEnc = getAddressEncoder();
                    u64 = getU64Encoder();
                    parts = [
                        relayHash,
                        Uint8Array.from(u64.encode(repaymentChainId)),
                        Uint8Array.from(addrEnc.encode(repaymentAddress)),
                    ];
                    seedHash = Buffer.from(keccak256(Buffer.concat(parts)).slice(2), "hex");
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: programId,
                            seeds: [Buffer.from("delegate"), seedHash],
                        })];
                case 1:
                    pda = (_a.sent())[0];
                    return [2 /*return*/, pda];
            }
        });
    });
}
/**
 * Checks if a CCTP message has been processed.
 * @param solanaClient The Solana client.
 * @param signer The signer of the transaction.
 * @param nonce The nonce to check.
 * @param sourceDomain The source domain.
 * @returns True if the message has been processed, false otherwise.
 */
export var hasCCTPV1MessageBeenProcessed = function (solanaClient, signer, nonce, sourceDomain) { return __awaiter(void 0, void 0, void 0, function () {
    var noncePda, isNonceUsedIx, parserFunction;
    return __generator(this, function (_a) {
        switch (_a.label) {
            case 0: return [4 /*yield*/, getCCTPNoncePda(solanaClient, signer, nonce, sourceDomain)];
            case 1:
                noncePda = _a.sent();
                return [4 /*yield*/, MessageTransmitterClient.getIsNonceUsedInstruction({
                        nonce: nonce,
                        usedNonces: noncePda,
                    })];
            case 2:
                isNonceUsedIx = _a.sent();
                parserFunction = function (buf) {
                    if (buf.length != 1) {
                        throw new Error("Invalid buffer length for isNonceUsedIx");
                    }
                    return Boolean(buf[0]);
                };
                return [4 /*yield*/, simulateAndDecode(solanaClient, isNonceUsedIx, signer, parserFunction)];
            case 3: return [2 /*return*/, _a.sent()];
        }
    });
}); };
/**
 * Returns the account metas for a tokenless message.
 * @returns The account metas for a tokenless message.
 */
export function getAccountMetasForTokenlessMessage() {
    return __awaiter(this, void 0, void 0, function () {
        var statePda, _a;
        var _b, _c;
        return __generator(this, function (_d) {
            switch (_d.label) {
                case 0: return [4 /*yield*/, getStatePda(SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS)];
                case 1:
                    statePda = _d.sent();
                    _a = [{ address: statePda, role: AccountRole.READONLY }];
                    _b = {};
                    return [4 /*yield*/, getSelfAuthority()];
                case 2:
                    _a = _a.concat([
                        (_b.address = _d.sent(), _b.role = AccountRole.READONLY, _b),
                        { address: SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS, role: AccountRole.READONLY },
                        { address: statePda, role: AccountRole.WRITABLE }
                    ]);
                    _c = {};
                    return [4 /*yield*/, getEventAuthority(SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS)];
                case 3: return [2 /*return*/, _a.concat([
                        (_c.address = _d.sent(), _c.role = AccountRole.READONLY, _c),
                        { address: SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS, role: AccountRole.READONLY }
                    ])];
            }
        });
    });
}
/**
 * Returns the account metas for a deposit message.
 * @param message The CCTP message.
 * @param hubChainId The chain ID of the hub.
 * @param tokenMessengerMinter The token messenger minter address.
 * @returns The account metas for a deposit message.
 */
function getAccountMetasForDepositMessage(message, hubChainId, tokenMessengerMinter) {
    return __awaiter(this, void 0, void 0, function () {
        var l1Usdc, l2Usdc, tokenMessengerPda, tokenMinterPda, localTokenPda, tokenMessengerEventAuthorityPda, custodyTokenAccountPda, state, tokenProgram, vault, tokenPairPda, remoteTokenMessengerPda;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    l1Usdc = EvmAddress.from(TOKEN_SYMBOLS_MAP.USDC.addresses[hubChainId]);
                    l2Usdc = SvmAddress.from(TOKEN_SYMBOLS_MAP.USDC.addresses[chainIsProd(hubChainId) ? CHAIN_IDs.SOLANA : CHAIN_IDs.SOLANA_DEVNET]);
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: tokenMessengerMinter,
                            seeds: ["token_messenger"],
                        })];
                case 1:
                    tokenMessengerPda = (_a.sent())[0];
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: tokenMessengerMinter,
                            seeds: ["token_minter"],
                        })];
                case 2:
                    tokenMinterPda = (_a.sent())[0];
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: tokenMessengerMinter,
                            seeds: ["local_token", bs58.decode(l2Usdc.toBase58())],
                        })];
                case 3:
                    localTokenPda = (_a.sent())[0];
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: tokenMessengerMinter,
                            seeds: ["__event_authority"],
                        })];
                case 4:
                    tokenMessengerEventAuthorityPda = (_a.sent())[0];
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: tokenMessengerMinter,
                            seeds: ["custody", bs58.decode(l2Usdc.toBase58())],
                        })];
                case 5:
                    custodyTokenAccountPda = (_a.sent())[0];
                    return [4 /*yield*/, getStatePda(SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS)];
                case 6:
                    state = _a.sent();
                    tokenProgram = TOKEN_PROGRAM_ADDRESS;
                    return [4 /*yield*/, getAssociatedTokenAddress(SvmAddress.from(state), SvmAddress.from(l2Usdc.toBase58()), tokenProgram)];
                case 7:
                    vault = _a.sent();
                    return [4 /*yield*/, 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")),
                            ],
                        })];
                case 8:
                    tokenPairPda = (_a.sent())[0];
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: tokenMessengerMinter,
                            seeds: ["remote_token_messenger", String(message.sourceDomain)],
                        })];
                case 9:
                    remoteTokenMessengerPda = (_a.sent())[0];
                    return [2 /*return*/, [
                            { address: tokenMessengerPda, role: AccountRole.READONLY },
                            { address: remoteTokenMessengerPda, role: AccountRole.READONLY },
                            { address: tokenMinterPda, role: AccountRole.WRITABLE },
                            { address: localTokenPda, role: AccountRole.WRITABLE },
                            { address: tokenPairPda, role: AccountRole.READONLY },
                            { address: vault, role: AccountRole.WRITABLE },
                            { address: custodyTokenAccountPda, role: AccountRole.WRITABLE },
                            { address: TOKEN_PROGRAM_ADDRESS, role: AccountRole.READONLY },
                            { address: tokenMessengerEventAuthorityPda, role: AccountRole.READONLY },
                            { address: tokenMessengerMinter, role: AccountRole.READONLY },
                        ]];
            }
        });
    });
}
/**
 * Returns the CCTP v1 receive message transaction.
 * @param solanaClient The Solana client.
 * @param signer The signer of the transaction.
 * @param message The CCTP message.
 * @param hubChainId The chain ID of the hub.
 * @returns The CCTP v1 receive message transaction.
 */
export function getCCTPV1ReceiveMessageTx(solanaClient, signer, message, hubChainId) {
    return __awaiter(this, void 0, void 0, function () {
        var messageTransmitterPda, eventAuthorityPda, cctpMessageReceiver, authorityPda, usedNonces, accountMetas, _a, messageBytes, input;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0: return [4 /*yield*/, getProgramDerivedAddress({
                        programAddress: MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
                        seeds: ["message_transmitter"],
                    })];
                case 1:
                    messageTransmitterPda = (_b.sent())[0];
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
                            seeds: ["__event_authority"],
                        })];
                case 2:
                    eventAuthorityPda = (_b.sent())[0];
                    cctpMessageReceiver = isDepositForBurnEvent(message)
                        ? TokenMessengerMinterClient.TOKEN_MESSENGER_MINTER_PROGRAM_ADDRESS
                        : SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS;
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
                            seeds: ["message_transmitter_authority", bs58.decode(cctpMessageReceiver)],
                        })];
                case 3:
                    authorityPda = (_b.sent())[0];
                    return [4 /*yield*/, getCCTPNoncePda(solanaClient, signer, message.nonce, message.sourceDomain)];
                case 4:
                    usedNonces = _b.sent();
                    if (!isDepositForBurnEvent(message)) return [3 /*break*/, 6];
                    return [4 /*yield*/, getAccountMetasForDepositMessage(message, hubChainId, TokenMessengerMinterClient.TOKEN_MESSENGER_MINTER_PROGRAM_ADDRESS)];
                case 5:
                    _a = _b.sent();
                    return [3 /*break*/, 8];
                case 6: return [4 /*yield*/, getAccountMetasForTokenlessMessage()];
                case 7:
                    _a = _b.sent();
                    _b.label = 8;
                case 8:
                    accountMetas = _a;
                    messageBytes = message.messageBytes.startsWith("0x")
                        ? Buffer.from(message.messageBytes.slice(2), "hex")
                        : Buffer.from(message.messageBytes, "hex");
                    input = {
                        program: MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
                        payer: signer,
                        caller: signer,
                        authorityPda: authorityPda,
                        messageTransmitter: messageTransmitterPda,
                        eventAuthority: eventAuthorityPda,
                        usedNonces: usedNonces,
                        receiver: SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS,
                        systemProgram: SYSTEM_PROGRAM_ADDRESS,
                        message: messageBytes,
                        attestation: Buffer.from(message.attestation.slice(2), "hex"),
                    };
                    return [2 /*return*/, createReceiveMessageInstruction(signer, solanaClient, input, accountMetas)];
            }
        });
    });
}
/**
 * Finalizes CCTP deposits and messages on Solana.
 *
 * @param solanaClient The Solana client.
 * @param attestedMessages The CCTP messages to Solana.
 * @param signer A base signer to be converted into a Solana signer.
 * @param simulate Whether to simulate the transaction.
 * @param hubChainId The chain ID of the hub.
 * @returns A list of executed transaction signatures.
 */
export function finalizeCCTPV1Messages(solanaClient, attestedMessages, signer, simulate, hubChainId) {
    var _this = this;
    if (simulate === void 0) { simulate = false; }
    if (hubChainId === void 0) { hubChainId = 1; }
    return mapAsync(attestedMessages, function (message) { return __awaiter(_this, void 0, void 0, function () {
        var receiveMessageIx, result, _a, _b, _c, signedTransaction, signature, encodedTransaction;
        return __generator(this, function (_d) {
            switch (_d.label) {
                case 0: return [4 /*yield*/, getCCTPV1ReceiveMessageTx(solanaClient, signer, message, hubChainId)];
                case 1:
                    receiveMessageIx = _d.sent();
                    if (!simulate) return [3 /*break*/, 4];
                    _b = (_a = solanaClient)
                        .simulateTransaction;
                    _c = getBase64EncodedWireTransaction;
                    return [4 /*yield*/, signTransactionMessageWithSigners(receiveMessageIx)];
                case 2: return [4 /*yield*/, _b.apply(_a, [_c.apply(void 0, [_d.sent()]),
                        {
                            encoding: "base64",
                        }])
                        .send()];
                case 3:
                    result = _d.sent();
                    if (result.value.err) {
                        throw new Error(result.value.err.toString());
                    }
                    return [2 /*return*/, ""];
                case 4: return [4 /*yield*/, signTransactionMessageWithSigners(receiveMessageIx)];
                case 5:
                    signedTransaction = _d.sent();
                    signature = getSignatureFromTransaction(signedTransaction);
                    encodedTransaction = getBase64EncodedWireTransaction(signedTransaction);
                    return [4 /*yield*/, solanaClient
                            .sendTransaction(encodedTransaction, { preflightCommitment: "confirmed", encoding: "base64" })
                            .send()];
                case 6:
                    _d.sent();
                    return [2 /*return*/, signature];
            }
        });
    }); });
}
//# sourceMappingURL=SpokeUtils.js.map