@lifi/sdk
Version:
LI.FI Any-to-Any Cross-Chain-Swap SDK
178 lines • 6.78 kB
JavaScript
import { config } from '../config.js';
import { LiFiErrorCode } from '../errors/constants.js';
import { ProviderError } from '../errors/errors.js';
import { executionState } from './executionState.js';
import { prepareRestart } from './prepareRestart.js';
/**
* Execute a route.
* @param route - The route that should be executed. Cannot be an active route.
* @param executionOptions - An object containing settings and callbacks.
* @returns The executed route.
* @throws {LiFiError} Throws a LiFiError if the execution fails.
*/
export const executeRoute = async (route, executionOptions) => {
// Deep clone to prevent side effects
const clonedRoute = structuredClone(route);
let executionPromise = executionState.get(clonedRoute.id)?.promise;
// Check if route is already running
if (executionPromise) {
return executionPromise;
}
executionState.create({ route: clonedRoute, executionOptions });
executionPromise = executeSteps(clonedRoute);
executionState.update({
route: clonedRoute,
promise: executionPromise,
});
return executionPromise;
};
/**
* Resume the execution of a route that has been stopped or had an error while executing.
* @param route - The route that is to be executed. Cannot be an active route.
* @param executionOptions - An object containing settings and callbacks.
* @returns The executed route.
* @throws {LiFiError} Throws a LiFiError if the execution fails.
*/
export const resumeRoute = async (route, executionOptions) => {
const execution = executionState.get(route.id);
if (execution) {
const executionHalted = execution.executors.some((executor) => !executor.allowExecution);
if (!executionHalted) {
// Check if we want to resume route execution in the background
updateRouteExecution(route, {
executeInBackground: executionOptions?.executeInBackground,
});
if (!execution.promise) {
// We should never reach this point if we do clean-up properly
throw new Error('Route execution promise not found.');
}
return execution.promise;
}
}
prepareRestart(route);
return executeRoute(route, executionOptions);
};
const executeSteps = async (route) => {
// Loop over steps and execute them
for (let index = 0; index < route.steps.length; index++) {
const execution = executionState.get(route.id);
// Check if execution has stopped in the meantime
if (!execution) {
break;
}
const step = route.steps[index];
const previousStep = route.steps[index - 1];
// Check if the step is already done
//
if (step.execution?.status === 'DONE') {
continue;
}
// Update step fromAmount using output of the previous step execution. In the future this should be handled by calling `updateRoute`
if (previousStep?.execution?.toAmount) {
step.action.fromAmount = previousStep.execution.toAmount;
if (step.includedSteps?.length) {
step.includedSteps[0].action.fromAmount =
previousStep.execution.toAmount;
}
}
try {
const fromAddress = step.action.fromAddress;
if (!fromAddress) {
throw new Error('Action fromAddress is not specified.');
}
const provider = config
.get()
.providers.find((provider) => provider.isAddress(fromAddress));
if (!provider) {
throw new ProviderError(LiFiErrorCode.ProviderUnavailable, 'SDK Execution Provider not found.');
}
const stepExecutor = await provider.getStepExecutor({
routeId: route.id,
executionOptions: execution.executionOptions,
});
execution.executors.push(stepExecutor);
// Check if we want to execute this step in the background
if (execution.executionOptions) {
updateRouteExecution(route, execution.executionOptions);
}
const executedStep = await stepExecutor.executeStep(step);
// We may reach this point if user interaction isn't allowed. We want to stop execution until we resume it
if (executedStep.execution?.status !== 'DONE') {
stopRouteExecution(route);
}
// Execution stopped during the current step, we don't want to continue to the next step so we return already
if (!stepExecutor.allowExecution) {
return route;
}
}
catch (e) {
stopRouteExecution(route);
throw e;
}
}
// Clean up after the execution
executionState.delete(route.id);
return route;
};
/**
* Updates route execution to background or foreground state.
* @param route - A route that is currently in execution.
* @param options - An object with execution settings.
*/
export const updateRouteExecution = (route, options) => {
const execution = executionState.get(route.id);
if (!execution) {
return;
}
if ('executeInBackground' in options) {
for (const executor of execution.executors) {
executor.setInteraction({
allowInteraction: !options?.executeInBackground,
allowUpdates: true,
});
}
}
// Update active route settings so we know what the current state of execution is
execution.executionOptions = {
...execution.executionOptions,
...options,
};
};
/**
* Stops the execution of an active route.
* @param route - A route that is currently in execution.
* @returns The stopped route.
*/
export const stopRouteExecution = (route) => {
const execution = executionState.get(route.id);
if (!execution) {
return route;
}
for (const executor of execution.executors) {
executor.setInteraction({
allowInteraction: false,
allowUpdates: false,
allowExecution: false,
});
}
executionState.delete(route.id);
return execution.route;
};
/**
* Get the list of active routes.
* @returns A list of routes.
*/
export const getActiveRoutes = () => {
return Object.values(executionState.state)
.map((dict) => dict?.route)
.filter(Boolean);
};
/**
* Return the current route information for given route. The route has to be active.
* @param routeId - A route id.
* @returns The updated route.
*/
export const getActiveRoute = (routeId) => {
return executionState.get(routeId)?.route;
};
//# sourceMappingURL=execution.js.map