@getclave/lifi-sdk
Version:
LI.FI Any-to-Any Cross-Chain-Swap SDK
187 lines (186 loc) • 7.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getActiveRoute = exports.getActiveRoutes = exports.stopRouteExecution = exports.updateRouteExecution = exports.resumeRoute = exports.executeRoute = void 0;
const config_1 = require("../config");
const constants_1 = require("../errors/constants");
const errors_1 = require("../errors/errors");
const executionState_1 = require("./executionState");
const prepareRestart_1 = require("./prepareRestart");
/**
* 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.
*/
const executeRoute = async (route, executionOptions) => {
// Deep clone to prevent side effects
const clonedRoute = structuredClone(route);
let executionPromise = executionState_1.executionState.get(clonedRoute.id)?.promise;
// Check if route is already running
if (executionPromise) {
return executionPromise;
}
executionState_1.executionState.create({ route: clonedRoute, executionOptions });
executionPromise = executeSteps(clonedRoute);
executionState_1.executionState.update({
route: clonedRoute,
promise: executionPromise,
});
return executionPromise;
};
exports.executeRoute = executeRoute;
/**
* 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.
*/
const resumeRoute = async (route, executionOptions) => {
const execution = executionState_1.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
(0, exports.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;
}
}
await (0, prepareRestart_1.prepareRestart)(route);
return (0, exports.executeRoute)(route, executionOptions);
};
exports.resumeRoute = resumeRoute;
const executeSteps = async (route) => {
// Loop over steps and execute them
for (let index = 0; index < route.steps.length; index++) {
const execution = executionState_1.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_1.config
.get()
.providers.find((provider) => provider.isAddress(fromAddress));
if (!provider) {
throw new errors_1.ProviderError(constants_1.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) {
(0, exports.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') {
(0, exports.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) {
(0, exports.stopRouteExecution)(route);
throw e;
}
}
// Clean up after the execution
executionState_1.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.
*/
const updateRouteExecution = (route, options) => {
const execution = executionState_1.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,
};
};
exports.updateRouteExecution = updateRouteExecution;
/**
* Stops the execution of an active route.
* @param route - A route that is currently in execution.
* @returns The stopped route.
*/
const stopRouteExecution = (route) => {
const execution = executionState_1.executionState.get(route.id);
if (!execution) {
return route;
}
for (const executor of execution.executors) {
executor.setInteraction({
allowInteraction: false,
allowUpdates: false,
allowExecution: false,
});
}
executionState_1.executionState.delete(route.id);
return execution.route;
};
exports.stopRouteExecution = stopRouteExecution;
/**
* Get the list of active routes.
* @returns A list of routes.
*/
const getActiveRoutes = () => {
return Object.values(executionState_1.executionState.state)
.map((dict) => dict?.route)
.filter(Boolean);
};
exports.getActiveRoutes = getActiveRoutes;
/**
* Return the current route information for given route. The route has to be active.
* @param routeId - A route id.
* @returns The updated route.
*/
const getActiveRoute = (routeId) => {
return executionState_1.executionState.get(routeId)?.route;
};
exports.getActiveRoute = getActiveRoute;