"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SvmQuery = void 0;
const tslib_1 = require("tslib");
const assert_1 = tslib_1.__importDefault(require("assert"));
const kit_1 = require("@solana/kit");
const svm_1 = require("../../arch/svm");
const coingecko_1 = require("../../coingecko");
const constants_1 = require("../../constants");
const gasPriceOracle_1 = require("../../gasPriceOracle");
const utils_1 = require("../../utils");
const relayFeeCalculator_1 = require("../relayFeeCalculator");
const token_1 = require("@solana-program/token");
const token_2022_1 = require("@solana-program/token-2022");
const compute_budget_1 = require("@solana-program/compute-budget");
const __1 = require("../..");
class SvmQuery {
    provider;
    symbolMapping;
    spokePool;
    simulatedRelayerAddress;
    logger;
    coingeckoProApiKey;
    fixedGasPrice;
    coingeckoBaseCurrency;
    computeUnitEstimator;
    constructor(provider, symbolMapping, spokePool, simulatedRelayerAddress, logger, coingeckoProApiKey, fixedGasPrice, coingeckoBaseCurrency = "eth") {
        this.provider = provider;
        this.symbolMapping = symbolMapping;
        this.spokePool = spokePool;
        this.simulatedRelayerAddress = simulatedRelayerAddress;
        this.logger = logger;
        this.coingeckoProApiKey = coingeckoProApiKey;
        this.fixedGasPrice = fixedGasPrice;
        this.coingeckoBaseCurrency = coingeckoBaseCurrency;
        this.computeUnitEstimator = (0, compute_budget_1.estimateComputeUnitLimitFactory)({
            rpc: provider,
        });
    }
    async getGasCosts(relayData, relayer = (0, relayFeeCalculator_1.getDefaultRelayer)(relayData.destinationChainId), options = {}) {
        const { destinationChainId, recipient, outputToken, exclusiveRelayer } = relayData;
        (0, assert_1.default)(recipient.isSVM(), `getGasCosts: recipient not an SVM address (${recipient})`);
        (0, assert_1.default)(outputToken.isSVM(), `getGasCosts: outputToken not an SVM address (${outputToken})`);
        (0, assert_1.default)(exclusiveRelayer.isSVM(), `getGasCosts: exclusiveRelayer not an SVM address (${exclusiveRelayer})`);
        (0, assert_1.default)(relayer.isSVM());
        const [repaymentChainId, repaymentAddress] = [destinationChainId, relayer];
        const _fillRelayTx = await this.getFillRelayTx({ ...relayData, recipient, outputToken, exclusiveRelayer }, (0, svm_1.SolanaVoidSigner)(relayer.toBase58()), repaymentChainId, repaymentAddress);
        const fillTooLarge = (0, svm_1.isSVMFillTooLarge)(_fillRelayTx);
        const fillRelayTx = fillTooLarge.tooLarge
            ? await this.getFillRelayTx({ ...relayData, recipient, outputToken, exclusiveRelayer, message: "0x" }, (0, svm_1.SolanaVoidSigner)(relayer.toBase58()), repaymentChainId, repaymentAddress)
            : _fillRelayTx;
        const [_computeUnitsConsumed, gasPriceEstimate, tokenAccountInfo] = await Promise.all([
            fillTooLarge.tooLarge
                ? this.estimateComputeUnits({ ...relayData, recipient, outputToken, exclusiveRelayer }, relayer, repaymentChainId, repaymentAddress)
                : this.computeUnitEstimator(fillRelayTx),
            (0, gasPriceOracle_1.getGasPriceEstimate)(this.provider, {
                unsignedTx: fillRelayTx,
                baseFeeMultiplier: options.baseFeeMultiplier,
                priorityFeeMultiplier: options.priorityFeeMultiplier,
            }),
            this.provider.getAccountInfo((0, svm_1.toAddress)(outputToken), { encoding: "base58" }).send(),
        ]);
        const computeUnitsConsumed = (0, utils_1.toBN)(_computeUnitsConsumed);
        const tokenOwner = tokenAccountInfo.value.owner;
        (0, assert_1.default)(tokenOwner === token_2022_1.TOKEN_2022_PROGRAM_ADDRESS || tokenOwner === token_1.TOKEN_PROGRAM_ADDRESS, `${outputToken} has invalid token account owner ${tokenOwner}.`);
        const recipientAta = await (0, svm_1.getAssociatedTokenAddress)(recipient, outputToken, tokenOwner);
        const encodedAta = await (0, kit_1.fetchEncodedAccount)(this.provider, recipientAta);
        const gasPrice = gasPriceEstimate.baseFee.add(gasPriceEstimate.microLamportsPerComputeUnit.mul(computeUnitsConsumed).div((0, utils_1.toBN)(1_000_000)));
        let tokenGasCost = gasPrice;
        if (!encodedAta.exists) {
            let extensions = undefined;
            if (tokenOwner === token_2022_1.TOKEN_2022_PROGRAM_ADDRESS) {
                const mint = await (0, token_2022_1.fetchMint)(this.provider, (0, svm_1.toAddress)(outputToken));
                extensions = (0, kit_1.isSome)(mint.data.extensions) ? mint.data.extensions.value : undefined;
            }
            const tokenAccountSize = (0, token_2022_1.getTokenSize)(extensions);
            const rentCostInLamports = await this.provider.getMinimumBalanceForRentExemption(BigInt(tokenAccountSize)).send();
            tokenGasCost = tokenGasCost.add((0, utils_1.toBN)(Number(rentCostInLamports)));
        }
        return {
            nativeGasCost: computeUnitsConsumed,
            tokenGasCost,
            gasPrice,
        };
    }
    async getNativeGasCost(deposit, relayer = (0, relayFeeCalculator_1.getDefaultRelayer)(deposit.destinationChainId)) {
        const { destinationChainId, recipient, outputToken, exclusiveRelayer } = deposit;
        (0, assert_1.default)(recipient.isSVM(), `getNativeGasCost: recipient not an SVM address (${recipient})`);
        (0, assert_1.default)(outputToken.isSVM(), `getNativeGasCost: outputToken not an SVM address (${outputToken})`);
        (0, assert_1.default)(exclusiveRelayer.isSVM(), `getNativeGasCost: exclusiveRelayer not an SVM address (${exclusiveRelayer})`);
        (0, assert_1.default)(relayer.isSVM());
        const [repaymentChainId, repaymentAddress] = [destinationChainId, relayer];
        const fillRelayTx = await this.getFillRelayTx({ ...deposit, recipient, outputToken, exclusiveRelayer }, (0, svm_1.SolanaVoidSigner)(relayer.toBase58()), repaymentChainId, repaymentAddress);
        return (0, utils_1.toBN)(await this.computeUnitEstimator(fillRelayTx));
    }
    getAuxiliaryNativeTokenCost(deposit) {
        return __1.arch.svm.getAuxiliaryNativeTokenCost(deposit);
    }
    async getFillRelayTx(relayData, signer, repaymentChainId, repaymentAddress) {
        return await (0, svm_1.getFillRelayTx)(this.spokePool, this.provider, relayData, signer, repaymentChainId, repaymentAddress);
    }
    async getTokenPrice(tokenSymbol) {
        if (!this.symbolMapping[tokenSymbol])
            throw new Error(`${tokenSymbol} does not exist in mapping`);
        const coingeckoInstance = coingecko_1.Coingecko.get(this.logger, this.coingeckoProApiKey);
        const [, price] = await coingeckoInstance.getCurrentPriceByContract(this.symbolMapping[tokenSymbol].addresses[constants_1.CHAIN_IDs.MAINNET], this.coingeckoBaseCurrency);
        return price;
    }
    getTokenDecimals(tokenSymbol) {
        if (!this.symbolMapping[tokenSymbol])
            throw new Error(`${tokenSymbol} does not exist in mapping`);
        return this.symbolMapping[tokenSymbol].decimals;
    }
    async estimateComputeUnits(relayData, relayer, repaymentChainId, repaymentAddress) {
        const provider = this.provider;
        const spokePoolAddr = (0, svm_1.toAddress)(this.spokePool);
        const voidSigner = (0, svm_1.SolanaVoidSigner)(relayer.toBase58());
        const [instructionParamsIxs, _fillRelayTx] = await Promise.all([
            (0, svm_1.getFillRelayViaInstructionParamsInstructions)(spokePoolAddr, relayData, repaymentChainId, repaymentAddress, voidSigner, provider),
            (0, svm_1.getIPFillRelayTx)(this.spokePool, provider, relayData, voidSigner, repaymentChainId, repaymentAddress),
        ]);
        const computeUnitLimitIx = (0, compute_budget_1.getSetComputeUnitLimitInstruction)({ units: 10_000_000 });
        const fillRelayTx = (0, kit_1.pipe)(_fillRelayTx, (tx) => (0, kit_1.appendTransactionMessageInstruction)(computeUnitLimitIx, tx));
        const instructionParamsTxs = await (0, utils_1.mapAsync)(instructionParamsIxs, async (ix) => {
            return (0, kit_1.pipe)(await (0, svm_1.createDefaultTransaction)(provider, voidSigner), (tx) => (0, kit_1.appendTransactionMessageInstruction)(ix, tx));
        });
        const bundleTxns = [...instructionParamsTxs, fillRelayTx].map((txn) => {
            const compiled = (0, kit_1.compileTransaction)(txn);
            return (0, kit_1.getBase64EncodedWireTransaction)(compiled);
        });
        const executionAccounts = bundleTxns.map(() => {
            return { accountIndex: 0, addresses: [this.spokePool.toBase58(), relayer.toBase58()] };
        });
        const simulateBundleResponse = await provider
            .simulateBundle({
            encodedTransactions: bundleTxns,
        }, {
            skipSigVerify: true,
            preExecutionAccountsConfigs: executionAccounts,
            postExecutionAccountsConfigs: executionAccounts,
        })
            .send();
        if (simulateBundleResponse.value.summary !== "succeeded") {
            const { TransactionFailure: failure } = simulateBundleResponse.value.summary.failed.error;
            throw new Error(`simulateBundle failed with result: ${failure[1]}`);
        }
        const totalCuSpent = simulateBundleResponse.value.transactionResults.reduce((sum, res) => res.unitsConsumed + sum, BigInt(0));
        return totalCuSpent;
    }
}
exports.SvmQuery = SvmQuery;
//# sourceMappingURL=svmQuery.js.map