@openocean.finance/widget-sdk
Version:
OpenOcean Any-to-Any Cross-Chain-Swap SDK
391 lines • 19.7 kB
JavaScript
"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