@lifi/sdk
Advanced tools
Comparing version 1.4.1 to 1.5.0
@@ -5,2 +5,9 @@ # Changelog | ||
## [1.5.0](https://github.com/lifinance/sdk/compare/v1.4.1...v1.5.0) (2022-09-06) | ||
### Features | ||
* add an option to start or resume execution in background ([#103](https://github.com/lifinance/sdk/issues/103)) ([c452559](https://github.com/lifinance/sdk/commit/c45255991ebcf494715c094db77f6c7599080c5d)), closes [#106](https://github.com/lifinance/sdk/issues/106) [#104](https://github.com/lifinance/sdk/issues/104) | ||
### [1.4.1](https://github.com/lifinance/sdk/compare/v1.4.0...v1.4.1) (2022-09-06) | ||
@@ -7,0 +14,0 @@ |
import { Signer } from 'ethers'; | ||
import { Chain, Step, Token } from '../types'; | ||
import { Chain, InternalExecutionSettings, Step } from '../types'; | ||
import { StatusManager } from './StatusManager'; | ||
export declare const checkAllowance: (signer: Signer, step: Step, chain: Chain, token: Token, amount: string, spenderAddress: string, statusManager: StatusManager, infiniteApproval?: boolean, allowUserInteraction?: boolean) => Promise<void>; | ||
export declare const checkAllowance: (signer: Signer, step: Step, statusManager: StatusManager, settings: InternalExecutionSettings, chain: Chain, allowUserInteraction?: boolean) => Promise<void>; |
@@ -21,30 +21,24 @@ "use strict"; | ||
const parseError_1 = require("../utils/parseError"); | ||
const checkAllowance = (signer, step, chain, token, amount, spenderAddress, statusManager, infiniteApproval = false, allowUserInteraction = false | ||
// eslint-disable-next-line max-params | ||
) => __awaiter(void 0, void 0, void 0, function* () { | ||
// Ask user to set allowance | ||
// -> set currentExecution | ||
let allowanceProcess = statusManager.findOrCreateProcess('TOKEN_ALLOWANCE', step); | ||
// -> check allowance | ||
const checkAllowance = (signer, step, statusManager, settings, chain, allowUserInteraction = false) => __awaiter(void 0, void 0, void 0, function* () { | ||
// Ask the user to set an allowance | ||
let allowanceProcess = statusManager.findOrCreateProcess(step, 'TOKEN_ALLOWANCE'); | ||
// Check allowance | ||
try { | ||
if (allowanceProcess.txHash) { | ||
allowanceProcess = statusManager.updateProcess(step, allowanceProcess.type, 'PENDING'); | ||
if (allowanceProcess.txHash && allowanceProcess.status !== 'DONE') { | ||
if (allowanceProcess.status !== 'PENDING') { | ||
allowanceProcess = statusManager.updateProcess(step, allowanceProcess.type, 'PENDING'); | ||
} | ||
yield (0, getProvider_1.getProvider)(signer).waitForTransaction(allowanceProcess.txHash); | ||
allowanceProcess = statusManager.updateProcess(step, allowanceProcess.type, 'DONE'); | ||
// TODO: Do we need this check? | ||
} | ||
else if (allowanceProcess.status === 'DONE') { | ||
allowanceProcess = statusManager.updateProcess(step, allowanceProcess.type, 'DONE'); | ||
} | ||
else { | ||
const approved = yield (0, utils_1.getApproved)(signer, token.address, spenderAddress); | ||
if (new bignumber_js_1.default(amount).gt(approved)) { | ||
const approved = yield (0, utils_1.getApproved)(signer, step.action.fromToken.address, step.estimate.approvalAddress); | ||
if (new bignumber_js_1.default(step.action.fromAmount).gt(approved)) { | ||
if (!allowUserInteraction) { | ||
return; | ||
} | ||
const approvalAmount = infiniteApproval | ||
const approvalAmount = settings.infiniteApproval | ||
? ethers_1.constants.MaxUint256.toString() | ||
: amount; | ||
const approveTx = yield (0, utils_1.setApproval)(signer, token.address, spenderAddress, approvalAmount); | ||
// update currentExecution | ||
: step.action.fromAmount; | ||
const approveTx = yield (0, utils_1.setApproval)(signer, step.action.fromToken.address, step.estimate.approvalAddress, approvalAmount); | ||
allowanceProcess = statusManager.updateProcess(step, allowanceProcess.type, 'PENDING', { | ||
@@ -54,3 +48,3 @@ txHash: approveTx.hash, | ||
}); | ||
// wait for transcation | ||
// Wait for the transcation | ||
yield approveTx.wait(); | ||
@@ -65,3 +59,2 @@ allowanceProcess = statusManager.updateProcess(step, allowanceProcess.type, 'DONE'); | ||
catch (e) { | ||
// -> set status | ||
if (e.code === 'TRANSACTION_REPLACED' && e.replacement) { | ||
@@ -68,0 +61,0 @@ yield transactionReplaced(e.replacement, allowanceProcess, step, chain, statusManager); |
import { Execution } from '@lifi/types'; | ||
import { ExecuteCrossParams } from '../../types'; | ||
export declare class BridgeExecutionManager { | ||
shouldContinue: boolean; | ||
setShouldContinue: (val: boolean) => void; | ||
allowUserInteraction: boolean; | ||
allowInteraction: (value: boolean) => void; | ||
execute: ({ signer, step, statusManager, settings, }: ExecuteCrossParams) => Promise<Execution>; | ||
} |
@@ -16,3 +16,2 @@ "use strict"; | ||
exports.BridgeExecutionManager = void 0; | ||
const ethers_1 = require("ethers"); | ||
const ApiService_1 = __importDefault(require("../../services/ApiService")); | ||
@@ -31,88 +30,99 @@ const ChainsService_1 = __importDefault(require("../../services/ChainsService")); | ||
constructor() { | ||
this.shouldContinue = true; | ||
this.setShouldContinue = (val) => { | ||
this.shouldContinue = val; | ||
this.allowUserInteraction = true; | ||
this.allowInteraction = (value) => { | ||
this.allowUserInteraction = value; | ||
}; | ||
this.execute = ({ signer, step, statusManager, settings, }) => __awaiter(this, void 0, void 0, function* () { | ||
var _a, _b, _c, _d; | ||
const { action, estimate } = step; | ||
step.execution = statusManager.initExecutionObject(step); | ||
const chainsService = ChainsService_1.default.getInstance(); | ||
const fromChain = yield chainsService.getChainById(action.fromChainId); | ||
const toChain = yield chainsService.getChainById(action.toChainId); | ||
// STEP 1: Check Allowance //////////////////////////////////////////////// | ||
// approval still needed? | ||
const fromChain = yield chainsService.getChainById(step.action.fromChainId); | ||
const toChain = yield chainsService.getChainById(step.action.toChainId); | ||
// STEP 1: Check allowance | ||
const oldCrossProcess = step.execution.process.find((p) => p.type === 'CROSS_CHAIN'); | ||
if (!(oldCrossProcess === null || oldCrossProcess === void 0 ? void 0 : oldCrossProcess.txHash)) { | ||
if (action.fromToken.address !== ethers_1.constants.AddressZero) { | ||
// Check Token Approval only if fromToken is not the native token => no approval needed in that case | ||
yield (0, allowance_execute_1.checkAllowance)(signer, step, fromChain, action.fromToken, action.fromAmount, estimate.approvalAddress, statusManager, settings.infiniteApproval, this.shouldContinue); | ||
} | ||
// Check token approval only if fromToken is not the native token => no approval needed in that case | ||
if (!(oldCrossProcess === null || oldCrossProcess === void 0 ? void 0 : oldCrossProcess.txHash) && | ||
!(0, utils_1.isZeroAddress)(step.action.fromToken.address)) { | ||
yield (0, allowance_execute_1.checkAllowance)(signer, step, statusManager, settings, fromChain, this.allowUserInteraction); | ||
} | ||
// STEP 2: Get Transaction //////////////////////////////////////////////// | ||
let crossChainProcess = statusManager.findOrCreateProcess('CROSS_CHAIN', step); | ||
try { | ||
let tx; | ||
if (crossChainProcess.txHash) { | ||
// load exiting transaction | ||
tx = yield (0, getProvider_1.getProvider)(signer).getTransaction(crossChainProcess.txHash); | ||
} | ||
else { | ||
// check balance | ||
yield (0, balanceCheck_execute_1.balanceCheck)(signer, step); | ||
if (!step.transactionRequest) { | ||
const personalizedStep = yield (0, utils_1.personalizeStep)(signer, step); | ||
const updatedStep = yield ApiService_1.default.getStepTransaction(personalizedStep); | ||
step = Object.assign(Object.assign({}, (yield (0, stepComparison_1.stepComparison)(statusManager, personalizedStep, updatedStep, settings.acceptSlippageUpdateHook, this.shouldContinue))), { execution: step.execution }); | ||
// STEP 2: Get transaction | ||
let crossChainProcess = statusManager.findOrCreateProcess(step, 'CROSS_CHAIN'); | ||
if (crossChainProcess.status !== 'DONE') { | ||
try { | ||
let transaction; | ||
if (crossChainProcess.txHash) { | ||
// Make sure that the chain is still correct | ||
const updatedSigner = yield (0, switchChain_1.switchChain)(signer, statusManager, step, settings.switchChainHook, this.allowUserInteraction); | ||
if (!updatedSigner) { | ||
// Chain switch was not successful, stop execution here | ||
return step.execution; | ||
} | ||
signer = updatedSigner; | ||
// Load exiting transaction | ||
transaction = yield (0, getProvider_1.getProvider)(signer).getTransaction(crossChainProcess.txHash); | ||
} | ||
const { transactionRequest } = step; | ||
if (!transactionRequest) { | ||
throw new errors_1.TransactionError(errors_1.LifiErrorCode.TransactionUnprepared, 'Unable to prepare transaction.'); | ||
else { | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'STARTED'); | ||
// Check balance | ||
yield (0, balanceCheck_execute_1.balanceCheck)(signer, step); | ||
// Create new transaction | ||
if (!step.transactionRequest) { | ||
const personalizedStep = yield (0, utils_1.personalizeStep)(signer, step); | ||
const updatedStep = yield ApiService_1.default.getStepTransaction(personalizedStep); | ||
step = Object.assign(Object.assign({}, (yield (0, stepComparison_1.stepComparison)(statusManager, personalizedStep, updatedStep, settings.acceptSlippageUpdateHook, this.allowUserInteraction))), { execution: step.execution }); | ||
} | ||
const { transactionRequest } = step; | ||
if (!transactionRequest) { | ||
throw new errors_1.TransactionError(errors_1.LifiErrorCode.TransactionUnprepared, 'Unable to prepare transaction.'); | ||
} | ||
// STEP 3: Send the transaction | ||
// Make sure that the chain is still correct | ||
const updatedSigner = yield (0, switchChain_1.switchChain)(signer, statusManager, step, settings.switchChainHook, this.allowUserInteraction); | ||
if (!updatedSigner) { | ||
// Chain switch was not successful, stop execution here | ||
return step.execution; | ||
} | ||
signer = updatedSigner; | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'ACTION_REQUIRED'); | ||
if (!this.allowUserInteraction) { | ||
return step.execution; | ||
} | ||
// Submit the transaction | ||
transaction = yield signer.sendTransaction(transactionRequest); | ||
// STEP 4: Wait for the transaction | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'PENDING', { | ||
txHash: transaction.hash, | ||
txLink: fromChain.metamask.blockExplorerUrls[0] + | ||
'tx/' + | ||
transaction.hash, | ||
}); | ||
} | ||
// STEP 3: Send Transaction /////////////////////////////////////////////// | ||
// make sure that chain is still correct | ||
const updatedSigner = yield (0, switchChain_1.switchChain)(signer, statusManager, step, settings.switchChainHook, this.shouldContinue); | ||
if (!updatedSigner) { | ||
// chain switch was not successful, stop execution here | ||
return step.execution; | ||
yield transaction.wait(); | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'DONE'); | ||
} | ||
catch (e) { | ||
if (e.code === 'TRANSACTION_REPLACED' && e.replacement) { | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'DONE', { | ||
txHash: e.replacement.hash, | ||
txLink: fromChain.metamask.blockExplorerUrls[0] + | ||
'tx/' + | ||
e.replacement.hash, | ||
}); | ||
} | ||
signer = updatedSigner; | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'ACTION_REQUIRED'); | ||
if (!this.shouldContinue) { | ||
return step.execution; | ||
else { | ||
const error = yield (0, parseError_1.parseError)(e, step, crossChainProcess); | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'FAILED', { | ||
error: { | ||
message: error.message, | ||
htmlMessage: error.htmlMessage, | ||
code: error.code, | ||
}, | ||
}); | ||
statusManager.updateExecution(step, 'FAILED'); | ||
throw error; | ||
} | ||
tx = yield signer.sendTransaction(transactionRequest); | ||
// STEP 4: Wait for Transaction /////////////////////////////////////////// | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'PENDING', { | ||
txHash: tx.hash, | ||
txLink: fromChain.metamask.blockExplorerUrls[0] + 'tx/' + tx.hash, | ||
}); | ||
} | ||
yield tx.wait(); | ||
} | ||
catch (e) { | ||
if (e.code === 'TRANSACTION_REPLACED' && e.replacement) { | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'PENDING', { | ||
txHash: e.replacement.hash, | ||
txLink: fromChain.metamask.blockExplorerUrls[0] + | ||
'tx/' + | ||
e.replacement.hash, | ||
}); | ||
} | ||
else { | ||
const error = yield (0, parseError_1.parseError)(e, step, crossChainProcess); | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'FAILED', { | ||
error: { | ||
message: error.message, | ||
htmlMessage: error.htmlMessage, | ||
code: error.code, | ||
}, | ||
}); | ||
statusManager.updateExecution(step, 'FAILED'); | ||
throw error; | ||
} | ||
} | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'DONE'); | ||
// STEP 5: Wait for Receiver ////////////////////////////////////// | ||
let receivingChainProcess = statusManager.findOrCreateProcess('RECEIVING_CHAIN', step, 'PENDING'); | ||
// STEP 5: Wait for the receiving chain | ||
let receivingChainProcess = statusManager.findOrCreateProcess(step, 'RECEIVING_CHAIN', 'PENDING'); | ||
let statusResponse; | ||
@@ -124,2 +134,18 @@ try { | ||
statusResponse = yield (0, utils_2.waitForReceivingTransaction)(crossChainProcess.txHash, statusManager, receivingChainProcess.type, step); | ||
receivingChainProcess = statusManager.updateProcess(step, receivingChainProcess.type, 'DONE', { | ||
substatus: statusResponse.substatus, | ||
substatusMessage: statusResponse.substatusMessage || | ||
(0, utils_2.getSubstatusMessage)(statusResponse.status, statusResponse.substatus), | ||
txHash: (_a = statusResponse.receiving) === null || _a === void 0 ? void 0 : _a.txHash, | ||
txLink: toChain.metamask.blockExplorerUrls[0] + | ||
'tx/' + | ||
((_b = statusResponse.receiving) === null || _b === void 0 ? void 0 : _b.txHash), | ||
}); | ||
statusManager.updateExecution(step, 'DONE', { | ||
fromAmount: statusResponse.sending.amount, | ||
toAmount: (_c = statusResponse.receiving) === null || _c === void 0 ? void 0 : _c.amount, | ||
toToken: (_d = statusResponse.receiving) === null || _d === void 0 ? void 0 : _d.token, | ||
gasUsed: statusResponse.sending.gasUsed, | ||
gasPrice: statusResponse.sending.gasPrice, | ||
}); | ||
} | ||
@@ -135,20 +161,5 @@ catch (e) { | ||
statusManager.updateExecution(step, 'FAILED'); | ||
console.warn(e); | ||
throw e; | ||
} | ||
receivingChainProcess = statusManager.updateProcess(step, receivingChainProcess.type, 'DONE', { | ||
substatus: statusResponse.substatus, | ||
substatusMessage: statusResponse.substatusMessage || | ||
(0, utils_2.getSubstatusMessage)(statusResponse.status, statusResponse.substatus), | ||
txHash: (_a = statusResponse.receiving) === null || _a === void 0 ? void 0 : _a.txHash, | ||
txLink: toChain.metamask.blockExplorerUrls[0] + | ||
'tx/' + | ||
((_b = statusResponse.receiving) === null || _b === void 0 ? void 0 : _b.txHash), | ||
}); | ||
statusManager.updateExecution(step, 'DONE', { | ||
fromAmount: statusResponse.sending.amount, | ||
toAmount: (_c = statusResponse.receiving) === null || _c === void 0 ? void 0 : _c.amount, | ||
toToken: (_d = statusResponse.receiving) === null || _d === void 0 ? void 0 : _d.token, | ||
gasUsed: statusResponse.sending.gasUsed, | ||
gasPrice: statusResponse.sending.gasPrice, | ||
}); | ||
// DONE | ||
@@ -155,0 +166,0 @@ return step.execution; |
import { Execution } from '@lifi/types'; | ||
import { ExecuteSwapParams } from '../../types'; | ||
export declare class SwapExecutionManager { | ||
shouldContinue: boolean; | ||
setShouldContinue: (val: boolean) => void; | ||
allowUserInteraction: boolean; | ||
allowInteraction: (value: boolean) => void; | ||
execute: ({ signer, step, statusManager, settings, }: ExecuteSwapParams) => Promise<Execution>; | ||
} |
@@ -16,3 +16,2 @@ "use strict"; | ||
exports.SwapExecutionManager = void 0; | ||
const ethers_1 = require("ethers"); | ||
const ApiService_1 = __importDefault(require("../../services/ApiService")); | ||
@@ -31,35 +30,39 @@ const ChainsService_1 = __importDefault(require("../../services/ChainsService")); | ||
constructor() { | ||
this.shouldContinue = true; | ||
this.setShouldContinue = (val) => { | ||
this.shouldContinue = val; | ||
this.allowUserInteraction = true; | ||
this.allowInteraction = (value) => { | ||
this.allowUserInteraction = value; | ||
}; | ||
this.execute = ({ signer, step, statusManager, settings, }) => __awaiter(this, void 0, void 0, function* () { | ||
// setup | ||
var _a, _b, _c, _d; | ||
const { action, estimate } = step; | ||
step.execution = statusManager.initExecutionObject(step); | ||
const chainsService = ChainsService_1.default.getInstance(); | ||
const fromChain = yield chainsService.getChainById(action.fromChainId); | ||
// Approval | ||
if (action.fromToken.address !== ethers_1.constants.AddressZero) { | ||
yield (0, allowance_execute_1.checkAllowance)(signer, step, fromChain, action.fromToken, action.fromAmount, estimate.approvalAddress, statusManager, settings.infiniteApproval, this.shouldContinue); | ||
const fromChain = yield chainsService.getChainById(step.action.fromChainId); | ||
// STEP 1: Check allowance | ||
if (!(0, utils_1.isZeroAddress)(step.action.fromToken.address)) { | ||
yield (0, allowance_execute_1.checkAllowance)(signer, step, statusManager, settings, fromChain, this.allowUserInteraction); | ||
} | ||
// Start Swap | ||
// -> set step.execution | ||
let swapProcess = statusManager.findOrCreateProcess('SWAP', step); | ||
// -> swapping | ||
let tx; | ||
// STEP 2: Get transaction | ||
let swapProcess = statusManager.findOrCreateProcess(step, 'SWAP'); | ||
let transaction; | ||
try { | ||
if (swapProcess.txHash) { | ||
// -> restore existing tx | ||
tx = yield (0, getProvider_1.getProvider)(signer).getTransaction(swapProcess.txHash); | ||
// Make sure that the chain is still correct | ||
const updatedSigner = yield (0, switchChain_1.switchChain)(signer, statusManager, step, settings.switchChainHook, this.allowUserInteraction); | ||
if (!updatedSigner) { | ||
// Chain switch was not successful, stop execution here | ||
return step.execution; | ||
} | ||
signer = updatedSigner; | ||
// Load exiting transaction | ||
transaction = yield (0, getProvider_1.getProvider)(signer).getTransaction(swapProcess.txHash); | ||
} | ||
else { | ||
// -> check balance | ||
swapProcess = statusManager.updateProcess(step, swapProcess.type, 'STARTED'); | ||
// Check balance | ||
yield (0, balanceCheck_execute_1.balanceCheck)(signer, step); | ||
// -> get tx from backend | ||
// Create new transaction | ||
if (!step.transactionRequest) { | ||
const personalizedStep = yield (0, utils_1.personalizeStep)(signer, step); | ||
const updatedStep = yield ApiService_1.default.getStepTransaction(personalizedStep); | ||
step = Object.assign(Object.assign({}, (yield (0, stepComparison_1.stepComparison)(statusManager, personalizedStep, updatedStep, settings.acceptSlippageUpdateHook, this.shouldContinue))), { execution: step.execution }); | ||
step = Object.assign(Object.assign({}, (yield (0, stepComparison_1.stepComparison)(statusManager, personalizedStep, updatedStep, settings.acceptSlippageUpdateHook, this.allowUserInteraction))), { execution: step.execution }); | ||
} | ||
@@ -70,44 +73,26 @@ const { transactionRequest } = step; | ||
} | ||
// make sure that chain is still correct | ||
const updatedSigner = yield (0, switchChain_1.switchChain)(signer, statusManager, step, settings.switchChainHook, this.shouldContinue); | ||
// STEP 3: Send the transaction | ||
// Make sure that the chain is still correct | ||
const updatedSigner = yield (0, switchChain_1.switchChain)(signer, statusManager, step, settings.switchChainHook, this.allowUserInteraction); | ||
if (!updatedSigner) { | ||
// chain switch was not successful, stop execution here | ||
// Chain switch was not successful, stop execution here | ||
return step.execution; | ||
} | ||
signer = updatedSigner; | ||
// -> set step.execution | ||
swapProcess = swapProcess = statusManager.updateProcess(step, swapProcess.type, 'ACTION_REQUIRED'); | ||
if (!this.shouldContinue) { | ||
return step.execution; // stop before user interaction is needed | ||
swapProcess = statusManager.updateProcess(step, swapProcess.type, 'ACTION_REQUIRED'); | ||
if (!this.allowUserInteraction) { | ||
return step.execution; | ||
} | ||
// -> submit tx | ||
tx = yield signer.sendTransaction(transactionRequest); | ||
// Submit the transaction | ||
transaction = yield signer.sendTransaction(transactionRequest); | ||
} | ||
} | ||
catch (e) { | ||
const error = yield (0, parseError_1.parseError)(e, step, swapProcess); | ||
swapProcess = statusManager.updateProcess(step, swapProcess.type, 'FAILED', { | ||
error: { | ||
message: error.message, | ||
htmlMessage: error.htmlMessage, | ||
code: error.code, | ||
}, | ||
// STEP 4: Wait for the transaction | ||
swapProcess = statusManager.updateProcess(step, swapProcess.type, 'PENDING', { | ||
txLink: fromChain.metamask.blockExplorerUrls[0] + 'tx/' + transaction.hash, | ||
txHash: transaction.hash, | ||
}); | ||
statusManager.updateExecution(step, 'FAILED'); | ||
throw error; | ||
yield transaction.wait(); | ||
} | ||
// Wait for Transaction | ||
swapProcess = statusManager.updateProcess(step, swapProcess.type, 'PENDING', { | ||
txLink: fromChain.metamask.blockExplorerUrls[0] + 'tx/' + tx.hash, | ||
txHash: tx.hash, | ||
}); | ||
// -> waiting | ||
let receipt; | ||
try { | ||
receipt = yield tx.wait(); | ||
} | ||
catch (e) { | ||
// -> set status | ||
if (e.code === 'TRANSACTION_REPLACED' && e.replacement) { | ||
receipt = e.replacement; | ||
swapProcess = statusManager.updateProcess(step, swapProcess.type, 'PENDING', { | ||
@@ -133,2 +118,3 @@ txHash: e.replacement.hash, | ||
} | ||
// STEP 5: Wait for the receiving chain | ||
let statusResponse; | ||
@@ -135,0 +121,0 @@ try { |
@@ -45,3 +45,3 @@ import { Execution, InternalExecutionSettings, Process, ProcessType, Route, Status, Step, Token } from '../types'; | ||
*/ | ||
findOrCreateProcess: (type: ProcessType, step: Step, status?: Status) => Process; | ||
findOrCreateProcess: (step: Step, type: ProcessType, status?: Status) => Process; | ||
/** | ||
@@ -64,4 +64,4 @@ * Update a process object. | ||
updateStepInRoute: (step: Step) => Step; | ||
setShouldUpdate(value: boolean): void; | ||
allowUpdates(value: boolean): void; | ||
} | ||
export {}; |
@@ -43,4 +43,5 @@ "use strict"; | ||
*/ | ||
this.findOrCreateProcess = (type, step, status) => { | ||
if (!step.execution || !step.execution.process) { | ||
this.findOrCreateProcess = (step, type, status) => { | ||
var _a; | ||
if (!((_a = step.execution) === null || _a === void 0 ? void 0 : _a.process)) { | ||
throw new Error("Execution hasn't been initialized."); | ||
@@ -50,2 +51,6 @@ } | ||
if (process) { | ||
if (status && process.status !== status) { | ||
process.status = status; | ||
this.updateStepInRoute(step); | ||
} | ||
return process; | ||
@@ -72,3 +77,3 @@ } | ||
this.updateProcess = (step, type, status, params) => { | ||
var _a; | ||
var _a, _b, _c; | ||
if (!step.execution) { | ||
@@ -98,5 +103,2 @@ throw new Error("Can't update an empty step execution."); | ||
break; | ||
case 'CHAIN_SWITCH_REQUIRED': | ||
step.execution.status = 'CHAIN_SWITCH_REQUIRED'; | ||
break; | ||
default: | ||
@@ -113,2 +115,7 @@ break; | ||
} | ||
// Sort processes, the ones with DONE status go first | ||
step.execution.process = [ | ||
...(_b = step === null || step === void 0 ? void 0 : step.execution) === null || _b === void 0 ? void 0 : _b.process.filter((process) => process.status === 'DONE'), | ||
...(_c = step === null || step === void 0 ? void 0 : step.execution) === null || _c === void 0 ? void 0 : _c.process.filter((process) => process.status !== 'DONE'), | ||
]; | ||
this.updateStepInRoute(step); // updates the step in the route | ||
@@ -168,3 +175,3 @@ return currentProcess; | ||
} | ||
setShouldUpdate(value) { | ||
allowUpdates(value) { | ||
this.shouldUpdate = value; | ||
@@ -171,0 +178,0 @@ } |
import { Signer } from 'ethers'; | ||
import { HaltingSettings, InternalExecutionSettings, Step } from '../types'; | ||
import { InteractionSettings, InternalExecutionSettings, Step } from '../types'; | ||
import { StatusManager } from './StatusManager'; | ||
@@ -9,5 +9,7 @@ export declare class StepExecutor { | ||
private bridgeExecutionManager; | ||
allowUserInteraction: boolean; | ||
executionStopped: boolean; | ||
constructor(statusManager: StatusManager, settings: InternalExecutionSettings); | ||
stopStepExecution: (settings?: HaltingSettings) => void; | ||
setInteraction: (settings?: InteractionSettings) => void; | ||
checkChain: () => never; | ||
executeStep: (signer: Signer, step: Step) => Promise<Step>; | ||
@@ -14,0 +16,0 @@ private executeSwap; |
@@ -16,4 +16,7 @@ "use strict"; | ||
const switchChain_1 = require("./switchChain"); | ||
const defaultExecutionHaltSettings = { | ||
// Please be careful when changing the defaults as it may break the behavior (e.g., background execution) | ||
const defaultInteractionSettings = { | ||
allowInteraction: true, | ||
allowUpdates: true, | ||
stopExecution: false, | ||
}; | ||
@@ -24,15 +27,23 @@ class StepExecutor { | ||
this.bridgeExecutionManager = new bridge_execute_1.BridgeExecutionManager(); | ||
this.allowUserInteraction = true; | ||
this.executionStopped = false; | ||
this.stopStepExecution = (settings) => { | ||
const haltingSettings = Object.assign(Object.assign({}, defaultExecutionHaltSettings), settings); | ||
this.swapExecutionManager.setShouldContinue(false); | ||
this.bridgeExecutionManager.setShouldContinue(false); | ||
this.statusManager.setShouldUpdate(haltingSettings.allowUpdates); | ||
this.executionStopped = true; | ||
this.setInteraction = (settings) => { | ||
const interactionSettings = Object.assign(Object.assign({}, defaultInteractionSettings), settings); | ||
this.allowUserInteraction = interactionSettings.allowInteraction; | ||
this.swapExecutionManager.allowInteraction(interactionSettings.allowInteraction); | ||
this.bridgeExecutionManager.allowInteraction(interactionSettings.allowInteraction); | ||
this.statusManager.allowUpdates(interactionSettings.allowUpdates); | ||
this.executionStopped = interactionSettings.stopExecution; | ||
}; | ||
// TODO: add checkChain method and update signer inside executors | ||
// This can come in handy when we execute multiple routes simultaneously and | ||
// should be sure that we are on the right chain when waiting for transactions. | ||
this.checkChain = () => { | ||
throw new Error('checkChain is not implemented.'); | ||
}; | ||
this.executeStep = (signer, step) => __awaiter(this, void 0, void 0, function* () { | ||
// check if signer is for correct chain | ||
const updatedSigner = yield (0, switchChain_1.switchChain)(signer, this.statusManager, step, this.settings.switchChainHook, !this.executionStopped); | ||
// Make sure that the chain is still correct | ||
const updatedSigner = yield (0, switchChain_1.switchChain)(signer, this.statusManager, step, this.settings.switchChainHook, this.allowUserInteraction); | ||
if (!updatedSigner) { | ||
// chain switch was not successful, stop execution here | ||
// Chain switch was not successful, stop execution here | ||
return step; | ||
@@ -54,3 +65,3 @@ } | ||
}); | ||
this.executeSwap = (signer, step) => __awaiter(this, void 0, void 0, function* () { | ||
this.executeSwap = (signer, step) => { | ||
const swapParams = { | ||
@@ -62,5 +73,5 @@ signer, | ||
}; | ||
return yield this.swapExecutionManager.execute(swapParams); | ||
}); | ||
this.executeCross = (signer, step) => __awaiter(this, void 0, void 0, function* () { | ||
return this.swapExecutionManager.execute(swapParams); | ||
}; | ||
this.executeCross = (signer, step) => { | ||
const crossParams = { | ||
@@ -72,4 +83,4 @@ signer, | ||
}; | ||
return yield this.bridgeExecutionManager.execute(crossParams); | ||
}); | ||
return this.bridgeExecutionManager.execute(crossParams); | ||
}; | ||
this.statusManager = statusManager; | ||
@@ -76,0 +87,0 @@ this.settings = settings; |
@@ -32,4 +32,4 @@ "use strict"; | ||
step.execution = statusManager.initExecutionObject(step); | ||
statusManager.updateExecution(step, 'CHAIN_SWITCH_REQUIRED'); | ||
let switchProcess = statusManager.findOrCreateProcess('SWITCH_CHAIN', step, 'PENDING'); | ||
statusManager.updateExecution(step, 'ACTION_REQUIRED'); | ||
let switchProcess = statusManager.findOrCreateProcess(step, 'SWITCH_CHAIN', 'ACTION_REQUIRED'); | ||
if (!allowUserInteraction) { | ||
@@ -52,3 +52,3 @@ return; | ||
message: error.message, | ||
code: error.code, | ||
code: errors_1.LifiErrorCode.ChainSwitchError, | ||
}, | ||
@@ -55,0 +55,0 @@ }); |
@@ -16,3 +16,3 @@ "use strict"; | ||
// If ethereum.request() exists, the provider is probably EIP-1193 compliant. | ||
if (!ethereum || !ethereum.request) { | ||
if (!(ethereum === null || ethereum === void 0 ? void 0 : ethereum.request)) { | ||
throw new Error('Provider not available.'); | ||
@@ -19,0 +19,0 @@ } |
@@ -107,5 +107,12 @@ import { FallbackProvider } from '@ethersproject/providers'; | ||
* @param {Route} route - A route that is currently in execution. | ||
* @deprecated use updateRouteExecution instead. | ||
*/ | ||
moveExecutionToBackground: (route: Route) => void; | ||
/** | ||
* Updates route execution to background or foreground state. | ||
* @param {Route} route - A route that is currently in execution. | ||
* @param {boolean} settings - An object with execution settings. | ||
*/ | ||
updateRouteExecution: (route: Route, settings: Pick<ExecutionSettings, 'executeInBackground'>) => void; | ||
/** | ||
* Execute a route. | ||
@@ -112,0 +119,0 @@ * @param {Signer} signer - The signer required to send the transactions. |
@@ -157,3 +157,7 @@ "use strict"; | ||
for (const executor of this.activeRouteDictionary[route.id].executors) { | ||
executor.stopStepExecution({ allowUpdates: false }); | ||
executor.setInteraction({ | ||
allowInteraction: false, | ||
allowUpdates: false, | ||
stopExecution: true, | ||
}); | ||
} | ||
@@ -166,12 +170,34 @@ delete this.activeRouteDictionary[route.id]; | ||
* @param {Route} route - A route that is currently in execution. | ||
* @deprecated use updateRouteExecution instead. | ||
*/ | ||
this.moveExecutionToBackground = (route) => { | ||
if (!this.activeRouteDictionary[route.id]) { | ||
const activeRoute = this.activeRouteDictionary[route.id]; | ||
if (!activeRoute) { | ||
return; | ||
} | ||
for (const executor of this.activeRouteDictionary[route.id].executors) { | ||
executor.stopStepExecution({ allowUpdates: true }); | ||
for (const executor of activeRoute.executors) { | ||
executor.setInteraction({ allowInteraction: false, allowUpdates: true }); | ||
} | ||
activeRoute.settings = Object.assign(Object.assign({}, activeRoute.settings), { executeInBackground: true }); | ||
}; | ||
/** | ||
* Updates route execution to background or foreground state. | ||
* @param {Route} route - A route that is currently in execution. | ||
* @param {boolean} settings - An object with execution settings. | ||
*/ | ||
this.updateRouteExecution = (route, settings) => { | ||
const activeRoute = this.activeRouteDictionary[route.id]; | ||
if (!activeRoute) { | ||
return; | ||
} | ||
for (const executor of activeRoute.executors) { | ||
executor.setInteraction({ | ||
allowInteraction: !settings.executeInBackground, | ||
allowUpdates: true, | ||
}); | ||
} | ||
// Update active route settings so we know what the current state of execution is | ||
activeRoute.settings = Object.assign(Object.assign({}, activeRoute.settings), settings); | ||
}; | ||
/** | ||
* Execute a route. | ||
@@ -185,4 +211,5 @@ * @param {Signer} signer - The signer required to send the transactions. | ||
this.executeRoute = (signer, route, settings) => __awaiter(this, void 0, void 0, function* () { | ||
const clonedRoute = (0, utils_1.deepClone)(route); // deep clone to prevent side effects | ||
// check if route is already running | ||
// Deep clone to prevent side effects | ||
const clonedRoute = (0, utils_1.deepClone)(route); | ||
// Check if route is already running | ||
if (this.activeRouteDictionary[clonedRoute.id]) { | ||
@@ -203,3 +230,4 @@ // TODO: maybe inform user why nothing happens? | ||
this.resumeRoute = (signer, route, settings) => __awaiter(this, void 0, void 0, function* () { | ||
const clonedRoute = (0, utils_1.deepClone)(route); // deep clone to prevent side effects | ||
// Deep clone to prevent side effects | ||
const clonedRoute = (0, utils_1.deepClone)(route); | ||
const activeRoute = this.activeRouteDictionary[clonedRoute.id]; | ||
@@ -209,2 +237,6 @@ if (activeRoute) { | ||
if (!executionHalted) { | ||
// Check if we want to resume route execution in the background | ||
this.updateRouteExecution(route, { | ||
executeInBackground: settings === null || settings === void 0 ? void 0 : settings.executeInBackground, | ||
}); | ||
return clonedRoute; | ||
@@ -217,4 +249,5 @@ } | ||
this.executeSteps = (signer, route, settings) => __awaiter(this, void 0, void 0, function* () { | ||
var _a, _b, _c; | ||
const config = this.configService.getConfig(); | ||
const execData = { | ||
const executionData = { | ||
route, | ||
@@ -224,27 +257,39 @@ executors: [], | ||
}; | ||
this.activeRouteDictionary[route.id] = execData; | ||
const statusManager = new StatusManager_1.StatusManager(route, this.activeRouteDictionary[route.id].settings, (route) => (this.activeRouteDictionary[route.id].route = route)); | ||
// loop over steps and execute them | ||
this.activeRouteDictionary[route.id] = executionData; | ||
const statusManager = new StatusManager_1.StatusManager(route, this.activeRouteDictionary[route.id].settings, (route) => { | ||
if (this.activeRouteDictionary[route.id]) { | ||
this.activeRouteDictionary[route.id].route = route; | ||
} | ||
}); | ||
// Loop over steps and execute them | ||
for (let index = 0; index < route.steps.length; index++) { | ||
//check if execution has stopped in meantime | ||
if (!this.activeRouteDictionary[route.id]) { | ||
const activeRoute = this.activeRouteDictionary[route.id]; | ||
// Check if execution has stopped in the meantime | ||
if (!activeRoute) { | ||
break; | ||
} | ||
const step = route.steps[index]; | ||
const previousStep = index !== 0 ? route.steps[index - 1] : undefined; | ||
// check if step already done | ||
if (step.execution && step.execution.status === 'DONE') { | ||
const previousStep = route.steps[index - 1]; | ||
// Check if the step is already done | ||
if (((_a = step.execution) === null || _a === void 0 ? void 0 : _a.status) === 'DONE') { | ||
continue; | ||
} | ||
// update amount using output of previous execution. In the future this should be handled by calling `updateRoute` | ||
if (previousStep && | ||
previousStep.execution && | ||
previousStep.execution.toAmount) { | ||
// Update amount using output of previous execution. In the future this should be handled by calling `updateRoute` | ||
if ((_b = previousStep === null || previousStep === void 0 ? void 0 : previousStep.execution) === null || _b === void 0 ? void 0 : _b.toAmount) { | ||
step.action.fromAmount = previousStep.execution.toAmount; | ||
} | ||
let stepExecutor; | ||
try { | ||
stepExecutor = new StepExecutor_1.StepExecutor(statusManager, this.activeRouteDictionary[route.id].settings); | ||
this.activeRouteDictionary[route.id].executors.push(stepExecutor); | ||
yield stepExecutor.executeStep(signer, step); | ||
const stepExecutor = new StepExecutor_1.StepExecutor(statusManager, activeRoute.settings); | ||
activeRoute.executors.push(stepExecutor); | ||
// Check if we want to execute this step in the background | ||
this.updateRouteExecution(route, activeRoute.settings); | ||
const executedStep = yield stepExecutor.executeStep(signer, step); | ||
// We may reach this point if user interaction isn't allowed. We want to stop execution until we resume it | ||
if (((_c = executedStep.execution) === null || _c === void 0 ? void 0 : _c.status) !== 'DONE') { | ||
this.stopExecution(route); | ||
} | ||
// Execution stopped during the current step, we don't want to continue to the next step so we return already | ||
if (stepExecutor.executionStopped) { | ||
return route; | ||
} | ||
} | ||
@@ -255,8 +300,4 @@ catch (e) { | ||
} | ||
// execution stopped during the current step, we don't want to continue to the next step so we return already | ||
if (stepExecutor.executionStopped) { | ||
return route; | ||
} | ||
} | ||
//clean up after execution | ||
// Clean up after the execution | ||
delete this.activeRouteDictionary[route.id]; | ||
@@ -378,3 +419,4 @@ return route; | ||
if (configUpdate) { | ||
this.configService.updateConfig(configUpdate); // update API urls before we request chains | ||
// Update API urls before we request chains | ||
this.configService.updateConfig(configUpdate); | ||
} | ||
@@ -381,0 +423,0 @@ this.chainsService = ChainsService_1.default.getInstance(); |
@@ -19,2 +19,3 @@ "use strict"; | ||
infiniteApproval: false, | ||
executeInBackground: false, | ||
}; | ||
@@ -21,0 +22,0 @@ class ConfigService { |
@@ -67,2 +67,3 @@ import { Route, RouteOptions, Step, Token } from '@lifi/types'; | ||
infiniteApproval?: boolean; | ||
executeInBackground?: boolean; | ||
} | ||
@@ -74,2 +75,3 @@ export interface InternalExecutionSettings extends ExecutionSettings { | ||
infiniteApproval: boolean; | ||
executeInBackground: boolean; | ||
} | ||
@@ -86,5 +88,7 @@ export declare type EnforcedObjectProperties<T> = T & { | ||
}; | ||
export interface HaltingSettings { | ||
export interface InteractionSettings { | ||
allowInteraction?: boolean; | ||
allowUpdates?: boolean; | ||
stopExecution?: boolean; | ||
} | ||
export {}; |
@@ -80,3 +80,3 @@ "use strict"; | ||
const parseError = (e, step, process) => __awaiter(void 0, void 0, void 0, function* () { | ||
var _a, _b; | ||
var _a, _b, _c; | ||
if (e instanceof errors_1.LifiError) { | ||
@@ -92,8 +92,7 @@ return e; | ||
if (e.code === eth_rpc_errors_1.errorCodes.rpc.internal && | ||
e.message && | ||
e.message.includes('underpriced')) { | ||
((_a = e.message) === null || _a === void 0 ? void 0 : _a.includes('underpriced'))) { | ||
return new errors_1.RPCError(errors_1.LifiErrorCode.TransactionUnderpriced, 'Transaction is underpriced.', yield (0, exports.getTransactionNotSentMessage)(step, process), e.stack); | ||
} | ||
if (((_a = e.message) === null || _a === void 0 ? void 0 : _a.includes('intrinsic gas too low')) || | ||
((_b = e.message) === null || _b === void 0 ? void 0 : _b.includes('out of gas'))) { | ||
if (((_b = e.message) === null || _b === void 0 ? void 0 : _b.includes('intrinsic gas too low')) || | ||
((_c = e.message) === null || _c === void 0 ? void 0 : _c.includes('out of gas'))) { | ||
return new errors_1.TransactionError(errors_1.LifiErrorCode.GasLimitError, 'Gas limit is too low.', yield (0, exports.getTransactionNotSentMessage)(step, process), e.stack); | ||
@@ -100,0 +99,0 @@ } |
@@ -30,3 +30,3 @@ "use strict"; | ||
if (route.steps[index].execution) { | ||
route.steps[index].execution.process = route.steps[index].execution.process.filter((process) => process.status !== 'FAILED'); | ||
route.steps[index].execution.process = route.steps[index].execution.process.filter((process) => process.status === 'DONE'); | ||
} | ||
@@ -33,0 +33,0 @@ }; |
export declare const name = "@lifi/sdk"; | ||
export declare const version = "1.4.1"; | ||
export declare const version = "1.5.0"; |
@@ -5,2 +5,2 @@ "use strict"; | ||
exports.name = '@lifi/sdk'; | ||
exports.version = '1.4.1'; | ||
exports.version = '1.5.0'; |
import { Signer } from 'ethers'; | ||
import { Chain, Step, Token } from '../types'; | ||
import { Chain, InternalExecutionSettings, Step } from '../types'; | ||
import { StatusManager } from './StatusManager'; | ||
export declare const checkAllowance: (signer: Signer, step: Step, chain: Chain, token: Token, amount: string, spenderAddress: string, statusManager: StatusManager, infiniteApproval?: boolean, allowUserInteraction?: boolean) => Promise<void>; | ||
export declare const checkAllowance: (signer: Signer, step: Step, statusManager: StatusManager, settings: InternalExecutionSettings, chain: Chain, allowUserInteraction?: boolean) => Promise<void>; |
@@ -15,30 +15,24 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
import { parseError } from '../utils/parseError'; | ||
export const checkAllowance = (signer, step, chain, token, amount, spenderAddress, statusManager, infiniteApproval = false, allowUserInteraction = false | ||
// eslint-disable-next-line max-params | ||
) => __awaiter(void 0, void 0, void 0, function* () { | ||
// Ask user to set allowance | ||
// -> set currentExecution | ||
let allowanceProcess = statusManager.findOrCreateProcess('TOKEN_ALLOWANCE', step); | ||
// -> check allowance | ||
export const checkAllowance = (signer, step, statusManager, settings, chain, allowUserInteraction = false) => __awaiter(void 0, void 0, void 0, function* () { | ||
// Ask the user to set an allowance | ||
let allowanceProcess = statusManager.findOrCreateProcess(step, 'TOKEN_ALLOWANCE'); | ||
// Check allowance | ||
try { | ||
if (allowanceProcess.txHash) { | ||
allowanceProcess = statusManager.updateProcess(step, allowanceProcess.type, 'PENDING'); | ||
if (allowanceProcess.txHash && allowanceProcess.status !== 'DONE') { | ||
if (allowanceProcess.status !== 'PENDING') { | ||
allowanceProcess = statusManager.updateProcess(step, allowanceProcess.type, 'PENDING'); | ||
} | ||
yield getProvider(signer).waitForTransaction(allowanceProcess.txHash); | ||
allowanceProcess = statusManager.updateProcess(step, allowanceProcess.type, 'DONE'); | ||
// TODO: Do we need this check? | ||
} | ||
else if (allowanceProcess.status === 'DONE') { | ||
allowanceProcess = statusManager.updateProcess(step, allowanceProcess.type, 'DONE'); | ||
} | ||
else { | ||
const approved = yield getApproved(signer, token.address, spenderAddress); | ||
if (new BigNumber(amount).gt(approved)) { | ||
const approved = yield getApproved(signer, step.action.fromToken.address, step.estimate.approvalAddress); | ||
if (new BigNumber(step.action.fromAmount).gt(approved)) { | ||
if (!allowUserInteraction) { | ||
return; | ||
} | ||
const approvalAmount = infiniteApproval | ||
const approvalAmount = settings.infiniteApproval | ||
? constants.MaxUint256.toString() | ||
: amount; | ||
const approveTx = yield setApproval(signer, token.address, spenderAddress, approvalAmount); | ||
// update currentExecution | ||
: step.action.fromAmount; | ||
const approveTx = yield setApproval(signer, step.action.fromToken.address, step.estimate.approvalAddress, approvalAmount); | ||
allowanceProcess = statusManager.updateProcess(step, allowanceProcess.type, 'PENDING', { | ||
@@ -48,3 +42,3 @@ txHash: approveTx.hash, | ||
}); | ||
// wait for transcation | ||
// Wait for the transcation | ||
yield approveTx.wait(); | ||
@@ -59,3 +53,2 @@ allowanceProcess = statusManager.updateProcess(step, allowanceProcess.type, 'DONE'); | ||
catch (e) { | ||
// -> set status | ||
if (e.code === 'TRANSACTION_REPLACED' && e.replacement) { | ||
@@ -62,0 +55,0 @@ yield transactionReplaced(e.replacement, allowanceProcess, step, chain, statusManager); |
import { Execution } from '@lifi/types'; | ||
import { ExecuteCrossParams } from '../../types'; | ||
export declare class BridgeExecutionManager { | ||
shouldContinue: boolean; | ||
setShouldContinue: (val: boolean) => void; | ||
allowUserInteraction: boolean; | ||
allowInteraction: (value: boolean) => void; | ||
execute: ({ signer, step, statusManager, settings, }: ExecuteCrossParams) => Promise<Execution>; | ||
} |
@@ -10,3 +10,2 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
}; | ||
import { constants } from 'ethers'; | ||
import ApiService from '../../services/ApiService'; | ||
@@ -17,3 +16,3 @@ import ChainsService from '../../services/ChainsService'; | ||
import { getTransactionFailedMessage, parseError } from '../../utils/parseError'; | ||
import { personalizeStep } from '../../utils/utils'; | ||
import { isZeroAddress, personalizeStep } from '../../utils/utils'; | ||
import { checkAllowance } from '../allowance.execute'; | ||
@@ -26,88 +25,99 @@ import { balanceCheck } from '../balanceCheck.execute'; | ||
constructor() { | ||
this.shouldContinue = true; | ||
this.setShouldContinue = (val) => { | ||
this.shouldContinue = val; | ||
this.allowUserInteraction = true; | ||
this.allowInteraction = (value) => { | ||
this.allowUserInteraction = value; | ||
}; | ||
this.execute = ({ signer, step, statusManager, settings, }) => __awaiter(this, void 0, void 0, function* () { | ||
var _a, _b, _c, _d; | ||
const { action, estimate } = step; | ||
step.execution = statusManager.initExecutionObject(step); | ||
const chainsService = ChainsService.getInstance(); | ||
const fromChain = yield chainsService.getChainById(action.fromChainId); | ||
const toChain = yield chainsService.getChainById(action.toChainId); | ||
// STEP 1: Check Allowance //////////////////////////////////////////////// | ||
// approval still needed? | ||
const fromChain = yield chainsService.getChainById(step.action.fromChainId); | ||
const toChain = yield chainsService.getChainById(step.action.toChainId); | ||
// STEP 1: Check allowance | ||
const oldCrossProcess = step.execution.process.find((p) => p.type === 'CROSS_CHAIN'); | ||
if (!(oldCrossProcess === null || oldCrossProcess === void 0 ? void 0 : oldCrossProcess.txHash)) { | ||
if (action.fromToken.address !== constants.AddressZero) { | ||
// Check Token Approval only if fromToken is not the native token => no approval needed in that case | ||
yield checkAllowance(signer, step, fromChain, action.fromToken, action.fromAmount, estimate.approvalAddress, statusManager, settings.infiniteApproval, this.shouldContinue); | ||
} | ||
// Check token approval only if fromToken is not the native token => no approval needed in that case | ||
if (!(oldCrossProcess === null || oldCrossProcess === void 0 ? void 0 : oldCrossProcess.txHash) && | ||
!isZeroAddress(step.action.fromToken.address)) { | ||
yield checkAllowance(signer, step, statusManager, settings, fromChain, this.allowUserInteraction); | ||
} | ||
// STEP 2: Get Transaction //////////////////////////////////////////////// | ||
let crossChainProcess = statusManager.findOrCreateProcess('CROSS_CHAIN', step); | ||
try { | ||
let tx; | ||
if (crossChainProcess.txHash) { | ||
// load exiting transaction | ||
tx = yield getProvider(signer).getTransaction(crossChainProcess.txHash); | ||
} | ||
else { | ||
// check balance | ||
yield balanceCheck(signer, step); | ||
if (!step.transactionRequest) { | ||
const personalizedStep = yield personalizeStep(signer, step); | ||
const updatedStep = yield ApiService.getStepTransaction(personalizedStep); | ||
step = Object.assign(Object.assign({}, (yield stepComparison(statusManager, personalizedStep, updatedStep, settings.acceptSlippageUpdateHook, this.shouldContinue))), { execution: step.execution }); | ||
// STEP 2: Get transaction | ||
let crossChainProcess = statusManager.findOrCreateProcess(step, 'CROSS_CHAIN'); | ||
if (crossChainProcess.status !== 'DONE') { | ||
try { | ||
let transaction; | ||
if (crossChainProcess.txHash) { | ||
// Make sure that the chain is still correct | ||
const updatedSigner = yield switchChain(signer, statusManager, step, settings.switchChainHook, this.allowUserInteraction); | ||
if (!updatedSigner) { | ||
// Chain switch was not successful, stop execution here | ||
return step.execution; | ||
} | ||
signer = updatedSigner; | ||
// Load exiting transaction | ||
transaction = yield getProvider(signer).getTransaction(crossChainProcess.txHash); | ||
} | ||
const { transactionRequest } = step; | ||
if (!transactionRequest) { | ||
throw new TransactionError(LifiErrorCode.TransactionUnprepared, 'Unable to prepare transaction.'); | ||
else { | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'STARTED'); | ||
// Check balance | ||
yield balanceCheck(signer, step); | ||
// Create new transaction | ||
if (!step.transactionRequest) { | ||
const personalizedStep = yield personalizeStep(signer, step); | ||
const updatedStep = yield ApiService.getStepTransaction(personalizedStep); | ||
step = Object.assign(Object.assign({}, (yield stepComparison(statusManager, personalizedStep, updatedStep, settings.acceptSlippageUpdateHook, this.allowUserInteraction))), { execution: step.execution }); | ||
} | ||
const { transactionRequest } = step; | ||
if (!transactionRequest) { | ||
throw new TransactionError(LifiErrorCode.TransactionUnprepared, 'Unable to prepare transaction.'); | ||
} | ||
// STEP 3: Send the transaction | ||
// Make sure that the chain is still correct | ||
const updatedSigner = yield switchChain(signer, statusManager, step, settings.switchChainHook, this.allowUserInteraction); | ||
if (!updatedSigner) { | ||
// Chain switch was not successful, stop execution here | ||
return step.execution; | ||
} | ||
signer = updatedSigner; | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'ACTION_REQUIRED'); | ||
if (!this.allowUserInteraction) { | ||
return step.execution; | ||
} | ||
// Submit the transaction | ||
transaction = yield signer.sendTransaction(transactionRequest); | ||
// STEP 4: Wait for the transaction | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'PENDING', { | ||
txHash: transaction.hash, | ||
txLink: fromChain.metamask.blockExplorerUrls[0] + | ||
'tx/' + | ||
transaction.hash, | ||
}); | ||
} | ||
// STEP 3: Send Transaction /////////////////////////////////////////////// | ||
// make sure that chain is still correct | ||
const updatedSigner = yield switchChain(signer, statusManager, step, settings.switchChainHook, this.shouldContinue); | ||
if (!updatedSigner) { | ||
// chain switch was not successful, stop execution here | ||
return step.execution; | ||
yield transaction.wait(); | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'DONE'); | ||
} | ||
catch (e) { | ||
if (e.code === 'TRANSACTION_REPLACED' && e.replacement) { | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'DONE', { | ||
txHash: e.replacement.hash, | ||
txLink: fromChain.metamask.blockExplorerUrls[0] + | ||
'tx/' + | ||
e.replacement.hash, | ||
}); | ||
} | ||
signer = updatedSigner; | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'ACTION_REQUIRED'); | ||
if (!this.shouldContinue) { | ||
return step.execution; | ||
else { | ||
const error = yield parseError(e, step, crossChainProcess); | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'FAILED', { | ||
error: { | ||
message: error.message, | ||
htmlMessage: error.htmlMessage, | ||
code: error.code, | ||
}, | ||
}); | ||
statusManager.updateExecution(step, 'FAILED'); | ||
throw error; | ||
} | ||
tx = yield signer.sendTransaction(transactionRequest); | ||
// STEP 4: Wait for Transaction /////////////////////////////////////////// | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'PENDING', { | ||
txHash: tx.hash, | ||
txLink: fromChain.metamask.blockExplorerUrls[0] + 'tx/' + tx.hash, | ||
}); | ||
} | ||
yield tx.wait(); | ||
} | ||
catch (e) { | ||
if (e.code === 'TRANSACTION_REPLACED' && e.replacement) { | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'PENDING', { | ||
txHash: e.replacement.hash, | ||
txLink: fromChain.metamask.blockExplorerUrls[0] + | ||
'tx/' + | ||
e.replacement.hash, | ||
}); | ||
} | ||
else { | ||
const error = yield parseError(e, step, crossChainProcess); | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'FAILED', { | ||
error: { | ||
message: error.message, | ||
htmlMessage: error.htmlMessage, | ||
code: error.code, | ||
}, | ||
}); | ||
statusManager.updateExecution(step, 'FAILED'); | ||
throw error; | ||
} | ||
} | ||
crossChainProcess = statusManager.updateProcess(step, crossChainProcess.type, 'DONE'); | ||
// STEP 5: Wait for Receiver ////////////////////////////////////// | ||
let receivingChainProcess = statusManager.findOrCreateProcess('RECEIVING_CHAIN', step, 'PENDING'); | ||
// STEP 5: Wait for the receiving chain | ||
let receivingChainProcess = statusManager.findOrCreateProcess(step, 'RECEIVING_CHAIN', 'PENDING'); | ||
let statusResponse; | ||
@@ -119,2 +129,18 @@ try { | ||
statusResponse = yield waitForReceivingTransaction(crossChainProcess.txHash, statusManager, receivingChainProcess.type, step); | ||
receivingChainProcess = statusManager.updateProcess(step, receivingChainProcess.type, 'DONE', { | ||
substatus: statusResponse.substatus, | ||
substatusMessage: statusResponse.substatusMessage || | ||
getSubstatusMessage(statusResponse.status, statusResponse.substatus), | ||
txHash: (_a = statusResponse.receiving) === null || _a === void 0 ? void 0 : _a.txHash, | ||
txLink: toChain.metamask.blockExplorerUrls[0] + | ||
'tx/' + | ||
((_b = statusResponse.receiving) === null || _b === void 0 ? void 0 : _b.txHash), | ||
}); | ||
statusManager.updateExecution(step, 'DONE', { | ||
fromAmount: statusResponse.sending.amount, | ||
toAmount: (_c = statusResponse.receiving) === null || _c === void 0 ? void 0 : _c.amount, | ||
toToken: (_d = statusResponse.receiving) === null || _d === void 0 ? void 0 : _d.token, | ||
gasUsed: statusResponse.sending.gasUsed, | ||
gasPrice: statusResponse.sending.gasPrice, | ||
}); | ||
} | ||
@@ -130,20 +156,5 @@ catch (e) { | ||
statusManager.updateExecution(step, 'FAILED'); | ||
console.warn(e); | ||
throw e; | ||
} | ||
receivingChainProcess = statusManager.updateProcess(step, receivingChainProcess.type, 'DONE', { | ||
substatus: statusResponse.substatus, | ||
substatusMessage: statusResponse.substatusMessage || | ||
getSubstatusMessage(statusResponse.status, statusResponse.substatus), | ||
txHash: (_a = statusResponse.receiving) === null || _a === void 0 ? void 0 : _a.txHash, | ||
txLink: toChain.metamask.blockExplorerUrls[0] + | ||
'tx/' + | ||
((_b = statusResponse.receiving) === null || _b === void 0 ? void 0 : _b.txHash), | ||
}); | ||
statusManager.updateExecution(step, 'DONE', { | ||
fromAmount: statusResponse.sending.amount, | ||
toAmount: (_c = statusResponse.receiving) === null || _c === void 0 ? void 0 : _c.amount, | ||
toToken: (_d = statusResponse.receiving) === null || _d === void 0 ? void 0 : _d.token, | ||
gasUsed: statusResponse.sending.gasUsed, | ||
gasPrice: statusResponse.sending.gasPrice, | ||
}); | ||
// DONE | ||
@@ -150,0 +161,0 @@ return step.execution; |
import { Execution } from '@lifi/types'; | ||
import { ExecuteSwapParams } from '../../types'; | ||
export declare class SwapExecutionManager { | ||
shouldContinue: boolean; | ||
setShouldContinue: (val: boolean) => void; | ||
allowUserInteraction: boolean; | ||
allowInteraction: (value: boolean) => void; | ||
execute: ({ signer, step, statusManager, settings, }: ExecuteSwapParams) => Promise<Execution>; | ||
} |
@@ -10,3 +10,2 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
}; | ||
import { constants } from 'ethers'; | ||
import ApiService from '../../services/ApiService'; | ||
@@ -17,3 +16,3 @@ import ChainsService from '../../services/ChainsService'; | ||
import { getTransactionFailedMessage, parseError } from '../../utils/parseError'; | ||
import { personalizeStep } from '../../utils/utils'; | ||
import { isZeroAddress, personalizeStep } from '../../utils/utils'; | ||
import { checkAllowance } from '../allowance.execute'; | ||
@@ -26,35 +25,39 @@ import { balanceCheck } from '../balanceCheck.execute'; | ||
constructor() { | ||
this.shouldContinue = true; | ||
this.setShouldContinue = (val) => { | ||
this.shouldContinue = val; | ||
this.allowUserInteraction = true; | ||
this.allowInteraction = (value) => { | ||
this.allowUserInteraction = value; | ||
}; | ||
this.execute = ({ signer, step, statusManager, settings, }) => __awaiter(this, void 0, void 0, function* () { | ||
// setup | ||
var _a, _b, _c, _d; | ||
const { action, estimate } = step; | ||
step.execution = statusManager.initExecutionObject(step); | ||
const chainsService = ChainsService.getInstance(); | ||
const fromChain = yield chainsService.getChainById(action.fromChainId); | ||
// Approval | ||
if (action.fromToken.address !== constants.AddressZero) { | ||
yield checkAllowance(signer, step, fromChain, action.fromToken, action.fromAmount, estimate.approvalAddress, statusManager, settings.infiniteApproval, this.shouldContinue); | ||
const fromChain = yield chainsService.getChainById(step.action.fromChainId); | ||
// STEP 1: Check allowance | ||
if (!isZeroAddress(step.action.fromToken.address)) { | ||
yield checkAllowance(signer, step, statusManager, settings, fromChain, this.allowUserInteraction); | ||
} | ||
// Start Swap | ||
// -> set step.execution | ||
let swapProcess = statusManager.findOrCreateProcess('SWAP', step); | ||
// -> swapping | ||
let tx; | ||
// STEP 2: Get transaction | ||
let swapProcess = statusManager.findOrCreateProcess(step, 'SWAP'); | ||
let transaction; | ||
try { | ||
if (swapProcess.txHash) { | ||
// -> restore existing tx | ||
tx = yield getProvider(signer).getTransaction(swapProcess.txHash); | ||
// Make sure that the chain is still correct | ||
const updatedSigner = yield switchChain(signer, statusManager, step, settings.switchChainHook, this.allowUserInteraction); | ||
if (!updatedSigner) { | ||
// Chain switch was not successful, stop execution here | ||
return step.execution; | ||
} | ||
signer = updatedSigner; | ||
// Load exiting transaction | ||
transaction = yield getProvider(signer).getTransaction(swapProcess.txHash); | ||
} | ||
else { | ||
// -> check balance | ||
swapProcess = statusManager.updateProcess(step, swapProcess.type, 'STARTED'); | ||
// Check balance | ||
yield balanceCheck(signer, step); | ||
// -> get tx from backend | ||
// Create new transaction | ||
if (!step.transactionRequest) { | ||
const personalizedStep = yield personalizeStep(signer, step); | ||
const updatedStep = yield ApiService.getStepTransaction(personalizedStep); | ||
step = Object.assign(Object.assign({}, (yield stepComparison(statusManager, personalizedStep, updatedStep, settings.acceptSlippageUpdateHook, this.shouldContinue))), { execution: step.execution }); | ||
step = Object.assign(Object.assign({}, (yield stepComparison(statusManager, personalizedStep, updatedStep, settings.acceptSlippageUpdateHook, this.allowUserInteraction))), { execution: step.execution }); | ||
} | ||
@@ -65,44 +68,26 @@ const { transactionRequest } = step; | ||
} | ||
// make sure that chain is still correct | ||
const updatedSigner = yield switchChain(signer, statusManager, step, settings.switchChainHook, this.shouldContinue); | ||
// STEP 3: Send the transaction | ||
// Make sure that the chain is still correct | ||
const updatedSigner = yield switchChain(signer, statusManager, step, settings.switchChainHook, this.allowUserInteraction); | ||
if (!updatedSigner) { | ||
// chain switch was not successful, stop execution here | ||
// Chain switch was not successful, stop execution here | ||
return step.execution; | ||
} | ||
signer = updatedSigner; | ||
// -> set step.execution | ||
swapProcess = swapProcess = statusManager.updateProcess(step, swapProcess.type, 'ACTION_REQUIRED'); | ||
if (!this.shouldContinue) { | ||
return step.execution; // stop before user interaction is needed | ||
swapProcess = statusManager.updateProcess(step, swapProcess.type, 'ACTION_REQUIRED'); | ||
if (!this.allowUserInteraction) { | ||
return step.execution; | ||
} | ||
// -> submit tx | ||
tx = yield signer.sendTransaction(transactionRequest); | ||
// Submit the transaction | ||
transaction = yield signer.sendTransaction(transactionRequest); | ||
} | ||
} | ||
catch (e) { | ||
const error = yield parseError(e, step, swapProcess); | ||
swapProcess = statusManager.updateProcess(step, swapProcess.type, 'FAILED', { | ||
error: { | ||
message: error.message, | ||
htmlMessage: error.htmlMessage, | ||
code: error.code, | ||
}, | ||
// STEP 4: Wait for the transaction | ||
swapProcess = statusManager.updateProcess(step, swapProcess.type, 'PENDING', { | ||
txLink: fromChain.metamask.blockExplorerUrls[0] + 'tx/' + transaction.hash, | ||
txHash: transaction.hash, | ||
}); | ||
statusManager.updateExecution(step, 'FAILED'); | ||
throw error; | ||
yield transaction.wait(); | ||
} | ||
// Wait for Transaction | ||
swapProcess = statusManager.updateProcess(step, swapProcess.type, 'PENDING', { | ||
txLink: fromChain.metamask.blockExplorerUrls[0] + 'tx/' + tx.hash, | ||
txHash: tx.hash, | ||
}); | ||
// -> waiting | ||
let receipt; | ||
try { | ||
receipt = yield tx.wait(); | ||
} | ||
catch (e) { | ||
// -> set status | ||
if (e.code === 'TRANSACTION_REPLACED' && e.replacement) { | ||
receipt = e.replacement; | ||
swapProcess = statusManager.updateProcess(step, swapProcess.type, 'PENDING', { | ||
@@ -128,2 +113,3 @@ txHash: e.replacement.hash, | ||
} | ||
// STEP 5: Wait for the receiving chain | ||
let statusResponse; | ||
@@ -130,0 +116,0 @@ try { |
@@ -45,3 +45,3 @@ import { Execution, InternalExecutionSettings, Process, ProcessType, Route, Status, Step, Token } from '../types'; | ||
*/ | ||
findOrCreateProcess: (type: ProcessType, step: Step, status?: Status) => Process; | ||
findOrCreateProcess: (step: Step, type: ProcessType, status?: Status) => Process; | ||
/** | ||
@@ -64,4 +64,4 @@ * Update a process object. | ||
updateStepInRoute: (step: Step) => Step; | ||
setShouldUpdate(value: boolean): void; | ||
allowUpdates(value: boolean): void; | ||
} | ||
export {}; |
@@ -40,4 +40,5 @@ import { emptyExecution, } from '../types'; | ||
*/ | ||
this.findOrCreateProcess = (type, step, status) => { | ||
if (!step.execution || !step.execution.process) { | ||
this.findOrCreateProcess = (step, type, status) => { | ||
var _a; | ||
if (!((_a = step.execution) === null || _a === void 0 ? void 0 : _a.process)) { | ||
throw new Error("Execution hasn't been initialized."); | ||
@@ -47,2 +48,6 @@ } | ||
if (process) { | ||
if (status && process.status !== status) { | ||
process.status = status; | ||
this.updateStepInRoute(step); | ||
} | ||
return process; | ||
@@ -69,3 +74,3 @@ } | ||
this.updateProcess = (step, type, status, params) => { | ||
var _a; | ||
var _a, _b, _c; | ||
if (!step.execution) { | ||
@@ -95,5 +100,2 @@ throw new Error("Can't update an empty step execution."); | ||
break; | ||
case 'CHAIN_SWITCH_REQUIRED': | ||
step.execution.status = 'CHAIN_SWITCH_REQUIRED'; | ||
break; | ||
default: | ||
@@ -110,2 +112,7 @@ break; | ||
} | ||
// Sort processes, the ones with DONE status go first | ||
step.execution.process = [ | ||
...(_b = step === null || step === void 0 ? void 0 : step.execution) === null || _b === void 0 ? void 0 : _b.process.filter((process) => process.status === 'DONE'), | ||
...(_c = step === null || step === void 0 ? void 0 : step.execution) === null || _c === void 0 ? void 0 : _c.process.filter((process) => process.status !== 'DONE'), | ||
]; | ||
this.updateStepInRoute(step); // updates the step in the route | ||
@@ -165,5 +172,5 @@ return currentProcess; | ||
} | ||
setShouldUpdate(value) { | ||
allowUpdates(value) { | ||
this.shouldUpdate = value; | ||
} | ||
} |
import { Signer } from 'ethers'; | ||
import { HaltingSettings, InternalExecutionSettings, Step } from '../types'; | ||
import { InteractionSettings, InternalExecutionSettings, Step } from '../types'; | ||
import { StatusManager } from './StatusManager'; | ||
@@ -9,5 +9,7 @@ export declare class StepExecutor { | ||
private bridgeExecutionManager; | ||
allowUserInteraction: boolean; | ||
executionStopped: boolean; | ||
constructor(statusManager: StatusManager, settings: InternalExecutionSettings); | ||
stopStepExecution: (settings?: HaltingSettings) => void; | ||
setInteraction: (settings?: InteractionSettings) => void; | ||
checkChain: () => never; | ||
executeStep: (signer: Signer, step: Step) => Promise<Step>; | ||
@@ -14,0 +16,0 @@ private executeSwap; |
@@ -13,4 +13,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
import { switchChain } from './switchChain'; | ||
const defaultExecutionHaltSettings = { | ||
// Please be careful when changing the defaults as it may break the behavior (e.g., background execution) | ||
const defaultInteractionSettings = { | ||
allowInteraction: true, | ||
allowUpdates: true, | ||
stopExecution: false, | ||
}; | ||
@@ -21,15 +24,23 @@ export class StepExecutor { | ||
this.bridgeExecutionManager = new BridgeExecutionManager(); | ||
this.allowUserInteraction = true; | ||
this.executionStopped = false; | ||
this.stopStepExecution = (settings) => { | ||
const haltingSettings = Object.assign(Object.assign({}, defaultExecutionHaltSettings), settings); | ||
this.swapExecutionManager.setShouldContinue(false); | ||
this.bridgeExecutionManager.setShouldContinue(false); | ||
this.statusManager.setShouldUpdate(haltingSettings.allowUpdates); | ||
this.executionStopped = true; | ||
this.setInteraction = (settings) => { | ||
const interactionSettings = Object.assign(Object.assign({}, defaultInteractionSettings), settings); | ||
this.allowUserInteraction = interactionSettings.allowInteraction; | ||
this.swapExecutionManager.allowInteraction(interactionSettings.allowInteraction); | ||
this.bridgeExecutionManager.allowInteraction(interactionSettings.allowInteraction); | ||
this.statusManager.allowUpdates(interactionSettings.allowUpdates); | ||
this.executionStopped = interactionSettings.stopExecution; | ||
}; | ||
// TODO: add checkChain method and update signer inside executors | ||
// This can come in handy when we execute multiple routes simultaneously and | ||
// should be sure that we are on the right chain when waiting for transactions. | ||
this.checkChain = () => { | ||
throw new Error('checkChain is not implemented.'); | ||
}; | ||
this.executeStep = (signer, step) => __awaiter(this, void 0, void 0, function* () { | ||
// check if signer is for correct chain | ||
const updatedSigner = yield switchChain(signer, this.statusManager, step, this.settings.switchChainHook, !this.executionStopped); | ||
// Make sure that the chain is still correct | ||
const updatedSigner = yield switchChain(signer, this.statusManager, step, this.settings.switchChainHook, this.allowUserInteraction); | ||
if (!updatedSigner) { | ||
// chain switch was not successful, stop execution here | ||
// Chain switch was not successful, stop execution here | ||
return step; | ||
@@ -51,3 +62,3 @@ } | ||
}); | ||
this.executeSwap = (signer, step) => __awaiter(this, void 0, void 0, function* () { | ||
this.executeSwap = (signer, step) => { | ||
const swapParams = { | ||
@@ -59,5 +70,5 @@ signer, | ||
}; | ||
return yield this.swapExecutionManager.execute(swapParams); | ||
}); | ||
this.executeCross = (signer, step) => __awaiter(this, void 0, void 0, function* () { | ||
return this.swapExecutionManager.execute(swapParams); | ||
}; | ||
this.executeCross = (signer, step) => { | ||
const crossParams = { | ||
@@ -69,4 +80,4 @@ signer, | ||
}; | ||
return yield this.bridgeExecutionManager.execute(crossParams); | ||
}); | ||
return this.bridgeExecutionManager.execute(crossParams); | ||
}; | ||
this.statusManager = statusManager; | ||
@@ -73,0 +84,0 @@ this.settings = settings; |
@@ -29,4 +29,4 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
step.execution = statusManager.initExecutionObject(step); | ||
statusManager.updateExecution(step, 'CHAIN_SWITCH_REQUIRED'); | ||
let switchProcess = statusManager.findOrCreateProcess('SWITCH_CHAIN', step, 'PENDING'); | ||
statusManager.updateExecution(step, 'ACTION_REQUIRED'); | ||
let switchProcess = statusManager.findOrCreateProcess(step, 'SWITCH_CHAIN', 'ACTION_REQUIRED'); | ||
if (!allowUserInteraction) { | ||
@@ -49,3 +49,3 @@ return; | ||
message: error.message, | ||
code: error.code, | ||
code: LifiErrorCode.ChainSwitchError, | ||
}, | ||
@@ -52,0 +52,0 @@ }); |
@@ -13,3 +13,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
// If ethereum.request() exists, the provider is probably EIP-1193 compliant. | ||
if (!ethereum || !ethereum.request) { | ||
if (!(ethereum === null || ethereum === void 0 ? void 0 : ethereum.request)) { | ||
throw new Error('Provider not available.'); | ||
@@ -16,0 +16,0 @@ } |
@@ -107,5 +107,12 @@ import { FallbackProvider } from '@ethersproject/providers'; | ||
* @param {Route} route - A route that is currently in execution. | ||
* @deprecated use updateRouteExecution instead. | ||
*/ | ||
moveExecutionToBackground: (route: Route) => void; | ||
/** | ||
* Updates route execution to background or foreground state. | ||
* @param {Route} route - A route that is currently in execution. | ||
* @param {boolean} settings - An object with execution settings. | ||
*/ | ||
updateRouteExecution: (route: Route, settings: Pick<ExecutionSettings, 'executeInBackground'>) => void; | ||
/** | ||
* Execute a route. | ||
@@ -112,0 +119,0 @@ * @param {Signer} signer - The signer required to send the transactions. |
102
dist/Lifi.js
@@ -152,3 +152,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
for (const executor of this.activeRouteDictionary[route.id].executors) { | ||
executor.stopStepExecution({ allowUpdates: false }); | ||
executor.setInteraction({ | ||
allowInteraction: false, | ||
allowUpdates: false, | ||
stopExecution: true, | ||
}); | ||
} | ||
@@ -161,12 +165,34 @@ delete this.activeRouteDictionary[route.id]; | ||
* @param {Route} route - A route that is currently in execution. | ||
* @deprecated use updateRouteExecution instead. | ||
*/ | ||
this.moveExecutionToBackground = (route) => { | ||
if (!this.activeRouteDictionary[route.id]) { | ||
const activeRoute = this.activeRouteDictionary[route.id]; | ||
if (!activeRoute) { | ||
return; | ||
} | ||
for (const executor of this.activeRouteDictionary[route.id].executors) { | ||
executor.stopStepExecution({ allowUpdates: true }); | ||
for (const executor of activeRoute.executors) { | ||
executor.setInteraction({ allowInteraction: false, allowUpdates: true }); | ||
} | ||
activeRoute.settings = Object.assign(Object.assign({}, activeRoute.settings), { executeInBackground: true }); | ||
}; | ||
/** | ||
* Updates route execution to background or foreground state. | ||
* @param {Route} route - A route that is currently in execution. | ||
* @param {boolean} settings - An object with execution settings. | ||
*/ | ||
this.updateRouteExecution = (route, settings) => { | ||
const activeRoute = this.activeRouteDictionary[route.id]; | ||
if (!activeRoute) { | ||
return; | ||
} | ||
for (const executor of activeRoute.executors) { | ||
executor.setInteraction({ | ||
allowInteraction: !settings.executeInBackground, | ||
allowUpdates: true, | ||
}); | ||
} | ||
// Update active route settings so we know what the current state of execution is | ||
activeRoute.settings = Object.assign(Object.assign({}, activeRoute.settings), settings); | ||
}; | ||
/** | ||
* Execute a route. | ||
@@ -180,4 +206,5 @@ * @param {Signer} signer - The signer required to send the transactions. | ||
this.executeRoute = (signer, route, settings) => __awaiter(this, void 0, void 0, function* () { | ||
const clonedRoute = deepClone(route); // deep clone to prevent side effects | ||
// check if route is already running | ||
// Deep clone to prevent side effects | ||
const clonedRoute = deepClone(route); | ||
// Check if route is already running | ||
if (this.activeRouteDictionary[clonedRoute.id]) { | ||
@@ -198,3 +225,4 @@ // TODO: maybe inform user why nothing happens? | ||
this.resumeRoute = (signer, route, settings) => __awaiter(this, void 0, void 0, function* () { | ||
const clonedRoute = deepClone(route); // deep clone to prevent side effects | ||
// Deep clone to prevent side effects | ||
const clonedRoute = deepClone(route); | ||
const activeRoute = this.activeRouteDictionary[clonedRoute.id]; | ||
@@ -204,2 +232,6 @@ if (activeRoute) { | ||
if (!executionHalted) { | ||
// Check if we want to resume route execution in the background | ||
this.updateRouteExecution(route, { | ||
executeInBackground: settings === null || settings === void 0 ? void 0 : settings.executeInBackground, | ||
}); | ||
return clonedRoute; | ||
@@ -212,4 +244,5 @@ } | ||
this.executeSteps = (signer, route, settings) => __awaiter(this, void 0, void 0, function* () { | ||
var _a, _b, _c; | ||
const config = this.configService.getConfig(); | ||
const execData = { | ||
const executionData = { | ||
route, | ||
@@ -219,27 +252,39 @@ executors: [], | ||
}; | ||
this.activeRouteDictionary[route.id] = execData; | ||
const statusManager = new StatusManager(route, this.activeRouteDictionary[route.id].settings, (route) => (this.activeRouteDictionary[route.id].route = route)); | ||
// loop over steps and execute them | ||
this.activeRouteDictionary[route.id] = executionData; | ||
const statusManager = new StatusManager(route, this.activeRouteDictionary[route.id].settings, (route) => { | ||
if (this.activeRouteDictionary[route.id]) { | ||
this.activeRouteDictionary[route.id].route = route; | ||
} | ||
}); | ||
// Loop over steps and execute them | ||
for (let index = 0; index < route.steps.length; index++) { | ||
//check if execution has stopped in meantime | ||
if (!this.activeRouteDictionary[route.id]) { | ||
const activeRoute = this.activeRouteDictionary[route.id]; | ||
// Check if execution has stopped in the meantime | ||
if (!activeRoute) { | ||
break; | ||
} | ||
const step = route.steps[index]; | ||
const previousStep = index !== 0 ? route.steps[index - 1] : undefined; | ||
// check if step already done | ||
if (step.execution && step.execution.status === 'DONE') { | ||
const previousStep = route.steps[index - 1]; | ||
// Check if the step is already done | ||
if (((_a = step.execution) === null || _a === void 0 ? void 0 : _a.status) === 'DONE') { | ||
continue; | ||
} | ||
// update amount using output of previous execution. In the future this should be handled by calling `updateRoute` | ||
if (previousStep && | ||
previousStep.execution && | ||
previousStep.execution.toAmount) { | ||
// Update amount using output of previous execution. In the future this should be handled by calling `updateRoute` | ||
if ((_b = previousStep === null || previousStep === void 0 ? void 0 : previousStep.execution) === null || _b === void 0 ? void 0 : _b.toAmount) { | ||
step.action.fromAmount = previousStep.execution.toAmount; | ||
} | ||
let stepExecutor; | ||
try { | ||
stepExecutor = new StepExecutor(statusManager, this.activeRouteDictionary[route.id].settings); | ||
this.activeRouteDictionary[route.id].executors.push(stepExecutor); | ||
yield stepExecutor.executeStep(signer, step); | ||
const stepExecutor = new StepExecutor(statusManager, activeRoute.settings); | ||
activeRoute.executors.push(stepExecutor); | ||
// Check if we want to execute this step in the background | ||
this.updateRouteExecution(route, activeRoute.settings); | ||
const executedStep = yield stepExecutor.executeStep(signer, step); | ||
// We may reach this point if user interaction isn't allowed. We want to stop execution until we resume it | ||
if (((_c = executedStep.execution) === null || _c === void 0 ? void 0 : _c.status) !== 'DONE') { | ||
this.stopExecution(route); | ||
} | ||
// Execution stopped during the current step, we don't want to continue to the next step so we return already | ||
if (stepExecutor.executionStopped) { | ||
return route; | ||
} | ||
} | ||
@@ -250,8 +295,4 @@ catch (e) { | ||
} | ||
// execution stopped during the current step, we don't want to continue to the next step so we return already | ||
if (stepExecutor.executionStopped) { | ||
return route; | ||
} | ||
} | ||
//clean up after execution | ||
// Clean up after the execution | ||
delete this.activeRouteDictionary[route.id]; | ||
@@ -373,3 +414,4 @@ return route; | ||
if (configUpdate) { | ||
this.configService.updateConfig(configUpdate); // update API urls before we request chains | ||
// Update API urls before we request chains | ||
this.configService.updateConfig(configUpdate); | ||
} | ||
@@ -376,0 +418,0 @@ this.chainsService = ChainsService.getInstance(); |
@@ -17,2 +17,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
infiniteApproval: false, | ||
executeInBackground: false, | ||
}; | ||
@@ -19,0 +20,0 @@ export default class ConfigService { |
@@ -67,2 +67,3 @@ import { Route, RouteOptions, Step, Token } from '@lifi/types'; | ||
infiniteApproval?: boolean; | ||
executeInBackground?: boolean; | ||
} | ||
@@ -74,2 +75,3 @@ export interface InternalExecutionSettings extends ExecutionSettings { | ||
infiniteApproval: boolean; | ||
executeInBackground: boolean; | ||
} | ||
@@ -86,5 +88,7 @@ export declare type EnforcedObjectProperties<T> = T & { | ||
}; | ||
export interface HaltingSettings { | ||
export interface InteractionSettings { | ||
allowInteraction?: boolean; | ||
allowUpdates?: boolean; | ||
stopExecution?: boolean; | ||
} | ||
export {}; |
@@ -72,3 +72,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
export const parseError = (e, step, process) => __awaiter(void 0, void 0, void 0, function* () { | ||
var _a, _b; | ||
var _a, _b, _c; | ||
if (e instanceof LifiError) { | ||
@@ -84,8 +84,7 @@ return e; | ||
if (e.code === MetaMaskErrorCodes.rpc.internal && | ||
e.message && | ||
e.message.includes('underpriced')) { | ||
((_a = e.message) === null || _a === void 0 ? void 0 : _a.includes('underpriced'))) { | ||
return new RPCError(LifiErrorCode.TransactionUnderpriced, 'Transaction is underpriced.', yield getTransactionNotSentMessage(step, process), e.stack); | ||
} | ||
if (((_a = e.message) === null || _a === void 0 ? void 0 : _a.includes('intrinsic gas too low')) || | ||
((_b = e.message) === null || _b === void 0 ? void 0 : _b.includes('out of gas'))) { | ||
if (((_b = e.message) === null || _b === void 0 ? void 0 : _b.includes('intrinsic gas too low')) || | ||
((_c = e.message) === null || _c === void 0 ? void 0 : _c.includes('out of gas'))) { | ||
return new TransactionError(LifiErrorCode.GasLimitError, 'Gas limit is too low.', yield getTransactionNotSentMessage(step, process), e.stack); | ||
@@ -92,0 +91,0 @@ } |
@@ -26,3 +26,3 @@ import { LifiErrorCode } from './errors'; | ||
if (route.steps[index].execution) { | ||
route.steps[index].execution.process = route.steps[index].execution.process.filter((process) => process.status !== 'FAILED'); | ||
route.steps[index].execution.process = route.steps[index].execution.process.filter((process) => process.status === 'DONE'); | ||
} | ||
@@ -29,0 +29,0 @@ }; |
export declare const name = "@lifi/sdk"; | ||
export declare const version = "1.4.1"; | ||
export declare const version = "1.5.0"; |
export const name = '@lifi/sdk'; | ||
export const version = '1.4.1'; | ||
export const version = '1.5.0'; |
{ | ||
"name": "@lifi/sdk", | ||
"version": "1.4.1", | ||
"version": "1.5.0", | ||
"description": "LI.FI Any-to-Any Cross-Chain-Swap SDK", | ||
@@ -86,16 +86,16 @@ "main": "./dist/cjs/index.js", | ||
"devDependencies": { | ||
"@commitlint/cli": "^17.0.3", | ||
"@commitlint/config-conventional": "^17.0.3", | ||
"@commitlint/cli": "^17.1.2", | ||
"@commitlint/config-conventional": "^17.1.0", | ||
"@types/bip39": "^3.0.0", | ||
"@types/chai": "^4.3.3", | ||
"@types/hdkey": "^2.0.0", | ||
"@types/jest": "^28.1.8", | ||
"@types/jest": "^29.0.0", | ||
"@types/websocket": "^1.0.4", | ||
"@typescript-eslint/eslint-plugin": "^5.34.0", | ||
"@typescript-eslint/parser": "^5.34.0", | ||
"eslint": "^8.22.0", | ||
"@typescript-eslint/eslint-plugin": "^5.36.1", | ||
"@typescript-eslint/parser": "^5.36.1", | ||
"eslint": "^8.23.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
"eslint-plugin-prettier": "^4.2.1", | ||
"husky": "^8.0.1", | ||
"jest": "^28.1.2", | ||
"jest": "^29.0.2", | ||
"lint-staged": "^13.0.3", | ||
@@ -108,3 +108,3 @@ "npm-run-all": "^4.1.5", | ||
"ts-loader": "^9.3.1", | ||
"typescript": "^4.7.4", | ||
"typescript": "^4.8.2", | ||
"webpack": "^5.73.0", | ||
@@ -111,0 +111,0 @@ "webpack-cli": "^4.10.0" |
409786
7916