UNPKG

@openocean.finance/widget-sdk

Version:

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

391 lines 19.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EVMStepExecutor = void 0; const actions_1 = require("viem/actions"); const experimental_1 = require("viem/experimental"); const utils_1 = require("viem/utils"); 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 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 checkAllowance_js_1 = require("./checkAllowance.js"); const isBatchingSupported_js_1 = require("./isBatchingSupported.js"); const parseEVMErrors_js_1 = require("./parseEVMErrors.js"); const encodeNativePermitData_js_1 = require("./permits/encodeNativePermitData.js"); const encodePermit2Data_js_1 = require("./permits/encodePermit2Data.js"); const signPermit2Message_js_1 = require("./permits/signPermit2Message.js"); const switchChain_js_1 = require("./switchChain.js"); const typeguards_js_1 = require("./typeguards.js"); const utils_js_1 = require("./utils.js"); const waitForBatchTransactionReceipt_js_1 = require("./waitForBatchTransactionReceipt.js"); const waitForRelayedTransactionReceipt_js_1 = require("./waitForRelayedTransactionReceipt.js"); const waitForTransactionReceipt_js_1 = require("./waitForTransactionReceipt.js"); class EVMStepExecutor extends BaseStepExecutor_js_1.BaseStepExecutor { client; constructor(options) { super(options); this.client = options.client; } checkClient = async (step, process) => { const updatedClient = await (0, switchChain_js_1.switchChain)(this.client, this.statusManager, step, this.allowUserInteraction, this.executionOptions?.switchChainHook); if (updatedClient) { this.client = updatedClient; } let accountAddress = this.client.account?.address; if (!accountAddress) { const accountAddresses = (await (0, utils_1.getAction)(this.client, actions_1.getAddresses, 'getAddresses')(undefined)); accountAddress = accountAddresses?.[0]; } if (accountAddress?.toLowerCase() !== step.action.fromAddress?.toLowerCase()) { let processToUpdate = process; if (!processToUpdate) { processToUpdate = this.statusManager.findOrCreateProcess({ step, type: 'TRANSACTION', }); } const errorMessage = 'The wallet address that requested the quote does not match the wallet address attempting to sign the transaction.'; this.statusManager.updateProcess(step, processToUpdate.type, 'FAILED', { error: { code: constants_js_1.OpenOceanErrorCode.WalletChangedDuringExecution, message: errorMessage, }, }); this.statusManager.updateExecution(step, 'FAILED'); throw await (0, parseEVMErrors_js_1.parseEVMErrors)(new errors_js_1.TransactionError(constants_js_1.OpenOceanErrorCode.WalletChangedDuringExecution, errorMessage), step, process); } return updatedClient; }; waitForTransaction = async ({ step, process, fromChain, toChain, txType, txHash, isBridgeExecution, }) => { let transactionReceipt; switch (txType) { case 'batched': transactionReceipt = await (0, waitForBatchTransactionReceipt_js_1.waitForBatchTransactionReceipt)(this.client, txHash); break; case 'relayed': transactionReceipt = await (0, waitForRelayedTransactionReceipt_js_1.waitForRelayedTransactionReceipt)(txHash, step); break; default: transactionReceipt = await (0, waitForTransactionReceipt_js_1.waitForTransactionReceipt)({ client: this.client, chainId: fromChain.id, txHash, onReplaced: (response) => { this.statusManager.updateProcess(step, process.type, 'PENDING', { txHash: response.transaction.hash, txLink: `${fromChain.metamask.blockExplorerUrls[0]}tx/${response.transaction.hash}`, }); }, }); } if (transactionReceipt?.transactionHash && transactionReceipt.transactionHash !== txHash) { process = this.statusManager.updateProcess(step, process.type, 'PENDING', { txHash: transactionReceipt.transactionHash, txLink: `${fromChain.metamask.blockExplorerUrls[0]}tx/${transactionReceipt.transactionHash}`, }); } if (isBridgeExecution) { process = this.statusManager.updateProcess(step, process.type, 'DONE'); } await (0, waitForDestinationChainTransaction_js_1.waitForDestinationChainTransaction)(step, process, fromChain, toChain, this.statusManager); }; executeStep = async (step) => { step.execution = this.statusManager.initExecutionObject(step); const destinationChainProcess = step.execution?.process.find((process) => process.type === 'RECEIVING_CHAIN'); if (destinationChainProcess?.substatus !== 'WAIT_DESTINATION_TRANSACTION') { const updatedClient = await this.checkClient(step); if (!updatedClient) { return step; } } const fromChain = await config_js_1.config.getChainById(step.action.fromChainId); const toChain = await config_js_1.config.getChainById(step.action.toChainId); const calls = []; const batchingSupported = await (0, isBatchingSupported_js_1.isBatchingSupported)({ client: this.client, chainId: fromChain.id, }); const isBridgeExecution = fromChain.id !== toChain.id; const currentProcessType = isBridgeExecution ? 'CROSS_CHAIN' : 'SWAP'; const existingProcess = step.execution.process.find((p) => p.type === currentProcessType); const isFromNativeToken = fromChain.nativeToken.address === step.action.fromToken.address; const isRelayerTransaction = (0, typeguards_js_1.isRelayerStep)(step); const disableMessageSigning = this.executionOptions?.disableMessageSigning; const permit2Supported = !!fromChain.permit2 && !!fromChain.permit2Proxy && !batchingSupported && !isFromNativeToken && !disableMessageSigning; const checkForAllowance = !existingProcess?.txHash && !isFromNativeToken; let signedNativePermitTypedData; if (checkForAllowance) { const allowanceResult = await (0, checkAllowance_js_1.checkAllowance)({ client: this.client, chain: fromChain, step, statusManager: this.statusManager, executionOptions: this.executionOptions, allowUserInteraction: this.allowUserInteraction, batchingSupported, permit2Supported, disableMessageSigning, }); if (allowanceResult.status === 'BATCH_APPROVAL') { if (batchingSupported) { calls.push(allowanceResult.data); } } if (allowanceResult.status === 'NATIVE_PERMIT') { signedNativePermitTypedData = allowanceResult.data; } if (allowanceResult.status === 'ACTION_REQUIRED' && !this.allowUserInteraction) { return step; } } let process = this.statusManager.findProcess(step, currentProcessType); if (process?.status === 'DONE') { await (0, waitForDestinationChainTransaction_js_1.waitForDestinationChainTransaction)(step, process, fromChain, toChain, this.statusManager); return step; } try { if (process?.txHash) { const updatedClient = await this.checkClient(step, process); if (!updatedClient) { return step; } const txHash = process.txHash; const txType = process.txType; await this.waitForTransaction({ step, process, fromChain, toChain, txType, txHash, isBridgeExecution, }); return step; } const permitRequired = !batchingSupported && !signedNativePermitTypedData && permit2Supported; process = this.statusManager.findOrCreateProcess({ step, type: permitRequired ? 'PERMIT' : currentProcessType, status: 'STARTED', chainId: fromChain.id, }); await (0, checkBalance_js_1.checkBalance)(this.client.account.address, step); if (!step.transactionRequest) { const { execution, ...stepBase } = step; let updatedStep; if (isRelayerTransaction) { const updatedRelayedStep = await (0, api_js_1.getRelayerQuote)({ fromChain: stepBase.action.fromChainId, fromToken: stepBase.action.fromToken.address, fromAddress: stepBase.action.fromAddress, fromAmount: stepBase.action.fromAmount, toChain: stepBase.action.toChainId, toToken: stepBase.action.toToken.address, slippage: stepBase.action.slippage, toAddress: stepBase.action.toAddress, allowBridges: [stepBase.tool], }); updatedStep = { ...updatedRelayedStep, id: stepBase.id, }; } else { 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) { throw new errors_js_1.TransactionError(constants_js_1.OpenOceanErrorCode.TransactionUnprepared, 'Unable to prepare transaction.'); } let transactionRequest = { to: step.transactionRequest.to, from: step.transactionRequest.from, data: step.transactionRequest.data, value: step.transactionRequest.value ? BigInt(step.transactionRequest.value) : undefined, gas: step.transactionRequest.gasLimit ? BigInt(step.transactionRequest.gasLimit) : undefined, maxPriorityFeePerGas: this.client.account?.type === 'local' ? await (0, utils_js_1.getMaxPriorityFeePerGas)(this.client) : step.transactionRequest.maxPriorityFeePerGas ? BigInt(step.transactionRequest.maxPriorityFeePerGas) : undefined, }; if (this.executionOptions?.updateTransactionRequestHook) { const customizedTransactionRequest = await this.executionOptions.updateTransactionRequestHook({ requestType: 'transaction', ...transactionRequest, }); transactionRequest = { ...transactionRequest, ...customizedTransactionRequest, }; } const updatedClient = await this.checkClient(step, process); if (!updatedClient) { return step; } process = this.statusManager.updateProcess(step, process.type, 'ACTION_REQUIRED'); if (!this.allowUserInteraction) { return step; } let txHash; let txType = 'standard'; if (batchingSupported) { const transferCall = { chainId: fromChain.id, data: transactionRequest.data, to: transactionRequest.to, value: transactionRequest.value, }; calls.push(transferCall); const { id } = await (0, utils_1.getAction)(this.client, experimental_1.sendCalls, 'sendCalls')({ account: this.client.account, calls, }); txHash = id; txType = 'batched'; } else if (isRelayerTransaction) { const relayerTypedData = step.typedData.find((p) => p.primaryType === 'PermitWitnessTransferFrom' || p.primaryType === 'Order'); if (!relayerTypedData) { throw new errors_js_1.TransactionError(constants_js_1.OpenOceanErrorCode.TransactionUnprepared, 'Unable to prepare transaction. Typed data for transfer is not found.'); } const signature = await (0, utils_1.getAction)(this.client, actions_1.signTypedData, 'signTypedData')({ account: this.client.account, primaryType: relayerTypedData.primaryType, domain: relayerTypedData.domain, types: relayerTypedData.types, message: relayerTypedData.message, }); this.statusManager.updateProcess(step, process.type, 'DONE'); process = this.statusManager.findOrCreateProcess({ step, type: currentProcessType, status: 'PENDING', chainId: fromChain.id, }); const signedTypedData = [ { ...relayerTypedData, signature: signature, }, ]; if (signedNativePermitTypedData) { signedTypedData.unshift(signedNativePermitTypedData); } const { execution, ...stepBase } = step; const relayedTransaction = await (0, api_js_1.relayTransaction)({ ...stepBase, typedData: signedTypedData, }); txHash = relayedTransaction.taskId; txType = 'relayed'; } else { if (signedNativePermitTypedData) { transactionRequest.data = (0, encodeNativePermitData_js_1.encodeNativePermitData)(step.action.fromToken.address, BigInt(step.action.fromAmount), signedNativePermitTypedData.message.deadline, signedNativePermitTypedData.signature, transactionRequest.data); } else if (permit2Supported) { const permit2Signature = await (0, signPermit2Message_js_1.signPermit2Message)({ client: this.client, chain: fromChain, tokenAddress: step.action.fromToken.address, amount: BigInt(step.action.fromAmount), data: transactionRequest.data, }); this.statusManager.updateProcess(step, process.type, 'DONE'); process = this.statusManager.findOrCreateProcess({ step, type: currentProcessType, status: 'PENDING', chainId: fromChain.id, }); transactionRequest.data = (0, encodePermit2Data_js_1.encodePermit2Data)(step.action.fromToken.address, BigInt(step.action.fromAmount), permit2Signature.message.nonce, permit2Signature.message.deadline, transactionRequest.data, permit2Signature.signature); } if (signedNativePermitTypedData || permit2Supported) { try { transactionRequest.to = fromChain.permit2Proxy; const estimatedGas = await (0, actions_1.estimateGas)(this.client, { account: this.client.account, to: transactionRequest.to, data: transactionRequest.data, value: transactionRequest.value, }); transactionRequest.gas = transactionRequest.gas && transactionRequest.gas > estimatedGas ? transactionRequest.gas : estimatedGas; } catch { transactionRequest.gas = undefined; } finally { this.statusManager.updateProcess(step, process.type, 'DONE'); } } process = this.statusManager.updateProcess(step, process.type, 'ACTION_REQUIRED'); txHash = await (0, utils_1.getAction)(this.client, actions_1.sendTransaction, 'sendTransaction')({ to: transactionRequest.to, account: this.client.account, data: transactionRequest.data, value: transactionRequest.value, gas: transactionRequest.gas, gasPrice: transactionRequest.gasPrice, maxFeePerGas: transactionRequest.maxFeePerGas, maxPriorityFeePerGas: transactionRequest.maxPriorityFeePerGas, chain: (0, utils_js_1.convertExtendedChain)(fromChain), }); } process = this.statusManager.updateProcess(step, process.type, 'PENDING', { txHash, txType, txLink: txType === 'standard' ? `${fromChain.metamask.blockExplorerUrls[0]}tx/${txHash}` : undefined, }); await this.waitForTransaction({ step, process, fromChain, toChain, txHash, txType, isBridgeExecution, }); return step; } catch (e) { const error = await (0, parseEVMErrors_js_1.parseEVMErrors)(e, step, process); process = this.statusManager.updateProcess(step, process?.type || currentProcessType, 'FAILED', { error: { message: error.cause.message, code: error.code, }, }); this.statusManager.updateExecution(step, 'FAILED'); throw error; } }; } exports.EVMStepExecutor = EVMStepExecutor; //# sourceMappingURL=EVMStepExecutor.js.map