"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.numberToU8a32 = exports.bigToU8a32 = exports.getEmergencyDeleteRootBundleRootBundleId = exports.isEmergencyDeleteRootBundleMessageBody = exports.isRelayRootBundleMessageBody = exports.getCCTPNoncePda = exports.simulateAndDecode = exports.createDefaultTransaction = exports.getSelfAuthority = exports.SolanaVoidSigner = void 0;
exports.isDevnet = isDevnet;
exports.toAddress = toAddress;
exports.getNearestSlotTime = getNearestSlotTime;
exports.getLatestFinalizedSlotWithBlock = getLatestFinalizedSlotWithBlock;
exports.parseEventData = parseEventData;
exports.decodeEvent = decodeEvent;
exports.getEventName = getEventName;
exports.unwrapEventData = unwrapEventData;
exports.getStatePda = getStatePda;
exports.getFillStatusPda = getFillStatusPda;
exports.getRoutePda = getRoutePda;
exports.getTransferLiabilityPda = getTransferLiabilityPda;
exports.getRootBundlePda = getRootBundlePda;
exports.getInstructionParamsPda = getInstructionParamsPda;
exports.getClaimAccountPda = getClaimAccountPda;
exports.getEventAuthority = getEventAuthority;
exports.getHandlerSignerPda = getHandlerSignerPda;
exports.getRandomSvmAddress = getRandomSvmAddress;
exports.toSvmRelayData = toSvmRelayData;
exports.isDepositForBurnEvent = isDepositForBurnEvent;
exports.bigintToU8a32 = bigintToU8a32;
const tslib_1 = require("tslib");
const contracts_1 = require("@across-protocol/contracts");
const typechain_1 = require("../../typechain");
const anchor_1 = require("@coral-xyz/anchor");
const kit_1 = require("@solana/kit");
const assert_1 = tslib_1.__importDefault(require("assert"));
const bs58_1 = tslib_1.__importDefault(require("bs58"));
const ethers_1 = require("ethers");
const interfaces_1 = require("../../interfaces");
const utils_1 = require("../../utils");
const SpokeUtils_1 = require("./SpokeUtils");
const types_1 = require("./types");
const SolanaVoidSigner = (simulationAddress) => {
    return {
        address: (0, kit_1.address)(simulationAddress),
        signAndSendTransactions: async () => {
            return await Promise.resolve([]);
        },
    };
};
exports.SolanaVoidSigner = SolanaVoidSigner;
async function isDevnet(rpc) {
    const genesisHash = await rpc.getGenesisHash().send();
    return genesisHash === "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG";
}
function toAddress(address) {
    return address.toBase58();
}
async function getNearestSlotTime(provider, opts = { commitment: "confirmed" }, logger) {
    let timestamp;
    let slot = "slot" in opts ? opts.slot : await (0, SpokeUtils_1.getSlot)(provider, opts.commitment, logger);
    const maxRetries = undefined;
    do {
        timestamp = await (0, SpokeUtils_1.getTimestampForSlot)(provider, slot, maxRetries, logger);
    } while (!(0, utils_1.isDefined)(timestamp) && --slot);
    (0, assert_1.default)((0, utils_1.isDefined)(timestamp), `Unable to resolve block time for SVM slot ${slot}`);
    return { slot, timestamp };
}
async function getLatestFinalizedSlotWithBlock(provider, logger, maxSlot, maxLookback = 1000) {
    const opts = { maxSupportedTransactionVersion: 0, transactionDetails: "none", rewards: false };
    const { slot: finalizedSlot } = await getNearestSlotTime(provider, { commitment: "finalized" }, logger);
    const endSlot = (0, utils_1.biMin)(maxSlot, finalizedSlot);
    let slot = endSlot;
    do {
        const block = await provider.getBlock(slot, opts).send();
        if ((0, utils_1.isDefined)(block) && [block.blockHeight, block.blockTime].every(utils_1.isDefined)) {
            break;
        }
    } while (--maxLookback > 0 && --slot > 0);
    if (maxLookback === 0) {
        throw new Error(`Unable to find Solana block between slots [${slot}, ${endSlot}]`);
    }
    return Number(slot);
}
function parseEventData(eventData) {
    if (!eventData)
        return eventData;
    if (Array.isArray(eventData)) {
        return eventData.map(parseEventData);
    }
    if (Buffer.isBuffer(eventData)) {
        return new Uint8Array(eventData);
    }
    if (typeof eventData === "object") {
        const stringTag = Object.prototype.toString.call(eventData);
        if (stringTag.includes("PublicKey")) {
            return (0, kit_1.address)(eventData.toString());
        }
        if (anchor_1.BN.isBN(eventData)) {
            return BigInt(eventData.toString());
        }
        return Object.fromEntries(Object.entries(eventData).map(([key, value]) => [snakeToCamel(key), parseEventData(value)]));
    }
    return eventData;
}
function decodeEvent(idl, rawEvent) {
    const event = new anchor_1.BorshEventCoder(idl).decode(rawEvent);
    if (!event)
        throw new Error(`Malformed rawEvent for IDL ${idl.address}: ${rawEvent}`);
    return {
        name: event.name,
        data: parseEventData(event.data),
    };
}
function snakeToCamel(s) {
    return s.replace(/(_\w)/g, (match) => match[1].toUpperCase());
}
function getEventName(rawName) {
    if (Object.values(types_1.SVMEventNames).some((name) => rawName.includes(name)))
        return rawName;
    throw new Error(`Unknown event name: ${rawName}`);
}
function unwrapEventData(data, uint8ArrayKeysAsBigInt = ["depositId", "outputAmount", "inputAmount"], currentKey) {
    if (data == null) {
        return data;
    }
    if (typeof data === "bigint") {
        const bigIntKeysAsNumber = ["originChainId", "destinationChainId", "repaymentChainId", "chainId"];
        if (currentKey && bigIntKeysAsNumber.includes(currentKey)) {
            return Number(data);
        }
        return utils_1.BigNumber.from(data);
    }
    if (data instanceof Uint8Array || (0, utils_1.isUint8Array)(data)) {
        const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
        const hex = ethers_1.ethers.utils.hexlify(bytes);
        if (currentKey && uint8ArrayKeysAsBigInt.includes(currentKey)) {
            return utils_1.BigNumber.from(hex);
        }
        return hex;
    }
    if (Array.isArray(data)) {
        return data.map((item) => unwrapEventData(item, uint8ArrayKeysAsBigInt));
    }
    if (typeof data === "string" && (0, kit_1.isAddress)(data)) {
        return ethers_1.ethers.utils.hexlify(bs58_1.default.decode(data));
    }
    if (typeof data === "object") {
        if (currentKey === "fillType") {
            const fillType = Object.keys(data)[0];
            switch (fillType) {
                case "FastFill":
                    return interfaces_1.FillType.FastFill;
                case "ReplacedSlowFill":
                    return interfaces_1.FillType.ReplacedSlowFill;
                case "SlowFill":
                    return interfaces_1.FillType.SlowFill;
                default:
                    throw new Error(`Unknown fill type: ${fillType}`);
            }
        }
        if (Object.keys(data).length === 0) {
            return "0x";
        }
        return Object.fromEntries(Object.entries(data).map(([key, value]) => [
            key,
            unwrapEventData(value, uint8ArrayKeysAsBigInt, key),
        ]));
    }
    return data;
}
async function getStatePda(programId) {
    const intEncoder = (0, kit_1.getU64Encoder)();
    const seed = intEncoder.encode(0);
    const [statePda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: programId,
        seeds: ["state", seed],
    });
    return statePda;
}
async function getFillStatusPda(programId, relayData, destinationChainId) {
    const messageHash = relayData.messageHash ?? (0, utils_1.getMessageHash)(relayData.message);
    const relayDataHash = (0, SpokeUtils_1.getRelayDataHash)({ ...relayData, messageHash }, destinationChainId);
    const uint8RelayDataHash = new Uint8Array(Buffer.from(relayDataHash.slice(2), "hex"));
    const [fillStatusPda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: programId,
        seeds: ["fills", uint8RelayDataHash],
    });
    return fillStatusPda;
}
async function getRoutePda(originToken, seed, routeChainId) {
    const intEncoder = (0, kit_1.getU64Encoder)();
    const addressEncoder = (0, kit_1.getAddressEncoder)();
    const [pda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: (0, kit_1.address)(contracts_1.SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS),
        seeds: ["route", addressEncoder.encode(originToken), intEncoder.encode(seed), intEncoder.encode(routeChainId)],
    });
    return pda;
}
async function getTransferLiabilityPda(programId, originToken) {
    const addressEncoder = (0, kit_1.getAddressEncoder)();
    const [pda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: programId,
        seeds: ["transfer_liability", addressEncoder.encode(originToken)],
    });
    return pda;
}
async function getRootBundlePda(programId, rootBundleId) {
    const seedEncoder = (0, kit_1.getU64Encoder)();
    const seed = seedEncoder.encode(0);
    const intEncoder = (0, kit_1.getU32Encoder)();
    const [pda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: programId,
        seeds: ["root_bundle", seed, intEncoder.encode(rootBundleId)],
    });
    return pda;
}
async function getInstructionParamsPda(programId, signer) {
    const addressEncoder = (0, kit_1.getAddressEncoder)();
    const [pda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: programId,
        seeds: ["instruction_params", addressEncoder.encode(signer)],
    });
    return pda;
}
async function getClaimAccountPda(programId, mint, tokenOwner) {
    const addressEncoder = (0, kit_1.getAddressEncoder)();
    const [pda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: programId,
        seeds: ["claim_account", addressEncoder.encode(mint), addressEncoder.encode(tokenOwner)],
    });
    return pda;
}
async function getEventAuthority(programId) {
    const [eventAuthority] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: programId,
        seeds: ["__event_authority"],
    });
    return eventAuthority;
}
async function getHandlerSignerPda(programId) {
    const [handlerSignerPda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: programId,
        seeds: ["handler_signer"],
    });
    return handlerSignerPda;
}
const getSelfAuthority = async () => {
    const [selfAuthority] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: (0, kit_1.address)(contracts_1.SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS),
        seeds: ["self_authority"],
    });
    return selfAuthority;
};
exports.getSelfAuthority = getSelfAuthority;
function getRandomSvmAddress() {
    const bytes = ethers_1.ethers.utils.randomBytes(32);
    const base58Address = bs58_1.default.encode(bytes);
    return (0, kit_1.address)(base58Address);
}
const createDefaultTransaction = async (rpcClient, signer, latestBlockhash) => {
    latestBlockhash = (0, utils_1.isDefined)(latestBlockhash) ? latestBlockhash : (await rpcClient.getLatestBlockhash().send()).value;
    return (0, kit_1.pipe)((0, kit_1.createTransactionMessage)({ version: 0 }), (tx) => (0, kit_1.setTransactionMessageFeePayerSigner)(signer, tx), (tx) => (0, kit_1.setTransactionMessageLifetimeUsingBlockhash)(latestBlockhash, tx));
};
exports.createDefaultTransaction = createDefaultTransaction;
const simulateAndDecode = async (solanaClient, ix, signer, parser, latestBlockhash) => {
    const simulationTx = (0, kit_1.appendTransactionMessageInstruction)(ix, await (0, exports.createDefaultTransaction)(solanaClient, signer, latestBlockhash));
    const simulationResult = await solanaClient
        .simulateTransaction((0, kit_1.getBase64EncodedWireTransaction)(await (0, kit_1.signTransactionMessageWithSigners)(simulationTx)), {
        encoding: "base64",
    })
        .send();
    if (!simulationResult.value.returnData?.data[0]) {
        throw new Error("svm::simulateAndDecode: simulateTransaction failed. No return data.");
    }
    return parser(Buffer.from(simulationResult.value.returnData.data[0], "base64"));
};
exports.simulateAndDecode = simulateAndDecode;
function toSvmRelayData(relayData) {
    return {
        originChainId: BigInt(relayData.originChainId),
        depositor: (0, kit_1.address)(relayData.depositor.toBase58()),
        recipient: (0, kit_1.address)(relayData.recipient.toBase58()),
        depositId: ethers_1.ethers.utils.arrayify(ethers_1.ethers.utils.hexZeroPad(relayData.depositId.toHexString(), 32)),
        inputToken: (0, kit_1.address)(relayData.inputToken.toBase58()),
        outputToken: (0, kit_1.address)(relayData.outputToken.toBase58()),
        inputAmount: ethers_1.ethers.utils.arrayify(ethers_1.ethers.utils.hexZeroPad(relayData.inputAmount.toHexString(), 32)),
        outputAmount: relayData.outputAmount.toBigInt(),
        message: Uint8Array.from(Buffer.from(relayData.message.slice(2), "hex")),
        fillDeadline: relayData.fillDeadline,
        exclusiveRelayer: (0, kit_1.address)(relayData.exclusiveRelayer.toBase58()),
        exclusivityDeadline: relayData.exclusivityDeadline,
    };
}
const getCCTPNoncePda = async (solanaClient, signer, nonce, sourceDomain, latestBlockhash) => {
    const [messageTransmitterPda] = await (0, kit_1.getProgramDerivedAddress)({
        programAddress: contracts_1.MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
        seeds: ["message_transmitter"],
    });
    const getNonceIx = await contracts_1.MessageTransmitterClient.getGetNoncePdaInstruction({
        messageTransmitter: messageTransmitterPda,
        nonce,
        sourceDomain: sourceDomain,
    });
    const parserFunction = (buf) => {
        if (buf.length === 32) {
            return (0, kit_1.address)(bs58_1.default.encode(buf));
        }
        throw new Error("Invalid buffer");
    };
    return await (0, exports.simulateAndDecode)(solanaClient, getNonceIx, signer, parserFunction, latestBlockhash);
};
exports.getCCTPNoncePda = getCCTPNoncePda;
function isDepositForBurnEvent(event) {
    return event.type === "transfer";
}
const isRelayRootBundleMessageBody = (body) => {
    if (body.length < 4)
        return false;
    const spokePoolInterface = new ethers_1.ethers.utils.Interface(typechain_1.SpokePool__factory.abi);
    const relayRootBundleSelector = spokePoolInterface.getSighash("relayRootBundle");
    return body.slice(0, 4).equals(Buffer.from(relayRootBundleSelector.slice(2), "hex"));
};
exports.isRelayRootBundleMessageBody = isRelayRootBundleMessageBody;
const isEmergencyDeleteRootBundleMessageBody = (body) => {
    if (body.length < 4)
        return false;
    const spokePoolInterface = new ethers_1.ethers.utils.Interface(typechain_1.SpokePool__factory.abi);
    const emergencyDeleteRootBundleSelector = spokePoolInterface.getSighash("emergencyDeleteRootBundle");
    return body.slice(0, 4).equals(Buffer.from(emergencyDeleteRootBundleSelector.slice(2), "hex"));
};
exports.isEmergencyDeleteRootBundleMessageBody = isEmergencyDeleteRootBundleMessageBody;
const getEmergencyDeleteRootBundleRootBundleId = (body) => {
    const spokePoolInterface = new ethers_1.ethers.utils.Interface(typechain_1.SpokePool__factory.abi);
    const result = spokePoolInterface.decodeFunctionData("emergencyDeleteRootBundle", body);
    return result.rootBundleId.toNumber();
};
exports.getEmergencyDeleteRootBundleRootBundleId = getEmergencyDeleteRootBundleRootBundleId;
function bigintToU8a32(n) {
    if (n < BigInt(0) || n > ethers_1.ethers.constants.MaxUint256.toBigInt()) {
        throw new RangeError("Value must fit in 256 bits");
    }
    const hexPadded = ethers_1.ethers.utils.hexZeroPad("0x" + n.toString(16), 32);
    return ethers_1.ethers.utils.arrayify(hexPadded);
}
const bigToU8a32 = (bn) => bigintToU8a32(typeof bn === "bigint" ? bn : BigInt(bn.toString()));
exports.bigToU8a32 = bigToU8a32;
const numberToU8a32 = (n) => bigintToU8a32(BigInt(n));
exports.numberToU8a32 = numberToU8a32;
//# sourceMappingURL=utils.js.map