UNPKG

@lifi/sdk

Version:

LI.FI Any-to-Any Cross-Chain-Swap SDK

175 lines 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SolanaStepExecutor = void 0; const web3_js_1 = require("@solana/web3.js"); const viem_1 = require("viem"); const config_js_1 = require("../../config.js"); const constants_js_1 = require("../../errors/constants.js"); const errors_js_1 = require("../../errors/errors.js"); const api_js_1 = require("../../services/api.js"); const base64ToUint8Array_js_1 = require("../../utils/base64ToUint8Array.js"); const BaseStepExecutor_js_1 = require("../BaseStepExecutor.js"); const checkBalance_js_1 = require("../checkBalance.js"); const stepComparison_js_1 = require("../stepComparison.js"); const waitForDestinationChainTransaction_js_1 = require("../waitForDestinationChainTransaction.js"); const connection_js_1 = require("./connection.js"); const sendAndConfirmBundle_js_1 = require("./jito/sendAndConfirmBundle.js"); const parseSolanaErrors_js_1 = require("./parseSolanaErrors.js"); const sendAndConfirmTransaction_js_1 = require("./sendAndConfirmTransaction.js"); class SolanaStepExecutor extends BaseStepExecutor_js_1.BaseStepExecutor { walletAdapter; skipSimulation; constructor(options) { super(options); this.walletAdapter = options.walletAdapter; this.skipSimulation = options.skipSimulation ?? false; } checkWalletAdapter = (step) => { if (this.walletAdapter.publicKey.toString() !== step.action.fromAddress) { throw new errors_js_1.TransactionError(constants_js_1.LiFiErrorCode.WalletChangedDuringExecution, 'The wallet address that requested the quote does not match the wallet address attempting to sign the transaction.'); } }; deserializeTransactions(transactionRequest) { if (!transactionRequest.data?.length) { throw new errors_js_1.TransactionError(constants_js_1.LiFiErrorCode.TransactionUnprepared, 'Unable to prepare transaction.'); } if (Array.isArray(transactionRequest.data)) { return transactionRequest.data.map((tx) => web3_js_1.VersionedTransaction.deserialize((0, base64ToUint8Array_js_1.base64ToUint8Array)(tx))); } else { return [ web3_js_1.VersionedTransaction.deserialize((0, base64ToUint8Array_js_1.base64ToUint8Array)(transactionRequest.data)), ]; } } shouldUseJitoBundle(transactions) { const isJitoBundleEnabled = Boolean(config_js_1.config.get().routeOptions?.jitoBundle); if (transactions.length > 1 && !isJitoBundleEnabled) { throw new errors_js_1.TransactionError(constants_js_1.LiFiErrorCode.TransactionUnprepared, `Received ${transactions.length} transactions but Jito bundle is not enabled. Multiple transactions require Jito bundle support. Please enable jitoBundle in routeOptions.`); } return transactions.length > 1 && isJitoBundleEnabled; } executeStep = async (step) => { step.execution = this.statusManager.initExecutionObject(step); const fromChain = await config_js_1.config.getChainById(step.action.fromChainId); const toChain = await config_js_1.config.getChainById(step.action.toChainId); const isBridgeExecution = fromChain.id !== toChain.id; const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP'; let process = this.statusManager.findOrCreateProcess({ step, type: currentProcessType, chainId: fromChain.id, }); if (process.status !== 'DONE') { try { process = this.statusManager.updateProcess(step, process.type, 'STARTED'); await (0, checkBalance_js_1.checkBalance)(this.walletAdapter.publicKey.toString(), step); if (!step.transactionRequest) { const { execution, ...stepBase } = step; const updatedStep = await (0, api_js_1.getStepTransaction)(stepBase); const comparedStep = await (0, stepComparison_js_1.stepComparison)(this.statusManager, step, updatedStep, this.allowUserInteraction, this.executionOptions); Object.assign(step, { ...comparedStep, execution: step.execution, }); } if (!step.transactionRequest?.data) { throw new errors_js_1.TransactionError(constants_js_1.LiFiErrorCode.TransactionUnprepared, 'Unable to prepare transaction.'); } process = this.statusManager.updateProcess(step, process.type, 'ACTION_REQUIRED'); if (!this.allowUserInteraction) { return step; } let transactionRequest = { data: step.transactionRequest.data, }; if (this.executionOptions?.updateTransactionRequestHook) { const customizedTransactionRequest = await this.executionOptions.updateTransactionRequestHook({ requestType: 'transaction', ...transactionRequest, }); transactionRequest = { ...transactionRequest, ...customizedTransactionRequest, }; } const transactions = this.deserializeTransactions(transactionRequest); const shouldUseJitoBundle = this.shouldUseJitoBundle(transactions); this.checkWalletAdapter(step); const signedTransactions = await (0, viem_1.withTimeout)(() => this.walletAdapter.signAllTransactions(transactions), { timeout: 120_000, errorInstance: new errors_js_1.TransactionError(constants_js_1.LiFiErrorCode.TransactionExpired, 'Transaction has expired: blockhash is no longer recent enough.'), }); process = this.statusManager.updateProcess(step, process.type, 'PENDING'); if (!signedTransactions.length) { throw new errors_js_1.TransactionError(constants_js_1.LiFiErrorCode.TransactionUnprepared, 'There was a problem signing the transactions. Wallet adapter did not return any signed transactions.'); } let confirmedTransaction; if (shouldUseJitoBundle) { const bundleResult = await (0, sendAndConfirmBundle_js_1.sendAndConfirmBundle)(signedTransactions); const allConfirmed = bundleResult.signatureResults.every((result) => result !== null); if (!allConfirmed) { throw new errors_js_1.TransactionError(constants_js_1.LiFiErrorCode.TransactionExpired, 'One or more bundle transactions were not confirmed within the expected time frame.'); } const failedResult = bundleResult.signatureResults.find((result) => result?.err !== null); if (failedResult) { const reason = typeof failedResult.err === 'object' ? JSON.stringify(failedResult.err) : failedResult.err; throw new errors_js_1.TransactionError(constants_js_1.LiFiErrorCode.TransactionFailed, `Bundle transaction failed: ${reason}`); } confirmedTransaction = { signatureResult: bundleResult.signatureResults[0], txSignature: bundleResult.txSignatures[0], bundleId: bundleResult.bundleId, }; } else { const signedTransaction = signedTransactions[0]; if (!this.skipSimulation) { const simulationResult = await (0, connection_js_1.callSolanaWithRetry)((connection) => connection.simulateTransaction(signedTransaction, { commitment: 'confirmed', replaceRecentBlockhash: true, })); if (simulationResult.value.err) { throw new errors_js_1.TransactionError(constants_js_1.LiFiErrorCode.TransactionSimulationFailed, 'Transaction simulation failed'); } } confirmedTransaction = await (0, sendAndConfirmTransaction_js_1.sendAndConfirmTransaction)(signedTransaction); } if (!confirmedTransaction.signatureResult) { throw new errors_js_1.TransactionError(constants_js_1.LiFiErrorCode.TransactionExpired, 'Transaction has expired: The block height has exceeded the maximum allowed limit.'); } if (confirmedTransaction.signatureResult.err) { const reason = typeof confirmedTransaction.signatureResult.err === 'object' ? JSON.stringify(confirmedTransaction.signatureResult.err) : confirmedTransaction.signatureResult.err; throw new errors_js_1.TransactionError(constants_js_1.LiFiErrorCode.TransactionFailed, `Transaction failed: ${reason}`); } process = this.statusManager.updateProcess(step, process.type, 'PENDING', { txHash: confirmedTransaction.txSignature, txLink: `${fromChain.metamask.blockExplorerUrls[0]}tx/${confirmedTransaction.txSignature}`, }); if (isBridgeExecution) { process = this.statusManager.updateProcess(step, process.type, 'DONE'); } } catch (e) { const error = await (0, parseSolanaErrors_js_1.parseSolanaErrors)(e, step, process); process = this.statusManager.updateProcess(step, process.type, 'FAILED', { error: { message: error.cause.message, code: error.code, }, }); this.statusManager.updateExecution(step, 'FAILED'); throw error; } } await (0, waitForDestinationChainTransaction_js_1.waitForDestinationChainTransaction)(step, process, fromChain, toChain, this.statusManager); return step; }; } exports.SolanaStepExecutor = SolanaStepExecutor; //# sourceMappingURL=SolanaStepExecutor.js.map