import { getDeployedAddress, SvmSpokeIdl } from "@across-protocol/contracts";
import { getSolanaChainId } from "@across-protocol/contracts/dist/src/svm/web3-v1";
import { address, getProgramDerivedAddress, } from "@solana/kit";
import { bs58, chainIsSvm, getMessageHash, isDefined, toAddressType } from "../../utils";
import { decodeEvent, isDevnet } from "./utils";
import { unwrapEventData } from "./";
import assert from "assert";
export class SvmCpiEventsClient {
    rpc;
    programAddress;
    programEventAuthority;
    idl;
    /**
     * Note: Strongly prefer to use the async create() method to instantiate.
     */
    constructor(rpc, address, eventAuthority, idl) {
        this.rpc = rpc;
        this.programAddress = address;
        this.programEventAuthority = eventAuthority;
        this.idl = idl;
    }
    /**
     * Factory method to asynchronously create an instance of SvmSpokeEventsClient.
     */
    static async create(rpc) {
        const isTestnet = await isDevnet(rpc);
        const programId = getDeployedAddress("SvmSpoke", getSolanaChainId(isTestnet ? "devnet" : "mainnet").toString());
        if (!programId)
            throw new Error("Program not found");
        return this.createFor(rpc, programId, SvmSpokeIdl);
    }
    static async createFor(rpc, programId, idl) {
        const programAddress = address(programId);
        const [eventAuthority] = await getProgramDerivedAddress({
            programAddress,
            seeds: ["__event_authority"],
        });
        return new SvmCpiEventsClient(rpc, programAddress, eventAuthority, idl);
    }
    /**
     * Queries events for the SvmSpoke program filtered by event name.
     *
     * @param eventName - The name of the event to filter by.
     * @param fromSlot - Optional starting slot.
     * @param toSlot - Optional ending slot.
     * @param options - Options for fetching signatures.
     * @returns A promise that resolves to an array of events matching the eventName.
     */
    async queryEvents(eventName, fromSlot, toSlot, options = { limit: 1000, commitment: "confirmed" }) {
        const events = await this.queryAllEvents(fromSlot, toSlot, options);
        return events.filter((event) => event.name === eventName);
    }
    /**
     * Queries events for the provided derived address at instantiation filtered by event name.
     *
     * @param eventName - The name of the event to filter by.
     * @param fromSlot - Optional starting slot.
     * @param toSlot - Optional ending slot.
     * @param options - Options for fetching signatures.
     * @returns A promise that resolves to an array of events matching the eventName.
     */
    async queryDerivedAddressEvents(eventName, derivedAddress, fromSlot, toSlot, options = { limit: 1000, commitment: "confirmed" }) {
        const events = await this.queryAllEvents(fromSlot, toSlot, options, derivedAddress);
        return events.filter((event) => event.name === eventName);
    }
    /**
     * Queries all events for a specific program.
     *
     * @param fromSlot - Optional starting slot.
     * @param toSlot - Optional ending slot.
     * @param options - Options for fetching signatures.
     * @param forDerivedAddress - Whether to query events for the program or the derived address.
     * @returns A promise that resolves to an array of all events with additional metadata.
     */
    async queryAllEvents(fromSlot, toSlot, options = { limit: 1000, commitment: "confirmed" }, derivedAddress) {
        const addressToQuery = derivedAddress || this.programAddress;
        const allSignatures = [];
        let hasMoreSignatures = true;
        let currentOptions = { ...options, encoding: "json" };
        while (hasMoreSignatures) {
            const signatures = await this.rpc
                .getSignaturesForAddress(addressToQuery, currentOptions)
                .send();
            // Signatures are sorted by slot in descending order.
            allSignatures.push(...signatures);
            // Update options for the next batch. Set "before" to the last fetched signature.
            if (signatures.length > 0) {
                currentOptions = { ...currentOptions, before: signatures[signatures.length - 1].signature };
            }
            if (fromSlot && allSignatures.length > 0 && allSignatures[allSignatures.length - 1].slot < fromSlot) {
                hasMoreSignatures = false;
            }
            hasMoreSignatures = Boolean(hasMoreSignatures && currentOptions.limit && signatures.length === currentOptions.limit);
        }
        const filteredSignatures = allSignatures.filter((signatureTransaction) => {
            if (fromSlot && signatureTransaction.slot < fromSlot)
                return false;
            if (toSlot && signatureTransaction.slot > toSlot)
                return false;
            return true;
        });
        // Fetch events for all signatures in parallel.
        const eventsWithSlots = await Promise.all(filteredSignatures.map(async (signatureTransaction) => {
            const events = await this.readEventsFromSignature(signatureTransaction.signature, options.commitment);
            return events.map((event) => ({
                ...event,
                confirmationStatus: signatureTransaction.confirmationStatus,
                blockTime: signatureTransaction.blockTime,
                signature: signatureTransaction.signature,
                slot: signatureTransaction.slot,
            }));
        }));
        return eventsWithSlots.flat();
    }
    /**
     * Reads events from a transaction signature.
     *
     * @param txSignature - The transaction signature.
     * @param commitment - Commitment level.
     * @returns A promise that resolves to an array of events.
     */
    async readEventsFromSignature(txSignature, commitment = "confirmed") {
        const txResult = await this.rpc
            .getTransaction(txSignature, { commitment, maxSupportedTransactionVersion: 0, encoding: "json" })
            .send();
        return this.processEventFromTx(txResult);
    }
    /**
     * Processes events from a transaction.
     *
     * @param txResult - The transaction result.
     * @returns A promise that resolves to an array of events with their data and name.
     */
    processEventFromTx(txResult) {
        if (!isDefined(txResult) || isDefined(txResult.meta?.err))
            return [];
        const events = [];
        const accountKeys = txResult.transaction.message.accountKeys;
        const messageAccountKeys = [...accountKeys];
        // Writable accounts come first, then readonly.
        // See https://docs.anza.xyz/proposals/versioned-transactions#new-transaction-format
        messageAccountKeys.push(...(txResult?.meta?.loadedAddresses?.writable ?? []));
        messageAccountKeys.push(...(txResult?.meta?.loadedAddresses?.readonly ?? []));
        for (const ixBlock of txResult.meta?.innerInstructions ?? []) {
            for (const ix of ixBlock.instructions) {
                const ixProgramId = messageAccountKeys[ix.programIdIndex];
                const singleIxAccount = ix.accounts.length === 1 ? messageAccountKeys[ix.accounts[0]] : undefined;
                if (ixProgramId !== undefined &&
                    singleIxAccount !== undefined &&
                    this.programAddress === ixProgramId &&
                    this.programEventAuthority === singleIxAccount) {
                    const ixData = bs58.decode(ix.data);
                    // Skip the first 8 bytes (assumed header) and encode the rest.
                    const eventData = Buffer.from(ixData.slice(8)).toString("base64");
                    const { name, data } = decodeEvent(this.idl, eventData);
                    events.push({ program: this.programAddress, name, data });
                }
            }
        }
        return events;
    }
    /**
     * Finds all FundsDeposited events for a given transaction signature.
     *
     * @param originChainId - The chain ID where the deposit originated.
     * @param txSignature - The transaction signature to search for events.
     * @param commitment - Optional commitment level for the transaction query.
     * @returns A promise that resolves to an array of deposit events for the transaction, or undefined if none found.
     */
    async getDepositEventsFromSignature(originChainId, txSignature, commitment = "confirmed") {
        assert(chainIsSvm(originChainId), `Origin chain ${originChainId} is not an SVM chain`);
        const [events, txDetails] = await Promise.all([
            this.readEventsFromSignature(txSignature, commitment),
            this.rpc
                .getTransaction(txSignature, {
                commitment,
                maxSupportedTransactionVersion: 0,
                encoding: "json",
            })
                .send(),
        ]);
        // Filter for FundsDeposited events only
        const depositEvents = events?.filter((event) => event?.name === "FundsDeposited");
        if (!txDetails || !depositEvents?.length) {
            return;
        }
        return depositEvents.map((event) => {
            const unwrappedEventArgs = unwrapEventData(event, [
                "depositId",
                "outputAmount",
            ]);
            const { data } = unwrappedEventArgs;
            return {
                ...data,
                depositor: toAddressType(data.depositor, data.originChainId),
                recipient: toAddressType(data.recipient, data.destinationChainId),
                exclusiveRelayer: toAddressType(data.exclusiveRelayer, data.destinationChainId),
                inputToken: toAddressType(data.inputToken, data.originChainId),
                outputToken: toAddressType(data.outputToken, data.destinationChainId),
                depositTimestamp: Number(txDetails.blockTime),
                originChainId,
                messageHash: getMessageHash(data.message),
                blockNumber: Number(txDetails.slot),
                txnIndex: 0,
                txnRef: txSignature,
                logIndex: 0,
            };
        });
    }
    /**
     * Finds all FilledRelay events for a given transaction signature.
     *
     * @param destinationChainId - The destination chain ID (must be an SVM chain).
     * @param txSignature - The transaction signature to search for events.
     * @returns A promise that resolves to an array of fill events for the transaction, or undefined if none found.
     */
    async getFillEventsFromSignature(destinationChainId, txSignature, commitment = "confirmed") {
        assert(chainIsSvm(destinationChainId), `Destination chain ${destinationChainId} is not an SVM chain`);
        // Find all events from the transaction signature and get transaction details
        const [events, txDetails] = await Promise.all([
            this.readEventsFromSignature(txSignature, commitment),
            this.rpc
                .getTransaction(txSignature, {
                commitment,
                maxSupportedTransactionVersion: 0,
                encoding: "json",
            })
                .send(),
        ]);
        // Filter for FilledRelay events only
        const fillEvents = events?.filter((event) => event?.name === "FilledRelay");
        if (!txDetails || !fillEvents?.length) {
            return;
        }
        return fillEvents.map((event) => {
            const unwrappedEventData = unwrapEventData(event, [
                "depositId",
                "inputAmount",
            ]);
            const { data } = unwrappedEventData;
            return {
                ...data,
                depositor: toAddressType(data.depositor, data.originChainId),
                recipient: toAddressType(data.recipient, data.destinationChainId),
                exclusiveRelayer: toAddressType(data.exclusiveRelayer, data.destinationChainId),
                inputToken: toAddressType(data.inputToken, data.originChainId),
                outputToken: toAddressType(data.outputToken, data.destinationChainId),
                fillTimestamp: Number(txDetails.blockTime),
                blockNumber: Number(txDetails.slot),
                txnRef: txSignature,
                txnIndex: 0,
                logIndex: 0,
                destinationChainId,
            };
        });
    }
    getProgramAddress() {
        return this.programAddress;
    }
    getRpc() {
        return this.rpc;
    }
}
//# sourceMappingURL=eventsClient.js.map