@lifi/sdk 2.1.2 → 2.1.3-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { Signer } from 'ethers';
1
+ import { PopulatedTransaction, Signer } from 'ethers';
2
2
  import { StatusManager } from '../execution/StatusManager';
3
3
  import { Chain, InternalExecutionSettings, LifiStep } from '../types';
4
- export declare const checkAllowance: (signer: Signer, step: LifiStep, statusManager: StatusManager, settings: InternalExecutionSettings, chain: Chain, allowUserInteraction?: boolean) => Promise<void>;
4
+ export declare const checkAllowance: (signer: Signer, step: LifiStep, statusManager: StatusManager, settings: InternalExecutionSettings, chain: Chain, allowUserInteraction?: boolean, shouldBatchTransactions?: boolean) => Promise<void | PopulatedTransaction>;
@@ -1,9 +1,9 @@
1
1
  import BigNumber from 'bignumber.js';
2
- import { constants } from 'ethers';
2
+ import { constants, } from 'ethers';
3
3
  import { getApproved, setApproval } from '../allowance/utils';
4
4
  import { getProvider } from '../utils/getProvider';
5
5
  import { parseError } from '../utils/parseError';
6
- export const checkAllowance = async (signer, step, statusManager, settings, chain, allowUserInteraction = false) => {
6
+ export const checkAllowance = async (signer, step, statusManager, settings, chain, allowUserInteraction = false, shouldBatchTransactions = false) => {
7
7
  // Ask the user to set an allowance
8
8
  let allowanceProcess = statusManager.findOrCreateProcess(step, 'TOKEN_ALLOWANCE');
9
9
  // Check allowance
@@ -41,7 +41,12 @@ export const checkAllowance = async (signer, step, statusManager, settings, chai
41
41
  const approvalAmount = settings.infiniteApproval
42
42
  ? constants.MaxUint256.toString()
43
43
  : step.action.fromAmount;
44
- const approveTx = await setApproval(signer, step.action.fromToken.address, step.estimate.approvalAddress, approvalAmount);
44
+ if (shouldBatchTransactions) {
45
+ const populatedTransaction = await setApproval(signer, step.action.fromToken.address, step.estimate.approvalAddress, approvalAmount, true);
46
+ allowanceProcess = statusManager.updateProcess(step, allowanceProcess.type, 'DONE');
47
+ return populatedTransaction;
48
+ }
49
+ const approveTx = (await setApproval(signer, step.action.fromToken.address, step.estimate.approvalAddress, approvalAmount));
45
50
  allowanceProcess = statusManager.updateProcess(step, allowanceProcess.type, 'PENDING', {
46
51
  txHash: approveTx.hash,
47
52
  txLink: chain.metamask.blockExplorerUrls[0] + 'tx/' + approveTx.hash,
@@ -1,10 +1,10 @@
1
1
  import { TransactionRequest } from '@ethersproject/abstract-provider';
2
2
  import { ChainId, Token } from '@lifi/types';
3
3
  import BigNumber from 'bignumber.js';
4
- import { ContractTransaction, Signer } from 'ethers';
4
+ import { ContractTransaction, PopulatedTransaction, Signer } from 'ethers';
5
5
  import { RevokeTokenData } from '../types';
6
6
  export declare const getApproved: (signer: Signer, tokenAddress: string, contractAddress: string, transactionRequest?: TransactionRequest) => Promise<BigNumber>;
7
- export declare const setApproval: (signer: Signer, tokenAddress: string, contractAddress: string, amount: string) => Promise<ContractTransaction>;
7
+ export declare const setApproval: (signer: Signer, tokenAddress: string, contractAddress: string, amount: string, returnPopulatedTransaction?: boolean) => Promise<ContractTransaction | PopulatedTransaction>;
8
8
  export declare const getAllowanceViaMulticall: (signer: Signer, chainId: ChainId, tokenData: RevokeTokenData[]) => Promise<{
9
9
  token: Token;
10
10
  approvalAddress: string;
@@ -1,5 +1,5 @@
1
1
  import BigNumber from 'bignumber.js';
2
- import { Contract, ethers } from 'ethers';
2
+ import { Contract, ethers, } from 'ethers';
3
3
  import ChainsService from '../services/ChainsService';
4
4
  import { ERC20_ABI } from '../types';
5
5
  import { ServerError } from '../utils/errors';
@@ -20,8 +20,11 @@ export const getApproved = async (signer, tokenAddress, contractAddress, transac
20
20
  return new BigNumber(0);
21
21
  }
22
22
  };
23
- export const setApproval = async (signer, tokenAddress, contractAddress, amount) => {
23
+ export const setApproval = async (signer, tokenAddress, contractAddress, amount, returnPopulatedTransaction) => {
24
24
  const erc20 = new Contract(tokenAddress, ERC20_ABI, signer);
25
+ if (returnPopulatedTransaction) {
26
+ return erc20.populateTransaction.approve(contractAddress, amount);
27
+ }
25
28
  const transactionRequest = await erc20.populateTransaction.approve(contractAddress, amount);
26
29
  try {
27
30
  const estimatedGasLimit = await signer.estimateGas(transactionRequest);
@@ -31,7 +34,7 @@ export const setApproval = async (signer, tokenAddress, contractAddress, amount)
31
34
  }
32
35
  }
33
36
  catch (error) { }
34
- return signer.sendTransaction(transactionRequest);
37
+ return erc20.approve(contractAddress, amount);
35
38
  };
36
39
  export const getAllowanceViaMulticall = async (signer, chainId, tokenData) => {
37
40
  const chainsService = ChainsService.getInstance();
@@ -1,4 +1,4 @@
1
- import { Signer } from 'ethers';
1
+ import { PopulatedTransaction, Signer } from 'ethers';
2
2
  import { StatusManager } from '../execution/StatusManager';
3
3
  import { Chain, InternalExecutionSettings, LifiStep } from '../types';
4
- export declare const checkAllowance: (signer: Signer, step: LifiStep, statusManager: StatusManager, settings: InternalExecutionSettings, chain: Chain, allowUserInteraction?: boolean) => Promise<void>;
4
+ export declare const checkAllowance: (signer: Signer, step: LifiStep, statusManager: StatusManager, settings: InternalExecutionSettings, chain: Chain, allowUserInteraction?: boolean, shouldBatchTransactions?: boolean) => Promise<void | PopulatedTransaction>;
@@ -9,7 +9,7 @@ const ethers_1 = require("ethers");
9
9
  const utils_1 = require("../allowance/utils");
10
10
  const getProvider_1 = require("../utils/getProvider");
11
11
  const parseError_1 = require("../utils/parseError");
12
- const checkAllowance = async (signer, step, statusManager, settings, chain, allowUserInteraction = false) => {
12
+ const checkAllowance = async (signer, step, statusManager, settings, chain, allowUserInteraction = false, shouldBatchTransactions = false) => {
13
13
  // Ask the user to set an allowance
14
14
  let allowanceProcess = statusManager.findOrCreateProcess(step, 'TOKEN_ALLOWANCE');
15
15
  // Check allowance
@@ -47,7 +47,12 @@ const checkAllowance = async (signer, step, statusManager, settings, chain, allo
47
47
  const approvalAmount = settings.infiniteApproval
48
48
  ? ethers_1.constants.MaxUint256.toString()
49
49
  : step.action.fromAmount;
50
- const approveTx = await (0, utils_1.setApproval)(signer, step.action.fromToken.address, step.estimate.approvalAddress, approvalAmount);
50
+ if (shouldBatchTransactions) {
51
+ const populatedTransaction = await (0, utils_1.setApproval)(signer, step.action.fromToken.address, step.estimate.approvalAddress, approvalAmount, true);
52
+ allowanceProcess = statusManager.updateProcess(step, allowanceProcess.type, 'DONE');
53
+ return populatedTransaction;
54
+ }
55
+ const approveTx = (await (0, utils_1.setApproval)(signer, step.action.fromToken.address, step.estimate.approvalAddress, approvalAmount));
51
56
  allowanceProcess = statusManager.updateProcess(step, allowanceProcess.type, 'PENDING', {
52
57
  txHash: approveTx.hash,
53
58
  txLink: chain.metamask.blockExplorerUrls[0] + 'tx/' + approveTx.hash,
@@ -1,10 +1,10 @@
1
1
  import { TransactionRequest } from '@ethersproject/abstract-provider';
2
2
  import { ChainId, Token } from '@lifi/types';
3
3
  import BigNumber from 'bignumber.js';
4
- import { ContractTransaction, Signer } from 'ethers';
4
+ import { ContractTransaction, PopulatedTransaction, Signer } from 'ethers';
5
5
  import { RevokeTokenData } from '../types';
6
6
  export declare const getApproved: (signer: Signer, tokenAddress: string, contractAddress: string, transactionRequest?: TransactionRequest) => Promise<BigNumber>;
7
- export declare const setApproval: (signer: Signer, tokenAddress: string, contractAddress: string, amount: string) => Promise<ContractTransaction>;
7
+ export declare const setApproval: (signer: Signer, tokenAddress: string, contractAddress: string, amount: string, returnPopulatedTransaction?: boolean) => Promise<ContractTransaction | PopulatedTransaction>;
8
8
  export declare const getAllowanceViaMulticall: (signer: Signer, chainId: ChainId, tokenData: RevokeTokenData[]) => Promise<{
9
9
  token: Token;
10
10
  approvalAddress: string;
@@ -27,8 +27,11 @@ const getApproved = async (signer, tokenAddress, contractAddress, transactionReq
27
27
  }
28
28
  };
29
29
  exports.getApproved = getApproved;
30
- const setApproval = async (signer, tokenAddress, contractAddress, amount) => {
30
+ const setApproval = async (signer, tokenAddress, contractAddress, amount, returnPopulatedTransaction) => {
31
31
  const erc20 = new ethers_1.Contract(tokenAddress, types_1.ERC20_ABI, signer);
32
+ if (returnPopulatedTransaction) {
33
+ return erc20.populateTransaction.approve(contractAddress, amount);
34
+ }
32
35
  const transactionRequest = await erc20.populateTransaction.approve(contractAddress, amount);
33
36
  try {
34
37
  const estimatedGasLimit = await signer.estimateGas(transactionRequest);
@@ -38,7 +41,7 @@ const setApproval = async (signer, tokenAddress, contractAddress, amount) => {
38
41
  }
39
42
  }
40
43
  catch (error) { }
41
- return signer.sendTransaction(transactionRequest);
44
+ return erc20.approve(contractAddress, amount);
42
45
  };
43
46
  exports.setApproval = setApproval;
44
47
  const getAllowanceViaMulticall = async (signer, chainId, tokenData) => {
@@ -10,7 +10,7 @@ interface Receipt {
10
10
  gasAmountUSD?: string;
11
11
  }
12
12
  type InternalUpdateRouteCallback = (route: Route) => void;
13
- type OptionalParameters = Partial<Pick<Process, 'doneAt' | 'failedAt' | 'txHash' | 'txLink' | 'error' | 'substatus' | 'substatusMessage'>>;
13
+ type OptionalParameters = Partial<Pick<Process, 'doneAt' | 'failedAt' | 'txHash' | 'txLink' | 'error' | 'substatus' | 'substatusMessage' | 'multisigTxHash'>>;
14
14
  /**
15
15
  * Manages status updates of a route and provides various functions for tracking processes
16
16
  * @param {Route} route The route this StatusManger belongs to.
@@ -16,6 +16,8 @@ const utils_1 = require("../utils/utils");
16
16
  const stepComparison_1 = require("./stepComparison");
17
17
  const switchChain_1 = require("./switchChain");
18
18
  const utils_2 = require("./utils");
19
+ const ConfigService_1 = __importDefault(require("../services/ConfigService"));
20
+ const multisig_1 = require("./multisig");
19
21
  class StepExecutionManager {
20
22
  constructor() {
21
23
  this.allowUserInteraction = true;
@@ -23,6 +25,11 @@ class StepExecutionManager {
23
25
  this.allowUserInteraction = value;
24
26
  };
25
27
  this.execute = async ({ signer, step, statusManager, settings, }) => {
28
+ const config = ConfigService_1.default.getInstance().getConfig();
29
+ const isMultisigSigner = !!config.multisigConfig?.isMultisigSigner;
30
+ const multisigBatchTransactions = [];
31
+ const shouldBatchTransactions = config.multisigConfig?.shouldBatchTransactions &&
32
+ !!config.multisigConfig.sendBatchTransaction;
26
33
  step.execution = statusManager.initExecutionObject(step);
27
34
  const chainsService = ChainsService_1.default.getInstance();
28
35
  const fromChain = await chainsService.getChainById(step.action.fromChainId);
@@ -32,14 +39,43 @@ class StepExecutionManager {
32
39
  // STEP 1: Check allowance
33
40
  const existingProcess = step.execution.process.find((p) => p.type === currentProcessType);
34
41
  // Check token approval only if fromToken is not the native token => no approval needed in that case
35
- if (!existingProcess?.txHash &&
36
- !(0, utils_1.isZeroAddress)(step.action.fromToken.address)) {
37
- await (0, allowance_1.checkAllowance)(signer, step, statusManager, settings, fromChain, this.allowUserInteraction);
42
+ const checkForAllowance = !existingProcess?.txHash &&
43
+ !(0, utils_1.isZeroAddress)(step.action.fromToken.address) &&
44
+ (shouldBatchTransactions || !isMultisigSigner);
45
+ if (checkForAllowance) {
46
+ const populatedTransaction = await (0, allowance_1.checkAllowance)(signer, step, statusManager, settings, fromChain, this.allowUserInteraction, shouldBatchTransactions);
47
+ if (populatedTransaction) {
48
+ const { to, data } = populatedTransaction;
49
+ if (to && data) {
50
+ // allowance doesn't need value
51
+ const cleanedPopulatedTransaction = {
52
+ value: ethers_1.BigNumber.from(0).toString(),
53
+ to,
54
+ data,
55
+ };
56
+ multisigBatchTransactions.push(cleanedPopulatedTransaction);
57
+ }
58
+ }
38
59
  }
39
60
  // STEP 2: Get transaction
40
61
  let process = statusManager.findOrCreateProcess(step, currentProcessType);
41
62
  if (process.status !== 'DONE') {
63
+ const multisigProcess = step.execution.process.find((p) => !!p.multisigTxHash);
42
64
  try {
65
+ if (isMultisigSigner && multisigProcess) {
66
+ if (!multisigProcess) {
67
+ throw new errors_1.ValidationError('Multisig process is undefined.');
68
+ }
69
+ if (!config.multisigConfig?.getMultisigTransactionDetails) {
70
+ throw new errors_1.ValidationError('"getMultisigTransactionDetails()" is missing in Multisig config.');
71
+ }
72
+ const multisigTxHash = multisigProcess.multisigTxHash;
73
+ if (!multisigTxHash) {
74
+ // need to check what happens in failed tx
75
+ throw new errors_1.ValidationError('Multisig internal transaction hash is undefined.');
76
+ }
77
+ await (0, multisig_1.updateMultisigRouteProcess)(multisigTxHash, step, statusManager, process, fromChain);
78
+ }
43
79
  let transaction;
44
80
  if (process.txHash) {
45
81
  // Make sure that the chain is still correct
@@ -105,8 +141,52 @@ class StepExecutionManager {
105
141
  catch (error) { }
106
142
  }
107
143
  // Submit the transaction
108
- transaction = await signer.sendTransaction(transactionRequest);
144
+ if (shouldBatchTransactions &&
145
+ config.multisigConfig?.sendBatchTransaction) {
146
+ const { to, data, value } = await signer.populateTransaction(transactionRequest);
147
+ const isValidTransaction = to && data;
148
+ if (isValidTransaction) {
149
+ const populatedTransaction = {
150
+ value: value?.toString() ?? ethers_1.BigNumber.from(0).toString(),
151
+ to,
152
+ data: data.toString(),
153
+ };
154
+ multisigBatchTransactions.push(populatedTransaction);
155
+ transaction = await config.multisigConfig?.sendBatchTransaction(multisigBatchTransactions);
156
+ }
157
+ else {
158
+ throw new errors_1.TransactionError(errors_1.LifiErrorCode.TransactionUnprepared, 'Unable to prepare transaction.');
159
+ }
160
+ }
161
+ else {
162
+ transaction = await signer.sendTransaction(transactionRequest);
163
+ }
109
164
  // STEP 4: Wait for the transaction
165
+ if (isMultisigSigner) {
166
+ process = statusManager.updateProcess(step, process.type, 'ACTION_REQUIRED', {
167
+ multisigTxHash: transaction.hash,
168
+ });
169
+ }
170
+ else {
171
+ process = statusManager.updateProcess(step, process.type, 'PENDING', {
172
+ txHash: transaction.hash,
173
+ txLink: fromChain.metamask.blockExplorerUrls[0] +
174
+ 'tx/' +
175
+ transaction.hash,
176
+ });
177
+ }
178
+ }
179
+ await transaction.wait?.();
180
+ // if it's multisig signer and the process is in ACTION_REQUIRED
181
+ // then signatures are still needed
182
+ if (isMultisigSigner &&
183
+ process.status === 'ACTION_REQUIRED' &&
184
+ transaction.hash) {
185
+ // Return the execution object without updating the process
186
+ // The execution would progress once all multisigs signer approve
187
+ await (0, multisig_1.updateMultisigRouteProcess)(transaction.hash, step, statusManager, process, fromChain);
188
+ }
189
+ if (!isMultisigSigner) {
110
190
  process = statusManager.updateProcess(step, process.type, 'PENDING', {
111
191
  txHash: transaction.hash,
112
192
  txLink: fromChain.metamask.blockExplorerUrls[0] +
@@ -114,11 +194,6 @@ class StepExecutionManager {
114
194
  transaction.hash,
115
195
  });
116
196
  }
117
- await transaction.wait();
118
- process = statusManager.updateProcess(step, process.type, 'PENDING', {
119
- txHash: transaction.hash,
120
- txLink: fromChain.metamask.blockExplorerUrls[0] + 'tx/' + transaction.hash,
121
- });
122
197
  if (isBridgeExecution) {
123
198
  process = statusManager.updateProcess(step, process.type, 'DONE');
124
199
  }
@@ -0,0 +1,3 @@
1
+ import { ExtendedChain, LifiStep, Process } from '@lifi/types';
2
+ import { StatusManager } from '.';
3
+ export declare const updateMultisigRouteProcess: (internalTxHash: string, step: LifiStep, statusManager: StatusManager, process: Process, fromChain: ExtendedChain) => Promise<void>;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.updateMultisigRouteProcess = void 0;
7
+ const ConfigService_1 = __importDefault(require("../services/ConfigService"));
8
+ const errors_1 = require("../utils/errors");
9
+ const updateMultisigRouteProcess = async (internalTxHash, step, statusManager, process, fromChain) => {
10
+ const config = ConfigService_1.default.getInstance().getConfig();
11
+ if (!config.multisigConfig?.getMultisigTransactionDetails) {
12
+ throw new Error('"getMultisigTransactionDetails()" is missing in Multisig config.');
13
+ }
14
+ const multisigStatusResponse = await config.multisigConfig?.getMultisigTransactionDetails(internalTxHash, fromChain.id);
15
+ if (multisigStatusResponse.status === 'DONE') {
16
+ process = statusManager.updateProcess(step, process.type, 'PENDING', {
17
+ txHash: multisigStatusResponse.txHash,
18
+ multisigTxHash: undefined,
19
+ txLink: fromChain.metamask.blockExplorerUrls[0] +
20
+ 'tx/' +
21
+ multisigStatusResponse.txHash,
22
+ });
23
+ }
24
+ if (multisigStatusResponse.status === 'FAILED') {
25
+ throw new errors_1.TransactionError(errors_1.LifiErrorCode.TransactionFailed, 'Multisig transaction failed.');
26
+ }
27
+ if (multisigStatusResponse.status === 'CANCELLED') {
28
+ throw new errors_1.TransactionError(errors_1.LifiErrorCode.TransactionRejected, 'Transaction was rejected by users.');
29
+ }
30
+ };
31
+ exports.updateMultisigRouteProcess = updateMultisigRouteProcess;
@@ -42,6 +42,8 @@ class ConfigService {
42
42
  this.config.integrator = configUpdate.integrator || this.config.integrator;
43
43
  this.config.widgetVersion =
44
44
  configUpdate.widgetVersion || this.config.widgetVersion;
45
+ this.config.multisigConfig =
46
+ configUpdate.multisigConfig || this.config.multisigConfig;
45
47
  return this.config;
46
48
  };
47
49
  this.updateChains = (chains) => {
@@ -35,7 +35,27 @@ export type Config = {
35
35
  userId?: string;
36
36
  integrator: string;
37
37
  widgetVersion?: string;
38
+ multisigConfig?: MultisigConfig;
38
39
  };
40
+ export interface MultisigTxDetails {
41
+ status: 'DONE' | 'FAILED' | 'PENDING' | 'CANCELLED';
42
+ message: string;
43
+ txHash?: string;
44
+ }
45
+ export interface MultisigTransactionResponse {
46
+ hash: string;
47
+ }
48
+ export interface BaseTransaction {
49
+ to: string;
50
+ value: string;
51
+ data: string;
52
+ }
53
+ export interface MultisigConfig {
54
+ isMultisigSigner: boolean;
55
+ getMultisigTransactionDetails: (txHash: string, fromChainId: number) => Promise<MultisigTxDetails>;
56
+ sendBatchTransaction?: (batchTransactions: BaseTransaction[]) => Promise<MultisigTransactionResponse>;
57
+ shouldBatchTransactions?: boolean;
58
+ }
39
59
  export type ConfigUpdate = {
40
60
  apiUrl?: string;
41
61
  rpcs?: Record<number, string[]>;
@@ -46,6 +66,7 @@ export type ConfigUpdate = {
46
66
  userId?: string;
47
67
  integrator: string;
48
68
  widgetVersion?: string;
69
+ multisigConfig?: MultisigConfig;
49
70
  };
50
71
  export type SwitchChainHook = (requiredChainId: number) => Promise<Signer | undefined>;
51
72
  export interface AcceptSlippageUpdateHookParams {
@@ -1,2 +1,2 @@
1
1
  export declare const name = "@lifi/sdk";
2
- export declare const version = "2.1.2";
2
+ export declare const version = "2.1.3-beta.0";
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.version = exports.name = void 0;
4
4
  exports.name = '@lifi/sdk';
5
- exports.version = '2.1.2';
5
+ exports.version = '2.1.3-beta.0';
@@ -10,7 +10,7 @@ interface Receipt {
10
10
  gasAmountUSD?: string;
11
11
  }
12
12
  type InternalUpdateRouteCallback = (route: Route) => void;
13
- type OptionalParameters = Partial<Pick<Process, 'doneAt' | 'failedAt' | 'txHash' | 'txLink' | 'error' | 'substatus' | 'substatusMessage'>>;
13
+ type OptionalParameters = Partial<Pick<Process, 'doneAt' | 'failedAt' | 'txHash' | 'txLink' | 'error' | 'substatus' | 'substatusMessage' | 'multisigTxHash'>>;
14
14
  /**
15
15
  * Manages status updates of a route and provides various functions for tracking processes
16
16
  * @param {Route} route The route this StatusManger belongs to.
@@ -3,13 +3,15 @@ import { checkAllowance } from '../allowance';
3
3
  import { checkBalance } from '../balance';
4
4
  import ApiService from '../services/ApiService';
5
5
  import ChainsService from '../services/ChainsService';
6
- import { LifiErrorCode, TransactionError } from '../utils/errors';
6
+ import { LifiErrorCode, TransactionError, ValidationError, } from '../utils/errors';
7
7
  import { getProvider } from '../utils/getProvider';
8
8
  import { getTransactionFailedMessage, parseError } from '../utils/parseError';
9
9
  import { isZeroAddress, personalizeStep } from '../utils/utils';
10
10
  import { stepComparison } from './stepComparison';
11
11
  import { switchChain } from './switchChain';
12
12
  import { getSubstatusMessage, waitForReceivingTransaction } from './utils';
13
+ import ConfigService from '../services/ConfigService';
14
+ import { updateMultisigRouteProcess } from './multisig';
13
15
  export class StepExecutionManager {
14
16
  constructor() {
15
17
  this.allowUserInteraction = true;
@@ -17,6 +19,11 @@ export class StepExecutionManager {
17
19
  this.allowUserInteraction = value;
18
20
  };
19
21
  this.execute = async ({ signer, step, statusManager, settings, }) => {
22
+ const config = ConfigService.getInstance().getConfig();
23
+ const isMultisigSigner = !!config.multisigConfig?.isMultisigSigner;
24
+ const multisigBatchTransactions = [];
25
+ const shouldBatchTransactions = config.multisigConfig?.shouldBatchTransactions &&
26
+ !!config.multisigConfig.sendBatchTransaction;
20
27
  step.execution = statusManager.initExecutionObject(step);
21
28
  const chainsService = ChainsService.getInstance();
22
29
  const fromChain = await chainsService.getChainById(step.action.fromChainId);
@@ -26,14 +33,43 @@ export class StepExecutionManager {
26
33
  // STEP 1: Check allowance
27
34
  const existingProcess = step.execution.process.find((p) => p.type === currentProcessType);
28
35
  // Check token approval only if fromToken is not the native token => no approval needed in that case
29
- if (!existingProcess?.txHash &&
30
- !isZeroAddress(step.action.fromToken.address)) {
31
- await checkAllowance(signer, step, statusManager, settings, fromChain, this.allowUserInteraction);
36
+ const checkForAllowance = !existingProcess?.txHash &&
37
+ !isZeroAddress(step.action.fromToken.address) &&
38
+ (shouldBatchTransactions || !isMultisigSigner);
39
+ if (checkForAllowance) {
40
+ const populatedTransaction = await checkAllowance(signer, step, statusManager, settings, fromChain, this.allowUserInteraction, shouldBatchTransactions);
41
+ if (populatedTransaction) {
42
+ const { to, data } = populatedTransaction;
43
+ if (to && data) {
44
+ // allowance doesn't need value
45
+ const cleanedPopulatedTransaction = {
46
+ value: BigNumber.from(0).toString(),
47
+ to,
48
+ data,
49
+ };
50
+ multisigBatchTransactions.push(cleanedPopulatedTransaction);
51
+ }
52
+ }
32
53
  }
33
54
  // STEP 2: Get transaction
34
55
  let process = statusManager.findOrCreateProcess(step, currentProcessType);
35
56
  if (process.status !== 'DONE') {
57
+ const multisigProcess = step.execution.process.find((p) => !!p.multisigTxHash);
36
58
  try {
59
+ if (isMultisigSigner && multisigProcess) {
60
+ if (!multisigProcess) {
61
+ throw new ValidationError('Multisig process is undefined.');
62
+ }
63
+ if (!config.multisigConfig?.getMultisigTransactionDetails) {
64
+ throw new ValidationError('"getMultisigTransactionDetails()" is missing in Multisig config.');
65
+ }
66
+ const multisigTxHash = multisigProcess.multisigTxHash;
67
+ if (!multisigTxHash) {
68
+ // need to check what happens in failed tx
69
+ throw new ValidationError('Multisig internal transaction hash is undefined.');
70
+ }
71
+ await updateMultisigRouteProcess(multisigTxHash, step, statusManager, process, fromChain);
72
+ }
37
73
  let transaction;
38
74
  if (process.txHash) {
39
75
  // Make sure that the chain is still correct
@@ -99,8 +135,52 @@ export class StepExecutionManager {
99
135
  catch (error) { }
100
136
  }
101
137
  // Submit the transaction
102
- transaction = await signer.sendTransaction(transactionRequest);
138
+ if (shouldBatchTransactions &&
139
+ config.multisigConfig?.sendBatchTransaction) {
140
+ const { to, data, value } = await signer.populateTransaction(transactionRequest);
141
+ const isValidTransaction = to && data;
142
+ if (isValidTransaction) {
143
+ const populatedTransaction = {
144
+ value: value?.toString() ?? BigNumber.from(0).toString(),
145
+ to,
146
+ data: data.toString(),
147
+ };
148
+ multisigBatchTransactions.push(populatedTransaction);
149
+ transaction = await config.multisigConfig?.sendBatchTransaction(multisigBatchTransactions);
150
+ }
151
+ else {
152
+ throw new TransactionError(LifiErrorCode.TransactionUnprepared, 'Unable to prepare transaction.');
153
+ }
154
+ }
155
+ else {
156
+ transaction = await signer.sendTransaction(transactionRequest);
157
+ }
103
158
  // STEP 4: Wait for the transaction
159
+ if (isMultisigSigner) {
160
+ process = statusManager.updateProcess(step, process.type, 'ACTION_REQUIRED', {
161
+ multisigTxHash: transaction.hash,
162
+ });
163
+ }
164
+ else {
165
+ process = statusManager.updateProcess(step, process.type, 'PENDING', {
166
+ txHash: transaction.hash,
167
+ txLink: fromChain.metamask.blockExplorerUrls[0] +
168
+ 'tx/' +
169
+ transaction.hash,
170
+ });
171
+ }
172
+ }
173
+ await transaction.wait?.();
174
+ // if it's multisig signer and the process is in ACTION_REQUIRED
175
+ // then signatures are still needed
176
+ if (isMultisigSigner &&
177
+ process.status === 'ACTION_REQUIRED' &&
178
+ transaction.hash) {
179
+ // Return the execution object without updating the process
180
+ // The execution would progress once all multisigs signer approve
181
+ await updateMultisigRouteProcess(transaction.hash, step, statusManager, process, fromChain);
182
+ }
183
+ if (!isMultisigSigner) {
104
184
  process = statusManager.updateProcess(step, process.type, 'PENDING', {
105
185
  txHash: transaction.hash,
106
186
  txLink: fromChain.metamask.blockExplorerUrls[0] +
@@ -108,11 +188,6 @@ export class StepExecutionManager {
108
188
  transaction.hash,
109
189
  });
110
190
  }
111
- await transaction.wait();
112
- process = statusManager.updateProcess(step, process.type, 'PENDING', {
113
- txHash: transaction.hash,
114
- txLink: fromChain.metamask.blockExplorerUrls[0] + 'tx/' + transaction.hash,
115
- });
116
191
  if (isBridgeExecution) {
117
192
  process = statusManager.updateProcess(step, process.type, 'DONE');
118
193
  }
@@ -0,0 +1,3 @@
1
+ import { ExtendedChain, LifiStep, Process } from '@lifi/types';
2
+ import { StatusManager } from '.';
3
+ export declare const updateMultisigRouteProcess: (internalTxHash: string, step: LifiStep, statusManager: StatusManager, process: Process, fromChain: ExtendedChain) => Promise<void>;
@@ -0,0 +1,24 @@
1
+ import ConfigService from '../services/ConfigService';
2
+ import { LifiErrorCode, TransactionError } from '../utils/errors';
3
+ export const updateMultisigRouteProcess = async (internalTxHash, step, statusManager, process, fromChain) => {
4
+ const config = ConfigService.getInstance().getConfig();
5
+ if (!config.multisigConfig?.getMultisigTransactionDetails) {
6
+ throw new Error('"getMultisigTransactionDetails()" is missing in Multisig config.');
7
+ }
8
+ const multisigStatusResponse = await config.multisigConfig?.getMultisigTransactionDetails(internalTxHash, fromChain.id);
9
+ if (multisigStatusResponse.status === 'DONE') {
10
+ process = statusManager.updateProcess(step, process.type, 'PENDING', {
11
+ txHash: multisigStatusResponse.txHash,
12
+ multisigTxHash: undefined,
13
+ txLink: fromChain.metamask.blockExplorerUrls[0] +
14
+ 'tx/' +
15
+ multisigStatusResponse.txHash,
16
+ });
17
+ }
18
+ if (multisigStatusResponse.status === 'FAILED') {
19
+ throw new TransactionError(LifiErrorCode.TransactionFailed, 'Multisig transaction failed.');
20
+ }
21
+ if (multisigStatusResponse.status === 'CANCELLED') {
22
+ throw new TransactionError(LifiErrorCode.TransactionRejected, 'Transaction was rejected by users.');
23
+ }
24
+ };
@@ -40,6 +40,8 @@ class ConfigService {
40
40
  this.config.integrator = configUpdate.integrator || this.config.integrator;
41
41
  this.config.widgetVersion =
42
42
  configUpdate.widgetVersion || this.config.widgetVersion;
43
+ this.config.multisigConfig =
44
+ configUpdate.multisigConfig || this.config.multisigConfig;
43
45
  return this.config;
44
46
  };
45
47
  this.updateChains = (chains) => {
@@ -35,7 +35,27 @@ export type Config = {
35
35
  userId?: string;
36
36
  integrator: string;
37
37
  widgetVersion?: string;
38
+ multisigConfig?: MultisigConfig;
38
39
  };
40
+ export interface MultisigTxDetails {
41
+ status: 'DONE' | 'FAILED' | 'PENDING' | 'CANCELLED';
42
+ message: string;
43
+ txHash?: string;
44
+ }
45
+ export interface MultisigTransactionResponse {
46
+ hash: string;
47
+ }
48
+ export interface BaseTransaction {
49
+ to: string;
50
+ value: string;
51
+ data: string;
52
+ }
53
+ export interface MultisigConfig {
54
+ isMultisigSigner: boolean;
55
+ getMultisigTransactionDetails: (txHash: string, fromChainId: number) => Promise<MultisigTxDetails>;
56
+ sendBatchTransaction?: (batchTransactions: BaseTransaction[]) => Promise<MultisigTransactionResponse>;
57
+ shouldBatchTransactions?: boolean;
58
+ }
39
59
  export type ConfigUpdate = {
40
60
  apiUrl?: string;
41
61
  rpcs?: Record<number, string[]>;
@@ -46,6 +66,7 @@ export type ConfigUpdate = {
46
66
  userId?: string;
47
67
  integrator: string;
48
68
  widgetVersion?: string;
69
+ multisigConfig?: MultisigConfig;
49
70
  };
50
71
  export type SwitchChainHook = (requiredChainId: number) => Promise<Signer | undefined>;
51
72
  export interface AcceptSlippageUpdateHookParams {
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export declare const name = "@lifi/sdk";
2
- export declare const version = "2.1.2";
2
+ export declare const version = "2.1.3-beta.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export const name = '@lifi/sdk';
2
- export const version = '2.1.2';
2
+ export const version = '2.1.3-beta.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifi/sdk",
3
- "version": "2.1.2",
3
+ "version": "2.1.3-beta.0",
4
4
  "description": "LI.FI Any-to-Any Cross-Chain-Swap SDK",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/index.js",