@hyperlane-xyz/rebalancer 3.2.1 → 25.5.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.
- package/dist/bridges/LiFiBridge.d.ts +12 -2
- package/dist/bridges/LiFiBridge.d.ts.map +1 -1
- package/dist/bridges/LiFiBridge.js +36 -1
- package/dist/bridges/LiFiBridge.js.map +1 -1
- package/dist/bridges/LiFiBridge.test.d.ts +2 -0
- package/dist/bridges/LiFiBridge.test.d.ts.map +1 -0
- package/dist/bridges/LiFiBridge.test.js +412 -0
- package/dist/bridges/LiFiBridge.test.js.map +1 -0
- package/dist/config/types.d.ts +1 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +1 -0
- package/dist/config/types.js.map +1 -1
- package/dist/e2e/harness/MockExternalBridge.d.ts.map +1 -1
- package/dist/e2e/harness/MockExternalBridge.js +20 -0
- package/dist/e2e/harness/MockExternalBridge.js.map +1 -1
- package/dist/interfaces/IExternalBridge.d.ts +3 -2
- package/dist/interfaces/IExternalBridge.d.ts.map +1 -1
- package/dist/service.js +0 -0
- package/dist/test/lifiMocks.d.ts +1 -0
- package/dist/test/lifiMocks.d.ts.map +1 -1
- package/dist/test/lifiMocks.js +36 -6
- package/dist/test/lifiMocks.js.map +1 -1
- package/dist/tracking/ActionTracker.d.ts.map +1 -1
- package/dist/tracking/ActionTracker.js +44 -4
- package/dist/tracking/ActionTracker.js.map +1 -1
- package/dist/tracking/ActionTracker.test.js +284 -1
- package/dist/tracking/ActionTracker.test.js.map +1 -1
- package/dist/tracking/types.d.ts +3 -0
- package/dist/tracking/types.d.ts.map +1 -1
- package/package.json +7 -10
- package/src/bridges/LiFiBridge.test.ts +510 -0
- package/src/bridges/LiFiBridge.ts +84 -48
- package/src/config/types.ts +2 -0
- package/src/e2e/harness/MockExternalBridge.ts +32 -0
- package/src/interfaces/IExternalBridge.ts +3 -2
- package/src/test/lifiMocks.ts +43 -6
- package/src/tracking/ActionTracker.test.ts +336 -1
- package/src/tracking/ActionTracker.ts +53 -4
- package/src/tracking/types.ts +3 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EVM,
|
|
3
|
+
type LiFiStep,
|
|
4
|
+
type Route,
|
|
3
5
|
type RouteExtended,
|
|
4
6
|
convertQuoteToRoute,
|
|
5
7
|
createConfig,
|
|
@@ -12,6 +14,7 @@ import type { Logger } from 'pino';
|
|
|
12
14
|
import { type Chain, createWalletClient, http } from 'viem';
|
|
13
15
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
14
16
|
import { arbitrum, base, mainnet, optimism } from 'viem/chains';
|
|
17
|
+
import { assert } from '@hyperlane-xyz/utils';
|
|
15
18
|
|
|
16
19
|
import type {
|
|
17
20
|
BridgeQuote,
|
|
@@ -28,41 +31,6 @@ import type {
|
|
|
28
31
|
*/
|
|
29
32
|
const LIFI_API_BASE = 'https://li.quest/v1';
|
|
30
33
|
|
|
31
|
-
/**
|
|
32
|
-
* LiFi quote response structure (partial, only fields we need).
|
|
33
|
-
*/
|
|
34
|
-
interface LiFiQuoteResponse {
|
|
35
|
-
id: string;
|
|
36
|
-
tool: string;
|
|
37
|
-
action: {
|
|
38
|
-
fromAmount: string;
|
|
39
|
-
toAmount?: string;
|
|
40
|
-
};
|
|
41
|
-
estimate: {
|
|
42
|
-
fromAmount: string;
|
|
43
|
-
toAmount: string;
|
|
44
|
-
toAmountMin: string;
|
|
45
|
-
executionDuration: number;
|
|
46
|
-
gasCosts?: Array<{
|
|
47
|
-
type: string;
|
|
48
|
-
amount: string;
|
|
49
|
-
token: {
|
|
50
|
-
address: string;
|
|
51
|
-
symbol: string;
|
|
52
|
-
};
|
|
53
|
-
}>;
|
|
54
|
-
feeCosts?: Array<{
|
|
55
|
-
name: string;
|
|
56
|
-
amount: string;
|
|
57
|
-
included: boolean;
|
|
58
|
-
token: {
|
|
59
|
-
address: string;
|
|
60
|
-
symbol: string;
|
|
61
|
-
};
|
|
62
|
-
}>;
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
34
|
/**
|
|
67
35
|
* Known chains for viem - add more as needed.
|
|
68
36
|
* TODO: can we think of a cleaner way to do this?
|
|
@@ -111,7 +79,7 @@ function getViemChain(chainId: number, rpcUrl?: string): Chain {
|
|
|
111
79
|
*/
|
|
112
80
|
export class LiFiBridge implements IExternalBridge {
|
|
113
81
|
private static readonly NATIVE_TOKEN_ADDRESS =
|
|
114
|
-
'
|
|
82
|
+
'0x0000000000000000000000000000000000000000';
|
|
115
83
|
|
|
116
84
|
readonly externalBridgeId = 'lifi';
|
|
117
85
|
readonly logger: Logger;
|
|
@@ -164,7 +132,7 @@ export class LiFiBridge implements IExternalBridge {
|
|
|
164
132
|
*
|
|
165
133
|
* Returns route data ready for execution.
|
|
166
134
|
*/
|
|
167
|
-
async quote(params: BridgeQuoteParams): Promise<BridgeQuote
|
|
135
|
+
async quote(params: BridgeQuoteParams): Promise<BridgeQuote<LiFiStep>> {
|
|
168
136
|
this.initialize();
|
|
169
137
|
|
|
170
138
|
// Validate that exactly one of fromAmount or toAmount is provided
|
|
@@ -177,6 +145,15 @@ export class LiFiBridge implements IExternalBridge {
|
|
|
177
145
|
throw new Error('Must specify either fromAmount or toAmount');
|
|
178
146
|
}
|
|
179
147
|
|
|
148
|
+
assert(
|
|
149
|
+
params.fromAmount === undefined || params.fromAmount > 0n,
|
|
150
|
+
'fromAmount must be positive',
|
|
151
|
+
);
|
|
152
|
+
assert(
|
|
153
|
+
params.toAmount === undefined || params.toAmount > 0n,
|
|
154
|
+
'toAmount must be positive',
|
|
155
|
+
);
|
|
156
|
+
|
|
180
157
|
// Dispatch to appropriate quote method
|
|
181
158
|
if (params.toAmount !== undefined) {
|
|
182
159
|
return this.quoteByReceivingAmount(params);
|
|
@@ -191,7 +168,7 @@ export class LiFiBridge implements IExternalBridge {
|
|
|
191
168
|
*/
|
|
192
169
|
private async quoteBySpendingAmount(
|
|
193
170
|
params: BridgeQuoteParams,
|
|
194
|
-
): Promise<BridgeQuote
|
|
171
|
+
): Promise<BridgeQuote<LiFiStep>> {
|
|
195
172
|
this.logger.debug({ params }, 'Requesting LiFi quote by spending amount');
|
|
196
173
|
|
|
197
174
|
const quote = await getQuote({
|
|
@@ -207,9 +184,7 @@ export class LiFiBridge implements IExternalBridge {
|
|
|
207
184
|
order: 'RECOMMENDED',
|
|
208
185
|
});
|
|
209
186
|
|
|
210
|
-
const { gasCosts, feeCosts } = this.extractCosts(
|
|
211
|
-
quote as unknown as LiFiQuoteResponse,
|
|
212
|
-
);
|
|
187
|
+
const { gasCosts, feeCosts } = this.extractCosts(quote);
|
|
213
188
|
|
|
214
189
|
this.logger.info(
|
|
215
190
|
{
|
|
@@ -235,6 +210,7 @@ export class LiFiBridge implements IExternalBridge {
|
|
|
235
210
|
gasCosts,
|
|
236
211
|
feeCosts,
|
|
237
212
|
route: quote, // Store full quote for conversion to route
|
|
213
|
+
requestParams: { ...params },
|
|
238
214
|
};
|
|
239
215
|
}
|
|
240
216
|
|
|
@@ -244,7 +220,7 @@ export class LiFiBridge implements IExternalBridge {
|
|
|
244
220
|
*/
|
|
245
221
|
private async quoteByReceivingAmount(
|
|
246
222
|
params: BridgeQuoteParams,
|
|
247
|
-
): Promise<BridgeQuote
|
|
223
|
+
): Promise<BridgeQuote<LiFiStep>> {
|
|
248
224
|
this.logger.debug({ params }, 'Requesting LiFi quote by receiving amount');
|
|
249
225
|
|
|
250
226
|
const queryParams = new URLSearchParams({
|
|
@@ -280,7 +256,7 @@ export class LiFiBridge implements IExternalBridge {
|
|
|
280
256
|
);
|
|
281
257
|
}
|
|
282
258
|
|
|
283
|
-
const quote:
|
|
259
|
+
const quote: LiFiStep = await response.json();
|
|
284
260
|
const { gasCosts, feeCosts } = this.extractCosts(quote);
|
|
285
261
|
|
|
286
262
|
this.logger.info(
|
|
@@ -307,6 +283,7 @@ export class LiFiBridge implements IExternalBridge {
|
|
|
307
283
|
gasCosts,
|
|
308
284
|
feeCosts,
|
|
309
285
|
route: quote, // Store full quote for conversion to route
|
|
286
|
+
requestParams: { ...params },
|
|
310
287
|
};
|
|
311
288
|
}
|
|
312
289
|
|
|
@@ -315,7 +292,7 @@ export class LiFiBridge implements IExternalBridge {
|
|
|
315
292
|
* - gasCosts: Sum of all gas costs (transaction fees)
|
|
316
293
|
* - feeCosts: Sum of non-included fee costs (protocol fees not deducted from amount)
|
|
317
294
|
*/
|
|
318
|
-
private extractCosts(quote:
|
|
295
|
+
private extractCosts(quote: LiFiStep): {
|
|
319
296
|
gasCosts: bigint;
|
|
320
297
|
feeCosts: bigint;
|
|
321
298
|
} {
|
|
@@ -350,15 +327,15 @@ export class LiFiBridge implements IExternalBridge {
|
|
|
350
327
|
* @param privateKey - Private key hex string (0x-prefixed) for signing the transaction
|
|
351
328
|
*/
|
|
352
329
|
async execute(
|
|
353
|
-
quote: BridgeQuote
|
|
330
|
+
quote: BridgeQuote<LiFiStep>,
|
|
354
331
|
privateKey: string,
|
|
355
332
|
): Promise<BridgeTransferResult> {
|
|
356
333
|
this.initialize();
|
|
357
334
|
|
|
358
335
|
// Convert quote to route for execution
|
|
359
|
-
const route = convertQuoteToRoute(
|
|
360
|
-
|
|
361
|
-
);
|
|
336
|
+
const route = convertQuoteToRoute(quote.route);
|
|
337
|
+
|
|
338
|
+
this.validateRouteAgainstRequest(route, quote.requestParams);
|
|
362
339
|
|
|
363
340
|
const fromChain = route.fromChainId;
|
|
364
341
|
const toChain = route.toChainId;
|
|
@@ -478,6 +455,65 @@ export class LiFiBridge implements IExternalBridge {
|
|
|
478
455
|
};
|
|
479
456
|
}
|
|
480
457
|
|
|
458
|
+
/**
|
|
459
|
+
* Validate that the route returned by LiFi matches the original request parameters.
|
|
460
|
+
* Prevents execution against wrong chains, tokens, or recipients if the bridge API
|
|
461
|
+
* returns a route that diverges from what was originally requested.
|
|
462
|
+
*
|
|
463
|
+
* TODO: Layer 2 validation — validate transaction calldata in route.steps[].transactionRequest
|
|
464
|
+
* and route.steps[0].estimate.approvalAddress against a known whitelist.
|
|
465
|
+
*/
|
|
466
|
+
private validateRouteAgainstRequest(
|
|
467
|
+
route: Route,
|
|
468
|
+
requestParams: BridgeQuoteParams,
|
|
469
|
+
): void {
|
|
470
|
+
assert(
|
|
471
|
+
route.fromChainId === requestParams.fromChain,
|
|
472
|
+
`Route fromChainId ${route.fromChainId} does not match requested ${requestParams.fromChain}`,
|
|
473
|
+
);
|
|
474
|
+
assert(
|
|
475
|
+
route.toChainId === requestParams.toChain,
|
|
476
|
+
`Route toChainId ${route.toChainId} does not match requested ${requestParams.toChain}`,
|
|
477
|
+
);
|
|
478
|
+
assert(
|
|
479
|
+
route.fromToken.address.toLowerCase() ===
|
|
480
|
+
requestParams.fromToken.toLowerCase(),
|
|
481
|
+
`Route fromToken ${route.fromToken.address} does not match requested ${requestParams.fromToken}`,
|
|
482
|
+
);
|
|
483
|
+
assert(
|
|
484
|
+
route.toToken.address.toLowerCase() ===
|
|
485
|
+
requestParams.toToken.toLowerCase(),
|
|
486
|
+
`Route toToken ${route.toToken.address} does not match requested ${requestParams.toToken}`,
|
|
487
|
+
);
|
|
488
|
+
const expectedToAddress = (
|
|
489
|
+
requestParams.toAddress ?? requestParams.fromAddress
|
|
490
|
+
).toLowerCase();
|
|
491
|
+
assert(
|
|
492
|
+
route.toAddress?.toLowerCase() === expectedToAddress,
|
|
493
|
+
`Route toAddress ${route.toAddress} does not match requested ${expectedToAddress}`,
|
|
494
|
+
);
|
|
495
|
+
assert(
|
|
496
|
+
route.fromAddress?.toLowerCase() ===
|
|
497
|
+
requestParams.fromAddress.toLowerCase(),
|
|
498
|
+
`Route fromAddress ${route.fromAddress} does not match requested ${requestParams.fromAddress}`,
|
|
499
|
+
);
|
|
500
|
+
const routeFromAmount = BigInt(route.fromAmount);
|
|
501
|
+
if (requestParams.fromAmount !== undefined) {
|
|
502
|
+
assert(
|
|
503
|
+
routeFromAmount === requestParams.fromAmount,
|
|
504
|
+
`Route fromAmount ${route.fromAmount} does not match requested ${requestParams.fromAmount}`,
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
if (requestParams.toAmount !== undefined) {
|
|
508
|
+
const routeToAmount = BigInt(route.toAmount);
|
|
509
|
+
assert(
|
|
510
|
+
routeToAmount >= requestParams.toAmount,
|
|
511
|
+
`Route toAmount ${route.toAmount} is less than requested ${requestParams.toAmount}`,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
assert(routeFromAmount > 0n, 'Route fromAmount must be positive');
|
|
515
|
+
}
|
|
516
|
+
|
|
481
517
|
/**
|
|
482
518
|
* Get the status of a bridge transfer.
|
|
483
519
|
* Uses SDK's built-in status tracking.
|
package/src/config/types.ts
CHANGED
|
@@ -118,6 +118,8 @@ export const RebalancerStrategySchema = z
|
|
|
118
118
|
export const DEFAULT_INTENT_TTL_S = 604800; // 7 days
|
|
119
119
|
export const DEFAULT_INTENT_TTL_MS = DEFAULT_INTENT_TTL_S * 1_000;
|
|
120
120
|
|
|
121
|
+
export const DEFAULT_MOVEMENT_STALENESS_MS = 30 * 60 * 1_000; // 30 minutes
|
|
122
|
+
|
|
121
123
|
export const LiFiBridgeConfigSchema = z.object({
|
|
122
124
|
integrator: z.string(),
|
|
123
125
|
defaultSlippage: z.number().optional(),
|
|
@@ -105,6 +105,7 @@ export class MockExternalBridge implements IExternalBridge {
|
|
|
105
105
|
gasCosts,
|
|
106
106
|
feeCosts: 0n,
|
|
107
107
|
route,
|
|
108
|
+
requestParams: params,
|
|
108
109
|
};
|
|
109
110
|
}
|
|
110
111
|
|
|
@@ -197,6 +198,14 @@ export class MockExternalBridge implements IExternalBridge {
|
|
|
197
198
|
return { status: 'not_found' };
|
|
198
199
|
}
|
|
199
200
|
|
|
201
|
+
const dispatchedMessages =
|
|
202
|
+
HyperlaneCore.getDispatchedMessages(dispatchTxReceipt);
|
|
203
|
+
assert(
|
|
204
|
+
dispatchedMessages.length === 1,
|
|
205
|
+
`Expected exactly 1 dispatched message, got ${dispatchedMessages.length} for tx ${txHash}`,
|
|
206
|
+
);
|
|
207
|
+
const dispatchedMsgId = dispatchedMessages[0].id;
|
|
208
|
+
|
|
200
209
|
const relayer = new HyperlaneRelayer({ core: this.core });
|
|
201
210
|
const receipts = await relayer.relayAll(dispatchTxReceipt);
|
|
202
211
|
|
|
@@ -206,7 +215,30 @@ export class MockExternalBridge implements IExternalBridge {
|
|
|
206
215
|
receipts[toChain] ??
|
|
207
216
|
receipts[destinationDomain];
|
|
208
217
|
|
|
218
|
+
// If relayAll didn't produce receipts (e.g. message already delivered),
|
|
219
|
+
// fall back to checking on-chain delivery status directly.
|
|
209
220
|
if (!destinationReceipts || destinationReceipts.length === 0) {
|
|
221
|
+
const destMailbox = this.core.getContracts(toChainName).mailbox;
|
|
222
|
+
const isDelivered = await destMailbox.delivered(dispatchedMsgId);
|
|
223
|
+
if (isDelivered) {
|
|
224
|
+
const receivedAmount = await this.getTransferredAmount(
|
|
225
|
+
provider,
|
|
226
|
+
dispatchTxReceipt,
|
|
227
|
+
);
|
|
228
|
+
// Find the actual destination chain process tx
|
|
229
|
+
const processEvents = await destMailbox.queryFilter(
|
|
230
|
+
destMailbox.filters.ProcessId(dispatchedMsgId),
|
|
231
|
+
);
|
|
232
|
+
assert(
|
|
233
|
+
processEvents.length > 0,
|
|
234
|
+
`No ProcessId event found for message ${dispatchedMsgId} on ${toChainName}`,
|
|
235
|
+
);
|
|
236
|
+
return {
|
|
237
|
+
status: 'complete',
|
|
238
|
+
receivingTxHash: processEvents[0].transactionHash,
|
|
239
|
+
receivedAmount,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
210
242
|
return { status: 'not_found' };
|
|
211
243
|
}
|
|
212
244
|
|
|
@@ -35,7 +35,7 @@ export interface BridgeQuoteParams {
|
|
|
35
35
|
/**
|
|
36
36
|
* Quote response from a bridge.
|
|
37
37
|
*/
|
|
38
|
-
export interface BridgeQuote {
|
|
38
|
+
export interface BridgeQuote<R = unknown> {
|
|
39
39
|
id: string; // Unique quote identifier
|
|
40
40
|
tool: string; // Bridge/DEX tool used (e.g., 'stargate', 'across')
|
|
41
41
|
fromAmount: bigint; // Amount being sent (input required)
|
|
@@ -44,7 +44,8 @@ export interface BridgeQuote {
|
|
|
44
44
|
executionDuration: number; // Estimated execution time in seconds
|
|
45
45
|
gasCosts: bigint; // Sum of gas costs for the bridge operation
|
|
46
46
|
feeCosts: bigint; // Sum of non-included fee costs (protocol fees, etc.)
|
|
47
|
-
route:
|
|
47
|
+
route: R; // Bridge-specific route data for execution
|
|
48
|
+
requestParams: BridgeQuoteParams; // Original request parameters
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
/**
|
package/src/test/lifiMocks.ts
CHANGED
|
@@ -2,6 +2,7 @@ import Sinon, { type SinonStub } from 'sinon';
|
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
4
|
BridgeQuote,
|
|
5
|
+
BridgeQuoteParams,
|
|
5
6
|
BridgeTransferStatus,
|
|
6
7
|
} from '../interfaces/IExternalBridge.js';
|
|
7
8
|
|
|
@@ -119,22 +120,58 @@ export function mockLiFiStatus(
|
|
|
119
120
|
|
|
120
121
|
/**
|
|
121
122
|
* Create a mock BridgeQuote for testing.
|
|
123
|
+
* The route structure includes all fields read by validateRouteAgainstRequest.
|
|
122
124
|
*/
|
|
123
125
|
export function createMockBridgeQuote(
|
|
124
126
|
overrides?: Partial<BridgeQuote>,
|
|
125
127
|
): BridgeQuote {
|
|
128
|
+
const route = overrides?.route as
|
|
129
|
+
| { action?: { fromChainId?: number; toChainId?: number } }
|
|
130
|
+
| undefined;
|
|
131
|
+
|
|
132
|
+
const defaultRequestParams: BridgeQuoteParams = {
|
|
133
|
+
fromChain: 42161,
|
|
134
|
+
toChain: 1399811149,
|
|
135
|
+
fromToken: '0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC',
|
|
136
|
+
toToken: '0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC',
|
|
137
|
+
fromAddress: '0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
138
|
+
toAddress: '0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
139
|
+
fromAmount: 10000000000n,
|
|
140
|
+
};
|
|
141
|
+
const requestParams: BridgeQuoteParams = {
|
|
142
|
+
...defaultRequestParams,
|
|
143
|
+
...overrides?.requestParams,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const fromChainId = route?.action?.fromChainId ?? requestParams.fromChain;
|
|
147
|
+
const toChainId = route?.action?.toChainId ?? requestParams.toChain;
|
|
148
|
+
const fromAmount =
|
|
149
|
+
overrides?.fromAmount ?? requestParams.fromAmount ?? 10000000000n;
|
|
150
|
+
const toAmount = overrides?.toAmount ?? 9950000000n;
|
|
151
|
+
const toAmountMin = overrides?.toAmountMin ?? 9900000000n;
|
|
152
|
+
|
|
126
153
|
return {
|
|
127
154
|
id: 'quote-123',
|
|
128
155
|
tool: 'across',
|
|
129
|
-
fromAmount
|
|
130
|
-
toAmount
|
|
131
|
-
toAmountMin
|
|
156
|
+
fromAmount,
|
|
157
|
+
toAmount,
|
|
158
|
+
toAmountMin,
|
|
132
159
|
executionDuration: 300,
|
|
133
|
-
gasCosts: 50000000n,
|
|
134
|
-
feeCosts: 0n,
|
|
160
|
+
gasCosts: 50000000n,
|
|
161
|
+
feeCosts: 0n,
|
|
162
|
+
// Route includes all fields read by validateRouteAgainstRequest for validation compatibility
|
|
135
163
|
route: {
|
|
136
|
-
action: { fromChainId
|
|
164
|
+
action: { fromChainId, toChainId },
|
|
165
|
+
fromChainId,
|
|
166
|
+
toChainId,
|
|
167
|
+
fromToken: { address: requestParams.fromToken },
|
|
168
|
+
toToken: { address: requestParams.toToken },
|
|
169
|
+
fromAddress: requestParams.fromAddress,
|
|
170
|
+
toAddress: requestParams.toAddress ?? requestParams.fromAddress,
|
|
171
|
+
fromAmount: fromAmount.toString(),
|
|
172
|
+
toAmount: toAmount.toString(),
|
|
137
173
|
},
|
|
174
|
+
requestParams,
|
|
138
175
|
...overrides,
|
|
139
176
|
};
|
|
140
177
|
}
|