UNPKG

@openocean.finance/widget-sdk

Version:

OpenOcean Any-to-Any Cross-Chain-Swap SDK

129 lines 7.28 kB
import { VersionedTransaction } from '@solana/web3.js'; import { withTimeout } from 'viem'; import { config } from '../../config.js'; import { OpenOceanErrorCode } from '../../errors/constants.js'; import { TransactionError } from '../../errors/errors.js'; import { getStepTransaction } from '../../services/api.js'; import { base64ToUint8Array } from '../../utils/base64ToUint8Array.js'; import { BaseStepExecutor } from '../BaseStepExecutor.js'; import { checkBalance } from '../checkBalance.js'; import { stepComparison } from '../stepComparison.js'; import { waitForDestinationChainTransaction } from '../waitForDestinationChainTransaction.js'; import { callSolanaWithRetry } from './connection.js'; import { parseSolanaErrors } from './parseSolanaErrors.js'; import { sendAndConfirmTransaction } from './sendAndConfirmTransaction.js'; export class SolanaStepExecutor extends BaseStepExecutor { walletAdapter; constructor(options) { super(options); this.walletAdapter = options.walletAdapter; } checkWalletAdapter = (step) => { // Prevent execution of the quote by wallet different from the one which requested the quote if (this.walletAdapter.publicKey.toString() !== step.action.fromAddress) { throw new TransactionError(OpenOceanErrorCode.WalletChangedDuringExecution, 'The wallet address that requested the quote does not match the wallet address attempting to sign the transaction.'); } }; executeStep = async (step) => { step.execution = this.statusManager.initExecutionObject(step); const fromChain = await config.getChainById(step.action.fromChainId); const toChain = await 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'); // Check balance await checkBalance(this.walletAdapter.publicKey.toString(), step); // Create new transaction if (!step.transactionRequest) { const { execution, ...stepBase } = step; const updatedStep = await getStepTransaction(stepBase); const comparedStep = await stepComparison(this.statusManager, step, updatedStep, this.allowUserInteraction, this.executionOptions); Object.assign(step, { ...comparedStep, execution: step.execution, }); } if (!step.transactionRequest?.data) { throw new TransactionError(OpenOceanErrorCode.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, }; } if (!transactionRequest.data) { throw new TransactionError(OpenOceanErrorCode.TransactionUnprepared, 'Unable to prepare transaction.'); } const versionedTransaction = VersionedTransaction.deserialize(base64ToUint8Array(transactionRequest.data)); this.checkWalletAdapter(step); // We give users 2 minutes to sign the transaction or it should be considered expired const signedTx = await withTimeout(() => this.walletAdapter.signTransaction(versionedTransaction), { // https://solana.com/docs/advanced/confirmation#transaction-expiration // Use 2 minutes to account for fluctuations timeout: 120_000, errorInstance: new TransactionError(OpenOceanErrorCode.TransactionExpired, 'Transaction has expired: blockhash is no longer recent enough.'), }); process = this.statusManager.updateProcess(step, process.type, 'PENDING'); const simulationResult = await callSolanaWithRetry((connection) => connection.simulateTransaction(signedTx, { commitment: 'confirmed', replaceRecentBlockhash: true, })); if (simulationResult.value.err) { throw new TransactionError(OpenOceanErrorCode.TransactionSimulationFailed, 'Transaction simulation failed'); } const confirmedTx = await sendAndConfirmTransaction(signedTx); if (!confirmedTx.signatureResult) { throw new TransactionError(OpenOceanErrorCode.TransactionExpired, 'Transaction has expired: The block height has exceeded the maximum allowed limit.'); } if (confirmedTx.signatureResult.err) { const reason = typeof confirmedTx.signatureResult.err === 'object' ? JSON.stringify(confirmedTx.signatureResult.err) : confirmedTx.signatureResult.err; throw new TransactionError(OpenOceanErrorCode.TransactionFailed, `Transaction failed: ${reason}`); } // Transaction has been confirmed and we can update the process process = this.statusManager.updateProcess(step, process.type, 'PENDING', { txHash: confirmedTx.txSignature, txLink: `${fromChain.metamask.blockExplorerUrls[0]}tx/${confirmedTx.txSignature}`, }); if (isBridgeExecution) { process = this.statusManager.updateProcess(step, process.type, 'DONE'); } } catch (e) { const error = await 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 waitForDestinationChainTransaction(step, process, fromChain, toChain, this.statusManager); // DONE return step; }; } //# sourceMappingURL=SolanaStepExecutor.js.map