"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LargeAccountsCoder = exports.readUInt256BE = exports.evmAddressToPublicKey = void 0;
exports.findProgramAddress = findProgramAddress;
exports.readEvents = readEvents;
exports.getEvent = getEvent;
exports.readProgramEvents = readProgramEvents;
exports.subscribeToCpiEventsForProgram = subscribeToCpiEventsForProgram;
exports.calculateRelayHashUint8Array = calculateRelayHashUint8Array;
//TODO: we will need to move this to a better location and integrate it more directly with other utils & files in time.
const anchor_1 = require("@coral-xyz/anchor");
const ethers_1 = require("ethers");
const web3_js_1 = require("@solana/web3.js");
function findProgramAddress(label, program, extraSeeds) {
    const seeds = [Buffer.from(anchor_1.utils.bytes.utf8.encode(label))];
    if (extraSeeds) {
        for (const extraSeed of extraSeeds) {
            if (typeof extraSeed === "string") {
                seeds.push(Buffer.from(anchor_1.utils.bytes.utf8.encode(extraSeed)));
            }
            else if (Array.isArray(extraSeed)) {
                seeds.push(Buffer.from(extraSeed));
            }
            else if (Buffer.isBuffer(extraSeed)) {
                seeds.push(extraSeed);
            }
            else {
                seeds.push(extraSeed.toBuffer());
            }
        }
    }
    const res = web3_js_1.PublicKey.findProgramAddressSync(seeds, program);
    return { publicKey: res[0], bump: res[1] };
}
async function readEvents(connection, txSignature, programs, commitment = "confirmed") {
    const txResult = await connection.getTransaction(txSignature, {
        commitment,
        maxSupportedTransactionVersion: 0,
    });
    let eventAuthorities = new Map();
    for (const program of programs) {
        eventAuthorities.set(program.programId.toString(), findProgramAddress("__event_authority", program.programId).publicKey.toString());
    }
    let events = [];
    // TODO: Add support for version 0 transactions.
    if (!txResult || txResult.transaction.message.version !== "legacy")
        return events;
    for (const ixBlock of txResult.meta?.innerInstructions ?? []) {
        for (const ix of ixBlock.instructions) {
            for (const program of programs) {
                const programStr = program.programId.toString();
                if (ix.accounts.length === 1 &&
                    txResult.transaction.message.accountKeys[ix.programIdIndex].toString() === programStr &&
                    txResult.transaction.message.accountKeys[ix.accounts[0]].toString() ===
                        eventAuthorities.get(programStr)) {
                    const ixData = anchor_1.utils.bytes.bs58.decode(ix.data);
                    const eventData = anchor_1.utils.bytes.base64.encode(Buffer.from(new Uint8Array(ixData).slice(8)));
                    const event = program.coder.events.decode(eventData);
                    events.push({
                        program: program.programId,
                        data: event?.data,
                        name: event?.name,
                    });
                }
            }
        }
    }
    return events;
}
function getEvent(events, program, eventName) {
    for (const event of events) {
        if (event.name === eventName && program.toString() === event.program.toString()) {
            return event.data;
        }
    }
    throw new Error("Event " + eventName + " not found");
}
async function readProgramEvents(connection, program, options, finality = "confirmed") {
    let events = [];
    const pastSignatures = await connection.getSignaturesForAddress(program.programId, options, finality);
    for (const signature of pastSignatures) {
        events.push(...(await readEvents(connection, signature.signature, [program], finality)));
    }
    return events;
}
async function subscribeToCpiEventsForProgram(connection, program, callback) {
    const subscriptionId = connection.onLogs(new web3_js_1.PublicKey(findProgramAddress("__event_authority", program.programId).publicKey.toString()), async (logs) => {
        callback(await readEvents(connection, logs.signature, [program], "confirmed"));
    }, "confirmed");
    return subscriptionId;
}
const evmAddressToPublicKey = (address) => {
    const bytes32Address = `0x000000000000000000000000${address.replace("0x", "")}`;
    return new web3_js_1.PublicKey(ethers_1.ethers.utils.arrayify(bytes32Address));
};
exports.evmAddressToPublicKey = evmAddressToPublicKey;
// TODO: we are inconsistant with where we are placing some utils. we have some stuff here, some stuff that we might
// want to re-use within the test directory. more over, when moving things into the canonical across repo, we should
// re-use the test utils there.
function calculateRelayHashUint8Array(relayData, chainId) {
    const messageBuffer = Buffer.alloc(4);
    messageBuffer.writeUInt32LE(relayData.message.length, 0);
    const contentToHash = Buffer.concat([
        relayData.depositor.toBuffer(),
        relayData.recipient.toBuffer(),
        relayData.exclusiveRelayer.toBuffer(),
        relayData.inputToken.toBuffer(),
        relayData.outputToken.toBuffer(),
        relayData.inputAmount.toArrayLike(Buffer, "le", 8),
        relayData.outputAmount.toArrayLike(Buffer, "le", 8),
        relayData.originChainId.toArrayLike(Buffer, "le", 8),
        new anchor_1.BN(relayData.depositId).toArrayLike(Buffer, "le", 4),
        new anchor_1.BN(relayData.fillDeadline).toArrayLike(Buffer, "le", 4),
        new anchor_1.BN(relayData.exclusivityDeadline).toArrayLike(Buffer, "le", 4),
        messageBuffer,
        relayData.message,
        chainId.toArrayLike(Buffer, "le", 8),
    ]);
    const relayHash = ethers_1.ethers.utils.keccak256(contentToHash);
    const relayHashBuffer = Buffer.from(relayHash.slice(2), "hex");
    return new Uint8Array(relayHashBuffer);
}
const readUInt256BE = (buffer) => {
    let result = BigInt(0);
    for (let i = 0; i < buffer.length; i++) {
        result = (result << BigInt(8)) + BigInt(buffer[i]);
    }
    return result;
};
exports.readUInt256BE = readUInt256BE;
// This is extended Anchor accounts coder to handle large account data that is required when passing instruction
// parameters from prefilled data account. Base implementation restricts the buffer to only 1000 bytes.
class LargeAccountsCoder extends anchor_1.BorshAccountsCoder {
    // Getter to access the private accountLayouts property from base class.
    getAccountLayouts() {
        return this.accountLayouts;
    }
    async encode(accountName, account) {
        const buffer = Buffer.alloc(10240); // We don't currently need anything above instruction data account reallocation limit.
        const layout = this.getAccountLayouts().get(accountName);
        if (!layout) {
            throw new Error(`Unknown account: ${accountName}`);
        }
        const len = layout.encode(account, buffer);
        const accountData = buffer.slice(0, len);
        const discriminator = this.accountDiscriminator(accountName);
        return Buffer.concat([discriminator, accountData]);
    }
}
exports.LargeAccountsCoder = LargeAccountsCoder;
